/* * 12-Jan-03 * * RCS: $Id$ * assembler for CS2200, LC-2003 */ /****************************************************************************\ * * * The LC is a 32-bit computer with 16 general registers plus a separate * * program counter (PC) register. All addresses are word addresses. * * Register 0 is wired to zero: it always reads as zero and writes to it * * are ignored. * * * * ----------------------------------------------------------------- * * Instruction Formats * * ----------------------------------------------------------------- * * There are four instruction formats. Bit 0 is the least-significant: * * * * R-type instructions (add, nand): * * bits 31-28: opcode * * bits 27-24: reg A * * bits 23-20: reg B * * bits 19-4: unused (should be all 0s) * * bits 3-0: reg DST * * * * I-type instructions (addi, lw, sw, beq): * * bits 31-28: opcode * * bits 27-24: reg A * * bits 23-20: reg B * * bits 19-0: OFFSET (a 20-bit, 2s complement number with a range * * of -524288 to +524287 * * * * J-type instructions (jalr): * * bits 31-28: opcode * * bits 27-24: reg A * * bits 23-20: reg B * * bits 19-0: unused (should be all 0s) * * * * O-type instructions (halt, noop): * * bits 31-28: opcode * * bits 27-0: unused (should be all 0s) * * * * ----------------------------------------------------------------- * * Assembly Syntax * * ----------------------------------------------------------------- * * Registers indicated with a '$' sign. The register names in assembly * * are according to their use in the assembly convention: * * * * regno name use callee-save * * ----- ---- ------------------------- ----------- * * 0 $zero always zero (by hardware) n.a. * * 1 $at reserved for assembler n.a. * * 2 $v0 return value no * * 3 $a0 argument or temporary no * * 4 $a1 argument or temporary no * * 5 $a2 argument or temporary no * * 6 $a3 argument or temporary no * * 7 $a4 argument or temporary no * * 8 $s0 saved register YES * * 9 $s1 saved register YES * * 10 $s2 saved register YES * * 11 $s3 saved register YES * * 12 $k0 reserved for OS/traps n.a. * * 13 $sp stack pointer YES * * 14 $fp frame pointer YES * * 15 $ra return address YES * * * * ----------------------------------------------------------------- * * Description of Machine Instructions * * ----------------------------------------------------------------- * * Assembly language Opcode in binary Action * * name for instruction (bits 31/30/29/28) * * ----------------------------------------------------------------- * * add (R-type format) 0000 add contents of A with * * ex: add $v0, $a0, $a1 contents of B, store results in * * DST. Ex: $v0 := $a0 + $a1 * * * * nand (R-type format) 0001 nand contents of A with * * ex: nand $v0, $a0, $a1 contents of B, store results in * * DST. Ex: $v0 := ~($a0 & $a1) * * * * addi (I-type format) 0010 Add OFFSET to the contents of A * * ex: addi $v0, $a0, 25 and store the result in B. * * Ex: $v0 := $a0 + 25 * * * * lw (I-type format) 0011 load B from memory. The memory * * ex: lw $v0, 0x42($fp) address is formed by adding * * OFFSET to the contents of A. * * Ex: $v0 := memory[$fp + 0x42] * * * * sw (I-type format) 0100 store B into memory. The memory * * ex: sw $a0, 0x42($fp) address is formed by adding * * OFFSET to the contents of A. * * Ex: memory[$fp + 0x42] := $a0 * * * * beq (I-type format) 0101 compare the contents of A and B. * * ex: beq $a0, $a1, done If they are the same, then * * branch to the address * * PC+1+OFFSET, where PC is the * * address of the beq instruction. * * Ex: if ($a0 == $a1) * * PC := PC+1+OFFSET * * * * *** NOTE *** * * * * For programmer convenience (and * * implementor confusion), the * * assembler *computes* the OFFSET * * value from the number or symbol * * given in the instruction and the * * assemblers idea of the PC. In the * * example, the assembler stores * * done-(PC+1) in OFFSET so that * * the machine will branch to label * * "done" at run time. * * * * jalr (J-type format) 0110 First store PC+1 into B, * * ex: jalr $at, $ra where PC is the address of the * * jalr instruction. Then branch to * * the address now contained in A. * * Note that if A is the same as B, * * the processor will first store * * PC+1 into that register, then end * * up branching to PC+1. * * Ex: $ra := PC+1; PC := $a0 * * * * halt (O-type format) 0111 halt the machine: i.e. do nothing * * ex: halt and let the simulator notice that * * the machine halted. * * * * noop (pseudo-op) n.a. No operation: does nothing (actually * * ex: noop Emits "add $zero, $zero, $zero") * * * * .word (pseudo-op) n.a. fill word with a value. * * ex: .word 32 Ex: fill the current location * * .word fib with the 32-bit represenation of * * the number "32" or the value of * * the symbol "fib". * * * \****************************************************************************/ #include #include /* strtol() */ #include /* strtok() */ #include /* index() */ #include /* * 0. global state (gross, but handy for consistent error reporting) */ static char *sourcefilename = ""; static int sourcelinenumber = 0; static int nerrors = 0; #define MAXLINELEN 1024 #define MAXTOKENS 64 #define COMMENTCHAR '!' /* comments between ! and end-of-line */ #define OTHERCOMMENTCHAR '#' /* MIPS uses '#' */ #define OUTEXTENSION ".lc" /* extention on the output file. */ /* * 1. representation of a single instruction */ #define OP_BITS 4 /* 4-bit opcodes */ #define REG_BITS 4 /* 16 registers */ #define OFFSET_BITS 20 /* 16-bit signed offset */ #define OP_OFFSET 28 /* position in the instruction word */ #define A_OFFSET 24 #define B_OFFSET 20 #define DST_OFFSET 0 #define OFFSET_OFFSET 0 #define NREGS (1 << REG_BITS) /* i.e. 16 */ #define OP_ADD 0 /* opcodes */ #define OP_NAND 1 #define OP_ADDI 2 #define OP_LW 3 #define OP_SW 4 #define OP_BEQ 5 #define OP_JALR 6 #define OP_HALT 7 /* * 2. Descriptions of the instructions: the opcode mnemonic, the opcode * value and the format, followed by a table of all the instructions. * Pseudo-ops also go in this table. */ typedef struct { int opcode; char *mnemonic; enum { R, I, II, J, O, S } format; } instruction_t; #define NINSTRS 11 /* this is unrelated to # of opcodes */ static instruction_t instruction_table[NINSTRS] = { { OP_ADD, "add", R }, { OP_NAND, "nand", R }, { OP_ADDI, "addi", II }, /* instr format is I but syntax is odd */ { OP_LW, "lw", I }, { OP_SW, "sw", I }, { OP_BEQ, "beq", II }, { OP_JALR, "jalr", J }, { OP_HALT, "halt", O }, { OP_ADD, "noop", O }, /* HACK HACK HACK: noop emits add 0 0 0 */ { -1, ".word", S }, /* pseudo-op */ { -1, ".fill", S } /* pseudo-op (same as .word) */ }; static char *register_names[NREGS] = { "$zero", /* 0 */ "$at", /* 1 */ "$v0", /* 2 */ "$a0", /* 3 */ "$a1", /* 4 */ "$a2", /* 5 */ "$a3", /* 6 */ "$a4", /* 7 */ "$s0", /* 8 */ "$s1", /* 9 */ "$s2", /* 10 */ "$s3", /* 11 */ "$k0", /* 12 */ "$sp", /* 13 */ "$fp", /* 14 */ "$ra" /* 15 */ }; /* * 3. symbol table entry. The symbol table is stored * in a single-linked list. */ typedef struct symbol { int value; char *name; struct symbol *link; } symbol_t; static symbol_t *symbol_table = NULL; /*************************************************************\ * * * Utility routine hextoi() for reading decimal/hex numbers. * * * \*************************************************************/ static long hextoi(char *string) { long number = 0; int index; /* * if it does NOT look like a hex number, assume decimal and use atoi. */ if (!(string[0] == '0' && (string[1] == 'x' || string[1] == 'X'))) return(atoi(string)); /* * otherwise, read a hex number. */ for (index = 2; string[index] != '\0'; index++) { char blah = string[index]; int digit; if (blah >= '0' && blah <= '9') digit = blah - '0'; else if (blah >= 'A' && blah <= 'F') digit = blah - 'A' + 10; else if (blah >= 'a' && blah <= 'f') digit = blah - 'a' + 10; else break; number = number * 16 + digit; } return(number); } /*********************************************************************\ * * * getline reads in a single line from the given file. It returns a * * pointer to the line (in static storage) or NULL on EOF. * * * \*********************************************************************/ static char *getline(FILE *input) { static char line[MAXLINELEN]; int kount; int blah; for (kount = 0; kount < MAXLINELEN - 1; kount++) { blah = getc(input); if (blah == EOF || blah == '\n') break; line[kount] = blah; } line[kount] = '\0'; if (kount == 0 && blah == EOF) return(NULL); return(line); } /***********************************************************************\ * * * lexer takes a string representing a line and returns an ARGV-style * * array of pointers to strings representing tokens. The array is in * * internal, static storage. * * * * 1. Everything after a "!" character is removed. * * 2. The last entry in the pointer array is NULL * * 3. The input string is preserved. * * * * For example: * * * * char **argv = lexer("this is a test ! that was a test") * * * * returns an array like this: * * * * argv[0] = "this" * * argv[1] = "is" * * argv[2] = "a" * * argv[3] = "test" * * argv[4] = "" * * * * and remaining entries are undefined. * * * \***********************************************************************/ static char *colon = ":"; static char *comma = ","; static char *lparen = "("; static char *rparen = ")"; static int is_whitespace(char blah) { return(blah == ' ' || blah == '\t'); } static int is_rawdelimiter(char blah) { return(blah == ':' || blah == ',' || blah == '(' || blah == ')'); } static int is_eol(char blah) { return(blah == '\0' || blah == COMMENTCHAR || blah == OTHERCOMMENTCHAR); } static char **lexer(char *input_line) { static char line[MAXLINELEN]; static char *tokens[MAXTOKENS]; int kount, index; /* * duplicate the line since strtok is going to change it and * it's useful to keep a copy for error messages. While we're * at it, strip any comment. */ for (index = 0; index < MAXLINELEN - 1; index++) { if (is_eol(input_line[index])) break; line[index] = input_line[index]; } line[index] = '\0'; /* * split the copy of the line into tokens. Note we modify * the line in-place and the tokens[] array ends up pointing into * pieces of the line. */ for (kount = 0, index = 0; kount < MAXTOKENS - 1; kount++) { while (is_whitespace(line[index])) /* zero the whitespace */ line[index++] = '\0'; if (is_eol(line[index])) break; if (is_rawdelimiter(line[index])) { tokens[kount] = (line[index] == ',' ? comma /* point to delim. */ : line[index] == ':' ? colon : line[index] == '(' ? lparen : rparen); line[index++] = '\0'; } else { tokens[kount] = &line[index]; /* point into fragment of the line */ while (!is_eol(line[index]) && !is_whitespace(line[index]) && !is_rawdelimiter(line[index])) index++; } } /* * add a blank token to mark end-of-tokens */ tokens[kount] = NULL; return(tokens); } /************************************************************************\ * * * Symbol_lookup looks up a symbol in the symbol table. It takes a * * string representing the name as an argument and returns a pointer to * * the symbol structure or NULL. * * * \************************************************************************/ static symbol_t *symbol_lookup(char *name) { symbol_t *symbol; for (symbol = symbol_table; symbol; symbol = symbol->link) if (strcmp(symbol->name, name) == 0) break; return(symbol); } /***********************************************************************\ * * * symbol_insert adds a symbol to the symbol table. Duplicate symbols * * are detected. Input is a symbol and a proposed value. A one is * * returned on success and a zero if a duplicate is detected. * * * \***********************************************************************/ static int symbol_insert(char *name, int value) { symbol_t *symbol; /* * test for an existing symbol */ if (symbol_lookup(name) != NULL) return(0); /* * allocate symbol and link it into the symbol table (a singly-linked list) */ symbol = (symbol_t *)malloc(sizeof(symbol_t)); symbol->name = strdup(name); symbol->value = value; symbol->link = symbol_table; symbol_table = symbol; return(1); } /*******************************\ * * * symbol_dump: debugging aid. * * * \*******************************/ static void symbol_dump(FILE *output) { char buffer[MAXLINELEN]; symbol_t *symbol; if (symbol_table == NULL) { fprintf(output, "\t\t!\n"); fprintf(output, "\t\t! No symbols used.\n"); } else { fprintf(output, "\t\t!\n"); fprintf(output, "\t\t! Symbol table:\n"); fprintf(output, "\t\t!\n"); for (symbol = symbol_table; symbol; symbol = symbol->link) { sprintf(buffer, "\"%s\"", symbol->name); fprintf(output, "\t\t! %-20s = 0x%08X, %d\n", buffer, symbol->value, symbol->value); } } } /****************************************************************************\ * * * first_pass reads the input file filling in the symbols only. The only * * error checking performed on this pass is checking for duplicate symbols. * * input is the file, return a one on succes and a zero on failure. * * * \****************************************************************************/ static int is_delimiter(char *token) { return(token == comma || token == colon || token == lparen || token == rparen); } static int first_pass(FILE *input) { int pc = 0; /* program counter for symbol values */ char *line; /* whole input line */ char **tokens; /* ARGV-style split of line */ sourcelinenumber = 0; nerrors = 0; while ((line = getline(input)) != NULL) { tokens = lexer(line); /* convert line to tokens */ sourcelinenumber++; /* update line number for errors */ /* * blank line -- ignore it */ if (tokens[0] == NULL) continue; /* * delimiter character as first token?! */ if (is_delimiter(tokens[0])) { fprintf(stderr, "%s:%d: bad delimiter character '%s' at begining of line\n", sourcefilename, sourcelinenumber, tokens[0]); nerrors++; continue; } /* * second token is ':' delimiter -- assume the first token * is a label and add it in with the value of the current PC. */ if (tokens[1] == colon) { if (!symbol_insert(tokens[0], pc)) { fprintf(stderr, "%s:%d: duplicate symbol '%s'\n", sourcefilename, sourcelinenumber, tokens[0]); nerrors++; } tokens++; /* advance ptr to skip the label */ tokens++; /* advance ptr to skip the delimiter */ } /* * if the line (also) contains an opcode, bump the PC */ if (tokens[0] != NULL) pc++; } fprintf(stderr, "%d errors on the first pass\n", nerrors); return(nerrors == 0); /* 1 on success, 0 on failure */ } /**************************************************************************\ * * * lookup_instruction takes a string representing a mnemonic and looks it * * up in the instruction table. A pointer to a instruction structure is * * returned on success, else NULL. * * * \**************************************************************************/ static instruction_t *lookup_instruction(char *mnemonic) { int kount; for (kount = 0; kount < NINSTRS; kount++) if (strcmp(mnemonic, instruction_table[kount].mnemonic) == 0) return(&instruction_table[kount]); return(NULL); } /****************************************************************\ * * * is_numeric returns true if a token looks like a valid number * * in decimal, hex or octal. * * * \****************************************************************/ static int is_decimal_char(char blah) { return(blah >= '0' && blah <= '9'); } static int is_octal_char(char blah) { return(blah >= '0' && blah <= '7'); } static int is_hexadecimal_char(char blah) { return(is_decimal_char(blah) || (blah >= 'A' && blah <= 'F') || (blah >= 'a' && blah <= 'f')); } static int is_numeric(char *token) { int kount; assert(token[0] != '\0'); /* blank token impossible */ if (token[0] == '-') /* negative okay */ token++; if (token[0] == '0') { /* * a token beginning with 0 may be octal or hexadecimal */ if (token[1] == 'x' || token[1] == 'X') { /* * begins with 0x or 0X: it's hexadecimal. Confirm that * it has at least one digit following the X and that * all of the digits are in hex. */ if (token[2] == '\0') return(0); for (kount = 2; token[kount] != '\0'; kount++) if (!is_hexadecimal_char(token[kount])) return(0); return(1); } else { /* * didn't begin with 0x, it's octal (or just a zero). Confirm * that the rest of the digits (if any) are octal. */ for (kount = 1; token[kount] != '\0'; kount++) if (!is_octal_char(token[kount])) return(0); return(1); } } else /* i.e. token[0] != '0' */ { /* * a token beginning with a non-zero is decimal (or garbage) */ for (kount = 0; token[kount] != '\0'; kount++) if (!is_decimal_char(token[kount])) return(0); return(1); } } /* [end of is_numeric()] */ /***********************************************************************\ * * * check_length takes a ARGV-style array of pointers to strings * * and tests that given number are non-null. This is a helper * * for the second pass. Returns one on success, else prints a message * * and returns zero. * * * \***********************************************************************/ static int check_length(char **tokens, int n, char *mnem) { int nfound = 0; int kount; for (kount = 0; kount <= n; kount++) /* check n+1 items */ if (tokens[kount] != NULL) nfound++; else break; if (nfound != n) { fprintf(stderr, "%s:%d: wrong number of tokens (%d instead of %d) to %s\n", sourcefilename, sourcelinenumber, nfound, n, mnem); nerrors++; } return(nfound == n); } /*************************************************************************\ * * * token_to_regno converts a symbolic register name to a register number * * or prints an error message on failure. * * * \*************************************************************************/ static int token_to_regno(char *token, char *mnem) { static int register_help_message = 0; int kount; for (kount = 0; kount < NREGS; kount++) if (!strcmp(token, register_names[kount])) return(kount); fprintf(stderr, "%s:%d: Read '%s' when expecting a register #. opcode %s\n", sourcefilename, sourcelinenumber, token, mnem); nerrors++; if (!register_help_message) { fprintf(stderr, "valid register names are:\n"); for (kount = 0; kount < NREGS; kount++) fprintf(stderr, " %s\n", register_names[kount]); register_help_message = 1; } return(0); } /******************************************\ * * * check_delimiter helps syntax checking. * * * \******************************************/ static void check_delimiter(char *token, char *delimiter, char *mnem) { if (token != delimiter) { fprintf(stderr, "%s:%d: Read '%s' when expecting a '%s'. opcode is %s\n", sourcefilename, sourcelinenumber, token, delimiter, mnem); nerrors++; } } /************************************************************************\ * * * check_offset checks that the given offset fits in a two's complement * * number in OFFSET_BITS bits. Return 1 if okay, else 0. * * * \************************************************************************/ static int check_offset(int offset, char *mnem) { int range = (1 << (OFFSET_BITS - 1)); /* i.e. 128 */ if (offset < -range) { fprintf(stderr, "%s:%d: Offset of %d is smaller than %d. opcode is %s\n", sourcefilename, sourcelinenumber, offset, -range, mnem); nerrors++; return(0); } else if (offset >= range) { fprintf(stderr, "%s:%d: Offset of %d is greater than %d. opcode is %s\n", sourcefilename, sourcelinenumber, offset, range - 1, mnem); nerrors++; return(0); } return(1); } /****************************************************\ * * * token_to_number returns a number or symbol value * * * \****************************************************/ static int token_to_number(char *token, char *mnem) { int number; if (is_numeric(token)) { number = hextoi(token); } else { symbol_t *symbol = symbol_lookup(token); if (symbol == NULL) { fprintf(stderr, "%s:%d: undefined symbol '%s'. opcode is %s\n", sourcefilename, sourcelinenumber, token, mnem); nerrors++; number = 0; } else { number = symbol->value; } } return(number); } /***********************************************************************\ * * * Constructors for particular instruction formats. The offset is a * * signed number and thus masked off to the right number of bits. The * * other inputs are expected to be the right sizes. * * * \***********************************************************************/ static long construct_R(int opcode, int a, int b, int dst) { return((opcode << OP_OFFSET) | (a << A_OFFSET) | (b << B_OFFSET) | (dst << DST_OFFSET)); } static long construct_I(int opcode, int a, int b, int offset) { return((opcode << OP_OFFSET) | (a << A_OFFSET) | (b << B_OFFSET) | ((offset & ((1 << OFFSET_BITS) - 1)) << OFFSET_OFFSET)); } static long construct_J(int opcode, int a, int b) { return((opcode << OP_OFFSET) | (a << A_OFFSET) | (b << B_OFFSET)); } static long construct_O(int opcode) { return(opcode << OP_OFFSET); } /**********************************************************************\ * * * second_pass does the bulk of the work. Re-read the input file and * * turn each line of assembly into a machine-code instruction. * * * \**********************************************************************/ static int second_pass(FILE *input, FILE *output) { int pc = 0; /* program counter for symbol values */ char *line; /* whole input line */ char **tokens; /* ARGV-style split of line */ char *mnem; /* handy pointer to mnemonic */ instruction_t *instruction; /* from the instruction table */ unsigned int machinecode; /* translated instruction */ sourcelinenumber = 0; nerrors = 0; while ((line = getline(input)) != NULL) { tokens = lexer(line); /* convert line to tokens */ sourcelinenumber++; /* update line number for errors */ /* * blank line -- ignore it */ if (tokens[0] == NULL) continue; /* * first token is a delimiter. We already complained about this * in the first pass to ignore this line also */ if (is_delimiter(tokens[0])) continue; /* * if the second token is a ':', consider it a label * and ignore it. */ if (tokens[1] == colon) { tokens++; /* advance pointer to skip the label */ tokens++; /* advance pointer to skip the colon */ } /* * no opcode on this line, ignore the whole line */ mnem = tokens[0]; /* handy */ if (mnem == NULL) continue; /* * delimiter at this point is another syntax error */ if (is_delimiter(tokens[0])) { fprintf(stderr, "%s:%d: bad delimiter '%s' where mnemonic should be\n", sourcefilename, sourcelinenumber, tokens[0]); nerrors++; continue; } /* * try to translate the opcode mnemonic. If it fails, * print a message and skip the rest of the line */ instruction = lookup_instruction(mnem); if (instruction == NULL) { fprintf(stderr, "%s:%d: bad opcode '%s'\n", sourcefilename, sourcelinenumber, mnem); nerrors++; machinecode = 0; } else { /* * now the good part: switch on the instruction format * and perform a consistency check for each format. */ switch(instruction->format) { /* * R-type is three register operands */ case R: { int a, b, dst; if (!check_length(&tokens[1], 5, mnem)) break; dst = token_to_regno(tokens[1], mnem); check_delimiter(tokens[2], comma, mnem); a = token_to_regno(tokens[3], mnem); check_delimiter(tokens[4], comma, mnem); b = token_to_regno(tokens[5], mnem); machinecode = construct_R(instruction->opcode, a, b, dst); break; } /* * I-type is two registers plus an "offset": LW R1, 4(R2) * The offset argument is either a decimal/hex number or a symbol. * The offset argument is treated as absolute by LW/SW */ case I: { int a, b, offset; if (!check_length(&tokens[1], 6, mnem)) break; b = token_to_regno(tokens[1], mnem); check_delimiter(tokens[2], comma, mnem); offset = token_to_number(tokens[3], mnem); check_offset(offset, mnem); check_delimiter(tokens[4], lparen, mnem); a = token_to_regno(tokens[5], mnem); check_delimiter(tokens[6], rparen, mnem); machinecode = construct_I(instruction->opcode, a, b, offset); break; } /* * II-type has two registers and offset: BEQ R1, R2, dst * The offset argument is either a decimal/hex number or a symbol. * The offset argument is treated as absolute for ADDI * and relative for BEQ */ case II: { int a, b, offset; if (!check_length(&tokens[1], 5, mnem)) break; b = token_to_regno(tokens[1], mnem); check_delimiter(tokens[2], comma, mnem); a = token_to_regno(tokens[3], mnem); check_delimiter(tokens[4], comma, mnem); offset = token_to_number(tokens[5], mnem); if (!strcmp(mnem, "beq")) offset = offset - (pc + 1); check_offset(offset, mnem); machinecode = construct_I(instruction->opcode, a, b, offset); break; } /* * J-type has two register operands */ case J: { int a, b; if (!check_length(&tokens[1], 3, mnem)) break; a = token_to_regno(tokens[1], mnem); check_delimiter(tokens[2], comma, mnem); b = token_to_regno(tokens[3], mnem); machinecode = construct_J(instruction->opcode, a, b); break; } /* * O-type has no operands */ case O: { if (!check_length(&tokens[1], 0, mnem)) break; machinecode = construct_O(instruction->opcode); break; } /* * S-type (a pseudo-op) has one operand. */ case S: { if (!check_length(&tokens[1], 1, mnem)) break; machinecode = token_to_number(tokens[1], mnem); break; } default: assert(0); } /* [end of switch on the instruction type] */ } /* [end of if/else known instruction] */ /* * spit out the translated instruction as a hex number. */ fprintf(output, "0x%08X ! 0x%04X: %s\n", machinecode, pc, mnem); pc++; } /* [end of the big loop on input lines] */ fprintf(stderr, "%d errors on the second pass\n", nerrors); return(nerrors == 0); /* 1 on success, 0 on failure */ } /* [end of second_pass()] */ /**********************************\ * * * construct the output filename. * * * \**********************************/ static void construct_output_filename(char *outname, char *inname) { char *ptr; strcpy(outname, inname); ptr = rindex(outname, '.'); if (ptr) strcpy(ptr, OUTEXTENSION); else strcat(outname, OUTEXTENSION); } /**********************************************************************\ * * * Main program: run the two passes! The input has to be from a file * * because we can't do a "rewind" on stdin. Output is to stdout. * * * \**********************************************************************/ int main(int argc, char *argv[]) { char outfilename[MAXLINELEN]; FILE *input, *output; /* * read the filename argument and open it. */ if (argc < 2) { fprintf(stderr, "Usage: assemble \n"); fprintf(stderr, " Reads LC-S98 assembly language from the named\n"); fprintf(stderr, " file. Writes the machine code as a sequence of\n"); fprintf(stderr, " hexadecimal numbers to stdout.\n"); return(1); } sourcefilename = argv[1]; input = fopen(sourcefilename, "r"); if (input == NULL) { fprintf(stderr, "Unable to open '%s' for read\n", sourcefilename); return(1); } /* * run the first pass. */ if (!first_pass(input)) { fprintf(stderr, "Exiting due to errors in the first pass\n"); fclose(input); return(1); } /* * rewind the input; */ rewind(input); /* * construct the output file name and open that. */ construct_output_filename(outfilename, sourcefilename); output = fopen(outfilename, "w"); if (output == NULL) { fprintf(stderr, "Unable to open '%s' for write\n", outfilename); fclose(input); return(1); } /* * run the second pass. */ if (!second_pass(input, output)) { fprintf(stderr, "Exiting due to errors in the second pass\n"); fclose(input); return(1); } /* * dump the symbol table */ symbol_dump(output); /* * success! */ fprintf(stderr, "Success!\n"); fclose(input); fclose(output); return(0); }