| /* |
| Assembler compatible with MACRO-11. |
| |
| Copyright (c) 2001, Richard Krehbiel |
| All rights reserved. |
| |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions are |
| met: |
| |
| o Redistributions of source code must retain the above copyright |
| notice, this list of conditions and the following disclaimer. |
| |
| o Redistributions in binary form must reproduce the above copyright |
| notice, this list of conditions and the following disclaimer in the |
| documentation and/or other materials provided with the distribution. |
| |
| o Neither the name of the copyright holder nor the names of its |
| contributors may be used to endorse or promote products derived from |
| this software without specific prior written permission. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR |
| TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
| USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| DAMAGE. |
| |
| */ |
| |
| #include <stdio.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <stdarg.h> |
| #include <assert.h> |
| #include <math.h> |
| #include <limits.h> |
| |
| #include "macro11.h" |
| |
| #include "rad50.h" |
| |
| #include "object.h" |
| |
| #include "stream2.h" |
| |
| #include "mlb.h" |
| |
| #include "util.h" |
| |
| #define SYMMAX 6 /* I will honor this many character |
| symbols */ |
| |
| #define issym(c) (isalpha(c) || isdigit(c) || (c) == '.' || (c) == '$') |
| |
| /* Program sections: */ |
| |
| typedef struct section |
| { |
| char *label; /* Section name */ |
| unsigned type; /* Section type */ |
| #define USER 1 /* user-defined */ |
| #define SYSTEM 2 /* A system symbol (like "."; value is an |
| enum) */ |
| #define INSTRUCTION 3 /* An instruction code (like "MOV"; value is |
| an enum) */ |
| #define PSEUDO 4 /* A pseudo-op (.PSECT, .TITLE, .MACRO, .IF; |
| value is an enum) */ |
| #define REGISTER 5 /* Symbol is a register (value 0=$0, value |
| 1=$1, ... $7) */ |
| #define USERMACRO 6 /* Symbol is a user macro */ |
| |
| unsigned flags; /* Flags, defined in object.h */ |
| unsigned pc; /* Current offset in the section */ |
| unsigned size; /* Current section size */ |
| unsigned sector; /* Used for complex relocation, and naught else */ |
| } SECTION; |
| |
| /* Symbol table entries */ |
| |
| typedef struct symbol |
| { |
| char *label; /* Symbol name */ |
| unsigned value; /* Symbol value */ |
| int stmtno; /* Statement number of symbol's definition */ |
| unsigned flags; /* Symbol flags */ |
| #define PERMANENT 1 /* Symbol may not be redefined */ |
| #define GLOBAL 2 /* Symbol is global */ |
| #define WEAK 4 /* Symbol definition is weak */ |
| #define DEFINITION 8 /* Symbol is a global definition, not |
| reference */ |
| #define UNDEFINED 16 /* Symbol is a phony, undefined */ |
| #define LOCAL 32 /* Set if this is a local label (i.e. 10$) */ |
| |
| SECTION *section; /* Section in which this symbol is defined */ |
| struct symbol *next; /* Next symbol with the same hash value */ |
| } SYMBOL; |
| |
| /* Arguments given to macros or .IRP/.IRPC blocks */ |
| |
| typedef struct arg |
| { |
| struct arg *next; /* Pointer in arg list */ |
| int locsym; /* Whether arg represents an optional |
| local symbol */ |
| char *label; /* Argument name */ |
| char *value; /* Default or active substitution */ |
| } ARG; |
| |
| /* A MACRO is a superstructure surrounding a SYMBOL. */ |
| |
| typedef struct macro |
| { |
| SYMBOL sym; /* Surrounds a symbol, contains the macro |
| name */ |
| ARG *args; /* The argument list */ |
| BUFFER *text; /* The macro text */ |
| } MACRO; |
| |
| typedef struct ex_tree |
| { |
| enum ex_type |
| { |
| EX_LIT=1, /* Expression is a literal value */ |
| EX_SYM=2, /* Expression has a symbol reference */ |
| EX_UNDEFINED_SYM=3, /* Expression is undefined sym reference */ |
| EX_TEMP_SYM=4, /* Expression is temp sym reference */ |
| |
| EX_COM=5, /* One's complement */ |
| EX_NEG=6, /* Negate */ |
| EX_ERR=7, /* Expression with an error */ |
| |
| EX_ADD=8, /* Add */ |
| EX_SUB=9, /* Subtract */ |
| EX_MUL=10, /* Multiply */ |
| EX_DIV=11, /* Divide */ |
| EX_AND=12, /* bitwise and */ |
| EX_OR=13 /* bitwise or */ |
| } type; |
| |
| char *cp; /* points to end of parsed expression */ |
| |
| union |
| { |
| struct |
| { |
| struct ex_tree *left, *right; /* Left, right children */ |
| } child; |
| unsigned lit; /* Literal value */ |
| SYMBOL *symbol; /* Symbol reference */ |
| } data; |
| |
| } EX_TREE; |
| |
| typedef struct addr_mode |
| { |
| unsigned type; /* The bits that represent the addressing mode */ |
| /* bits 0:2 are register number */ |
| /* bit 3 is indirect */ |
| /* bits 4:6 are mode, where 0=Rn, 1=(Rn)+, |
| 2=-(Rn), 3=offset(Rn) */ |
| int rel; /* the addressing mode is PC-relative */ |
| EX_TREE *offset; /* Expression giving the offset */ |
| } ADDR_MODE; |
| |
| #define FALSE 0 /* Everybody needs FALSE and TRUE */ |
| #define TRUE 1 |
| |
| enum pseudo_ops |
| { |
| P_ASCII, P_ASCIZ, P_ASECT, P_BLKB, P_BLKW, P_BYTE, P_CSECT, P_DSABL, |
| P_ENABL, P_END, P_ENDC, P_ENDM, P_ENDR, P_EOT, P_ERROR, P_EVEN, |
| P_FLT2, P_FLT4, P_GLOBL, P_IDENT, P_IF, P_IFF, P_IFT, P_IFTF, P_IIF, |
| P_IRP, P_IRPC, P_LIMIT, P_LIST, P_MCALL, P_MEXIT, P_NARG, P_NCHR, |
| P_NLIST, P_NTYPE, P_ODD, P_PACKED, P_PAGE, P_PRINT, P_PSECT, P_RADIX, |
| P_RAD50, P_REM, P_REPT, P_RESTORE, P_SAVE, P_SBTTL, P_TITLE, |
| P_WORD, P_MACRO, P_INCLU, P_WEAK, P_IFDF |
| }; |
| |
| enum instruction_ops |
| { |
| I_ADC = 0005500, I_ADCB = 0105500, I_ADD = 0060000, I_ASH = 0072000, |
| I_ASHC = 0073000, I_ASL = 0006300, I_ASLB = 0106300, I_ASR = 0006200, |
| I_ASRB = 0106200, I_BCC = 0103000, I_BCS = 0103400, I_BEQ = 0001400, |
| I_BGE = 0002000, I_BGT = 0003000, I_BHI = 0101000, I_BHIS = 0103000, |
| I_BIC = 0040000, I_BICB = 0140000, I_BIS = 0050000, I_BISB = 0150000, |
| I_BIT = 0030000, I_BITB = 0130000, I_BLE = 0003400, I_BLO = 0103400, |
| I_BLOS = 0101400, I_BLT = 0002400, I_BMI = 0100400, I_BNE = 0001000, |
| I_BPL = 0100000, I_BPT = 0000003, I_BR = 0000400, I_BVC = 0102000, |
| I_BVS = 0102400, I_CALL = 0004700, I_CALLR = 0000100, I_CCC = 0000257, |
| I_CLC = 0000241, I_CLN = 0000250, I_CLR = 0005000, I_CLRB = 0105000, |
| I_CLV = 0000242, I_CLZ = 0000244, I_CMP = 0020000, I_CMPB = 0120000, |
| I_COM = 0005100, I_COMB = 0105100, I_DEC = 0005300, I_DECB = 0105300, |
| I_DIV = 0071000, I_EMT = 0104000, I_FADD = 0075000, I_FDIV = 0075030, |
| I_FMUL = 0075020, I_FSUB = 0075010, I_HALT = 0000000, I_INC = 0005200, |
| I_INCB = 0105200, I_IOT = 0000004, I_JMP = 0000100, I_JSR = 0004000, |
| I_MARK = 0006400, I_MED6X = 0076600, I_MED74C= 0076601, I_MFPD = 0106500, |
| I_MFPI = 0006500, I_MFPS = 0106700, I_MOV = 0010000, I_MOVB = 0110000, |
| I_MTPD = 0106600, I_MTPI = 0006600, I_MTPS = 0106400, I_MUL = 0070000, |
| I_NEG = 0005400, I_NEGB = 0105400, I_NOP = 0000240, I_RESET = 0000005, |
| I_RETURN= 0000207, I_ROL = 0006100, I_ROLB = 0106100, I_ROR = 0006000, |
| I_RORB = 0106000, I_RTI = 0000002, I_RTS = 0000200, I_RTT = 0000006, |
| I_SBC = 0005600, I_SBCB = 0105600, I_SCC = 0000277, I_SEC = 0000261, |
| I_SEN = 0000270, I_SEV = 0000262, I_SEZ = 0000264, I_SOB = 0077000, |
| I_SPL = 0000230, I_SUB = 0160000, I_SWAB = 0000300, I_SXT = 0006700, |
| I_TRAP = 0104400, I_TST = 0005700, I_TSTB = 0105700, I_WAIT = 0000001, |
| I_XFC = 0076700, I_XOR = 0074000, I_MFPT = 0000007, |
| /* CIS not implemented - maybe later */ |
| /* FPU */ |
| I_ABSD = 0170600, I_ABSF = 0170600, I_ADDD = 0172000, I_ADDF = 0172000, |
| I_CFCC = 0170000, I_CLRD = 0170400, I_CLRF = 0170400, I_CMPD = 0173400, |
| I_CMPF = 0173400, I_DIVD = 0174400, I_DIVF = 0174400, I_LDCDF = 0177400, |
| I_LDCFD = 0177400, I_LDCID = 0177000, I_LDCIF = 0177000, I_LDCLD = 0177000, |
| I_LDCLF = 0177000, I_LDD = 0172400, I_LDEXP = 0176400, I_LDF = 0172400, |
| I_LDFPS = 0170100, I_MODD = 0171400, I_MODF = 0171400, I_MULD = 0171000, |
| I_MULF = 0171000, I_NEGD = 0170700, I_NEGF = 0170700, I_SETD = 0170011, |
| I_SETF = 0170001, I_SETI = 0170002, I_SETL = 0170012, I_STA0 = 0170005, |
| I_STB0 = 0170006, I_STCDF = 0176000, I_STCDI = 0175400, I_STCDL = 0175400, |
| I_STCFD = 0176000, I_STCFI = 0175400, I_STCFL = 0175400, I_STD = 0174000, |
| I_STEXP = 0175000, I_STF = 0174000, I_STFPS = 0170200, I_STST = 0170300, |
| I_SUBD = 0173000, I_SUBF = 0173000, I_TSTD = 0170500, I_TSTF = 0170500 |
| }; |
| |
| enum operand_codes |
| { |
| OC_MASK = 0xff00, /* mask over flags for operand types */ |
| OC_NONE = 0x0000, /* No operands */ |
| OC_1GEN = 0x0100, /* One general operand (CLR, TST, etc.) */ |
| OC_2GEN = 0x0200, /* Two general operand (MOV, CMP, etc.) */ |
| OC_BR = 0x0300, /* Branch */ |
| OC_ASH = 0x0400, /* ASH and ASHC (one gen, one reg) */ |
| OC_MARK = 0x0500, /* MARK instruction operand */ |
| OC_JSR = 0x0600, /* JSR, XOR (one reg, one gen) */ |
| OC_1REG = 0x0700, /* FADD, FSUB, FMUL, FDIV, RTS */ |
| OC_SOB = 0x0800, /* SOB */ |
| OC_1FIS = 0x0900, /* FIS (reg, gen) */ |
| OC_2FIS = 0x0a00, /* FIS (gen, reg) */ |
| OC__LAST = 0xff00 }; |
| |
| /* |
| format of a listing line |
| Interestingly, no instances of this struct are ever created. |
| It lives to be a way to layout the format of a list line. |
| I wonder if I should have bothered. |
| */ |
| |
| typedef struct lstformat |
| { |
| char flag[2]; /* Error flags */ |
| char line_number[6]; /* Line number */ |
| char pc[8]; /* Location */ |
| char words[8][3]; /* three instruction words */ |
| char source[1]; /* source line */ |
| } LSTFORMAT; |
| |
| #define SIZEOF_MEMBER(s, m) (sizeof((s *)0)->m) |
| |
| /* GLOBAL VARIABLES */ |
| |
| int pass = 0; /* The current assembly pass. 0 = first |
| pass. */ |
| |
| int stmtno = 0; /* The current source line number */ |
| int radix = 8; /* The current input conversion radix */ |
| int lsb = 0; /* The current local symbol section identifier */ |
| int last_lsb = 0; /* The last block in which a macro |
| automatic label was created */ |
| int last_locsym = 32768; /* The last local symbol number generated */ |
| |
| int enabl_debug = 0; /* Whether assembler debugging is enabled */ |
| |
| int enabl_ama = 0; /* When set, chooses absolute (037) versus |
| PC-relative */ |
| /* (067) addressing mode */ |
| int enabl_lsb = 0; /* When set, stops non-local symbol |
| definitions from delimiting local |
| symbol sections. */ |
| |
| int enabl_gbl = 1; /* Implicit definition of global symbols */ |
| |
| int list_md = 1; /* option to list macro/rept definition = yes */ |
| |
| int list_me = 1; /* option to list macro/rept expansion = yes */ |
| |
| int list_bex = 1; /* option to show binary */ |
| |
| int list_level = 1; /* Listing control level. .LIST |
| increments; .NLIST decrements */ |
| |
| char *listline; /* Source lines */ |
| |
| char *binline; /* for octal expansion */ |
| |
| FILE *lstfile = NULL; |
| |
| int suppressed = 0; /* Assembly suppressed by failed conditional */ |
| |
| #define MAX_MLBS 32 |
| MLB *mlbs[MAX_MLBS]; /* macro libraries specified on the |
| command line */ |
| int nr_mlbs = 0; /* Number of macro libraries */ |
| |
| typedef struct cond |
| { |
| int ok; /* What the condition evaluated to */ |
| char *file; /* What file and line it occurred */ |
| int line; |
| } COND; |
| |
| #define MAX_CONDS 256 |
| COND conds[MAX_CONDS]; /* Stack of recent conditions */ |
| int last_cond; /* 0 means no stacked cond. */ |
| |
| SECTION *sect_stack[32]; /* 32 saved sections */ |
| int sect_sp; /* Stack pointer */ |
| |
| char *module_name = NULL; /* The module name (taken from the 'TITLE'); */ |
| |
| char *ident = NULL; /* .IDENT name */ |
| |
| EX_TREE *xfer_address = NULL; /* The transfer address */ |
| |
| SYMBOL *current_pc; /* The current program counter */ |
| |
| unsigned last_dot_addr; /* Last coded PC... */ |
| SECTION *last_dot_section; /* ...and it's program section */ |
| |
| #define DOT (current_pc->value) /* Handy reference to the current location */ |
| |
| /* The following are dummy psects for symbols which have meaning to |
| the assembler: */ |
| |
| SECTION register_section = |
| { "", REGISTER, 0, 0 }; /* the section containing the registers */ |
| |
| SECTION pseudo_section = |
| { "", PSEUDO, 0, 0 }; /* the section containing the |
| pseudo-operations */ |
| |
| SECTION instruction_section = |
| { ". ABS.", INSTRUCTION, 0, 0 }; /* the section containing instructions */ |
| |
| SECTION macro_section = |
| { "", SYSTEM, 0, 0, 0 }; /* Section for macros */ |
| |
| /* These are real psects that get written out to the object file */ |
| |
| SECTION absolute_section = |
| { ". ABS.", SYSTEM, PSECT_GBL|PSECT_COM, 0, 0, 0}; /* The default |
| absolute section */ |
| |
| SECTION blank_section = |
| { "", SYSTEM, PSECT_REL, 0, 0, 1}; /* The default relocatable section */ |
| |
| SECTION *sections[256] = { /* Array of sections in the order they were |
| defined */ |
| &absolute_section, &blank_section, }; |
| |
| int sector = 2; /* number of such sections */ |
| |
| SYMBOL *reg_sym[8]; /* Keep the register symbols in a handy array */ |
| |
| /* symbol tables */ |
| |
| #define HASH_SIZE 1023 |
| |
| typedef struct symbol_table |
| { |
| SYMBOL *hash[HASH_SIZE]; |
| } SYMBOL_TABLE; |
| |
| SYMBOL_TABLE system_st; /* System symbols (Instructions, |
| pseudo-ops, registers) */ |
| |
| SYMBOL_TABLE section_st; /* Program sections */ |
| |
| SYMBOL_TABLE symbol_st; /* User symbols */ |
| |
| SYMBOL_TABLE macro_st; /* Macros */ |
| |
| SYMBOL_TABLE implicit_st; /* The symbols which may be implicit globals */ |
| |
| /* SYMBOL_ITER is used for iterating thru a symbol table. */ |
| |
| typedef struct symbol_iter |
| { |
| int subscript; /* Current hash subscript */ |
| SYMBOL *current; /* Current symbol */ |
| } SYMBOL_ITER; |
| |
| /* EOL says whether a char* is pointing at the end of a line */ |
| #define EOL(c) (!(c) || (c) == '\n' || (c) == ';') |
| |
| /* reports errors */ |
| static void report(STREAM *str, char *fmt, ...) |
| { |
| va_list ap; |
| char *name = "**"; |
| int line = 0; |
| |
| if(!pass) |
| return; /* Don't report now. */ |
| |
| if(str) |
| { |
| name = str->name; |
| line = str->line; |
| } |
| |
| fprintf(stderr, "%s:%d: ***ERROR ", name, line); |
| va_start(ap, fmt); |
| vfprintf(stderr, fmt, ap); |
| va_end(ap); |
| |
| if(lstfile) |
| { |
| fprintf(lstfile, "%s:%d: ***ERROR ", name, line); |
| va_start(ap, fmt); |
| vfprintf(lstfile, fmt, ap); |
| va_end(ap); |
| } |
| } |
| |
| /* memcheck - crash out if a pointer (returned from malloc) is NULL. */ |
| |
| void *memcheck(void *ptr) |
| { |
| if(ptr == NULL) |
| { |
| fprintf(stderr, "Out of memory.\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| return ptr; |
| } |
| |
| /* upcase turns a string to upper case */ |
| |
| static void upcase(char *str) |
| { |
| while(*str) |
| { |
| *str = toupper(*str); |
| str++; |
| } |
| } |
| |
| /* hash_name hashes a name into a value from 0-HASH_SIZE */ |
| |
| static int hash_name(char *label) |
| { |
| unsigned accum = 0; |
| |
| while(*label) |
| accum = (accum << 1) ^ *label++; |
| |
| accum %= HASH_SIZE; |
| |
| return accum; |
| } |
| |
| /* Allocate a new symbol. Does not add it to any symbol table. */ |
| |
| static SYMBOL *new_sym(char *label) |
| { |
| SYMBOL *sym = memcheck(malloc(sizeof(SYMBOL))); |
| sym->label = memcheck(strdup(label)); |
| sym->section = NULL; |
| sym->value = 0; |
| sym->flags = 0; |
| return sym; |
| } |
| |
| /* Free a symbol. Does not remove it from any symbol table. */ |
| |
| static void free_sym(SYMBOL *sym) |
| { |
| if(sym->label) |
| { |
| free(sym->label); |
| sym->label = NULL; |
| } |
| free(sym); |
| } |
| |
| /* remove_sym removes a symbol from it's symbol table. */ |
| |
| static void remove_sym(SYMBOL *sym, SYMBOL_TABLE *table) |
| { |
| SYMBOL **prevp, *symp; |
| int hash; |
| |
| hash = hash_name(sym->label); |
| prevp = &table->hash[hash]; |
| while(symp = *prevp, symp != NULL && symp != sym) |
| prevp = &symp->next; |
| |
| if(symp) |
| *prevp = sym->next; |
| } |
| |
| /* lookup_sym finds a symbol in a table */ |
| |
| static SYMBOL *lookup_sym(char *label, SYMBOL_TABLE *table) |
| { |
| unsigned hash; |
| SYMBOL *sym; |
| |
| hash = hash_name(label); |
| |
| sym = table->hash[hash]; |
| while(sym && strcmp(sym->label, label) != 0) |
| sym = sym->next; |
| |
| return sym; |
| } |
| |
| /* next_sym - returns the next symbol from a symbol table. Must be |
| preceeded by first_sym. Returns NULL after the last symbol. */ |
| |
| static SYMBOL *next_sym(SYMBOL_TABLE *table, SYMBOL_ITER *iter) |
| { |
| if(iter->current) |
| iter->current = iter->current->next; |
| |
| while(iter->current == NULL) |
| { |
| if(iter->subscript >= HASH_SIZE) |
| return NULL; /* No more symbols. */ |
| iter->current = table->hash[iter->subscript]; |
| iter->subscript++; |
| } |
| |
| return iter->current; /* Got a symbol. */ |
| } |
| |
| /* first_sym - returns the first symbol from a symbol table. Symbols |
| are stored in random order. */ |
| |
| static SYMBOL *first_sym(SYMBOL_TABLE *table, SYMBOL_ITER *iter) |
| { |
| iter->subscript = 0; |
| iter->current = NULL; |
| return next_sym(table, iter); |
| } |
| |
| /* add_table - add a symbol to a symbol table. */ |
| |
| static void add_table(SYMBOL *sym, SYMBOL_TABLE *table) |
| { |
| int hash = hash_name(sym->label); |
| sym->next = table->hash[hash]; |
| table->hash[hash] = sym; |
| } |
| |
| /* add_sym - used throughout to add or update symbols in a symbol |
| table. */ |
| |
| static SYMBOL *add_sym(char *label, unsigned value, unsigned flags, |
| SECTION *section, SYMBOL_TABLE *table) |
| { |
| SYMBOL *sym; |
| |
| sym = lookup_sym(label, table); |
| if(sym != NULL) |
| { |
| // A symbol registered as "undefined" can be changed. |
| |
| if((sym->flags & UNDEFINED) && |
| !(flags & UNDEFINED)) |
| { |
| sym->flags &= ~(PERMANENT|UNDEFINED); |
| } |
| |
| /* Check for compatible definition */ |
| else if(sym->section == section && |
| sym->value == value) |
| { |
| sym->flags |= flags; /* Merge flags quietly */ |
| return sym; /* 's okay */ |
| } |
| |
| if(!(sym->flags & PERMANENT)) |
| { |
| /* permit redefinition */ |
| sym->value = value; |
| sym->flags |= flags; |
| sym->section = section; |
| return sym; |
| } |
| |
| return NULL; /* Bad symbol redefinition */ |
| } |
| |
| sym = new_sym(label); |
| sym->flags = flags; |
| sym->stmtno = stmtno; |
| sym->section = section; |
| sym->value = value; |
| |
| add_table(sym, table); |
| |
| return sym; |
| } |
| |
| /* Allocate a new section */ |
| |
| static SECTION *new_section(void) |
| { |
| SECTION *sect = memcheck(malloc(sizeof(SECTION))); |
| sect->flags = 0; |
| sect->size = 0; |
| sect->pc = 0; |
| sect->type = 0; |
| sect->sector = 0; |
| sect->label = NULL; |
| return sect; |
| } |
| |
| /* Allocate a new ARG */ |
| |
| static ARG *new_arg(void) |
| { |
| ARG *arg = memcheck(malloc(sizeof(ARG))); |
| arg->locsym = 0; |
| arg->value = NULL; |
| arg->next = NULL; |
| arg->label = NULL; |
| return arg; |
| } |
| |
| /* Allocate a new macro */ |
| |
| static MACRO *new_macro(char *label) |
| { |
| MACRO *mac = memcheck(malloc(sizeof(MACRO))); |
| |
| mac->sym.flags = 0; |
| mac->sym.label = label; |
| mac->sym.stmtno = stmtno; |
| mac->sym.next = NULL; |
| mac->sym.section = ¯o_section; |
| mac->sym.value = 0; |
| mac->args = NULL; |
| mac->text = NULL; |
| |
| return mac; |
| } |
| |
| /* Free a list of args (as for a macro, or a macro expansion) */ |
| |
| static void free_args(ARG *arg) |
| { |
| ARG *next; |
| |
| while(arg) |
| { |
| next = arg->next; |
| if(arg->label) |
| { |
| free(arg->label); |
| arg->label = NULL; |
| } |
| if(arg->value) |
| { |
| free(arg->value); |
| arg->value = NULL; |
| } |
| free(arg); |
| arg = next; |
| } |
| } |
| |
| /* free a macro, it's args, it's text, etc. */ |
| |
| static void free_macro(MACRO *mac) |
| { |
| if(mac->text) |
| { |
| free(mac->text); |
| } |
| free_args(mac->args); |
| free_sym(&mac->sym); |
| } |
| |
| /* do_list returns TRUE if listing is enabled. */ |
| |
| static int dolist(void) |
| { |
| int ok = lstfile != NULL && pass > 0 && list_level > 0; |
| return ok; |
| } |
| |
| /* list_source saves a text line for later listing by list_flush */ |
| |
| static void list_source(STREAM *str, char *cp) |
| { |
| if(dolist()) |
| { |
| int len = strcspn(cp, "\n"); |
| /* Save the line text away for later... */ |
| if(listline) |
| free(listline); |
| listline = memcheck(malloc(len + 1)); |
| memcpy(listline, cp, len); |
| listline[len] = 0; |
| |
| if(!binline) |
| binline = memcheck(malloc(sizeof(LSTFORMAT) + 16)); |
| |
| sprintf(binline, "%*s%*d", |
| SIZEOF_MEMBER(LSTFORMAT, flag), "", |
| SIZEOF_MEMBER(LSTFORMAT, line_number), str->line); |
| } |
| } |
| |
| /* padto adds blanks to the end of a string until it's the given |
| length. */ |
| |
| static void padto(char *str, int to) |
| { |
| int needspace = to - strlen(str); |
| str += strlen(str); |
| while(needspace > 0) |
| *str++ = ' ', needspace--; |
| *str = 0; |
| } |
| |
| /* list_flush produces a buffered list line. */ |
| |
| static void list_flush(void) |
| { |
| if(dolist()) |
| { |
| padto(binline, offsetof(LSTFORMAT, source)); |
| fputs(binline, lstfile); |
| fputs(listline, lstfile); |
| fputc('\n', lstfile); |
| listline[0] = 0; |
| binline[0] = 0; |
| } |
| } |
| |
| /* list_fit checks to see if a word will fit in the current listing |
| line. If not, it flushes and prepares another line. */ |
| |
| static void list_fit(STREAM *str, unsigned addr) |
| { |
| int len = strlen(binline); |
| size_t col1 = offsetof(LSTFORMAT, source); |
| size_t col2 = offsetof(LSTFORMAT, pc); |
| |
| if(strlen(binline) >= col1) |
| { |
| int offset = offsetof(LSTFORMAT, pc); |
| list_flush(); |
| listline[0] = 0; |
| binline[0] = 0; |
| sprintf(binline, "%*s %6.6o", |
| offsetof(LSTFORMAT, pc), "", |
| addr); |
| padto(binline, offsetof(LSTFORMAT, words)); |
| } |
| else if(strlen(binline) <= col2) |
| { |
| sprintf(binline, "%*s%*d %6.6o", |
| SIZEOF_MEMBER(LSTFORMAT, flag), "", |
| SIZEOF_MEMBER(LSTFORMAT, line_number), str->line, |
| addr); |
| padto(binline, offsetof(LSTFORMAT, words)); |
| } |
| } |
| |
| /* list_value is used to show a computed value */ |
| |
| static void list_value(STREAM *str, unsigned word) |
| { |
| if(dolist()) |
| { |
| /* Print the value and go */ |
| binline[0] = 0; |
| sprintf(binline, "%*s%*d %6.6o", |
| SIZEOF_MEMBER(LSTFORMAT, flag), "", |
| SIZEOF_MEMBER(LSTFORMAT, line_number), str->line, |
| word & 0177777); |
| } |
| } |
| |
| /* Print a word to the listing file */ |
| |
| void list_word(STREAM *str, unsigned addr, unsigned value, int size, |
| char *flags) |
| { |
| if(dolist()) |
| { |
| list_fit(str, addr); |
| if(size == 1) |
| sprintf(binline + strlen(binline), " %3.3o%1.1s ", |
| value & 0377, flags); |
| else |
| sprintf(binline + strlen(binline), "%6.6o%1.1s ", |
| value & 0177777, flags); |
| } |
| } |
| |
| /* This is called by places that are about to store some code, or |
| which want to manually update DOT. */ |
| |
| static void change_dot(TEXT_RLD *tr, int size) |
| { |
| if(size > 0) |
| { |
| if(last_dot_section != current_pc->section) |
| { |
| text_define_location(tr, current_pc->section->label, |
| ¤t_pc->value); |
| last_dot_section = current_pc->section; |
| last_dot_addr = current_pc->value; |
| } |
| if(last_dot_addr != current_pc->value) |
| { |
| text_modify_location(tr, ¤t_pc->value); |
| last_dot_addr = current_pc->value; |
| } |
| |
| /* Update for next time */ |
| last_dot_addr += size; |
| } |
| |
| if(DOT+size > current_pc->section->size) |
| current_pc->section->size = DOT+size; |
| } |
| |
| /* store_word stores a word to the object file and lists it to the |
| listing file */ |
| |
| static int store_word(STREAM *str, TEXT_RLD *tr, int size, |
| unsigned word) |
| { |
| change_dot(tr, size); |
| list_word(str, DOT, word, size, ""); |
| return text_word(tr, &DOT, size, word); |
| } |
| |
| /* store_word stores a word to the object file and lists it to the |
| listing file */ |
| |
| static int store_displaced_word(STREAM *str, TEXT_RLD *tr, |
| int size, unsigned word) |
| { |
| change_dot(tr, size); |
| list_word(str, DOT, word, size, "'"); |
| return text_displaced_word(tr, &DOT, size, word); |
| } |
| |
| static int store_global_displaced_offset_word(STREAM *str, TEXT_RLD *tr, |
| int size, |
| unsigned word, char *global) |
| { |
| change_dot(tr, size); |
| list_word(str, DOT, word, size, "G"); |
| return text_global_displaced_offset_word(tr, &DOT, size, |
| word, global); |
| } |
| |
| static int store_global_offset_word(STREAM *str, TEXT_RLD *tr, |
| int size, |
| unsigned word, char *global) |
| { |
| change_dot(tr, size); |
| list_word(str, DOT, word, size, "G"); |
| return text_global_offset_word(tr, &DOT, size, word, global); |
| } |
| |
| static int store_internal_word(STREAM *str, TEXT_RLD *tr, |
| int size, unsigned word) |
| { |
| change_dot(tr, size); |
| list_word(str, DOT, word, size, ""); |
| return text_internal_word(tr, &DOT, size, word); |
| } |
| |
| static int store_psect_displaced_offset_word(STREAM *str, TEXT_RLD *tr, |
| int size, |
| unsigned word, char *name) |
| { |
| change_dot(tr, size); |
| list_word(str, DOT, word, size, ""); |
| return text_psect_displaced_offset_word(tr, &DOT, size, word, name); |
| } |
| |
| static int store_psect_offset_word(STREAM *str, TEXT_RLD *tr, |
| int size, |
| unsigned word, char *name) |
| { |
| change_dot(tr, size); |
| list_word(str, DOT, word, size, ""); |
| return text_psect_offset_word(tr, &DOT, size, word, name); |
| } |
| |
| static int store_limits(STREAM *str, TEXT_RLD *tr) |
| { |
| change_dot(tr, 4); |
| list_word(str, DOT, 0, 2, ""); |
| list_word(str, DOT+2, 0, 2, ""); |
| return text_limits(tr, &DOT); |
| } |
| |
| /* skipwhite - used everywhere to advance a char pointer past spaces */ |
| |
| static char *skipwhite(char *cp) |
| { |
| while(*cp == ' ' || *cp == '\t') |
| cp++; |
| return cp; |
| } |
| |
| /* skipdelim - used everywhere to advance between tokens. Whitespace |
| and one comma are allowed delims. */ |
| |
| static char *skipdelim(char *cp) |
| { |
| cp = skipwhite(cp); |
| if(*cp == ',') |
| cp = skipwhite(cp+1); |
| return cp; |
| } |
| |
| /* Parse PDP-11 64-bit floating point format. */ |
| /* Give a pointer to "size" words to receive the result. */ |
| /* Note: there are probably degenerate cases that store incorrect |
| results. For example, I think rounding up a FLT2 might cause |
| exponent overflow. Sorry. */ |
| /* Note also that the full 49 bits of precision probably aren't |
| available on the source platform, given the widespread application |
| of IEEE floating point formats, so expect some differences. Sorry |
| again. */ |
| |
| int parse_float(char *cp, char **endp, int size, unsigned *flt) |
| { |
| double d; /* value */ |
| double frac; /* fractional value */ |
| ulong64 ufrac; /* fraction converted to 49 bit |
| unsigned integer */ |
| int i; /* Number of fields converted by sscanf */ |
| int n; /* Number of characters converted by sscanf */ |
| int sexp; /* Signed exponent */ |
| unsigned exp; /* Unsigned excess-128 exponent */ |
| unsigned sign = 0; /* Sign mask */ |
| |
| i = sscanf(cp, "%lf%n", &d, &n); |
| if(i == 0) |
| return 0; /* Wasn't able to convert */ |
| |
| cp += n; |
| if(endp) |
| *endp = cp; |
| |
| if(d == 0.0) |
| { |
| flt[0] = flt[1] = flt[2] = flt[3] = 0; /* All-bits-zero equals zero */ |
| return 1; /* Good job. */ |
| } |
| |
| frac = frexp(d, &sexp); /* Separate into exponent and mantissa */ |
| if(sexp < -128 || sexp > 127) |
| return 0; /* Exponent out of range. */ |
| |
| exp = sexp + 128; /* Make excess-128 mode */ |
| exp &= 0xff; /* express in 8 bits */ |
| |
| if(frac < 0) |
| { |
| sign = 0100000; /* Negative sign */ |
| frac = -frac; /* fix the mantissa */ |
| } |
| |
| /* The following big literal is 2 to the 49th power: */ |
| ufrac = (ulong64) (frac * 72057594037927936.0); /* Align fraction bits */ |
| |
| /* Round from FLT4 to FLT2 */ |
| if(size < 4) |
| { |
| ufrac += 0x80000000; /* Round to nearest 32-bit |
| representation */ |
| |
| if(ufrac > 0x200000000000) /* Overflow? */ |
| { |
| ufrac >>= 1; /* Normalize */ |
| exp--; |
| } |
| } |
| |
| flt[0] = (unsigned) (sign | (exp << 7) | (ufrac >> 48) & 0x7F); |
| if(size > 1) |
| { |
| flt[1] = (unsigned) ((ufrac >> 32) & 0xffff); |
| if(size > 2) |
| { |
| flt[2] = (unsigned) ((ufrac >> 16) & 0xffff); |
| flt[3] = (unsigned) ((ufrac >> 0) & 0xffff); |
| } |
| } |
| |
| return 1; |
| } |
| |
| /* Allocate an EX_TREE */ |
| |
| static EX_TREE *new_ex_tree(void) |
| { |
| EX_TREE *tr = memcheck(malloc(sizeof(EX_TREE))); |
| return tr; |
| } |
| |
| |
| /* Create an EX_TREE representing a parse error */ |
| |
| static EX_TREE *ex_err(EX_TREE *tp, char *cp) |
| { |
| EX_TREE *errtp; |
| |
| errtp = new_ex_tree(); |
| errtp->cp = cp; |
| errtp->type = EX_ERR; |
| errtp->data.child.left = tp; |
| |
| return errtp; |
| } |
| |
| /* Create an EX_TREE representing a literal value */ |
| |
| static EX_TREE *new_ex_lit(unsigned value) |
| { |
| EX_TREE *tp; |
| |
| tp = new_ex_tree(); |
| tp->type = EX_LIT; |
| tp->data.lit = value; |
| |
| return tp; |
| } |
| |
| /* The recursive-descent expression parser parse_expr. */ |
| |
| /* This parser was designed for expressions with operator precedence. |
| However, MACRO-11 doesn't observe any sort of operator precedence. |
| If you feel your source deserves better, give the operators |
| appropriate precedence values right here. */ |
| |
| #define ADD_PREC 1 |
| #define MUL_PREC 1 |
| #define AND_PREC 1 |
| #define OR_PREC 1 |
| |
| EX_TREE *parse_unary(char *cp); /* Prototype for forward calls */ |
| |
| EX_TREE *parse_binary(char *cp, char term, int depth) |
| { |
| EX_TREE *leftp, *rightp, *tp; |
| |
| leftp = parse_unary(cp); |
| |
| while(leftp->type != EX_ERR) |
| { |
| cp = skipwhite(leftp->cp); |
| |
| if(*cp == term) |
| return leftp; |
| |
| switch(*cp) |
| { |
| case '+': |
| if(depth >= ADD_PREC) |
| return leftp; |
| |
| rightp = parse_binary(cp+1, term, ADD_PREC); |
| tp = new_ex_tree(); |
| tp->type = EX_ADD; |
| tp->data.child.left = leftp; |
| tp->data.child.right = rightp; |
| tp->cp = rightp->cp; |
| leftp = tp; |
| break; |
| |
| case '-': |
| if(depth >= ADD_PREC) |
| return leftp; |
| |
| rightp = parse_binary(cp+1, term, ADD_PREC); |
| tp = new_ex_tree(); |
| tp->type = EX_SUB; |
| tp->data.child.left = leftp; |
| tp->data.child.right = rightp; |
| tp->cp = rightp->cp; |
| leftp = tp; |
| break; |
| |
| case '*': |
| if(depth >= MUL_PREC) |
| return leftp; |
| |
| rightp = parse_binary(cp+1, term, MUL_PREC); |
| tp = new_ex_tree(); |
| tp->type = EX_MUL; |
| tp->data.child.left = leftp; |
| tp->data.child.right = rightp; |
| tp->cp = rightp->cp; |
| leftp = tp; |
| break; |
| |
| case '/': |
| if(depth >= MUL_PREC) |
| return leftp; |
| |
| rightp = parse_binary(cp+1, term, MUL_PREC); |
| tp = new_ex_tree(); |
| tp->type = EX_DIV; |
| tp->data.child.left = leftp; |
| tp->data.child.right = rightp; |
| tp->cp = rightp->cp; |
| leftp = tp; |
| break; |
| |
| case '!': |
| if(depth >= OR_PREC) |
| return leftp; |
| |
| rightp = parse_binary(cp+1, term, 2); |
| tp = new_ex_tree(); |
| tp->type = EX_OR; |
| tp->data.child.left = leftp; |
| tp->data.child.right = rightp; |
| tp->cp = rightp->cp; |
| leftp = tp; |
| break; |
| |
| case '&': |
| if(depth >= AND_PREC) |
| return leftp; |
| |
| rightp = parse_binary(cp+1, term, AND_PREC); |
| tp = new_ex_tree(); |
| tp->type = EX_AND; |
| tp->data.child.left = leftp; |
| tp->data.child.right = rightp; |
| tp->cp = rightp->cp; |
| leftp = tp; |
| break; |
| |
| default: |
| /* Some unknown character. Let caller decide if it's okay. */ |
| |
| return leftp; |
| |
| } /* end switch */ |
| } /* end while */ |
| |
| /* Can't be reached except by error. */ |
| return leftp; |
| } |
| |
| /* get_symbol is used all over the place to pull a symbol out of the |
| text. */ |
| |
| static char *get_symbol(char *cp, char **endp, int *islocal) |
| { |
| int len; |
| char *symcp; |
| int digits = 0; |
| |
| cp = skipwhite(cp); /* Skip leading whitespace */ |
| |
| if(!issym(*cp)) |
| return NULL; |
| |
| digits = 0; |
| if(isdigit(*cp)) |
| digits = 2; /* Think about digit count */ |
| |
| for(symcp = cp + 1; issym(*symcp); symcp++) |
| { |
| if(!isdigit(*symcp)) /* Not a digit? */ |
| digits--; /* Make a note. */ |
| } |
| |
| if(digits == 2) |
| return NULL; /* Not a symbol, it's a digit string */ |
| |
| if(endp) |
| *endp = symcp; |
| |
| len = symcp - cp; |
| |
| /* Now limit length */ |
| if(len > SYMMAX) |
| len = SYMMAX; |
| |
| symcp = memcheck(malloc(len + 1)); |
| |
| memcpy(symcp, cp, len); |
| symcp[len] = 0; |
| upcase(symcp); |
| |
| if(islocal) |
| { |
| *islocal = 0; |
| |
| /* Turn to local label format */ |
| if(digits == 1) |
| { |
| if(symcp[len-1] == '$') |
| { |
| char *newsym = memcheck(malloc(32)); /* Overkill */ |
| sprintf(newsym, "%d$%d", strtol(symcp, NULL, 10), lsb); |
| free(symcp); |
| symcp = newsym; |
| if(islocal) |
| *islocal = LOCAL; |
| } |
| else |
| { |
| free(symcp); |
| return NULL; |
| } |
| } |
| } |
| else |
| { |
| /* disallow local label format */ |
| if(isdigit(*symcp)) |
| { |
| free(symcp); |
| return NULL; |
| } |
| } |
| |
| return symcp; |
| } |
| |
| /* |
| brackrange is used to find a range of text which may or may not be |
| bracketed. |
| |
| If the brackets are <>, then nested brackets are detected. |
| If the brackets are of the form ^/.../ no detection of nesting is |
| attempted. |
| |
| Using brackets ^<...< will mess this routine up. What in the world |
| are you thinking? |
| */ |
| |
| int brackrange(char *cp, int *start, int *length, char **endp) |
| { |
| char endstr[6]; |
| int endlen; |
| int nest; |
| int len; |
| |
| switch(*cp) |
| { |
| case '^': |
| endstr[0] = cp[1]; |
| strcpy(endstr+1, "\n"); |
| *start = 2; |
| endlen = 1; |
| break; |
| case '<': |
| strcpy(endstr, "<>\n"); |
| endlen = 1; |
| *start = 1; |
| break; |
| default: |
| return FALSE; |
| } |
| |
| cp += *start; |
| |
| len = 0; |
| nest = 1; |
| while(nest) |
| { |
| int sublen; |
| sublen = strcspn(cp+len, endstr); |
| if(cp[len+sublen] == '<') |
| nest++; |
| else |
| nest--; |
| len += sublen; |
| } |
| |
| *length = len; |
| if(endp) |
| *endp = cp + len + endlen; |
| |
| return 1; |
| } |
| |
| /* parse_unary parses out a unary operator or leaf expression. */ |
| |
| EX_TREE *parse_unary(char *cp) |
| { |
| EX_TREE *tp; |
| |
| /* Skip leading whitespace */ |
| cp = skipwhite(cp); |
| |
| if(*cp == '%') /* Register notation */ |
| { |
| unsigned reg; |
| cp++; |
| reg = strtoul(cp, &cp, 8); |
| if(reg > 7) |
| return ex_err(NULL, cp); |
| |
| /* This returns references to the built-in register symbols */ |
| tp = new_ex_tree(); |
| tp->type = EX_SYM; |
| tp->data.symbol = reg_sym[reg]; |
| tp->cp = cp; |
| return tp; |
| } |
| |
| /* Unary negate */ |
| if(*cp == '-') |
| { |
| tp = new_ex_tree(); |
| tp->type = EX_NEG; |
| tp->data.child.left = parse_unary(cp+1); |
| tp->cp = tp->data.child.left->cp; |
| return tp; |
| } |
| |
| /* Unary + I can ignore. */ |
| if(*cp == '+') |
| return parse_unary(cp+1); |
| |
| if(*cp == '^') |
| { |
| int save_radix; |
| switch(tolower(cp[1])) |
| { |
| case 'c': |
| /* ^C, ones complement */ |
| tp = new_ex_tree(); |
| tp->type = EX_COM; |
| tp->data.child.left = parse_unary(cp+2); |
| tp->cp = tp->data.child.left->cp; |
| return tp; |
| case 'b': |
| /* ^B, binary radix modifier */ |
| save_radix = radix; |
| radix = 2; |
| tp = parse_unary(cp+2); |
| radix = save_radix; |
| return tp; |
| case 'o': |
| /* ^O, octal radix modifier */ |
| save_radix = radix; |
| radix = 8; |
| tp = parse_unary(cp+2); |
| radix = save_radix; |
| return tp; |
| case 'd': |
| /* ^D, decimal radix modifier */ |
| save_radix = radix; |
| radix = 10; |
| tp = parse_unary(cp+2); |
| radix = save_radix; |
| return tp; |
| case 'x': |
| /* An enhancement! ^X, hexadecimal radix modifier */ |
| save_radix = radix; |
| radix = 16; |
| tp = parse_unary(cp+2); |
| radix = save_radix; |
| return tp; |
| case 'r': |
| /* ^R, RAD50 literal */ |
| { |
| int start, len; |
| char *endcp; |
| unsigned value; |
| cp += 2; |
| if(brackrange(cp, &start, &len, &endcp)) |
| value = rad50(cp+start, NULL); |
| else |
| value = rad50(cp, &endcp); |
| tp = new_ex_lit(value); |
| tp->cp = endcp; |
| return tp; |
| } |
| case 'f': |
| /* ^F, single-word floating point literal indicator */ |
| { |
| unsigned flt[1]; |
| char *endcp; |
| if(!parse_float(cp+2, &endcp, 1, flt)) |
| { |
| tp = ex_err(NULL, cp+2); |
| } |
| else |
| { |
| tp = new_ex_lit(flt[0]); |
| tp->cp = endcp; |
| } |
| return tp; |
| } |
| } |
| |
| if(ispunct(cp[1])) |
| { |
| char *ecp; |
| /* oddly-bracketed expression like this: ^/expression/ */ |
| tp = parse_binary(cp+2, cp[1], 0); |
| ecp = skipwhite(tp->cp); |
| |
| if(*ecp != cp[1]) |
| return ex_err(tp, ecp); |
| |
| tp->cp = ecp + 1; |
| return tp; |
| } |
| } |
| |
| /* Bracketed subexpression */ |
| if(*cp == '<') |
| { |
| char *ecp; |
| tp = parse_binary(cp+1, '>', 0); |
| ecp = skipwhite(tp->cp); |
| if(*ecp != '>') |
| return ex_err(tp, ecp); |
| |
| tp->cp = ecp + 1; |
| return tp; |
| } |
| |
| /* Check for ASCII constants */ |
| |
| if(*cp == '\'') |
| { |
| /* 'x single ASCII character */ |
| cp++; |
| tp = new_ex_tree(); |
| tp->type = EX_LIT; |
| tp->data.lit = *cp & 0xff; |
| tp->cp = ++cp; |
| return tp; |
| } |
| |
| if(*cp == '\"') |
| { |
| /* "xx ASCII character pair */ |
| cp++; |
| tp = new_ex_tree(); |
| tp->type = EX_LIT; |
| tp->data.lit = (cp[0] & 0xff) | ((cp[1] & 0xff) << 8); |
| tp->cp = cp + 2; |
| return tp; |
| } |
| |
| /* Numeric constants are trickier than they need to be, */ |
| /* since local labels start with a digit too. */ |
| if(isdigit(*cp)) |
| { |
| char *label; |
| int local; |
| |
| if((label = get_symbol(cp, NULL, &local)) == NULL) |
| { |
| char *endcp; |
| unsigned long value; |
| int rad = radix; |
| |
| /* get_symbol returning NULL assures me that it's not a |
| local label. */ |
| |
| /* Look for a trailing period, to indicate decimal... */ |
| for(endcp = cp; isdigit(*endcp); endcp++) |
| ; |
| if(*endcp == '.') |
| rad = 10; |
| |
| value = strtoul(cp, &endcp, rad); |
| if(*endcp == '.') |
| endcp++; |
| |
| tp = new_ex_tree(); |
| tp->type = EX_LIT; |
| tp->data.lit = value; |
| tp->cp = endcp; |
| |
| return tp; |
| } |
| |
| free(label); |
| } |
| |
| /* Now check for a symbol */ |
| |
| { |
| char *label; |
| int local; |
| SYMBOL *sym; |
| |
| /* Optimization opportunity: I don't really need to call |
| get_symbol a second time. */ |
| |
| if(!(label = get_symbol(cp, &cp, &local))) |
| { |
| tp = ex_err(NULL, cp); /* Not a valid label. */ |
| return tp; |
| } |
| |
| sym = lookup_sym(label, &symbol_st); |
| if(sym == NULL) |
| { |
| /* A symbol from the "PST", which means an instruction |
| code. */ |
| sym = lookup_sym(label, &system_st); |
| } |
| |
| if(sym != NULL) |
| { |
| tp = new_ex_tree(); |
| tp->cp = cp; |
| tp->type = EX_SYM; |
| tp->data.symbol = sym; |
| |
| free(label); |
| return tp; |
| } |
| |
| /* The symbol was not found. Create an "undefined symbol" |
| reference. */ |
| sym = memcheck(malloc(sizeof(SYMBOL))); |
| sym->label = label; |
| sym->flags = UNDEFINED | local; |
| sym->stmtno = stmtno; |
| sym->next = NULL; |
| sym->section = &absolute_section; |
| sym->value = 0; |
| |
| tp = new_ex_tree(); |
| tp->cp = cp; |
| tp->type = EX_UNDEFINED_SYM; |
| tp->data.symbol = sym; |
| |
| return tp; |
| } |
| } |
| |
| /* Diagnostic: symflags returns a char* which gives flags I can use to |
| show the context of a symbol. */ |
| |
| static char *symflags(SYMBOL *sym) |
| { |
| static char temp[8]; |
| char *fp = temp; |
| if(sym->flags & GLOBAL) |
| *fp++ = 'G'; |
| if(sym->flags & PERMANENT) |
| *fp++ = 'P'; |
| if(sym->flags & DEFINITION) |
| *fp++ = 'D'; |
| *fp = 0; |
| return fp; |
| } |
| |
| /* Diagnostic: print an expression tree. I used this in various |
| places to help me diagnose parse problems, by putting in calls to |
| print_tree when I didn't understand why something wasn't working. |
| This is currently dead code, nothing calls it; but I don't want it |
| to go away. Hopefully the compiler will realize when it's dead, and |
| eliminate it. */ |
| |
| static void print_tree(FILE *printfile, EX_TREE *tp, int depth) |
| { |
| SYMBOL *sym; |
| |
| switch(tp->type) |
| { |
| case EX_LIT: |
| fprintf(printfile, "%o", tp->data.lit & 0177777); |
| break; |
| |
| case EX_SYM: |
| case EX_TEMP_SYM: |
| sym = tp->data.symbol; |
| fprintf(printfile, "%s{%s%o:%s}", tp->data.symbol->label, |
| symflags(sym), sym->value, sym->section->label); |
| break; |
| |
| case EX_UNDEFINED_SYM: |
| fprintf(printfile, "%s{%o:undefined}", tp->data.symbol->label, |
| tp->data.symbol->value); |
| break; |
| |
| case EX_COM: |
| fprintf(printfile, "^C<"); |
| print_tree(printfile, tp->data.child.left, depth+4); |
| fprintf(printfile, ">"); |
| break; |
| |
| case EX_NEG: |
| fprintf(printfile, "-<"); |
| print_tree(printfile, tp->data.child.left, depth+4); |
| fputc('>', printfile); |
| break; |
| |
| case EX_ERR: |
| fprintf(printfile, "{expression error}"); |
| if(tp->data.child.left) |
| { |
| fputc('<', printfile); |
| print_tree(printfile, tp->data.child.left, depth+4); |
| fputc('>', printfile); |
| } |
| break; |
| |
| case EX_ADD: |
| fputc('<', printfile); |
| print_tree(printfile, tp->data.child.left, depth+4); |
| fputc('+', printfile); |
| print_tree(printfile, tp->data.child.right, depth+4); |
| fputc('>', printfile); |
| break; |
| |
| case EX_SUB: |
| fputc('<', printfile); |
| print_tree(printfile, tp->data.child.left, depth+4); |
| fputc('-', printfile); |
| print_tree(printfile, tp->data.child.right, depth+4); |
| fputc('>', printfile); |
| break; |
| |
| case EX_MUL: |
| fputc('<', printfile); |
| print_tree(printfile, tp->data.child.left, depth+4); |
| fputc('*', printfile); |
| print_tree(printfile, tp->data.child.right, depth+4); |
| fputc('>', printfile); |
| break; |
| |
| case EX_DIV: |
| fputc('<', printfile); |
| print_tree(printfile, tp->data.child.left, depth+4); |
| fputc('/', printfile); |
| print_tree(printfile, tp->data.child.right, depth+4); |
| fputc('>', printfile); |
| break; |
| |
| case EX_AND: |
| fputc('<', printfile); |
| print_tree(printfile, tp->data.child.left, depth+4); |
| fputc('&', printfile); |
| print_tree(printfile, tp->data.child.right, depth+4); |
| fputc('>', printfile); |
| break; |
| |
| case EX_OR: |
| fputc('<', printfile); |
| print_tree(printfile, tp->data.child.left, depth+4); |
| fputc('!', printfile); |
| print_tree(printfile, tp->data.child.right, depth+4); |
| fputc('>', printfile); |
| break; |
| } |
| |
| if(depth == 0) |
| fputc('\n', printfile); |
| } |
| |
| /* free_tree frees an expression tree. */ |
| |
| static void free_tree(EX_TREE *tp) |
| { |
| switch(tp->type) |
| { |
| case EX_UNDEFINED_SYM: |
| case EX_TEMP_SYM: |
| free(tp->data.symbol->label); |
| free(tp->data.symbol); |
| case EX_LIT: |
| case EX_SYM: |
| free(tp); |
| break; |
| |
| case EX_COM: |
| case EX_NEG: |
| free_tree(tp->data.child.left); |
| free(tp); |
| break; |
| |
| case EX_ERR: |
| if(tp->data.child.left) |
| free_tree(tp->data.child.left); |
| free(tp); |
| break; |
| |
| case EX_ADD: |
| case EX_SUB: |
| case EX_MUL: |
| case EX_DIV: |
| case EX_AND: |
| case EX_OR: |
| free_tree(tp->data.child.left); |
| free_tree(tp->data.child.right); |
| free(tp); |
| break; |
| } |
| } |
| |
| /* new_temp_sym allocates a new EX_TREE entry of type "TEMPORARY |
| SYMBOL" (slight semantic difference from "UNDEFINED"). */ |
| |
| static EX_TREE *new_temp_sym(char *label, SECTION *section, unsigned value) |
| { |
| SYMBOL *sym; |
| EX_TREE *tp; |
| |
| sym = memcheck(malloc(sizeof(SYMBOL))); |
| sym->label = memcheck(strdup(label)); |
| sym->flags = 0; |
| sym->stmtno = stmtno; |
| sym->next = NULL; |
| sym->section = section; |
| sym->value = value; |
| |
| tp = new_ex_tree(); |
| tp->type = EX_TEMP_SYM; |
| tp->data.symbol = sym; |
| |
| return tp; |
| } |
| |
| #define RELTYPE(tp) (((tp)->type == EX_SYM || (tp)->type == EX_TEMP_SYM) && \ |
| (tp)->data.symbol->section->flags & PSECT_REL) |
| |
| /* evaluate "evaluates" an EX_TREE, ideally trying to produce a |
| constant value, else a symbol plus an offset. */ |
| |
| static EX_TREE *evaluate(EX_TREE *tp, int undef) |
| { |
| EX_TREE *res; |
| char *cp = tp->cp; |
| |
| switch(tp->type) |
| { |
| case EX_SYM: |
| { |
| SYMBOL *sym = tp->data.symbol; |
| |
| /* Change some symbols to "undefined" */ |
| |
| if(undef) |
| { |
| int change = 0; |
| |
| #if 0 /* I'd prefer this behavior, but |
| MACRO.SAV is a bit too |
| primitive. */ |
| /* A temporary symbol defined later is "undefined." */ |
| if(!(sym->flags & PERMANENT) && sym->stmtno > stmtno) |
| change = 1; |
| #endif |
| |
| /* A global symbol with no assignment is "undefined." */ |
| /* Go figure. */ |
| if((sym->flags & (GLOBAL|DEFINITION)) == GLOBAL) |
| change = 1; |
| |
| if(change) |
| { |
| res = new_temp_sym(tp->data.symbol->label, |
| tp->data.symbol->section, tp->data.symbol->value); |
| res->type = EX_UNDEFINED_SYM; |
| break; |
| } |
| } |
| |
| /* Turn defined absolute symbol to a literal */ |
| if(!(sym->section->flags & PSECT_REL) && |
| (sym->flags & (GLOBAL|DEFINITION)) != GLOBAL && |
| sym->section->type != REGISTER) |
| { |
| res = new_ex_lit(sym->value); |
| break; |
| } |
| |
| /* Make a temp copy of any reference to "." since it might |
| change as complex relocatable expressions are written out |
| */ |
| if(strcmp(sym->label, ".") == 0) |
| { |
| res = new_temp_sym(".", sym->section, sym->value); |
| break; |
| } |
| |
| /* Copy other symbol reference verbatim. */ |
| res = new_ex_tree(); |
| res->type = EX_SYM; |
| res->data.symbol = tp->data.symbol; |
| res->cp = tp->cp; |
| break; |
| } |
| |
| case EX_LIT: |
| res = new_ex_tree(); |
| *res = *tp; |
| break; |
| |
| case EX_TEMP_SYM: |
| case EX_UNDEFINED_SYM: |
| /* Copy temp and undefined symbols */ |
| res = new_temp_sym(tp->data.symbol->label, |
| tp->data.symbol->section, |
| tp->data.symbol->value); |
| res->type = tp->type; |
| break; |
| |
| case EX_COM: |
| /* Complement */ |
| tp = evaluate(tp->data.child.left, undef); |
| if(tp->type == EX_LIT) |
| { |
| /* Complement the literal */ |
| res = new_ex_lit(~tp->data.lit); |
| free_tree(tp); |
| } |
| else |
| { |
| /* Copy verbatim. */ |
| res = new_ex_tree(); |
| res->type = EX_NEG; |
| res->cp = tp->cp; |
| res->data.child.left = tp; |
| } |
| |
| break; |
| |
| case EX_NEG: |
| tp = evaluate(tp->data.child.left, undef); |
| if(tp->type == EX_LIT) |
| { |
| /* negate literal */ |
| res = new_ex_lit((unsigned)-(int)tp->data.lit); |
| free_tree(tp); |
| } |
| else if(tp->type == EX_SYM || tp->type == EX_TEMP_SYM) |
| { |
| /* Make a temp sym with the negative value of the given |
| sym (this works for symbols within relocatable sections |
| too) */ |
| res = new_temp_sym("*TEMP", tp->data.symbol->section, |
| (unsigned)-(int)tp->data.symbol->value); |
| res->cp = tp->cp; |
| free_tree(tp); |
| } |
| else |
| { |
| /* Copy verbatim. */ |
| res = new_ex_tree(); |
| res->type = EX_NEG; |
| res->cp = tp->cp; |
| res->data.child.left = tp; |
| } |
| break; |
| |
| case EX_ERR: |
| /* Copy */ |
| res = ex_err(tp->data.child.left, tp->cp); |
| break; |
| |
| case EX_ADD: |
| { |
| EX_TREE *left, *right; |
| |
| left = evaluate(tp->data.child.left, undef); |
| right = evaluate(tp->data.child.right, undef); |
| |
| /* Both literals? Sum them and return result. */ |
| if(left->type == EX_LIT && right->type == EX_LIT) |
| { |
| res = new_ex_lit(left->data.lit + right->data.lit); |
| free_tree(left); |
| free_tree(right); |
| break; |
| } |
| |
| /* Commutative: A+x == x+A. |
| Simplify by putting the literal on the right */ |
| if(left->type == EX_LIT) |
| { |
| EX_TREE *temp = left; |
| left = right; |
| right = temp; |
| } |
| |
| if(right->type == EX_LIT && /* Anything plus 0 == itself */ |
| right->data.lit == 0) |
| { |
| res = left; |
| free_tree(right); |
| break; |
| } |
| |
| /* Relative symbol plus lit is replaced with a temp sym |
| holding the sum */ |
| if(RELTYPE(left) && right->type == EX_LIT) |
| { |
| SYMBOL *sym = left->data.symbol; |
| res = new_temp_sym("*ADD", sym->section, sym->value + |
| right->data.lit); |
| free_tree(left); |
| free_tree(right); |
| break; |
| } |
| |
| /* Associative: <A+x>+y == A+<x+y> */ |
| /* and if x+y is constant, I can do that math. */ |
| if(left->type == EX_ADD && right->type == EX_LIT) |
| { |
| EX_TREE *leftright = left->data.child.right; |
| if(leftright->type == EX_LIT) |
| { |
| /* Do the shuffle */ |
| res = left; |
| leftright->data.lit += right->data.lit; |
| free_tree(right); |
| break; |
| } |
| } |
| |
| /* Associative: <A-x>+y == A+<y-x> */ |
| /* and if y-x is constant, I can do that math. */ |
| if(left->type == EX_SUB && right->type == EX_LIT) |
| { |
| EX_TREE *leftright = left->data.child.right; |
| if(leftright->type == EX_LIT) |
| { |
| /* Do the shuffle */ |
| res = left; |
| leftright->data.lit = right->data.lit - leftright->data.lit; |
| free_tree(right); |
| break; |
| } |
| } |
| |
| /* Anything else returns verbatim */ |
| res = new_ex_tree(); |
| res->type = EX_ADD; |
| res->data.child.left = left; |
| res->data.child.right = right; |
| } |
| break; |
| |
| case EX_SUB: |
| { |
| EX_TREE *left, *right; |
| |
| left = evaluate(tp->data.child.left, undef); |
| right = evaluate(tp->data.child.right, undef); |
| |
| /* Both literals? Subtract them and return a lit. */ |
| if(left->type == EX_LIT && right->type == EX_LIT) |
| { |
| res = new_ex_lit(left->data.lit - right->data.lit); |
| free_tree(left); |
| free_tree(right); |
| break; |
| } |
| |
| if(right->type == EX_LIT && /* Symbol minus 0 == symbol */ |
| right->data.lit == 0) |
| { |
| res = left; |
| free_tree(right); |
| break; |
| } |
| |
| /* A relocatable minus an absolute - make a new temp sym |
| to represent that. */ |
| if(RELTYPE(left) && |
| right->type == EX_LIT) |
| { |
| SYMBOL *sym = left->data.symbol; |
| res = new_temp_sym("*SUB", sym->section, |
| sym->value - right->data.lit); |
| free_tree(left); |
| free_tree(right); |
| break; |
| } |
| |
| if(RELTYPE(left) && |
| RELTYPE(right) && |
| left->data.symbol->section == right->data.symbol->section) |
| { |
| /* Two defined symbols in the same psect. Resolve |
| their difference as a literal. */ |
| res = new_ex_lit(left->data.symbol->value - |
| right->data.symbol->value); |
| free_tree(left); |
| free_tree(right); |
| break; |
| } |
| |
| /* Associative: <A+x>-y == A+<x-y> */ |
| /* and if x-y is constant, I can do that math. */ |
| if(left->type == EX_ADD && right->type == EX_LIT) |
| { |
| EX_TREE *leftright = left->data.child.right; |
| if(leftright->type == EX_LIT) |
| { |
| /* Do the shuffle */ |
| res = left; |
| leftright->data.lit -= right->data.lit; |
| free_tree(right); |
| break; |
| } |
| } |
| |
| /* Associative: <A-x>-y == A-<x+y> */ |
| /* and if x+y is constant, I can do that math. */ |
| if(left->type == EX_SUB && right->type == EX_LIT) |
| { |
| EX_TREE *leftright = left->data.child.right; |
| if(leftright->type == EX_LIT) |
| { |
| /* Do the shuffle */ |
| res = left; |
| leftright->data.lit += right->data.lit; |
| free_tree(right); |
| break; |
| } |
| } |
| |
| /* Anything else returns verbatim */ |
| res = new_ex_tree(); |
| res->type = EX_SUB; |
| res->data.child.left = left; |
| res->data.child.right = right; |
| } |
| break; |
| |
| case EX_MUL: |
| { |
| EX_TREE *left, *right; |
| |
| left = evaluate(tp->data.child.left, undef); |
| right = evaluate(tp->data.child.right, undef); |
| |
| /* Can only multiply if both are literals */ |
| if(left->type == EX_LIT && right->type == EX_LIT) |
| { |
| res = new_ex_lit(left->data.lit * right->data.lit); |
| free_tree(left); |
| free_tree(right); |
| break; |
| } |
| |
| /* Commutative: A*x == x*A. |
| Simplify by putting the literal on the right */ |
| if(left->type == EX_LIT) |
| { |
| EX_TREE *temp = left; |
| left = right; |
| right = temp; |
| } |
| |
| if(right->type == EX_LIT && /* Symbol times 1 == symbol */ |
| right->data.lit == 1) |
| { |
| res = left; |
| free_tree(right); |
| break; |
| } |
| |
| if(right->type == EX_LIT && /* Symbol times 0 == 0 */ |
| right->data.lit == 0) |
| { |
| res = right; |
| free_tree(left); |
| break; |
| } |
| |
| /* Associative: <A*x>*y == A*<x*y> */ |
| /* If x*y is constant, I can do this math. */ |
| /* Is this safe? I will potentially be doing it */ |
| /* with greater accuracy than the target platform. */ |
| /* Hmmm. */ |
| |
| if(left->type == EX_MUL && right->type == EX_LIT) |
| { |
| EX_TREE *leftright = left->data.child.right; |
| if(leftright->type == EX_LIT) |
| { |
| /* Do the shuffle */ |
| res = left; |
| leftright->data.lit *= right->data.lit; |
| free_tree(right); |
| break; |
| } |
| } |
| |
| /* Anything else returns verbatim */ |
| res = new_ex_tree(); |
| res->type = EX_MUL; |
| res->data.child.left = left; |
| res->data.child.right = right; |
| } |
| break; |
| |
| case EX_DIV: |
| { |
| EX_TREE *left, *right; |
| |
| left = evaluate(tp->data.child.left, undef); |
| right = evaluate(tp->data.child.right, undef); |
| |
| /* Can only divide if both are literals */ |
| if(left->type == EX_LIT && right->type == EX_LIT) |
| { |
| res = new_ex_lit(left->data.lit / right->data.lit); |
| free_tree(left); |
| free_tree(right); |
| break; |
| } |
| |
| if(right->type == EX_LIT && /* Symbol divided by 1 == symbol */ |
| right->data.lit == 1) |
| { |
| res = left; |
| free_tree(right); |
| break; |
| } |
| |
| /* Anything else returns verbatim */ |
| res = new_ex_tree(); |
| res->type = EX_DIV; |
| res->data.child.left = left; |
| res->data.child.right = right; |
| } |
| break; |
| |
| case EX_AND: |
| { |
| EX_TREE *left, *right; |
| |
| left = evaluate(tp->data.child.left, undef); |
| right = evaluate(tp->data.child.right, undef); |
| |
| /* Operate if both are literals */ |
| if(left->type == EX_LIT && right->type == EX_LIT) |
| { |
| res = new_ex_lit(left->data.lit & right->data.lit); |
| free_tree(left); |
| free_tree(right); |
| break; |
| } |
| |
| /* Commutative: A&x == x&A. |
| Simplify by putting the literal on the right */ |
| if(left->type == EX_LIT) |
| { |
| EX_TREE *temp = left; |
| left = right; |
| right = temp; |
| } |
| |
| if(right->type == EX_LIT && /* Symbol AND 0 == 0 */ |
| right->data.lit == 0) |
| { |
| res = new_ex_lit(0); |
| free_tree(left); |
| free_tree(right); |
| break; |
| } |
| |
| if(right->type == EX_LIT && /* Symbol AND 0177777 == symbol */ |
| right->data.lit == 0177777) |
| { |
| res = left; |
| free_tree(right); |
| break; |
| } |
| |
| /* Anything else returns verbatim */ |
| res = new_ex_tree(); |
| res->type = EX_AND; |
| res->data.child.left = left; |
| res->data.child.right = right; |
| } |
| break; |
| |
| case EX_OR: |
| { |
| EX_TREE *left, *right; |
| |
| left = evaluate(tp->data.child.left, undef); |
| right = evaluate(tp->data.child.right, undef); |
| |
| /* Operate if both are literals */ |
| if(left->type == EX_LIT && right->type == EX_LIT) |
| { |
| res = new_ex_lit(left->data.lit | right->data.lit); |
| free_tree(left); |
| free_tree(right); |
| break; |
| } |
| |
| /* Commutative: A!x == x!A. |
| Simplify by putting the literal on the right */ |
| if(left->type == EX_LIT) |
| { |
| EX_TREE *temp = left; |
| left = right; |
| right = temp; |
| } |
| |
| if(right->type == EX_LIT && /* Symbol OR 0 == symbol */ |
| right->data.lit == 0) |
| { |
| res = left; |
| free_tree(right); |
| break; |
| } |
| |
| if(right->type == EX_LIT && /* Symbol OR 0177777 == 0177777 */ |
| right->data.lit == 0177777) |
| { |
| res = new_ex_lit(0177777); |
| free_tree(left); |
| free_tree(right); |
| break; |
| } |
| |
| /* Anything else returns verbatim */ |
| res = new_ex_tree(); |
| res->type = EX_OR; |
| res->data.child.left = left; |
| res->data.child.right = right; |
| } |
| break; |
| } |
| |
| res->cp = cp; |
| return res; |
| } |
| |
| /* |
| parse_expr - this gets called everywhere. It parses and evaluates |
| an arithmetic expression. |
| */ |
| |
| EX_TREE *parse_expr(char *cp, int undef) |
| { |
| EX_TREE *expr; |
| EX_TREE *value; |
| |
| expr = parse_binary(cp, 0, 0); /* Parse into a tree */ |
| value = evaluate(expr, undef); /* Perform the arithmetic */ |
| value->cp = expr->cp; /* Pointer to end of text is part of |
| the rootmost node */ |
| free_tree(expr); /* Discard parse in favor of |
| evaluation */ |
| |
| return value; |
| } |
| |
| /* free_addr_mode frees the storage consumed by an addr_mode */ |
| |
| static void free_addr_mode(ADDR_MODE *mode) |
| { |
| if(mode->offset) |
| free_tree(mode->offset); |
| mode->offset = NULL; |
| } |
| |
| /* Get the register indicated by the expression */ |
| |
| #define NO_REG 0777 |
| |
| static unsigned get_register(EX_TREE *expr) |
| { |
| unsigned reg; |
| |
| if(expr->type == EX_LIT && |
| expr->data.lit <= 7) |
| { |
| reg = expr->data.lit; |
| return reg; |
| } |
| |
| if(expr->type == EX_SYM && |
| expr->data.symbol->section->type == REGISTER) |
| { |
| reg = expr->data.symbol->value; |
| return reg; |
| } |
| |
| return NO_REG; |
| } |
| |
| /* get_mode - parse a general addressing mode. */ |
| |
| int get_mode(char *cp, char **endp, ADDR_MODE *mode) |
| { |
| EX_TREE *value; |
| |
| mode->offset = NULL; |
| mode->rel = 0; |
| mode->type = 0; |
| |
| cp = skipwhite(cp); |
| |
| /* @ means "indirect," sets bit 3 */ |
| if(*cp == '@') |
| { |
| cp++; |
| mode->type |= 010; |
| } |
| |
| /* Immediate modes #imm and @#imm */ |
| if(*cp == '#') |
| { |
| cp++; |
| mode->type |= 027; |
| mode->offset = parse_expr(cp, 0); |
| if(endp) |
| *endp = mode->offset->cp; |
| return TRUE; |
| } |
| |
| /* Check for -(Rn) */ |
| |
| if(*cp == '-') |
| { |
| char *tcp = skipwhite(cp + 1); |
| if(*tcp++ == '(') |
| { |
| unsigned reg; |
| /* It's -(Rn) */ |
| value = parse_expr(tcp, 0); |
| reg = get_register(value); |
| if(reg == NO_REG || |
| (tcp = skipwhite(value->cp), *tcp++ != ')')) |
| { |
| free_tree(value); |
| return FALSE; |
| } |
| mode->type |= 040 | reg; |
| if(endp) |
| *endp = tcp; |
| free_tree(value); |
| return TRUE; |
| } |
| } |
| |
| /* Check for (Rn) */ |
| if(*cp == '(') |
| { |
| char *tcp; |
| unsigned reg; |
| value = parse_expr(cp + 1, 0); |
| reg = get_register(value); |
| |
| if(reg == NO_REG || |
| (tcp = skipwhite(value->cp), *tcp++ != ')')) |
| { |
| free_tree(value); |
| return FALSE; |
| } |
| |
| tcp = skipwhite(tcp); |
| if(*tcp == '+') |
| { |
| tcp++; /* It's (Rn)+ */ |
| if(endp) |
| *endp = tcp; |
| mode->type |= 020 | reg; |
| free_tree(value); |
| return TRUE; |
| } |
| |
| if(mode->type == 010) /* For @(Rn) there's an implied 0 offset */ |
| { |
| mode->offset = new_ex_lit(0); |
| mode->type |= 060 | reg; |
| free_tree(value); |
| if(endp) |
| *endp = tcp; |
| return TRUE; |
| } |
| |
| mode->type |= 010 | reg; /* Mode 10 is register indirect as |
| in (Rn) */ |
| free_tree(value); |
| if(endp) |
| *endp = tcp; |
| return TRUE; |
| } |
| |
| /* Modes with an offset */ |
| |
| mode->offset = parse_expr(cp, 0); |
| |
| cp = skipwhite(mode->offset->cp); |
| |
| if(*cp == '(') |
| { |
| unsigned reg; |
| /* indirect register plus offset */ |
| value = parse_expr(cp+1, 0); |
| reg = get_register(value); |
| if(reg == NO_REG || |
| (cp = skipwhite(value->cp), *cp++ != ')')) |
| { |
| free_tree(value); |
| return FALSE; /* Syntax error in addressing mode */ |
| } |
| |
| mode->type |= 060 | reg; |
| |
| free_tree(value); |
| |
| if(endp) |
| *endp = cp; |
| return TRUE; |
| } |
| |
| /* Plain old expression. */ |
| |
| if(endp) |
| *endp = cp; |
| |
| /* It might be a register, though. */ |
| if(mode->offset->type == EX_SYM) |
| { |
| SYMBOL *sym = mode->offset->data.symbol; |
| if(sym->section->type == REGISTER) |
| { |
| free_tree(mode->offset); |
| mode->offset = NULL; |
| mode->type |= sym->value; |
| return TRUE; |
| } |
| } |
| |
| /* It's either 067 (PC-relative) or 037 (absolute) mode, depending */ |
| /* on user option. */ |
| |
| if(mode->type & 010) /* Have already noted indirection? */ |
| { |
| mode->type |= 067; /* If so, then PC-relative is the only |
| option */ |
| mode->rel = 1; /* Note PC-relative */ |
| } |
| else if(enabl_ama) /* User asked for absolute adressing? */ |
| { |
| mode->type |= 037; /* Give it to him. */ |
| } |
| else |
| { |
| mode->type |= 067; /* PC-relative */ |
| mode->rel = 1; /* Note PC-relative */ |
| } |
| |
| return TRUE; |
| } |
| |
| /* |
| implicit_gbl is a self-recursive routine that adds undefined symbols |
| to the "implicit globals" symbol table. |
| */ |
| |
| void implicit_gbl(EX_TREE *value) |
| { |
| if(pass) |
| return; /* Only do this in first pass */ |
| |
| if(!enabl_gbl) |
| return; /* Option not enabled, don't do it. */ |
| |
| switch(value->type) |
| { |
| case EX_UNDEFINED_SYM: |
| { |
| SYMBOL *sym; |
| if(!(value->data.symbol->flags & LOCAL)) /* Unless it's a |
| local symbol, */ |
| { |
| sym = add_sym(value->data.symbol->label, |
| 0, GLOBAL, &absolute_section, &implicit_st); |
| } |
| } |
| break; |
| case EX_LIT: |
| case EX_SYM: |
| return; |
| case EX_ADD: |
| case EX_SUB: |
| case EX_MUL: |
| case EX_DIV: |
| case EX_AND: |
| case EX_OR: |
| implicit_gbl(value->data.child.right); |
| /* falls into... */ |
| case EX_COM: |
| case EX_NEG: |
| implicit_gbl(value->data.child.left); |
| break; |
| case EX_ERR: |
| if(value->data.child.left) |
| implicit_gbl(value->data.child.left); |
| break; |
| } |
| } |
| |
| /* Done between the first and second passes */ |
| /* Migrates the symbols from the "implicit" table into the main table. */ |
| |
| static void migrate_implicit(void) |
| { |
| SYMBOL_ITER iter; |
| SYMBOL *isym, *sym; |
| |
| for(isym = first_sym(&implicit_st, &iter); |
| isym != NULL; |
| isym = next_sym(&implicit_st, &iter)) |
| { |
| sym = lookup_sym(isym->label, &symbol_st); |
| if(sym) |
| continue; // It's already in there. Great. |
| sym = add_sym(isym->label, isym->value, isym->flags, |
| isym->section, &symbol_st); |
| // Just one other thing - migrate the stmtno |
| sym->stmtno = isym->stmtno; |
| } |
| } |
| |
| static int express_sym_offset(EX_TREE *value, SYMBOL **sym, unsigned *offset) |
| { |
| implicit_gbl(value); /* Translate tree's undefined syms |
| into global syms */ |
| |
| /* Internally relocatable symbols will have been summed down into |
| EX_TEMP_SYM's. */ |
| |
| if(value->type == EX_SYM || |
| value->type == EX_TEMP_SYM) |
| { |
| *sym = value->data.symbol; |
| *offset = 0; |
| return 1; |
| } |
| |
| /* What remains is external symbols. */ |
| |
| if(value->type == EX_ADD) |
| { |
| EX_TREE *left = value->data.child.left; |
| EX_TREE *right = value->data.child.right; |
| if((left->type != EX_SYM && |
| left->type != EX_UNDEFINED_SYM) || |
| right->type != EX_LIT) |
| return 0; /* Failed. */ |
| *sym = left->data.symbol; |
| *offset = right->data.lit; |
| return 1; |
| } |
| |
| if(value->type == EX_SUB) |
| { |
| EX_TREE *left = value->data.child.left; |
| EX_TREE *right = value->data.child.right; |
| if((left->type != EX_SYM && |
| left->type != EX_UNDEFINED_SYM) || |
| right->type != EX_LIT) |
| return 0; /* Failed. */ |
| *sym = left->data.symbol; |
| *offset = (unsigned)-(int)(right->data.lit); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| Translate an EX_TREE into a TEXT_COMPLEX suitable for encoding |
| into the object file. */ |
| |
| int complex_tree(TEXT_COMPLEX *tx, EX_TREE *tree) |
| { |
| switch(tree->type) |
| { |
| case EX_LIT: |
| text_complex_lit(tx, tree->data.lit); |
| return 1; |
| |
| case EX_TEMP_SYM: |
| case EX_SYM: |
| { |
| SYMBOL *sym = tree->data.symbol; |
| if((sym->flags & (GLOBAL|DEFINITION)) == GLOBAL) |
| { |
| text_complex_global(tx, sym->label); |
| } |
| else |
| { |
| text_complex_psect(tx, sym->section->sector, sym->value); |
| } |
| } |
| return 1; |
| |
| case EX_COM: |
| if(!complex_tree(tx, tree->data.child.left)) |
| return 0; |
| text_complex_com(tx); |
| return 1; |
| |
| case EX_NEG: |
| if(!complex_tree(tx, tree->data.child.left)) |
| return 0; |
| text_complex_neg(tx); |
| return 1; |
| |
| case EX_ADD: |
| if(!complex_tree(tx, tree->data.child.left)) |
| return 0; |
| if(!complex_tree(tx, tree->data.child.right)) |
| return 0; |
| text_complex_add(tx); |
| return 1; |
| |
| case EX_SUB: |
| if(!complex_tree(tx, tree->data.child.left)) |
| return 0; |
| if(!complex_tree(tx, tree->data.child.right)) |
| return 0; |
| text_complex_sub(tx); |
| return 1; |
| |
| case EX_MUL: |
| if(!complex_tree(tx, tree->data.child.left)) |
| return 0; |
| if(!complex_tree(tx, tree->data.child.right)) |
| return 0; |
| text_complex_mul(tx); |
| return 1; |
| |
| case EX_DIV: |
| if(!complex_tree(tx, tree->data.child.left)) |
| return 0; |
| if(!complex_tree(tx, tree->data.child.right)) |
| return 0; |
| text_complex_div(tx); |
| return 1; |
| |
| case EX_AND: |
| if(!complex_tree(tx, tree->data.child.left)) |
| return 0; |
| if(!complex_tree(tx, tree->data.child.right)) |
| return 0; |
| text_complex_and(tx); |
| return 1; |
| |
| case EX_OR: |
| if(!complex_tree(tx, tree->data.child.left)) |
| return 0; |
| if(!complex_tree(tx, tree->data.child.right)) |
| return 0; |
| text_complex_or(tx); |
| return 1; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* store a word which is represented by a complex expression. */ |
| |
| static void store_complex(STREAM *refstr, TEXT_RLD *tr, |
| int size, EX_TREE *value) |
| { |
| TEXT_COMPLEX tx; |
| |
| change_dot(tr, size); /* About to store - update DOT */ |
| |
| implicit_gbl(value); /* Turn undefined symbols into globals */ |
| |
| text_complex_begin(&tx); /* Open complex expression */ |
| |
| if(!complex_tree(&tx, value)) /* Translate */ |
| { |
| report(refstr, "Invalid expression\n"); |
| store_word(refstr, tr, size, 0); |
| } |
| else |
| { |
| list_word(refstr, DOT, 0, size, "C"); |
| text_complex_commit(tr, &DOT, size, &tx, 0); |
| } |
| } |
| |
| /* store_complex_displaced is the same as store_complex but uses the |
| "displaced" RLD code */ |
| |
| static void store_complex_displaced(STREAM *refstr, TEXT_RLD *tr, |
| int size, |
| EX_TREE *value) |
| { |
| TEXT_COMPLEX tx; |
| |
| change_dot(tr, size); |
| |
| implicit_gbl(value); /* Turn undefined symbols into globals */ |
| |
| text_complex_begin(&tx); |
| |
| if(!complex_tree(&tx, value)) |
| { |
| report(refstr, "Invalid expression\n"); |
| store_word(refstr, tr, size, 0); |
| } |
| else |
| { |
| list_word(refstr, DOT, 0, size, "C"); |
| text_complex_commit_displaced(tr, &DOT, size, &tx, 0); |
| } |
| } |
| |
| /* |
| mode_extension - writes the extension word required by an addressing |
| mode */ |
| |
| static void mode_extension(TEXT_RLD *tr, ADDR_MODE *mode, |
| STREAM *str) |
| { |
| EX_TREE *value = mode->offset; |
| SYMBOL *sym; |
| unsigned offset; |
| |
| /* Also frees the mode. */ |
| |
| if(value == NULL) |
| { |
| free_addr_mode(mode); |
| return; |
| } |
| |
| if(value->type == EX_LIT) |
| { |
| if(mode->rel) /* PC-relative? */ |
| store_displaced_word(str, tr, 2, value->data.lit); |
| else |
| store_word(str, tr, 2, value->data.lit); /* Just a |
| known |
| value. */ |
| } |
| else if(express_sym_offset(value, &sym, &offset)) |
| { |
| if((sym->flags & (GLOBAL|DEFINITION)) == GLOBAL) |
| { |
| /* Reference to a global symbol. */ |
| /* Global symbol plus offset */ |
| if(mode->rel) |
| store_global_displaced_offset_word(str, tr, |
| 2, offset, sym->label); |
| else |
| store_global_offset_word(str, tr, 2, offset, |
| sym->label); |
| } |
| else |
| { |
| /* Relative to non-external symbol. */ |
| if(current_pc->section == sym->section) |
| { |
| /* In the same section */ |
| if(mode->rel) |
| { |
| /* I can compute this myself. */ |
| store_word(str, tr, 2, |
| sym->value + offset - DOT - 2); |
| } |
| else |
| store_internal_word(str, tr, 2, sym->value+offset); |
| } |
| else |
| { |
| /* In a different section */ |
| if(mode->rel) |
| store_psect_displaced_offset_word(str, tr, 2, |
| sym->value+offset, sym->section->label); |
| else |
| store_psect_offset_word(str, tr, 2, |
| sym->value+offset, sym->section->label); |
| } |
| } |
| } |
| else |
| { |
| /* Complex relocation */ |
| |
| if(mode->rel) |
| store_complex_displaced(str, tr, 2, mode->offset); |
| else |
| store_complex(str, tr, 2, mode->offset); |
| } |
| |
| free_addr_mode(mode); |
| } |
| |
| /* eval_defined - take an EX_TREE and returns TRUE if the tree |
| represents "defined" symbols. */ |
| |
| int eval_defined(EX_TREE *value) |
| { |
| switch(value->type) |
| { |
| case EX_LIT: |
| return 1; |
| case EX_SYM: |
| return 1; |
| case EX_UNDEFINED_SYM: |
| return 0; |
| case EX_AND: |
| return eval_defined(value->data.child.left) && |
| eval_defined(value->data.child.right); |
| case EX_OR: |
| return eval_defined(value->data.child.left) || |
| eval_defined(value->data.child.right); |
| default: |
| return 0; |
| } |
| } |
| |
| /* eval_undefined - take an EX_TREE and returns TRUE if it represents |
| "undefined" symbols. */ |
| |
| int eval_undefined(EX_TREE *value) |
| { |
| switch(value->type) |
| { |
| case EX_UNDEFINED_SYM: |
| return 1; |
| case EX_SYM: |
| return 0; |
| case EX_AND: |
| return eval_undefined(value->data.child.left) && |
| eval_undefined(value->data.child.right); |
| case EX_OR: |
| return eval_undefined(value->data.child.left) || |
| eval_undefined(value->data.child.right); |
| default: |
| return 0; |
| } |
| } |
| |
| /* push_cond - a new conditional (.IF) block has been activated. Push |
| it's context. */ |
| |
| void push_cond(int ok, STREAM *str) |
| { |
| last_cond++; |
| assert(last_cond < MAX_CONDS); |
| conds[last_cond].ok = ok; |
| conds[last_cond].file = memcheck(strdup(str->name)); |
| conds[last_cond].line = str->line; |
| } |
| |
| /* |
| pop_cond - pop stacked conditionals. */ |
| |
| void pop_cond(int to) |
| { |
| while(last_cond > to) |
| { |
| free(conds[last_cond].file); |
| last_cond--; |
| } |
| } |
| |
| /* Parses a string from the input stream. */ |
| /* If not bracketed by <...> or ^/.../, then */ |
| /* the string is delimited by trailing comma or whitespace. */ |
| /* Allows nested <>'s */ |
| |
| char *getstring(char *cp, char **endp) |
| { |
| int len; |
| int start; |
| char *str; |
| |
| if(!brackrange(cp, &start, &len, endp)) |
| { |
| start = 0; |
| len = strcspn(cp, " \t\n,;"); |
| if(endp) |
| *endp = cp + len; |
| } |
| |
| str = memcheck(malloc(len + 1)); |
| memcpy(str, cp + start, len); |
| str[len] = 0; |
| |
| return str; |
| } |
| |
| /* Get what would be the operation code from the line. */ |
| /* Used to find the ends of streams without evaluating them, like |
| finding the closing .ENDM on a macro definition */ |
| |
| SYMBOL *get_op(char *cp, char **endp) |
| { |
| int local; |
| char *label; |
| SYMBOL *op; |
| |
| cp = skipwhite(cp); |
| if(EOL(*cp)) |
| return NULL; |
| |
| label = get_symbol(cp, &cp, &local); |
| if(label == NULL) |
| return NULL; /* No operation code. */ |
| |
| cp = skipwhite(cp); |
| if(*cp == ':') /* A label definition? */ |
| { |
| cp++; |
| if(*cp == ':') |
| cp++; /* Skip it */ |
| free(label); |
| label = get_symbol(cp, &cp, NULL); |
| if(label == NULL) |
| return NULL; |
| } |
| |
| op = lookup_sym(label, &system_st); |
| free(label); |
| |
| if(endp) |
| *endp = cp; |
| |
| return op; |
| } |
| |
| /* Here's where I pretend I'm a C++ compiler. :-/ */ |
| |
| /* *** derive a MACRO_STREAM from a BUFFER_STREAM with a few other args */ |
| |
| typedef struct macro_stream |
| { |
| BUFFER_STREAM bstr; /* Base class: buffer stream */ |
| int nargs; /* Add number-of-macro-arguments */ |
| int cond; /* Add saved conditional stack */ |
| } MACRO_STREAM; |
| |
| /* macro_stream_delete is called when a macro expansion is |
| exhausted. The unique behavior is to unwind any stacked |
| conditionals. This allows a nested .MEXIT to work. */ |
| |
| void macro_stream_delete(STREAM *str) |
| { |
| MACRO_STREAM *mstr = (MACRO_STREAM *)str; |
| pop_cond(mstr->cond); |
| buffer_stream_delete(str); |
| } |
| |
| STREAM_VTBL macro_stream_vtbl = { macro_stream_delete, |
| buffer_stream_gets, |
| buffer_stream_rewind }; |
| |
| STREAM *new_macro_stream(STREAM *refstr, BUFFER *buf, MACRO *mac, |
| ARG *args) |
| { |
| MACRO_STREAM *mstr = memcheck(malloc(sizeof(MACRO_STREAM))); |
| |
| { |
| char *name = memcheck(malloc(strlen(refstr->name) + 32)); |
| sprintf(name, "%s:%d->%s", refstr->name, refstr->line, |
| mac->sym.label); |
| buffer_stream_construct(&mstr->bstr, buf, name); |
| free(name); |
| } |
| |
| mstr->bstr.stream.vtbl = ¯o_stream_vtbl; |
| /* Count the args and save their number */ |
| for(mstr->nargs = 0; args; args = args->next, mstr->nargs++) |
| ; |
| mstr->cond = last_cond; |
| return &mstr->bstr.stream; |
| } |
| |
| /* read_body fetches the body of .MACRO, .REPT, .IRP, or .IRPC into a |
| BUFFER. */ |
| |
| void read_body(STACK *stack, BUFFER *gb, char *name, |
| int called) |
| { |
| int nest; |
| |
| /* Read the stream in until the end marker is hit */ |
| |
| /* Note: "called" says that this body is being pulled from a macro |
| library, and so under no circumstance should it be listed. */ |
| |
| nest = 1; |
| for(;;) |
| { |
| SYMBOL *op; |
| char *nextline; |
| char *cp; |
| |
| nextline = stack_gets(stack); /* Now read the line */ |
| if(nextline == NULL) /* End of file. */ |
| { |
| report(stack->top, "Macro body not closed\n"); |
| break; |
| } |
| |
| if(!called && (list_level - 1 + list_md) > 0) |
| { |
| list_flush(); |
| list_source(stack->top, nextline); |
| } |
| |
| op = get_op(nextline, &cp); |
| |
| if(op == NULL) /* Not a pseudo-op */ |
| { |
| buffer_append_line(gb, nextline); |
| continue; |
| } |
| if(op->section->type == PSEUDO) |
| { |
| if(op->value == P_MACRO || |
| op->value == P_REPT || |
| op->value == P_IRP || |
| op->value == P_IRPC) |
| nest++; |
| |
| if(op->value == P_ENDM || |
| op->value == P_ENDR) |
| { |
| nest--; |
| /* If there's a name on the .ENDM, then */ |
| /* close the body early if it matches the definition */ |
| if(name && op->value == P_ENDM) |
| { |
| cp = skipwhite(cp); |
| if(!EOL(*cp)) |
| { |
| char *label = get_symbol(cp, &cp, NULL); |
| if(label) |
| { |
| if(strcmp(label, name) == 0) |
| nest = 0; /* End of macro body. */ |
| free(label); |
| } |
| } |
| } |
| } |
| |
| if(nest == 0) |
| return; /* All done. */ |
| } |
| |
| buffer_append_line(gb, nextline); |
| } |
| } |
| |
| /* Diagnostic: dumpmacro dumps a macro definition to stdout. |
| I used this for debugging; it's not called at all right now, but |
| I hate to delete good code. */ |
| |
| void dumpmacro(MACRO *mac, FILE *fp) |
| { |
| ARG *arg; |
| |
| fprintf(fp, ".MACRO %s ", mac->sym.label); |
| |
| for(arg = mac->args; arg != NULL; arg = arg->next) |
| { |
| fputs(arg->label, fp); |
| if(arg->value) |
| { |
| fputc('=', fp); |
| fputs(arg->value, fp); |
| } |
| fputc(' ', fp); |
| } |
| fputc('\n', fp); |
| |
| fputs(mac->text->buffer, fp); |
| |
| fputs(".ENDM\n", fp); |
| } |
| |
| /* defmacro - define a macro. */ |
| /* Also used by .MCALL to pull macro definitions from macro libraries */ |
| |
| MACRO *defmacro(char *cp, STACK *stack, int called) |
| { |
| MACRO *mac; |
| ARG *arg, **argtail; |
| char *label; |
| |
| cp = skipwhite(cp); |
| label = get_symbol(cp, &cp, NULL); |
| if(label == NULL) |
| { |
| report(stack->top, "Invalid macro definition\n"); |
| return NULL; |
| } |
| |
| /* Allow redefinition of a macro; new definition replaces the old. */ |
| mac = (MACRO *)lookup_sym(label, ¯o_st); |
| if(mac) |
| { |
| /* Remove from the symbol table... */ |
| remove_sym(&mac->sym, ¯o_st); |
| free_macro(mac); |
| } |
| |
| mac = new_macro(label); |
| |
| add_table(&mac->sym, ¯o_st); |
| |
| argtail = &mac->args; |
| cp = skipdelim(cp); |
| |
| while(!EOL(*cp)) |
| { |
| arg = new_arg(); |
| if(arg->locsym = (*cp == '?')) /* special argument flag? */ |
| cp++; |
| arg->label = get_symbol(cp, &cp, NULL); |
| if(arg->label == NULL) |
| { |
| /* It turns out that I have code which is badly formatted |
| but which MACRO.SAV assembles. Sigh. */ |
| /* So, just quit defining arguments. */ |
| break; |
| #if 0 |
| report(str, "Illegal macro argument\n"); |
| remove_sym(&mac->sym, ¯o_st); |
| free_macro(mac); |
| return NULL; |
| #endif |
| } |
| |
| cp = skipwhite(cp); |
| if(*cp == '=') |
| { |
| /* Default substitution given */ |
| arg->value = getstring(cp+1, &cp); |
| if(arg->value == NULL) |
| { |
| report(stack->top, "Illegal macro argument\n"); |
| remove_sym(&mac->sym, ¯o_st); |
| free_macro(mac); |
| return NULL; |
| } |
| } |
| |
| /* Append to list of arguments */ |
| arg->next = NULL; |
| *argtail = arg; |
| argtail = &arg->next; |
| |
| cp = skipdelim(cp); |
| } |
| |
| /* Read the stream in until the end marker is hit */ |
| { |
| BUFFER *gb; |
| int levelmod = 0; |
| |
| gb = new_buffer(); |
| |
| if(!called && !list_md) |
| { |
| list_level--; |
| levelmod = 1; |
| } |
| |
| read_body(stack, gb, mac->sym.label, called); |
| |
| list_level += levelmod; |
| |
| if(mac->text != NULL) /* Discard old macro body */ |
| buffer_free(mac->text); |
| |
| mac->text = gb; |
| } |
| |
| return mac; |
| } |
| |
| /* find_arg - looks for an arg with the given name in the given |
| argument list */ |
| |
| static ARG *find_arg(ARG *arg, char *name) |
| { |
| for(; arg != NULL; arg = arg->next) |
| { |
| if(strcmp(arg->label, name) == 0) |
| return arg; |
| } |
| |
| return NULL; |
| } |
| |
| /* subst_args - given a BUFFER and a list of args, generate a new |
| BUFFER with argument replacement having taken place. */ |
| |
| BUFFER *subst_args(BUFFER *text, ARG *args) |
| { |
| char *in; |
| char *begin; |
| BUFFER *gb; |
| char *label; |
| ARG *arg; |
| |
| gb = new_buffer(); |
| |
| /* Blindly look for argument symbols in the input. */ |
| /* Don't worry about quotes or comments. */ |
| |
| for(begin = in = text->buffer; in < text->buffer + text->length;) |
| { |
| char *next; |
| |
| if(issym(*in)) |
| { |
| label = get_symbol(in, &next, NULL); |
| if(label) |
| { |
| if(arg = find_arg(args, label)) |
| { |
| /* An apostrophy may appear before or after the symbol. */ |
| /* In either case, remove it from the expansion. */ |
| |
| if(in > begin && in[-1] == '\'') |
| in --; /* Don't copy it. */ |
| if(*next == '\'') |
| next++; |
| |
| /* Copy prior characters */ |
| buffer_appendn(gb, begin, in-begin); |
| /* Copy replacement string */ |
| buffer_append_line(gb, arg->value); |
| in = begin = next; |
| --in; /* prepare for subsequent increment */ |
| } |
| free(label); |
| in = next; |
| } |
| else |
| in++; |
| } |
| else |
| in++; |
| } |
| |
| /* Append the rest of the text */ |
| buffer_appendn(gb, begin, in - begin); |
| |
| return gb; /* Done. */ |
| } |
| |
| /* eval_arg - the language allows an argument expression to be given |
| as "\expression" which means, evaluate the expression and |
| substitute the numeric value in the current radix. */ |
| |
| void eval_arg(STREAM *refstr, ARG *arg) |
| { |
| /* Check for value substitution */ |
| |
| if(arg->value[0] == '\\') |
| { |
| EX_TREE *value = parse_expr(arg->value+1, 0); |
| unsigned word = 0; |
| char temp[10]; |
| if(value->type != EX_LIT) |
| { |
| report(refstr, "Constant value required\n"); |
| } |
| else |
| word = value->data.lit; |
| |
| free_tree(value); |
| |
| /* printf can't do base 2. */ |
| my_ultoa(word & 0177777, temp, radix); |
| free(arg->value); |
| arg->value = memcheck(strdup(temp)); |
| } |
| |
| } |
| |
| /* expandmacro - return a STREAM containing the expansion of a macro */ |
| |
| STREAM *expandmacro(STREAM *refstr, MACRO *mac, char *cp) |
| { |
| ARG *arg, *args, *macarg; |
| char *label; |
| STREAM *str; |
| BUFFER *buf; |
| |
| args = NULL; |
| arg = NULL; |
| |
| /* Parse the arguments */ |
| |
| while(!EOL(*cp)) |
| { |
| char *nextcp; |
| /* Check for named argument */ |
| label = get_symbol(cp, &nextcp, NULL); |
| if(label && |
| (nextcp = skipwhite(nextcp), *nextcp == '=') && |
| (macarg = find_arg(mac->args, label))) |
| { |
| /* Check if I've already got a value for it */ |
| if(find_arg(args, label) != NULL) |
| { |
| report(refstr, "Duplicate submission of keyword " |
| "argument %s\n", label); |
| free(label); |
| free_args(args); |
| return NULL; |
| } |
| |
| arg = new_arg(); |
| arg->label = label; |
| nextcp = skipwhite(nextcp+1); |
| arg->value = getstring(nextcp, &nextcp); |
| } |
| else |
| { |
| if(label) |
| free(label); |
| |
| /* Find correct positional argument */ |
| |
| for(macarg = mac->args; macarg != NULL; macarg = macarg->next) |
| { |
| if(find_arg(args, macarg->label) == NULL) |
| break; /* This is the next positional arg */ |
| } |
| |
| if(macarg == NULL) |
| break; /* Don't pick up any more arguments. */ |
| |
| arg = new_arg(); |
| arg->label = memcheck(strdup(macarg->label)); /* Copy the name */ |
| arg->value = getstring(cp, &nextcp); |
| } |
| |
| arg->next = args; |
| args = arg; |
| |
| eval_arg(refstr, arg); /* Check for expression evaluation */ |
| |
| cp = skipdelim(nextcp); |
| } |
| |
| /* Now go back and fill in defaults */ |
| |
| { |
| int locsym; |
| if(last_lsb != lsb) |
| locsym = last_locsym = 32768; |
| else |
| locsym = last_locsym; |
| last_lsb = lsb; |
| |
| for(macarg = mac->args; macarg != NULL; macarg = macarg->next) |
| { |
| arg = find_arg(args, macarg->label); |
| if(arg == NULL) |
| { |
| arg = new_arg(); |
| arg->label = memcheck(strdup(macarg->label)); |
| if(macarg->locsym) |
| { |
| char temp[32]; |
| /* Here's where we generate local labels */ |
| sprintf(temp, "%d$", locsym++); |
| arg->value = memcheck(strdup(temp)); |
| } |
| else if(macarg->value) |
| { |
| arg->value = memcheck(strdup(macarg->value)); |
| } |
| else |
| arg->value = memcheck(strdup("")); |
| |
| arg->next = args; |
| args = arg; |
| } |
| } |
| |
| last_locsym = locsym; |
| } |
| |
| buf = subst_args(mac->text, args); |
| |
| str = new_macro_stream(refstr, buf, mac, args); |
| |
| free_args(args); |
| buffer_free(buf); |
| |
| return str; |
| } |
| |
| /* *** implement REPT_STREAM */ |
| |
| typedef struct rept_stream |
| { |
| BUFFER_STREAM bstr; |
| int count; /* The current repeat countdown */ |
| int savecond; /* conditional stack level at time of |
| expansion */ |
| } REPT_STREAM; |
| |
| /* rept_stream_gets gets a line from a repeat stream. At the end of |
| each count, the coutdown is decreated and the stream is reset to |
| it's beginning. */ |
| |
| char *rept_stream_gets(STREAM *str) |
| { |
| REPT_STREAM *rstr = (REPT_STREAM *)str; |
| char *cp; |
| |
| for(;;) |
| { |
| if((cp = buffer_stream_gets(str)) != NULL) |
| return cp; |
| |
| if(--rstr->count <= 0) |
| return NULL; |
| |
| buffer_stream_rewind(str); |
| } |
| } |
| |
| /* rept_stream_delete unwinds nested conditionals like .MEXIT does. */ |
| |
| void rept_stream_delete(STREAM *str) |
| { |
| REPT_STREAM *rstr = (REPT_STREAM *)str; |
| pop_cond(rstr->savecond); /* complete unterminated |
| conditionals */ |
| buffer_stream_delete(&rstr->bstr.stream); |
| } |
| |
| /* The VTBL */ |
| |
| STREAM_VTBL rept_stream_vtbl = { rept_stream_delete, |
| rept_stream_gets, |
| buffer_stream_rewind }; |
| |
| /* expand_rept is called when a .REPT is encountered in the input. */ |
| |
| STREAM *expand_rept(STACK *stack, char *cp) |
| { |
| EX_TREE *value; |
| BUFFER *gb; |
| REPT_STREAM *rstr; |
| int levelmod; |
| |
| value = parse_expr(cp, 0); |
| if(value->type != EX_LIT) |
| { |
| report(stack->top, ".REPT value must be constant\n"); |
| free_tree(value); |
| return NULL; |
| } |
| |
| gb = new_buffer(); |
| |
| levelmod = 0; |
| if(!list_md) |
| { |
| list_level--; |
| levelmod = 1; |
| } |
| |
| read_body(stack, gb, NULL, FALSE); |
| |
| list_level += levelmod; |
| |
| rstr = memcheck(malloc(sizeof(REPT_STREAM))); |
| { |
| char *name = memcheck(malloc(strlen(stack->top->name) + 32)); |
| sprintf(name, "%s:%d->.REPT", stack->top->name, stack->top->line); |
| buffer_stream_construct(&rstr->bstr, gb, name); |
| free(name); |
| } |
| |
| rstr->count = value->data.lit; |
| rstr->bstr.stream.vtbl = &rept_stream_vtbl; |
| rstr->savecond = last_cond; |
| |
| buffer_free(gb); |
| free_tree(value); |
| |
| return &rstr->bstr.stream; |
| } |
| |
| /* *** implement IRP_STREAM */ |
| |
| typedef struct irp_stream |
| { |
| BUFFER_STREAM bstr; |
| char *label; /* The substitution label */ |
| char *items; /* The substitution items (in source code |
| format) */ |
| int offset; /* Current offset into "items" */ |
| BUFFER *body; /* Original body */ |
| int savecond; /* Saved conditional level */ |
| } IRP_STREAM; |
| |
| /* irp_stream_gets expands the IRP as the stream is read. */ |
| /* Each time an iteration is exhausted, the next iteration is |
| generated. */ |
| |
| char *irp_stream_gets(STREAM *str) |
| { |
| IRP_STREAM *istr = (IRP_STREAM *)str; |
| char *cp; |
| BUFFER *buf; |
| ARG *arg; |
| |
| for(;;) |
| { |
| if((cp = buffer_stream_gets(str)) != NULL) |
| return cp; |
| |
| cp = istr->items + istr->offset; |
| |
| if(!*cp) |
| return NULL; /* No more items. EOF. */ |
| |
| arg = new_arg(); |
| arg->next = NULL; |
| arg->locsym = 0; |
| arg->label = istr->label; |
| arg->value = getstring(cp, &cp); |
| cp = skipdelim(cp); |
| istr->offset = cp - istr->items; |
| |
| eval_arg(str, arg); |
| buf = subst_args(istr->body, arg); |
| |
| free(arg->value); |
| free(arg); |
| buffer_stream_set_buffer(&istr->bstr, buf); |
| buffer_free(buf); |
| } |
| } |
| |
| /* irp_stream_delete - also pops the conditional stack */ |
| |
| void irp_stream_delete(STREAM *str) |
| { |
| IRP_STREAM *istr = (IRP_STREAM *)str; |
| |
| pop_cond(istr->savecond); /* complete unterminated |
| conditionals */ |
| |
| buffer_free(istr->body); |
| free(istr->items); |
| free(istr->label); |
| buffer_stream_delete(str); |
| } |
| |
| STREAM_VTBL irp_stream_vtbl = { irp_stream_delete, irp_stream_gets, |
| buffer_stream_rewind }; |
| |
| /* expand_irp is called when a .IRP is encountered in the input. */ |
| |
| STREAM *expand_irp(STACK *stack, char *cp) |
| { |
| char *label, *items; |
| BUFFER *gb; |
| int levelmod = 0; |
| IRP_STREAM *str; |
| |
| label = get_symbol(cp, &cp, NULL); |
| if(!label) |
| { |
| report(stack->top, "Illegal .IRP syntax\n"); |
| return NULL; |
| } |
| |
| cp = skipdelim(cp); |
| |
| items = getstring(cp, &cp); |
| if(!items) |
| { |
| report(stack->top, "Illegal .IRP syntax\n"); |
| free(label); |
| return NULL; |
| } |
| |
| gb = new_buffer(); |
| |
| levelmod = 0; |
| if(!list_md) |
| { |
| list_level--; |
| levelmod++; |
| } |
| |
| read_body(stack, gb, NULL, FALSE); |
| |
| list_level += levelmod; |
| |
| str = memcheck(malloc(sizeof(IRP_STREAM))); |
| { |
| char *name = memcheck(malloc(strlen(stack->top->name) + 32)); |
| sprintf(name, "%s:%d->.IRP", stack->top->name, stack->top->line); |
| buffer_stream_construct(&str->bstr, NULL, name); |
| free(name); |
| } |
| |
| str->bstr.stream.vtbl = &irp_stream_vtbl; |
| |
| str->body = gb; |
| str->items = items; |
| str->offset = 0; |
| str->label = label; |
| str->savecond = last_cond; |
| |
| return &str->bstr.stream; |
| } |
| |
| /* *** implement IRPC_STREAM */ |
| |
| typedef struct irpc_stream |
| { |
| BUFFER_STREAM bstr; |
| char *label; /* The substitution label */ |
| char *items; /* The substitution items (in source code |
| format) */ |
| int offset; /* Current offset in "items" */ |
| BUFFER *body; /* Original body */ |
| int savecond; /* conditional stack at invocation */ |
| } IRPC_STREAM; |
| |
| /* irpc_stream_gets - same comments apply as with irp_stream_gets, but |
| the substitution is character-by-character */ |
| |
| char *irpc_stream_gets(STREAM *str) |
| { |
| IRPC_STREAM *istr = (IRPC_STREAM *)str; |
| char *cp; |
| BUFFER *buf; |
| ARG *arg; |
| |
| for(;;) |
| { |
| if((cp = buffer_stream_gets(str)) != NULL) |
| return cp; |
| |
| cp = istr->items + istr->offset; |
| |
| if(!*cp) |
| return NULL; /* No more items. EOF. */ |
| |
| arg = new_arg(); |
| arg->next = NULL; |
| arg->locsym = 0; |
| arg->label = istr->label; |
| arg->value = memcheck(malloc(2)); |
| arg->value[0] = *cp++; |
| arg->value[1] = 0; |
| istr->offset = cp - istr->items; |
| |
| buf = subst_args(istr->body, arg); |
| |
| free(arg->value); |
| free(arg); |
| buffer_stream_set_buffer(&istr->bstr, buf); |
| buffer_free(buf); |
| } |
| } |
| |
| /* irpc_stream_delete - also pops contidionals */ |
| |
| void irpc_stream_delete(STREAM *str) |
| { |
| IRPC_STREAM *istr = (IRPC_STREAM *)str; |
| pop_cond(istr->savecond); /* complete unterminated |
| conditionals */ |
| buffer_free(istr->body); |
| free(istr->items); |
| free(istr->label); |
| buffer_stream_delete(str); |
| } |
| |
| STREAM_VTBL irpc_stream_vtbl = { irpc_stream_delete, |
| irpc_stream_gets, |
| buffer_stream_rewind }; |
| |
| /* expand_irpc - called when .IRPC is encountered in the input */ |
| |
| STREAM *expand_irpc(STACK *stack, char *cp) |
| { |
| char *label, *items; |
| BUFFER *gb; |
| int levelmod = 0; |
| IRPC_STREAM *str; |
| |
| label = get_symbol(cp, &cp, NULL); |
| if(!label) |
| { |
| report(stack->top, "Illegal .IRPC syntax\n"); |
| return NULL; |
| } |
| |
| cp = skipdelim(cp); |
| |
| items = getstring(cp, &cp); |
| if(!items) |
| { |
| report(stack->top, "Illegal .IRPC syntax\n"); |
| free(label); |
| return NULL; |
| } |
| |
| gb = new_buffer(); |
| |
| levelmod = 0; |
| if(!list_md) |
| { |
| list_level--; |
| levelmod++; |
| } |
| |
| read_body(stack, gb, NULL, FALSE); |
| |
| list_level += levelmod; |
| |
| str = memcheck(malloc(sizeof(IRPC_STREAM))); |
| { |
| char *name = memcheck(malloc(strlen(stack->top->name) + 32)); |
| sprintf(name, "%s:%d->.IRPC", stack->top->name, stack->top->line); |
| buffer_stream_construct(&str->bstr, NULL, name); |
| free(name); |
| } |
| |
| str->bstr.stream.vtbl = &irpc_stream_vtbl; |
| str->body = gb; |
| str->items = items; |
| str->offset = 0; |
| str->label = label; |
| str->savecond = last_cond; |
| |
| return &str->bstr.stream; |
| } |
| |
| /* go_section - sets current_pc to a new program section */ |
| |
| void go_section(TEXT_RLD *tr, SECTION *sect) |
| { |
| if(current_pc->section == sect) |
| return; /* This is too easy */ |
| |
| /* save current PC value for old section */ |
| current_pc->section->pc = DOT; |
| |
| /* Set current section and PC value */ |
| current_pc->section = sect; |
| DOT = sect->pc; |
| } |
| |
| /* |
| store_value - used to store a value represented by an expression |
| tree into the object file. Used by do_word and .ASCII/.ASCIZ. |
| */ |
| |
| static void store_value(STACK *stack, TEXT_RLD *tr, |
| int size, EX_TREE *value) |
| { |
| SYMBOL *sym; |
| unsigned offset; |
| |
| implicit_gbl(value); /* turn undefined symbols into globals */ |
| |
| if(value->type == EX_LIT) |
| { |
| store_word(stack->top, tr, size, value->data.lit); |
| } |
| else if(!express_sym_offset(value, &sym, &offset)) |
| { |
| store_complex(stack->top, tr, size, value); |
| } |
| else |
| { |
| if((sym->flags & (GLOBAL|DEFINITION)) == GLOBAL) |
| { |
| store_global_offset_word(stack->top, tr, size, |
| sym->value+offset, |
| sym->label); |
| } |
| else if(sym->section != current_pc->section) |
| { |
| store_psect_offset_word(stack->top, tr, size, |
| sym->value+offset, |
| sym->section->label); |
| } |
| else |
| { |
| store_internal_word(stack->top, tr, size, |
| sym->value+offset); |
| } |
| } |
| } |
| |
| /* do_word - used by .WORD, .BYTE, and implied .WORD. */ |
| |
| static int do_word(STACK *stack, TEXT_RLD *tr, char *cp, int size) |
| { |
| |
| if(size == 2 && (DOT & 1)) |
| { |
| report(stack->top, ".WORD on odd boundary\n"); |
| store_word(stack->top, tr, 1, 0); /* Align it */ |
| } |
| |
| do |
| { |
| EX_TREE *value = parse_expr(cp, 0); |
| |
| store_value(stack, tr, size, value); |
| |
| cp = skipdelim(value->cp); |
| |
| free_tree(value); |
| |
| } while(cp = skipdelim(cp), !EOL(*cp)); |
| |
| return 1; |
| } |
| |
| /* |
| check_branch - check branch distance. |
| */ |
| |
| static int check_branch(STACK *stack, unsigned offset, int min, int max) |
| { |
| int s_offset; |
| /* Sign-extend */ |
| if(offset & 0100000) |
| s_offset = offset | ~0177777; |
| else |
| s_offset = offset & 077777; |
| if(s_offset > max || s_offset < min) |
| { |
| char temp[16]; |
| /* printf can't do signed octal. */ |
| my_ltoa(s_offset, temp, 8); |
| report(stack->top, |
| "Branch target out of range (distance=%s)\n", |
| temp); |
| return 0; |
| } |
| return 1; |
| } |
| |
| /* assemble - read a line from the input stack, assemble it. */ |
| |
| /* This function is way way too large, because I just coded most of |
| the operation code and pseudo-op handling right in line. */ |
| |
| int assemble(STACK *stack, TEXT_RLD *tr) |
| { |
| char *cp; /* Parse character pointer */ |
| char *opcp; /* Points to operation mnemonic text */ |
| char *ncp; /* "next" cp */ |
| char *label; /* A label */ |
| char *line; /* The whole line */ |
| SYMBOL *op; /* The operation SYMBOL */ |
| int local; /* Whether a label is a local label or |
| not */ |
| |
| line = stack_gets(stack); |
| if(line == NULL) |
| return -1; /* Return code for EOF. */ |
| |
| cp = line; |
| |
| /* Frankly, I don't need to keep "line." But I found it quite |
| handy during debugging, to see what the whole operation was, |
| when I'm down to parsing the second operand and things aren't |
| going right. */ |
| |
| stmtno++; /* Increment statement number */ |
| |
| list_source(stack->top, line); /* List source */ |
| |
| if(suppressed) |
| { |
| /* Assembly is suppressed by unsatisfoed conditional. Look |
| for ending and enabling statements. */ |
| |
| op = get_op(cp, &cp); /* Look at operation code */ |
| |
| /* FIXME: this code will blindly look into .REM commentary and |
| find operation codes. Incidentally, so will read_body. */ |
| |
| if(op == NULL) |
| return 1; /* Not found. Don't care. */ |
| if(op->section->type != PSEUDO) |
| return 1; /* Not a pseudo-op. */ |
| switch(op->value) |
| { |
| case P_IF: |
| case P_IFDF: |
| suppressed++; /* Nested. Suppressed. */ |
| break; |
| case P_IFTF: |
| if(suppressed == 1) /* Reduce suppression from 1 to 0. */ |
| suppressed = 0; |
| break; |
| case P_IFF: |
| if(suppressed == 1) /* Can reduce suppression from 1 to 0. */ |
| { |
| if(!conds[last_cond].ok) |
| suppressed = 0; |
| } |
| break; |
| case P_IFT: |
| if(suppressed == 1) /* Can reduce suppression from 1 to 0. */ |
| { |
| if(conds[last_cond].ok) |
| suppressed = 0; |
| } |
| break; |
| case P_ENDC: |
| suppressed--; /* Un-nested. */ |
| if(suppressed == 0) |
| pop_cond(last_cond-1); /* Re-enabled. */ |
| break; |
| } |
| return 1; |
| } |
| |
| /* The line may begin with "label<ws>:[:]" */ |
| |
| opcp = cp; |
| if((label = get_symbol(cp, &ncp, &local)) != NULL) |
| { |
| int flag = PERMANENT|DEFINITION|local; |
| SYMBOL *sym; |
| |
| ncp = skipwhite(ncp); |
| if(*ncp == ':') /* Colon, for symbol definition? */ |
| { |
| ncp++; |
| /* maybe it's a global definition */ |
| if(*ncp == ':') |
| { |
| flag |= GLOBAL; /* Yes, include global flag */ |
| ncp++; |
| } |
| |
| sym = add_sym(label, DOT, flag, current_pc->section, &symbol_st); |
| cp = ncp; |
| |
| if(sym == NULL) |
| report(stack->top, "Illegal symbol definition %s\n", label); |
| |
| free(label); |
| |
| /* See if local symbol block should be incremented */ |
| if(!enabl_lsb && !local) |
| lsb++; |
| |
| cp = skipwhite(ncp); |
| opcp = cp; |
| label = get_symbol(cp, &ncp, NULL); /* Now, get what follows */ |
| } |
| } |
| |
| /* PSEUDO P_IIF jumps here. */ |
| reassemble: |
| cp = skipwhite(cp); |
| |
| if(EOL(*cp)) |
| return 1; /* It's commentary. All done. */ |
| |
| if(label) /* Something looks like a label. */ |
| { |
| /* detect assignment */ |
| |
| ncp = skipwhite(ncp); /* The pointer to the text that |
| follows the symbol */ |
| |
| if(*ncp == '=') |
| { |
| unsigned flags; |
| EX_TREE *value; |
| SYMBOL *sym; |
| |
| cp = ncp; |
| |
| /* Symbol assignment. */ |
| |
| flags = DEFINITION|local; |
| cp++; |
| if(*cp == '=') |
| { |
| flags |= GLOBAL; /* Global definition */ |
| cp++; |
| } |
| if(*cp == ':') |
| { |
| flags |= PERMANENT; |
| cp++; |
| } |
| |
| cp = skipwhite(cp); |
| |
| value = parse_expr(cp, 0); |
| |
| /* Special code: if the symbol is the program counter, |
| this is harder. */ |
| |
| if(strcmp(label, ".") == 0) |
| { |
| if(current_pc->section->flags & PSECT_REL) |
| { |
| SYMBOL *sym; |
| unsigned offset; |
| |
| /* Express the given expression as a symbol and an |
| offset. The symbol must not be global, the |
| section must = current. */ |
| |
| if(!express_sym_offset(value, &sym, &offset)) |
| { |
| report(stack->top, "Illegal ORG\n"); |
| } |
| else if((sym->flags & (GLOBAL|DEFINITION)) == GLOBAL) |
| { |
| report(stack->top, |
| "Can't ORG to external location\n"); |
| } |
| else if(sym->flags & UNDEFINED) |
| { |
| report(stack->top, "Can't ORG to undefined sym\n"); |
| } |
| else if(sym->section != current_pc->section) |
| { |
| report(stack->top, |
| "Can't ORG to alternate section " |
| "(use PSECT)\n"); |
| } |
| else |
| { |
| DOT = sym->value + offset; |
| list_value(stack->top, DOT); |
| change_dot(tr, 0); |
| } |
| } |
| else |
| { |
| /* If the current section is absolute, the value |
| must be a literal */ |
| if(value->type != EX_LIT) |
| { |
| report(stack->top, |
| "Can't ORG to non-absolute location\n"); |
| free_tree(value); |
| free(label); |
| return 0; |
| } |
| DOT = value->data.lit; |
| list_value(stack->top, DOT); |
| change_dot(tr, 0); |
| } |
| free_tree(value); |
| free(label); |
| return 1; |
| } |
| |
| /* regular symbols */ |
| if(value->type == EX_LIT) |
| { |
| sym = add_sym(label, value->data.lit, |
| flags, &absolute_section, &symbol_st); |
| } |
| else if(value->type == EX_SYM || |
| value->type == EX_TEMP_SYM) |
| { |
| sym = add_sym(label, value->data.symbol->value, |
| flags, value->data.symbol->section, &symbol_st); |
| } |
| else |
| { |
| report(stack->top, "Complex expression cannot be assigned " |
| "to a symbol\n"); |
| |
| if(!pass) |
| { |
| /* This may work better in pass 2 - something in |
| RT-11 monitor needs the symbol to apear to be |
| defined even if I can't resolve it's value. */ |
| sym = add_sym(label, 0, UNDEFINED, |
| &absolute_section, &symbol_st); |
| } |
| else |
| sym = NULL; |
| } |
| |
| if(sym != NULL) |
| list_value(stack->top, sym->value); |
| |
| free_tree(value); |
| free(label); |
| |
| return sym != NULL; |
| } |
| |
| /* Try to resolve macro */ |
| |
| op = lookup_sym(label, ¯o_st); |
| if(op && |
| op->stmtno < stmtno) |
| { |
| STREAM *macstr; |
| |
| free(label); |
| |
| macstr = expandmacro(stack->top, (MACRO *)op, ncp); |
| |
| stack_push(stack, macstr); /* Push macro expansion |
| onto input stream */ |
| |
| return 1; |
| } |
| |
| /* Try to resolve instruction or pseudo */ |
| op = lookup_sym(label, &system_st); |
| if(op) |
| { |
| cp = ncp; |
| |
| free(label); /* Don't need this hanging around anymore */ |
| |
| switch(op->section->type) |
| { |
| case PSEUDO: |
| switch(op->value) |
| { |
| case P_ENDR: |
| case P_ENDM: |
| case P_SBTTL: |
| case P_LIST: |
| case P_NLIST: |
| case P_PRINT: |
| return 1; /* Accepted, ignored. (An obvious |
| need: get assembly listing |
| controls working. ) */ |
| |
| case P_IDENT: |
| { |
| char endc[6]; |
| int len; |
| |
| cp = skipwhite(cp); |
| endc[0] = *cp++; |
| endc[1] = '\n'; |
| endc[2] = 0; |
| len = strcspn(cp, endc); |
| if(len > 6) |
| len = 6; |
| |
| if(ident) /* An existing ident? */ |
| free(ident); /* Discard it. */ |
| |
| ident = memcheck(malloc(len + 1)); |
| memcpy(ident, cp, len); |
| ident[len] = 0; |
| upcase(ident); |
| |
| return 1; |
| } |
| |
| case P_RADIX: |
| { |
| int old_radix = radix; |
| radix = strtoul(cp, &cp, 10); |
| if(radix != 8 && radix != 10 && radix != 16 && |
| radix != 2) |
| { |
| radix = old_radix; |
| report(stack->top, "Illegal radix\n"); |
| return 0; |
| } |
| return 1; |
| } |
| |
| case P_FLT4: |
| case P_FLT2: |
| { |
| int ok = 1; |
| |
| while(!EOL(*cp)) |
| { |
| unsigned flt[4]; |
| if(parse_float(cp, &cp, |
| (op->value == P_FLT4 ? 4 : 2), |
| flt)) |
| { |
| /* Store the word values */ |
| store_word(stack->top, tr, 2, flt[0]); |
| store_word(stack->top, tr, 2, flt[1]); |
| if(op->value == P_FLT4) |
| { |
| store_word(stack->top, tr, |
| 2, flt[2]); |
| store_word(stack->top, tr, |
| 2, flt[3]); |
| } |
| } |
| else |
| { |
| report(stack->top, |
| "Bad floating point format\n"); |
| ok = 0; |
| } |
| cp = skipdelim(cp); |
| } |
| return ok; |
| } |
| |
| case P_ERROR: |
| report(stack->top, "%.*s\n", strcspn(cp, "\n"), cp); |
| return 0; |
| |
| case P_SAVE: |
| sect_sp++; |
| sect_stack[sect_sp] = current_pc->section; |
| return 1; |
| |
| case P_RESTORE: |
| if(sect_sp < 0) |
| { |
| report(stack->top, "No saved section for .RESTORE\n"); |
| return 0; |
| } |
| else |
| { |
| go_section(tr, sect_stack[sect_sp]); |
| sect_sp++; |
| } |
| return 1; |
| |
| case P_NARG: |
| { |
| STREAM *str; |
| MACRO_STREAM *mstr; |
| int local; |
| |
| label = get_symbol(cp, &cp, &local); |
| |
| if(label == NULL) |
| { |
| report(stack->top, "Bad .NARG syntax\n"); |
| return 0; |
| } |
| |
| /* Walk up the stream stack to find the |
| topmost macro stream */ |
| for(str = stack->top; |
| str != NULL && |
| str->vtbl != ¯o_stream_vtbl; |
| str = str->next) |
| ; |
| |
| if(!str) |
| { |
| report(str, ".NARG not within macro expansion\n"); |
| free(label); |
| return 0; |
| } |
| |
| mstr = (MACRO_STREAM *)str; |
| |
| add_sym(label, mstr->nargs, DEFINITION|local, |
| &absolute_section, &symbol_st); |
| free(label); |
| return 1; |
| } |
| |
| case P_NCHR: |
| { |
| char *string; |
| int local; |
| label = get_symbol(cp, &cp, &local); |
| |
| if(label == NULL) |
| { |
| report(stack->top, "Bad .NCHR syntax\n"); |
| return 0; |
| } |
| |
| cp = skipdelim(cp); |
| |
| string = getstring(cp, &cp); |
| |
| add_sym(label, strlen(string), |
| DEFINITION|local, |
| &absolute_section, &symbol_st); |
| free(label); |
| free(string); |
| return 1; |
| } |
| |
| case P_NTYPE: |
| { |
| ADDR_MODE mode; |
| int local; |
| |
| label = get_symbol(cp, &cp, &local); |
| if(label == NULL) |
| { |
| report(stack->top, "Bad .NTYPE syntax\n"); |
| return 0; |
| } |
| |
| cp = skipdelim(cp); |
| |
| if(!get_mode(cp, &cp, &mode)) |
| { |
| report(stack->top, |
| "Bad .NTYPE addressing mode\n"); |
| free(label); |
| return 0; |
| } |
| |
| add_sym(label, mode.type, DEFINITION|local, |
| &absolute_section, &symbol_st); |
| free_addr_mode(&mode); |
| free(label); |
| |
| return 1; |
| } |
| |
| case P_INCLU: |
| { |
| char *name = getstring(cp, &cp); |
| STREAM *incl; |
| |
| if(name == NULL) |
| { |
| report(stack->top, "Bad .INCLUDE file name\n"); |
| return 0; |
| } |
| |
| incl = new_file_stream(name); |
| if(incl == NULL) |
| { |
| report(stack->top, |
| "Unable to open .INCLUDE file %s\n", name); |
| free(name); |
| return 0; |
| } |
| |
| free(name); |
| |
| stack_push(stack, incl); |
| |
| return 1; |
| } |
| |
| case P_REM: |
| { |
| char quote[4]; |
| /* Read and discard lines until one with a |
| closing quote */ |
| |
| cp = skipwhite(cp); |
| quote[0] = *cp++; |
| quote[1] = '\n'; |
| quote[2] = 0; |
| |
| for(;;) |
| { |
| cp += strcspn(cp, quote); |
| if(*cp == quote[0]) |
| break; /* Found closing quote */ |
| cp = stack_gets(stack); /* Read next input line */ |
| if(cp == NULL) |
| break; /* EOF */ |
| } |
| } |
| return 1; |
| |
| case P_IRP: |
| { |
| STREAM *str = expand_irp(stack, cp); |
| if(str) |
| stack_push(stack, str); |
| return str != NULL; |
| } |
| |
| case P_IRPC: |
| { |
| STREAM *str = expand_irpc(stack, cp); |
| if(str) |
| stack_push(stack, str); |
| return str != NULL; |
| } |
| |
| case P_MCALL: |
| { |
| STREAM *macstr; |
| BUFFER *macbuf; |
| char *maccp; |
| int saveline; |
| MACRO *mac; |
| int i; |
| char macfile[FILENAME_MAX]; |
| char hitfile[FILENAME_MAX]; |
| |
| for(;;) |
| { |
| cp = skipdelim(cp); |
| |
| if(EOL(*cp)) |
| return 1; |
| |
| label = get_symbol(cp, &cp, NULL); |
| if(!label) |
| { |
| report(stack->top, "Illegal .MCALL format\n"); |
| return 0; |
| } |
| |
| /* See if that macro's already defined */ |
| if(lookup_sym(label, ¯o_st)) |
| { |
| free(label); /* Macro already |
| registered. No |
| prob. */ |
| cp = skipdelim(cp); |
| continue; |
| } |
| |
| /* Find the macro in the list of included |
| macro libraries */ |
| macbuf = NULL; |
| for(i = 0; i < nr_mlbs; i++) |
| { |
| if((macbuf = mlb_entry(mlbs[i], |
| label)) != NULL) |
| break; |
| } |
| if(macbuf != NULL) |
| { |
| macstr = new_buffer_stream(macbuf, label); |
| buffer_free(macbuf); |
| } |
| else |
| { |
| strncpy(macfile, label, sizeof(macfile)); |
| strncat(macfile, ".MAC", sizeof(macfile) - strlen(macfile)); |
| my_searchenv(macfile, "MCALL", hitfile, sizeof(hitfile)); |
| if(hitfile[0]) |
| macstr = new_file_stream(hitfile); |
| } |
| |
| if(macstr != NULL) |
| { |
| for(;;) |
| { |
| char *mlabel; |
| maccp = macstr->vtbl->gets(macstr); |
| if(maccp == NULL) |
| break; |
| mlabel = get_symbol(maccp, &maccp, NULL); |
| if(mlabel == NULL) |
| continue; |
| op = lookup_sym(mlabel, &system_st); |
| free(mlabel); |
| if(op == NULL) |
| continue; |
| if(op->value == P_MACRO) |
| break; |
| } |
| |
| if(maccp != NULL) |
| { |
| STACK macstack = { macstr }; |
| int savelist = list_level; |
| saveline = stmtno; |
| list_level = -1; |
| mac = defmacro(maccp, &macstack, TRUE); |
| if(mac == NULL) |
| { |
| report(stack->top, |
| "Failed to define macro " |
| "called %s\n", |
| label); |
| } |
| |
| stmtno = saveline; |
| list_level = savelist; |
| } |
| |
| macstr->vtbl->delete(macstr); |
| } |
| else |
| report(stack->top, |
| "MACRO %s not found\n", label); |
| |
| free(label); |
| } |
| } |
| return 1; |
| |
| case P_MACRO: |
| { |
| MACRO *mac = defmacro(cp, stack, FALSE); |
| return mac != NULL; |
| } |
| |
| case P_MEXIT: |
| { |
| STREAM *macstr; |
| |
| /* Pop a stream from the input. */ |
| /* It must be the first stream, and it must be */ |
| /* a macro, rept, irp, or irpc. */ |
| macstr = stack->top; |
| if(macstr->vtbl != ¯o_stream_vtbl && |
| macstr->vtbl != &rept_stream_vtbl && |
| macstr->vtbl != &irp_stream_vtbl && |
| macstr->vtbl != &irpc_stream_vtbl) |
| { |
| report(stack->top, ".MEXIT not within a macro\n"); |
| return 0; |
| } |
| |
| /* and finally, pop the macro */ |
| stack_pop(stack); |
| |
| return 1; |
| } |
| |
| case P_REPT: |
| { |
| STREAM *reptstr = expand_rept(stack, cp); |
| if(reptstr) |
| stack_push(stack, reptstr); |
| return reptstr != NULL; |
| } |
| |
| case P_ENABL: |
| |
| /* FIXME - add all the rest of the options. */ |
| while(!EOL(*cp)) |
| { |
| label = get_symbol(cp, &cp, NULL); |
| if(strcmp(label, "AMA") == 0) |
| enabl_ama = 1; |
| else if(strcmp(label, "LSB") == 0) |
| { |
| enabl_lsb = 1; |
| lsb++; |
| } |
| else if(strcmp(label, "GBL") == 0) |
| enabl_gbl = 1; |
| free(label); |
| cp = skipdelim(cp); |
| } |
| return 1; |
| |
| case P_DSABL: |
| |
| /* FIXME Ditto as for .ENABL */ |
| while(!EOL(*cp)) |
| { |
| label = get_symbol(cp, &cp, NULL); |
| if(strcmp(label, "AMA") == 0) |
| enabl_ama = 0; |
| else if(strcmp(label, "LSB") == 0) |
| enabl_lsb = 0; |
| else if(strcmp(label, "GBL") == 0) |
| enabl_gbl = 0; |
| free(label); |
| cp = skipdelim(cp); |
| } |
| return 1; |
| |
| case P_LIMIT: |
| store_limits(stack->top, tr); |
| return 1; |
| |
| case P_TITLE: |
| /* accquire module name */ |
| if(module_name != NULL) |
| { |
| free(module_name); |
| } |
| module_name = get_symbol(cp, &cp, NULL); |
| return 1; |
| |
| case P_END: |
| /* Accquire transfer address */ |
| cp = skipwhite(cp); |
| if(!EOL(*cp)) |
| { |
| if(xfer_address) |
| free_tree(xfer_address); |
| xfer_address = parse_expr(cp, 0); |
| } |
| return 1; |
| |
| case P_IFDF: |
| opcp = skipwhite(opcp); |
| cp = opcp + 3; /* Point cp at the "DF" or |
| "NDF" part */ |
| /* Falls into... */ |
| case P_IIF: |
| case P_IF: |
| { |
| EX_TREE *value; |
| int ok; |
| |
| label = get_symbol(cp, &cp, NULL); /* Get condition */ |
| cp = skipdelim(cp); |
| |
| if(strcmp(label, "DF") == 0) |
| { |
| value = parse_expr(cp, 1); |
| cp = value->cp; |
| ok = eval_defined(value); |
| free_tree(value); |
| } |
| else if(strcmp(label, "NDF") == 0) |
| { |
| value = parse_expr(cp, 1); |
| cp = value->cp; |
| ok = eval_undefined(value); |
| free_tree(value); |
| } |
| else if(strcmp(label, "B") == 0) |
| { |
| char *thing; |
| cp = skipwhite(cp); |
| if(!EOL(*cp)) |
| thing = getstring(cp, &cp); |
| else |
| thing = memcheck(strdup("")); |
| ok = (*thing == 0); |
| free(thing); |
| } |
| else if(strcmp(label, "NB") == 0) |
| { |
| char *thing; |
| cp = skipwhite(cp); |
| if(!EOL(*cp)) |
| thing = getstring(cp, &cp); |
| else |
| thing = memcheck(strdup("")); |
| ok = (*thing != 0); |
| free(thing); |
| } |
| else if(strcmp(label, "IDN") == 0) |
| { |
| char *thing1, *thing2; |
| thing1 = getstring(cp, &cp); |
| cp = skipdelim(cp); |
| if(!EOL(*cp)) |
| thing2 = getstring(cp, &cp); |
| else |
| thing2 = memcheck(strdup("")); |
| ok = (strcmp(thing1, thing2) == 0); |
| free(thing1); |
| free(thing2); |
| } |
| else if(strcmp(label, "DIF") == 0) |
| { |
| char *thing1, *thing2; |
| thing1 = getstring(cp, &cp); |
| cp = skipdelim(cp); |
| if(!EOL(*cp)) |
| thing2 = getstring(cp, &cp); |
| else |
| thing2 = memcheck(strdup("")); |
| ok = (strcmp(thing1, thing2) != 0); |
| free(thing1); |
| free(thing2); |
| } |
| else |
| { |
| int sword; |
| unsigned uword; |
| EX_TREE *value = parse_expr(cp, 0); |
| |
| cp = value->cp; |
| |
| if(value->type != EX_LIT) |
| { |
| report(stack->top, "Bad .IF expression\n"); |
| list_value(stack->top, 0); |
| free_tree(value); |
| ok = FALSE; /* Pick something. */ |
| } |
| else |
| { |
| unsigned word; |
| /* Convert to signed and unsigned words */ |
| sword = value->data.lit & 0x7fff; |
| |
| /* FIXME I don't know if the following |
| is portable enough. */ |
| if(value->data.lit & 0x8000) |
| sword |= ~0xFFFF; /* Render negative */ |
| |
| /* Reduce unsigned value to 16 bits */ |
| uword = value->data.lit & 0xffff; |
| |
| if(strcmp(label, "EQ") == 0 || |
| strcmp(label, "Z") == 0) |
| ok = (uword == 0), word = uword; |
| else if(strcmp(label, "NE") == 0 || |
| strcmp(label, "NZ") == 0) |
| ok = (uword != 0), word = uword; |
| else if(strcmp(label, "GT") == 0 || |
| strcmp(label, "G") == 0) |
| ok = (sword > 0), word = sword; |
| else if(strcmp(label, "GE") == 0) |
| ok = (sword >= 0), word = sword; |
| else if(strcmp(label, "LT") == 0 || |
| strcmp(label, "L") == 0) |
| ok = (sword < 0), word = sword; |
| else if(strcmp(label, "LE") == 0) |
| ok = (sword <= 0), word = sword; |
| |
| list_value(stack->top, word); |
| |
| free_tree(value); |
| } |
| } |
| |
| free(label); |
| |
| if(op->value == P_IIF) |
| { |
| stmtno++; /* the second half is a |
| separate statement */ |
| if(ok) |
| { |
| /* The "immediate if" */ |
| /* Only slightly tricky. */ |
| cp = skipdelim(cp); |
| label = get_symbol(cp, &ncp, &local); |
| goto reassemble; |
| } |
| return 1; |
| } |
| |
| push_cond(ok, stack->top); |
| |
| if(!ok) |
| suppressed++; /* Assembly |
| suppressed |
| until .ENDC */ |
| } |
| return 1; |
| |
| case P_IFF: |
| if(last_cond < 0) |
| { |
| report(stack->top, "No conditional block active\n"); |
| return 0; |
| } |
| if(conds[last_cond].ok) /* Suppress if last cond |
| is true */ |
| suppressed++; |
| return 1; |
| |
| case P_IFT: |
| if(last_cond < 0) |
| { |
| report(stack->top, "No conditional block active\n"); |
| return 0; |
| } |
| if(!conds[last_cond].ok) /* Suppress if last cond |
| is false */ |
| suppressed++; |
| return 1; |
| |
| case P_IFTF: |
| if(last_cond < 0) |
| { |
| report(stack->top, "No conditional block active\n"); |
| return 0; |
| } |
| return 1; /* Don't suppress. */ |
| |
| case P_ENDC: |
| if(last_cond < 0) |
| { |
| report(stack->top, "No conditional block active\n"); |
| return 0; |
| } |
| |
| pop_cond(last_cond-1); |
| return 1; |
| |
| case P_EVEN: |
| if(DOT & 1) |
| { |
| list_word(stack->top, DOT, 0, 1, ""); |
| DOT++; |
| } |
| return 1; |
| |
| case P_ODD: |
| if(!(DOT & 1)) |
| { |
| list_word(stack->top, DOT, 0, 1, ""); |
| DOT++; |
| } |
| return 1; |
| |
| case P_ASECT: |
| go_section(tr, &absolute_section); |
| return 1; |
| |
| case P_CSECT: |
| case P_PSECT: |
| { |
| SYMBOL *sectsym; |
| SECTION *sect; |
| |
| label = get_symbol(cp, &cp, NULL); |
| if(label == NULL) |
| label = memcheck(strdup("")); /* Allow blank */ |
| |
| sectsym = lookup_sym(label, §ion_st); |
| if(sectsym) |
| { |
| sect = sectsym->section; |
| free(label); |
| } |
| else |
| { |
| sect = new_section(); |
| sect->label = label; |
| sect->flags = 0; |
| sect->pc = 0; |
| sect->size = 0; |
| sect->type = USER; |
| sections[sector++] = sect; |
| sectsym = add_sym(label, 0, 0, sect, §ion_st); |
| } |
| |
| if(op->value == P_PSECT) |
| sect->flags |= PSECT_REL; |
| else if(op->value == P_CSECT) |
| sect->flags |= PSECT_REL|PSECT_COM|PSECT_GBL; |
| |
| while(cp = skipdelim(cp), !EOL(*cp)) |
| { |
| /* Parse section options */ |
| label = get_symbol(cp, &cp, NULL); |
| if(strcmp(label, "ABS") == 0) |
| { |
| sect->flags &= ~PSECT_REL; /* Not relative */ |
| sect->flags |= PSECT_COM; /* implies common */ |
| } |
| else if(strcmp(label, "REL") == 0) |
| { |
| sect->flags |= PSECT_REL; /* Is relative */ |
| } |
| else if(strcmp(label, "SAV") == 0) |
| { |
| sect->flags |= PSECT_SAV; /* Is root */ |
| } |
| else if(strcmp(label, "OVR") == 0) |
| { |
| sect->flags |= PSECT_COM; /* Is common */ |
| } |
| else if(strcmp(label, "RW") == 0) |
| { |
| sect->flags &= ~PSECT_RO; /* Not read-only */ |
| } |
| else if(strcmp(label, "RO") == 0) |
| { |
| sect->flags |= PSECT_RO; /* Is read-only */ |
| } |
| else if(strcmp(label, "I") == 0) |
| { |
| sect->flags &= ~PSECT_DATA; /* Not data */ |
| } |
| else if(strcmp(label, "D") == 0) |
| { |
| sect->flags |= PSECT_DATA; /* data */ |
| } |
| else if(strcmp(label, "GBL") == 0) |
| { |
| sect->flags |= PSECT_GBL; /* Global */ |
| } |
| else if(strcmp(label, "LCL") == 0) |
| { |
| sect->flags &= ~PSECT_GBL; /* Local */ |
| } |
| else |
| { |
| report(stack->top, |
| "Unknown flag %s given to " |
| ".PSECT directive\n", label); |
| free(label); |
| return 0; |
| } |
| |
| free(label); |
| } |
| |
| go_section(tr, sect); |
| |
| return 1; |
| } /* end PSECT code */ |
| break; |
| |
| case P_WEAK: |
| case P_GLOBL: |
| { |
| SYMBOL *sym; |
| while(!EOL(*cp)) |
| { |
| /* Loop and make definitions for |
| comma-separated symbols */ |
| label = get_symbol(cp, &ncp, NULL); |
| if(label == NULL) |
| { |
| report(stack->top, |
| "Illegal .GLOBL/.WEAK " |
| "syntax\n"); |
| return 0; |
| } |
| |
| sym = lookup_sym(label, &symbol_st); |
| if(sym) |
| { |
| sym->flags |= |
| GLOBAL| |
| (op->value == P_WEAK ? WEAK : 0); |
| } |
| else |
| sym = add_sym(label, 0, |
| GLOBAL| |
| (op->value == P_WEAK ? WEAK : 0), |
| &absolute_section, &symbol_st); |
| |
| free(label); |
| cp = skipdelim(ncp); |
| } |
| } |
| return 1; |
| |
| case P_WORD: |
| { |
| /* .WORD might be followed by nothing, which |
| is an implicit .WORD 0 */ |
| if(EOL(*cp)) |
| { |
| if(DOT & 1) |
| { |
| report(stack->top, ".WORD on odd " |
| "boundary\n"); |
| DOT++; /* Fix it, too */ |
| } |
| store_word(stack->top, tr, 2, 0); |
| return 1; |
| } |
| else |
| return do_word(stack, tr, cp, 2); |
| } |
| |
| case P_BYTE: |
| if(EOL(*cp)) |
| { |
| /* Blank .BYTE. Same as .BYTE 0 */ |
| store_word(stack->top, tr, 1, 0); |
| return 1; |
| } |
| else |
| return do_word(stack, tr, cp, 1); |
| |
| case P_BLKW: |
| case P_BLKB: |
| { |
| EX_TREE *value = parse_expr(cp, 0); |
| int ok = 1; |
| if(value->type != EX_LIT) |
| { |
| report(stack->top, |
| "Argument to .BLKB/.BLKW " |
| "must be constant\n"); |
| ok = 0; |
| } |
| else |
| { |
| list_value(stack->top, DOT); |
| DOT += value->data.lit * |
| (op->value == P_BLKW ? 2 : 1); |
| change_dot(tr, 0); |
| } |
| free_tree(value); |
| return ok; |
| } |
| |
| case P_ASCIZ: |
| case P_ASCII: |
| { |
| EX_TREE *value; |
| |
| do |
| { |
| cp = skipwhite(cp); |
| if(*cp == '<' || *cp == '^') |
| { |
| /* A byte value */ |
| value = parse_expr(cp, 0); |
| cp = value->cp; |
| store_value(stack, tr, 1, value); |
| free_tree(value); |
| } |
| else |
| { |
| char quote = *cp++; |
| while(*cp && *cp != '\n' && *cp != quote) |
| { |
| store_word(stack->top, tr, 1, *cp++); |
| } |
| cp++; /* Skip closing quote */ |
| } |
| |
| cp = skipwhite(cp); |
| } while(!EOL(*cp)); |
| |
| if(op->value == P_ASCIZ) |
| { |
| store_word(stack->top, tr, 1, 0); |
| } |
| |
| return 1; |
| } |
| |
| case P_RAD50: |
| |
| if(DOT & 1) |
| { |
| report(stack->top, ".RAD50 on odd " |
| "boundary\n"); |
| DOT++; /* Fix it */ |
| } |
| |
| while(!EOL(*cp)) |
| { |
| char endstr[6]; |
| int len; |
| char *radstr; |
| char *radp; |
| |
| endstr[0] = *cp++; |
| endstr[1] = '\n'; |
| endstr[2] = 0; |
| |
| len = strcspn(cp, endstr); |
| radstr = memcheck(malloc(len + 1)); |
| memcpy(radstr, cp, len); |
| radstr[len] = 0; |
| cp += len; |
| if(*cp && *cp != '\n') |
| cp++; |
| for(radp = radstr; *radp;) |
| { |
| unsigned rad; |
| rad = rad50(radp, &radp); |
| store_word(stack->top, tr, 2, rad); |
| } |
| free(radstr); |
| |
| cp = skipwhite(cp); |
| } |
| return 1; |
| |
| default: |
| report(stack->top, "Unimplemented directive %s\n", |
| op->label); |
| return 0; |
| |
| } /* end switch (PSEUDO operation) */ |
| |
| case INSTRUCTION: |
| { |
| /* The PC must always be even. */ |
| if(DOT & 1) |
| { |
| report(stack->top, |
| "Instruction on odd address\n"); |
| DOT++; /* ...and fix it... */ |
| } |
| |
| switch(op->flags & OC_MASK) |
| { |
| case OC_NONE: |
| /* No operands. */ |
| store_word(stack->top, tr, 2, op->value); |
| return 1; |
| |
| case OC_MARK: |
| /* MARK, EMT, TRAP */ |
| { |
| EX_TREE *value; |
| unsigned word; |
| |
| cp = skipwhite(cp); |
| if(*cp == '#') |
| cp++; /* Allow the hash, but |
| don't require it */ |
| value = parse_expr(cp, 0); |
| if(value->type != EX_LIT) |
| { |
| report(stack->top, |
| "Instruction requires " |
| "simple literal operand\n"); |
| word = op->value; |
| } |
| else |
| { |
| word = op->value | value->data.lit; |
| } |
| |
| store_word(stack->top, tr, 2, word); |
| free_tree(value); |
| } |
| return 1; |
| |
| case OC_1GEN: |
| /* One general addressing mode */ |
| { |
| ADDR_MODE mode; |
| unsigned word; |
| |
| if(!get_mode(cp, &cp, &mode)) |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| return 0; |
| } |
| |
| if(op->value == 0100 && |
| (mode.type & 07) == 0) |
| { |
| report(stack->top, |
| "JMP Rn is illegal\n"); |
| /* But encode it anyway... */ |
| } |
| |
| /* Build instruction word */ |
| word = op->value | mode.type; |
| store_word(stack->top, tr, 2, word); |
| mode_extension(tr, &mode, stack->top); |
| } |
| return 1; |
| |
| case OC_2GEN: |
| /* Two general addressing modes */ |
| { |
| ADDR_MODE left, right; |
| unsigned word; |
| |
| if(!get_mode(cp, &cp, &left)) |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| return 0; |
| } |
| |
| if(*cp++ != ',') |
| { |
| report(stack->top, "Illegal syntax\n"); |
| free_addr_mode(&left); |
| return 0; |
| } |
| |
| if(!get_mode(cp, &cp, &right)) |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| free_addr_mode(&left); |
| return 0; |
| } |
| |
| /* Build instruction word */ |
| word = op->value | left.type << 6 | right.type; |
| store_word(stack->top, tr, 2, word); |
| mode_extension(tr, &left, stack->top); |
| mode_extension(tr, &right, stack->top); |
| } |
| return 1; |
| |
| case OC_BR: |
| /* branches */ |
| { |
| EX_TREE *value; |
| unsigned offset; |
| |
| value = parse_expr(cp, 0); |
| cp = value->cp; |
| |
| /* Relative PSECT or absolute? */ |
| if(current_pc->section->flags & PSECT_REL) |
| { |
| SYMBOL *sym; |
| |
| /* Can't branch unless I can |
| calculate the offset. */ |
| |
| /* You know, I *could* branch |
| between sections if I feed the |
| linker a complex relocation |
| expression to calculate the |
| offset. But I won't. */ |
| |
| if(!express_sym_offset(value, |
| &sym, |
| &offset) || |
| sym->section != current_pc->section) |
| { |
| report(stack->top, |
| "Bad branch target\n"); |
| store_word(stack->top, tr, |
| 2, op->value); |
| free_tree(value); |
| return 0; |
| } |
| |
| /* Compute the branch offset and |
| check for addressability */ |
| offset += sym->value; |
| offset -= DOT + 2; |
| } |
| else |
| { |
| if(value->type != EX_LIT) |
| { |
| report(stack->top, |
| "Bad branch target\n"); |
| store_word(stack->top, tr, |
| 2, op->value); |
| free_tree(value); |
| return 0; |
| } |
| |
| offset = value->data.lit - |
| (DOT + 2); |
| } |
| |
| if(!check_branch(stack, offset, -256, |
| 255)) |
| offset = 0; |
| |
| /* Emit the branch code */ |
| offset &= 0777;/* Reduce to 9 bits */ |
| offset >>= 1; /* Shift to become |
| word offset */ |
| |
| store_word(stack->top, tr, |
| 2, op->value | offset); |
| |
| free_tree(value); |
| } |
| return 1; |
| |
| case OC_SOB: |
| { |
| EX_TREE *value; |
| unsigned reg; |
| unsigned offset; |
| |
| value = parse_expr(cp, 0); |
| cp = value->cp; |
| |
| reg = get_register(value); |
| free_tree(value); |
| if(reg == NO_REG) |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| return 0; |
| } |
| |
| cp = skipwhite(cp); |
| if(*cp++ != ',') |
| { |
| report(stack->top, "Illegal syntax\n"); |
| return 0; |
| } |
| |
| value = parse_expr(cp, 0); |
| cp = value->cp; |
| |
| /* Relative PSECT or absolute? */ |
| if(current_pc->section->flags & PSECT_REL) |
| { |
| SYMBOL *sym; |
| |
| if(!express_sym_offset(value, |
| &sym, &offset)) |
| { |
| report(stack->top, |
| "Bad branch target\n"); |
| free_tree(value); |
| return 0; |
| } |
| /* Must be same section */ |
| if(sym->section != current_pc->section) |
| { |
| report(stack->top, |
| "Bad branch target\n"); |
| free_tree(value); |
| offset = 0; |
| } |
| else |
| { |
| /* Calculate byte offset */ |
| offset += DOT + 2; |
| offset -= sym->value; |
| } |
| } |
| else |
| { |
| if(value->type != EX_LIT) |
| { |
| report(stack->top, "Bad branch " |
| "target\n"); |
| offset = 0; |
| } |
| else |
| { |
| offset = DOT + 2 - |
| value->data.lit; |
| } |
| } |
| |
| if(!check_branch(stack, offset, 0, 126)) |
| offset = 0; |
| |
| offset &= 0177; /* Reduce to 7 bits */ |
| offset >>= 1; /* Shift to become word offset */ |
| store_word(stack->top, tr, 2, |
| op->value | offset | (reg << 6)); |
| |
| free_tree(value); |
| } |
| return 1; |
| |
| case OC_ASH: |
| /* First op is gen, second is register. */ |
| { |
| ADDR_MODE mode; |
| EX_TREE *value; |
| unsigned reg; |
| unsigned word; |
| |
| if(!get_mode(cp, &cp, &mode)) |
| { |
| report(stack->top, "Illegal addressing mode\n"); |
| return 0; |
| } |
| |
| cp = skipwhite(cp); |
| if(*cp++ != ',') |
| { |
| report(stack->top, "Illegal addressing mode\n"); |
| free_addr_mode(&mode); |
| return 0; |
| } |
| value = parse_expr(cp, 0); |
| cp = value->cp; |
| |
| reg = get_register(value); |
| if(reg == NO_REG) |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| free_tree(value); |
| free_addr_mode(&mode); |
| return 0; |
| } |
| |
| /* Instruction word */ |
| word = op->value | mode.type | (reg << 6); |
| store_word(stack->top, tr, 2, word); |
| mode_extension(tr, &mode, stack->top); |
| free_tree(value); |
| } |
| return 1; |
| |
| case OC_JSR: |
| /* First op is register, second is gen. */ |
| { |
| ADDR_MODE mode; |
| EX_TREE *value; |
| unsigned reg; |
| unsigned word; |
| |
| value = parse_expr(cp, 0); |
| cp = value->cp; |
| |
| reg = get_register(value); |
| if(reg == NO_REG) |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| free_tree(value); |
| return 0; |
| } |
| |
| cp = skipwhite(cp); |
| if(*cp++ != ',') |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| return 0; |
| } |
| |
| if(!get_mode(cp, &cp, &mode)) |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| free_tree(value); |
| return 0; |
| } |
| word = op->value | mode.type | (reg << 6); |
| store_word(stack->top, tr, 2, word); |
| mode_extension(tr, &mode, stack->top); |
| free_tree(value); |
| } |
| return 1; |
| |
| case OC_1REG: |
| /* One register (RTS) */ |
| { |
| EX_TREE *value; |
| unsigned reg; |
| |
| value = parse_expr(cp, 0); |
| cp = value->cp; |
| reg = get_register(value); |
| if(reg == NO_REG) |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| free_tree(value); |
| reg = 0; |
| } |
| |
| store_word(stack->top, tr, |
| 2, op->value | reg); |
| free_tree(value); |
| } |
| return 1; |
| |
| case OC_1FIS: |
| /* One one gen and one reg 0-3 */ |
| { |
| ADDR_MODE mode; |
| EX_TREE *value; |
| unsigned reg; |
| unsigned word; |
| |
| if(!get_mode(cp, &cp, &mode)) |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| return 0; |
| } |
| |
| cp = skipwhite(cp); |
| if(*cp++ != ',') |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| free_addr_mode(&mode); |
| return 0; |
| } |
| |
| value = parse_expr(cp, 0); |
| cp = value->cp; |
| |
| reg = get_register(value); |
| if(reg == NO_REG || reg > 4) |
| { |
| report(stack->top, |
| "Invalid destination register\n"); |
| reg = 0; |
| } |
| |
| word = op->value | mode.type | (reg << 6); |
| store_word(stack->top, tr, 2, word); |
| mode_extension(tr, &mode, stack->top); |
| free_tree(value); |
| } |
| return 1; |
| |
| case OC_2FIS: |
| /* One reg 0-3 and one gen */ |
| { |
| ADDR_MODE mode; |
| EX_TREE *value; |
| unsigned reg; |
| unsigned word; |
| int ok = 1; |
| |
| value = parse_expr(cp, 0); |
| cp = value->cp; |
| |
| reg = get_register(value); |
| if(reg == NO_REG || reg > 4) |
| { |
| report(stack->top, |
| "Illegal source register\n"); |
| reg = 0; |
| ok = 0; |
| } |
| |
| cp = skipwhite(cp); |
| if(*cp++ != ',') |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| free_tree(value); |
| return 0; |
| } |
| |
| if(!get_mode(cp, &cp, &mode)) |
| { |
| report(stack->top, |
| "Illegal addressing mode\n"); |
| free_tree(value); |
| return 0; |
| } |
| |
| word = op->value | mode.type | (reg << 6); |
| store_word(stack->top, tr, 2, word); |
| mode_extension(tr, &mode, stack->top); |
| free_tree(value); |
| } |
| return 1; |
| |
| default: |
| report(stack->top, |
| "Unimplemented instruction format\n"); |
| return 0; |
| } /* end(handle an instruction) */ |
| } |
| break; |
| } /* end switch(section type) */ |
| } /* end if (op is a symbol) */ |
| } |
| |
| /* Only thing left is an implied .WORD directive */ |
| |
| free(label); |
| |
| return do_word(stack, tr, cp, 2); |
| } |
| |
| /* assemble_stack assembles the input stack. It returns the error |
| count. */ |
| |
| static int assemble_stack(STACK *stack, TEXT_RLD *tr) |
| { |
| int res; |
| int count = 0; |
| |
| while((res = assemble(stack, tr)) >= 0) |
| { |
| list_flush(); |
| if(res == 0) |
| count++; /* Count an error */ |
| } |
| |
| return count; |
| } |
| |
| /* write_globals writes out the GSD prior to the second assembly pass */ |
| |
| static void write_globals(FILE *obj) |
| { |
| GSD gsd; |
| SYMBOL *sym; |
| SECTION *psect; |
| SYMBOL_ITER sym_iter; |
| int isect; |
| |
| if(obj == NULL) |
| return; /* Nothing to do if no OBJ file. */ |
| |
| gsd_init(&gsd, obj); |
| |
| gsd_mod(&gsd, module_name); |
| |
| if(ident) |
| gsd_ident(&gsd, ident); |
| |
| /* write out each PSECT with it's global stuff */ |
| /* Sections must be written out in the order that they |
| appear in the assembly file. */ |
| for(isect = 0; isect < sector; isect++) |
| { |
| psect = sections[isect]; |
| |
| gsd_psect(&gsd, psect->label, psect->flags, psect->size); |
| psect->sector = isect; /* Assign it a sector */ |
| psect->pc = 0; /* Reset it's PC for second pass */ |
| |
| sym = first_sym(&symbol_st, &sym_iter); |
| while(sym) |
| { |
| if((sym->flags & GLOBAL) && |
| sym->section == psect) |
| { |
| gsd_global(&gsd, sym->label, |
| (sym->flags & DEFINITION ? GLOBAL_DEF : 0) | |
| ((sym->flags & WEAK) ? GLOBAL_WEAK : 0) | |
| ((sym->section->flags & PSECT_REL) ? GLOBAL_REL : 0) | |
| 0100, /* Looks undefined, but add it in anyway */ |
| sym->value); |
| } |
| sym = next_sym(&symbol_st, &sym_iter); |
| } |
| } |
| |
| /* Now write out the transfer address */ |
| if(xfer_address->type == EX_LIT) |
| { |
| gsd_xfer(&gsd, ". ABS.", xfer_address->data.lit); |
| } |
| else |
| { |
| SYMBOL *sym; |
| unsigned offset; |
| if(!express_sym_offset(xfer_address, &sym, &offset)) |
| { |
| report(NULL, "Illegal program transfer address\n"); |
| } |
| else |
| { |
| gsd_xfer(&gsd, sym->section->label, sym->value + offset); |
| } |
| } |
| |
| gsd_flush(&gsd); |
| |
| gsd_end(&gsd); |
| } |
| |
| /* add_symbols adds all the internal symbols. */ |
| |
| static void add_symbols(SECTION *current_section) |
| { |
| current_pc = add_sym(".", 0, 0, current_section, &symbol_st); |
| |
| reg_sym[0] = add_sym("R0", 0, 0, ®ister_section, &system_st); |
| reg_sym[1] = add_sym("R1", 1, 0, ®ister_section, &system_st); |
| reg_sym[2] = add_sym("R2", 2, 0, ®ister_section, &system_st); |
| reg_sym[3] = add_sym("R3", 3, 0, ®ister_section, &system_st); |
| reg_sym[4] = add_sym("R4", 4, 0, ®ister_section, &system_st); |
| reg_sym[5] = add_sym("R5", 5, 0, ®ister_section, &system_st); |
| reg_sym[6] = add_sym("SP", 6, 0, ®ister_section, &system_st); |
| reg_sym[7] = add_sym("PC", 7, 0, ®ister_section, &system_st); |
| |
| add_sym(".ASCII", P_ASCII, 0, &pseudo_section, &system_st); |
| add_sym(".ASCIZ", P_ASCIZ, 0, &pseudo_section, &system_st); |
| add_sym(".ASECT", P_ASECT, 0, &pseudo_section, &system_st); |
| add_sym(".BLKB", P_BLKB, 0, &pseudo_section, &system_st); |
| add_sym(".BLKW", P_BLKW, 0, &pseudo_section, &system_st); |
| add_sym(".BYTE", P_BYTE, 0, &pseudo_section, &system_st); |
| add_sym(".CSECT", P_CSECT, 0, &pseudo_section, &system_st); |
| add_sym(".DSABL", P_DSABL, 0, &pseudo_section, &system_st); |
| add_sym(".ENABL", P_ENABL, 0, &pseudo_section, &system_st); |
| add_sym(".END", P_END, 0, &pseudo_section, &system_st); |
| add_sym(".ENDC", P_ENDC, 0, &pseudo_section, &system_st); |
| add_sym(".ENDM", P_ENDM, 0, &pseudo_section, &system_st); |
| add_sym(".ENDR", P_ENDR, 0, &pseudo_section, &system_st); |
| add_sym(".EOT", P_EOT, 0, &pseudo_section, &system_st); |
| add_sym(".ERROR", P_ERROR, 0, &pseudo_section, &system_st); |
| add_sym(".EVEN", P_EVEN, 0, &pseudo_section, &system_st); |
| add_sym(".FLT2", P_FLT2, 0, &pseudo_section, &system_st); |
| add_sym(".FLT4", P_FLT4, 0, &pseudo_section, &system_st); |
| add_sym(".GLOBL", P_GLOBL, 0, &pseudo_section, &system_st); |
| add_sym(".IDENT", P_IDENT, 0, &pseudo_section, &system_st); |
| add_sym(".IF", P_IF, 0, &pseudo_section, &system_st); |
| add_sym(".IFDF", P_IFDF, 0, &pseudo_section, &system_st); |
| add_sym(".IFNDF", P_IFDF, 0, &pseudo_section, &system_st); |
| add_sym(".IFF", P_IFF, 0, &pseudo_section, &system_st); |
| add_sym(".IFT", P_IFT, 0, &pseudo_section, &system_st); |
| add_sym(".IFTF", P_IFTF, 0, &pseudo_section, &system_st); |
| add_sym(".IIF", P_IIF, 0, &pseudo_section, &system_st); |
| add_sym(".IRP", P_IRP, 0, &pseudo_section, &system_st); |
| add_sym(".IRPC", P_IRPC, 0, &pseudo_section, &system_st); |
| add_sym(".LIMIT", P_LIMIT, 0, &pseudo_section, &system_st); |
| add_sym(".LIST", P_LIST, 0, &pseudo_section, &system_st); |
| add_sym(".MCALL", P_MCALL, 0, &pseudo_section, &system_st); |
| add_sym(".MEXIT", P_MEXIT, 0, &pseudo_section, &system_st); |
| add_sym(".NARG", P_NARG, 0, &pseudo_section, &system_st); |
| add_sym(".NCHR", P_NCHR, 0, &pseudo_section, &system_st); |
| add_sym(".NLIST", P_NLIST, 0, &pseudo_section, &system_st); |
| add_sym(".NTYPE", P_NTYPE, 0, &pseudo_section, &system_st); |
| add_sym(".ODD", P_ODD, 0, &pseudo_section, &system_st); |
| add_sym(".PACKE", P_PACKED, 0, &pseudo_section, &system_st); |
| add_sym(".PAGE", P_PAGE, 0, &pseudo_section, &system_st); |
| add_sym(".PRINT", P_PRINT, 0, &pseudo_section, &system_st); |
| add_sym(".PSECT", P_PSECT, 0, &pseudo_section, &system_st); |
| add_sym(".RADIX", P_RADIX, 0, &pseudo_section, &system_st); |
| add_sym(".RAD50", P_RAD50, 0, &pseudo_section, &system_st); |
| add_sym(".REM", P_REM, 0, &pseudo_section, &system_st); |
| add_sym(".REPT", P_REPT, 0, &pseudo_section, &system_st); |
| add_sym(".RESTO", P_RESTORE, 0, &pseudo_section, &system_st); |
| add_sym(".SAVE", P_SAVE, 0, &pseudo_section, &system_st); |
| add_sym(".SBTTL", P_SBTTL, 0, &pseudo_section, &system_st); |
| add_sym(".TITLE", P_TITLE, 0, &pseudo_section, &system_st); |
| add_sym(".WORD", P_WORD, 0, &pseudo_section, &system_st); |
| add_sym(".MACRO", P_MACRO, 0, &pseudo_section, &system_st); |
| add_sym(".WEAK", P_WEAK, 0, &pseudo_section, &system_st); |
| |
| add_sym("ADC", I_ADC, OC_1GEN, &instruction_section, &system_st); |
| add_sym("ADCB", I_ADCB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("ADD", I_ADD, OC_2GEN, &instruction_section, &system_st); |
| add_sym("ASH", I_ASH, OC_ASH, &instruction_section, &system_st); |
| add_sym("ASHC", I_ASHC, OC_ASH, &instruction_section, &system_st); |
| add_sym("ASL", I_ASL, OC_1GEN, &instruction_section, &system_st); |
| add_sym("ASLB", I_ASLB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("ASR", I_ASR, OC_1GEN, &instruction_section, &system_st); |
| add_sym("ASRB", I_ASRB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("BCC", I_BCC, OC_BR, &instruction_section, &system_st); |
| add_sym("BCS", I_BCS, OC_BR, &instruction_section, &system_st); |
| add_sym("BEQ", I_BEQ, OC_BR, &instruction_section, &system_st); |
| add_sym("BGE", I_BGE, OC_BR, &instruction_section, &system_st); |
| add_sym("BGT", I_BGT, OC_BR, &instruction_section, &system_st); |
| add_sym("BHI", I_BHI, OC_BR, &instruction_section, &system_st); |
| add_sym("BHIS", I_BHIS, OC_BR, &instruction_section, &system_st); |
| add_sym("BIC", I_BIC, OC_2GEN, &instruction_section, &system_st); |
| add_sym("BICB", I_BICB, OC_2GEN, &instruction_section, &system_st); |
| add_sym("BIS", I_BIS, OC_2GEN, &instruction_section, &system_st); |
| add_sym("BISB", I_BISB, OC_2GEN, &instruction_section, &system_st); |
| add_sym("BIT", I_BIT, OC_2GEN, &instruction_section, &system_st); |
| add_sym("BITB", I_BITB, OC_2GEN, &instruction_section, &system_st); |
| add_sym("BLE", I_BLE, OC_BR, &instruction_section, &system_st); |
| add_sym("BLO", I_BLO, OC_BR, &instruction_section, &system_st); |
| add_sym("BLOS", I_BLOS, OC_BR, &instruction_section, &system_st); |
| add_sym("BLT", I_BLT, OC_BR, &instruction_section, &system_st); |
| add_sym("BMI", I_BMI, OC_BR, &instruction_section, &system_st); |
| add_sym("BNE", I_BNE, OC_BR, &instruction_section, &system_st); |
| add_sym("BPL", I_BPL, OC_BR, &instruction_section, &system_st); |
| add_sym("BPT", I_BPT, OC_NONE, &instruction_section, &system_st); |
| add_sym("BR", I_BR, OC_BR, &instruction_section, &system_st); |
| add_sym("BVC", I_BVC, OC_BR, &instruction_section, &system_st); |
| add_sym("BVS", I_BVS, OC_BR, &instruction_section, &system_st); |
| add_sym("CALL", I_CALL, OC_1GEN, &instruction_section, &system_st); |
| add_sym("CALLR", I_CALLR, OC_1GEN, &instruction_section, &system_st); |
| add_sym("CCC", I_CCC, OC_NONE, &instruction_section, &system_st); |
| add_sym("CLC", I_CLC, OC_NONE, &instruction_section, &system_st); |
| add_sym("CLN", I_CLN, OC_NONE, &instruction_section, &system_st); |
| add_sym("CLR", I_CLR, OC_1GEN, &instruction_section, &system_st); |
| add_sym("CLRB", I_CLRB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("CLV", I_CLV, OC_NONE, &instruction_section, &system_st); |
| add_sym("CLZ", I_CLZ, OC_NONE, &instruction_section, &system_st); |
| add_sym("CMP", I_CMP, OC_2GEN, &instruction_section, &system_st); |
| add_sym("CMPB", I_CMPB, OC_2GEN, &instruction_section, &system_st); |
| add_sym("COM", I_COM, OC_1GEN, &instruction_section, &system_st); |
| add_sym("COMB", I_COMB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("DEC", I_DEC, OC_1GEN, &instruction_section, &system_st); |
| add_sym("DECB", I_DECB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("DIV", I_DIV, OC_ASH, &instruction_section, &system_st); |
| add_sym("EMT", I_EMT, OC_MARK, &instruction_section, &system_st); |
| add_sym("FADD", I_FADD, OC_1REG, &instruction_section, &system_st); |
| add_sym("FDIV", I_FDIV, OC_1REG, &instruction_section, &system_st); |
| add_sym("FMUL", I_FMUL, OC_1REG, &instruction_section, &system_st); |
| add_sym("FSUB", I_FSUB, OC_1REG, &instruction_section, &system_st); |
| add_sym("HALT", I_HALT, OC_NONE, &instruction_section, &system_st); |
| add_sym("INC", I_INC, OC_1GEN, &instruction_section, &system_st); |
| add_sym("INCB", I_INCB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("IOT", I_IOT, OC_NONE, &instruction_section, &system_st); |
| add_sym("JMP", I_JMP, OC_1GEN, &instruction_section, &system_st); |
| add_sym("JSR", I_JSR, OC_JSR, &instruction_section, &system_st); |
| add_sym("MARK", I_MARK, OC_MARK, &instruction_section, &system_st); |
| add_sym("MED6X", I_MED6X, OC_NONE, &instruction_section, &system_st); |
| add_sym("MED74C", I_MED74C, OC_NONE, &instruction_section, &system_st); |
| add_sym("MFPD", I_MFPD, OC_1GEN, &instruction_section, &system_st); |
| add_sym("MFPI", I_MFPI, OC_1GEN, &instruction_section, &system_st); |
| add_sym("MFPS", I_MFPS, OC_1GEN, &instruction_section, &system_st); |
| add_sym("MOV", I_MOV, OC_2GEN, &instruction_section, &system_st); |
| add_sym("MOVB", I_MOVB, OC_2GEN, &instruction_section, &system_st); |
| add_sym("MTPD", I_MTPD, OC_1GEN, &instruction_section, &system_st); |
| add_sym("MTPI", I_MTPI, OC_1GEN, &instruction_section, &system_st); |
| add_sym("MTPS", I_MTPS, OC_1GEN, &instruction_section, &system_st); |
| add_sym("MUL", I_MUL, OC_ASH, &instruction_section, &system_st); |
| add_sym("NEG", I_NEG, OC_1GEN, &instruction_section, &system_st); |
| add_sym("NEGB", I_NEGB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("NOP", I_NOP, OC_NONE, &instruction_section, &system_st); |
| add_sym("RESET", I_RESET, OC_NONE, &instruction_section, &system_st); |
| add_sym("RETURN", I_RETURN, OC_NONE, &instruction_section, &system_st); |
| add_sym("ROL", I_ROL, OC_1GEN, &instruction_section, &system_st); |
| add_sym("ROLB", I_ROLB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("ROR", I_ROR, OC_1GEN, &instruction_section, &system_st); |
| add_sym("RORB", I_RORB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("RTI", I_RTI, OC_NONE, &instruction_section, &system_st); |
| add_sym("RTS", I_RTS, OC_1REG, &instruction_section, &system_st); |
| add_sym("RTT", I_RTT, OC_NONE, &instruction_section, &system_st); |
| add_sym("SBC", I_SBC, OC_1GEN, &instruction_section, &system_st); |
| add_sym("SBCB", I_SBCB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("SCC", I_SCC, OC_NONE, &instruction_section, &system_st); |
| add_sym("SEC", I_SEC, OC_NONE, &instruction_section, &system_st); |
| add_sym("SEN", I_SEN, OC_NONE, &instruction_section, &system_st); |
| add_sym("SEV", I_SEV, OC_NONE, &instruction_section, &system_st); |
| add_sym("SEZ", I_SEZ, OC_NONE, &instruction_section, &system_st); |
| add_sym("SOB", I_SOB, OC_SOB, &instruction_section, &system_st); |
| add_sym("SPL", I_SPL, OC_1REG, &instruction_section, &system_st); |
| add_sym("SUB", I_SUB, OC_2GEN, &instruction_section, &system_st); |
| add_sym("SWAB", I_SWAB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("SXT", I_SXT, OC_1GEN, &instruction_section, &system_st); |
| add_sym("TRAP", I_TRAP, OC_MARK, &instruction_section, &system_st); |
| add_sym("TST", I_TST, OC_1GEN, &instruction_section, &system_st); |
| add_sym("TSTB", I_TSTB, OC_1GEN, &instruction_section, &system_st); |
| add_sym("WAIT", I_WAIT, OC_NONE, &instruction_section, &system_st); |
| add_sym("XFC", I_XFC, OC_NONE, &instruction_section, &system_st); |
| add_sym("XOR", I_XOR, OC_JSR, &instruction_section, &system_st); |
| add_sym("MFPT", I_MFPT, OC_NONE, &instruction_section, &system_st); |
| |
| add_sym("ABSD", I_ABSD, OC_1GEN, &instruction_section, &system_st); |
| add_sym("ABSF", I_ABSF, OC_1GEN, &instruction_section, &system_st); |
| add_sym("ADDD", I_ADDD, OC_1FIS, &instruction_section, &system_st); |
| add_sym("ADDF", I_ADDF, OC_1FIS, &instruction_section, &system_st); |
| add_sym("CFCC", I_CFCC, OC_NONE, &instruction_section, &system_st); |
| add_sym("CLRD", I_CLRD, OC_1GEN, &instruction_section, &system_st); |
| add_sym("CLRF", I_CLRF, OC_1GEN, &instruction_section, &system_st); |
| add_sym("CMPD", I_CMPD, OC_1FIS, &instruction_section, &system_st); |
| add_sym("CMPF", I_CMPF, OC_1FIS, &instruction_section, &system_st); |
| add_sym("DIVD", I_DIVD, OC_1FIS, &instruction_section, &system_st); |
| add_sym("DIVF", I_DIVF, OC_1FIS, &instruction_section, &system_st); |
| add_sym("LDCDF", I_LDCDF, OC_1FIS, &instruction_section, &system_st); |
| add_sym("LDCID", I_LDCID, OC_1FIS, &instruction_section, &system_st); |
| add_sym("LDCIF", I_LDCIF, OC_1FIS, &instruction_section, &system_st); |
| add_sym("LDCLD", I_LDCLD, OC_1FIS, &instruction_section, &system_st); |
| add_sym("LDCLF", I_LDCLF, OC_1FIS, &instruction_section, &system_st); |
| add_sym("LDD", I_LDD, OC_1FIS, &instruction_section, &system_st); |
| add_sym("LDEXP", I_LDEXP, OC_1FIS, &instruction_section, &system_st); |
| add_sym("LDF", I_LDF, OC_1FIS, &instruction_section, &system_st); |
| add_sym("LDFPS", I_LDFPS, OC_1GEN, &instruction_section, &system_st); |
| add_sym("MODD", I_MODD, OC_1FIS, &instruction_section, &system_st); |
| add_sym("MODF", I_MODF, OC_1FIS, &instruction_section, &system_st); |
| add_sym("MULD", I_MULD, OC_1FIS, &instruction_section, &system_st); |
| add_sym("MULF", I_MULF, OC_1FIS, &instruction_section, &system_st); |
| add_sym("NEGD", I_NEGD, OC_1GEN, &instruction_section, &system_st); |
| add_sym("NEGF", I_NEGF, OC_1GEN, &instruction_section, &system_st); |
| add_sym("SETD", I_SETD, OC_NONE, &instruction_section, &system_st); |
| add_sym("SETF", I_SETF, OC_NONE, &instruction_section, &system_st); |
| add_sym("SETI", I_SETI, OC_NONE, &instruction_section, &system_st); |
| add_sym("SETL", I_SETL, OC_NONE, &instruction_section, &system_st); |
| add_sym("STA0", I_STA0, OC_NONE, &instruction_section, &system_st); |
| add_sym("STB0", I_STB0, OC_NONE, &instruction_section, &system_st); |
| add_sym("STCDF", I_STCDF, OC_2FIS, &instruction_section, &system_st); |
| add_sym("STCDI", I_STCDI, OC_2FIS, &instruction_section, &system_st); |
| add_sym("STCDL", I_STCDL, OC_2FIS, &instruction_section, &system_st); |
| add_sym("STCFD", I_STCFD, OC_2FIS, &instruction_section, &system_st); |
| add_sym("STCFI", I_STCFI, OC_2FIS, &instruction_section, &system_st); |
| add_sym("STCFL", I_STCFL, OC_2FIS, &instruction_section, &system_st); |
| add_sym("STD", I_STD, OC_2FIS, &instruction_section, &system_st); |
| add_sym("STEXP", I_STEXP, OC_2FIS, &instruction_section, &system_st); |
| add_sym("STF", I_STF, OC_2FIS, &instruction_section, &system_st); |
| add_sym("STFPS", I_STFPS, OC_1GEN, &instruction_section, &system_st); |
| add_sym("STST", I_STST, OC_1GEN, &instruction_section, &system_st); |
| add_sym("SUBD", I_SUBD, OC_1FIS, &instruction_section, &system_st); |
| add_sym("SUBF", I_SUBF, OC_1FIS, &instruction_section, &system_st); |
| add_sym("TSTD", I_TSTD, OC_1GEN, &instruction_section, &system_st); |
| add_sym("TSTF", I_TSTF, OC_1GEN, &instruction_section, &system_st); |
| |
| /* FIXME: The CIS instructions are missing! */ |
| |
| add_sym(current_section->label, 0, 0, current_section, §ion_st); |
| } |
| |
| /* dump_all_macros is a diagnostic function that's currently not |
| used. I used it while debugging, and I haven't removed it. */ |
| |
| static void dump_all_macros(void) |
| { |
| MACRO *mac; |
| SYMBOL_ITER iter; |
| |
| for(mac = (MACRO *)first_sym(¯o_st, &iter); |
| mac != NULL; mac = (MACRO *)next_sym(¯o_st, &iter)) |
| { |
| dumpmacro(mac, lstfile); |
| |
| printf("\n\n"); |
| } |
| } |
| |
| /* sym_hist is a diagnostic function that prints a histogram of the |
| hash table useage of a symbol table. I used this to try to tune |
| the hash function for better spread. It's not used now. */ |
| |
| static void sym_hist(SYMBOL_TABLE *st, char *name) |
| { |
| int i; |
| SYMBOL *sym; |
| fprintf(lstfile, "Histogram for symbol table %s\n", name); |
| for(i = 0; i < 1023; i++) |
| { |
| fprintf(lstfile, "%4d: ", i); |
| for(sym = st->hash[i]; sym != NULL; sym = sym->next) |
| fputc('#', lstfile); |
| fputc('\n', lstfile); |
| } |
| } |
| |
| /* enable_tf is called by command argument parsing to enable and |
| disable named options. */ |
| |
| static void enable_tf(char *opt, int tf) |
| { |
| if(strcmp(opt, "AMA") == 0) |
| enabl_ama = tf; |
| else if(strcmp(opt, "GBL") == 0) |
| enabl_gbl = tf; |
| else if(strcmp(opt, "ME") == 0) |
| list_me = tf; |
| else if(strcmp(opt, "BEX") == 0) |
| list_bex = tf; |
| else if(strcmp(opt, "MD") == 0) |
| list_md = tf; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| char *fnames[32]; |
| int nr_files = 0; |
| FILE *obj = NULL; |
| static char line[1024]; |
| TEXT_RLD tr; |
| char *macname = NULL; |
| char *objname = NULL; |
| char *lstname = NULL; |
| int arg; |
| int i; |
| STACK stack; |
| int count; |
| |
| for(arg = 1; arg < argc; arg++) |
| { |
| if(*argv[arg] == '-') |
| { |
| char *cp; |
| cp = argv[arg] + 1; |
| switch(tolower(*cp)) |
| { |
| case 'v': |
| fprintf(stderr, |
| "macro11 Copyright 2001 Richard Krehbiel\n" |
| "Version 0.2 July 15, 2001\n"); |
| break; |
| |
| case 'e': |
| /* Followed by options to enable */ |
| /* Since /SHOW and /ENABL option names don't overlap, |
| I consolidate. */ |
| upcase(argv[++arg]); |
| enable_tf(argv[arg], 1); |
| break; |
| |
| case 'd': |
| /* Followed by an option to disable */ |
| upcase(argv[++arg]); |
| enable_tf(argv[arg], 0); |
| break; |
| |
| case 'm': |
| /* Macro library */ |
| /* This option gives the name of an RT-11 compatible |
| macro library from which .MCALLed macros can be |
| found. */ |
| arg++; |
| mlbs[nr_mlbs] = mlb_open(argv[arg]); |
| if(mlbs[nr_mlbs] == NULL) |
| { |
| fprintf(stderr, |
| "Unable to register macro library %s\n", |
| argv[arg]); |
| exit(EXIT_FAILURE); |
| } |
| nr_mlbs++; |
| break; |
| |
| case 'p': /* P for search path */ |
| /* The -p option gives the name of a directory in |
| which .MCALLed macros may be found. */ |
| { |
| char *env = getenv("MCALL"); |
| char *temp; |
| |
| if(env == NULL) |
| env = ""; |
| |
| temp = memcheck(malloc(strlen(env) + |
| strlen(argv[arg+1]) + 8)); |
| strcpy(temp, "MCALL="); |
| strcat(temp, env); |
| strcat(temp, PATHSEP); |
| strcat(temp, argv[arg+1]); |
| putenv(temp); |
| arg++; |
| } |
| break; |
| |
| case 'o': |
| /* The -o option gives the object file name (.OBJ) */ |
| ++arg; |
| objname = argv[arg]; |
| break; |
| |
| case 'l': |
| /* The option -l gives the listing file name (.LST) */ |
| /* -l - enables listing to stdout. */ |
| lstname = argv[++arg]; |
| if(strcmp(lstname, "-") == 0) |
| lstfile = stdout; |
| else |
| lstfile = fopen(lstname, "w"); |
| break; |
| |
| case 'x': |
| /* The -x option invokes macro11 to expand the |
| contents of the registered macro libraries (see -m) |
| into individual .MAC files in the current |
| directory. No assembly of input is done. This |
| must be the last command line option. */ |
| { |
| int i; |
| for(i = 0; i < nr_mlbs; i++) |
| mlb_extract(mlbs[i]); |
| return EXIT_SUCCESS; |
| } |
| |
| default: |
| fprintf(stderr, "Unknown argument %s\n", argv[arg]); |
| exit(EXIT_FAILURE); |
| } |
| } |
| else |
| { |
| fnames[nr_files++] = argv[arg]; |
| } |
| } |
| |
| if(objname) |
| { |
| obj = fopen(objname, "wb"); |
| if(obj == NULL) |
| return EXIT_FAILURE; |
| } |
| |
| add_symbols(&blank_section); |
| |
| text_init(&tr, NULL, 0); |
| |
| module_name = memcheck(strdup("")); |
| |
| xfer_address = new_ex_lit(1); /* The undefined transfer address */ |
| |
| stack_init(&stack); |
| /* Push the files onto the input stream in reverse order */ |
| for(i = nr_files-1; i >= 0; --i) |
| { |
| STREAM *str = new_file_stream(fnames[i]); |
| if(str == NULL) |
| { |
| report(NULL, "Unable to open file %s\n", fnames[i]); |
| exit(EXIT_FAILURE); |
| } |
| stack_push(&stack, str); |
| } |
| |
| DOT = 0; |
| current_pc->section = &blank_section; |
| last_dot_section = NULL; |
| pass = 0; |
| stmtno = 0; |
| lsb = 0; |
| last_lsb = -1; |
| last_locsym = 32767; |
| last_cond = -1; |
| sect_sp = -1; |
| suppressed = 0; |
| |
| assemble_stack(&stack, &tr); |
| |
| #if 0 |
| if(enabl_debug) |
| dump_all_macros(); |
| #endif |
| |
| assert(stack.top == NULL); |
| |
| migrate_implicit(); /* Migrate the implicit globals */ |
| write_globals(obj); /* Write the global symbol dictionary */ |
| |
| #if 0 |
| sym_hist(&symbol_st, "symbol_st"); /* Draw a symbol table histogram */ |
| #endif |
| |
| |
| text_init(&tr, obj, 0); |
| |
| stack_init(&stack); /* Superfluous... */ |
| /* Re-push the files onto the input stream in reverse order */ |
| for(i = nr_files-1; i >= 0; --i) |
| { |
| STREAM *str = new_file_stream(fnames[i]); |
| if(str == NULL) |
| { |
| report(NULL, "Unable to open file %s\n", fnames[i]); |
| exit(EXIT_FAILURE); |
| } |
| stack_push(&stack, str); |
| } |
| |
| DOT = 0; |
| current_pc->section = &blank_section; |
| last_dot_section = NULL; |
| |
| pass = 1; |
| stmtno = 0; |
| lsb = 0; |
| last_lsb = -1; |
| last_locsym = 32767; |
| pop_cond(-1); |
| sect_sp = -1; |
| suppressed = 0; |
| |
| count = assemble_stack(&stack, &tr); |
| |
| text_flush(&tr); |
| |
| while(last_cond >= 0) |
| { |
| report(NULL, "%s:%d: Unterminated conditional\n", |
| conds[last_cond].file, conds[last_cond].line); |
| pop_cond(last_cond - 1); |
| count++; |
| } |
| |
| for(i = 0; i < nr_mlbs; i++) |
| mlb_close(mlbs[i]); |
| |
| write_endmod(obj); |
| |
| if(obj != NULL) |
| fclose(obj); |
| |
| if(count > 0) |
| fprintf(stderr, "%d Errors\n", count); |
| |
| if(lstfile && strcmp(lstname, "-") != 0) |
| fclose(lstfile); |
| |
| return count > 0 ? EXIT_FAILURE : EXIT_SUCCESS; |
| } |