#include #include #include #include #include #include #include #include "as-dcpu16.h" /* * quick and dirty assembler for dcpu16 * * Justin Wind * 2012 04 07 - implementation started * 2012 04 10 - functional * * TODO * needs ability to specify location for code or data */ static const char * const src_id_ = "$Id$"; const char const out_filename_default_[] = "a.out"; unsigned int verbose_ = 0; unsigned int dryrun_ = 0; #define DEBUG_PRINTF(...) do { if (verbose_ > 2) printf(__VA_ARGS__); } while (0) #define VERBOSE_PRINTF(...) do { if (verbose_) printf(__VA_ARGS__); } while (0) static void usage_(char *prog, unsigned int full) { FILE *f = full ? stdout : stderr; char *x = strrchr(prog, '/'); if (x && *(x + 1)) prog = x + 1; if (full) fprintf(f, "%s -- \n\n", prog); fprintf(f, "Usage: %s [-h] [-v] [-s] [-o file] file [file [...]]\n", prog); if (full) { fprintf(f, "\nOptions:\n" "\t-h -- this screen\n" "\t-o -- output to [default: %s]\n" "\t-s -- allow short labels in instruction words\n" "\t-d -- dry run, print results, do not write to file\n" "\t-v -- verbose output\n", out_filename_default_); fprintf(f, "\n%78s\n", src_id_); } } /* maintain an array of the instructions we have parsed */ static struct instruction_list_ *instr_list_new(void) { size_t init_size = 1024; struct instruction_list_ *il = malloc(IL_SIZE(init_size)); if (il == NULL) { fprintf(stderr, "%s():%s\n", "malloc", strerror(errno)); return NULL; } il->allocated = init_size; il->entries = 0; return il; } static int instr_list_insert(struct instruction_list_ **il, struct instruction_ *i) { /* make room make room */ if ((*il)->entries - 1 == (*il)->allocated) { size_t new_allocated = (*il)->allocated + 1024; void *tmp_ptr = realloc(*il, IL_SIZE(new_allocated)); if (tmp_ptr == NULL) { fprintf(stderr, "%s():%s\n", "realloc", strerror(errno)); return -1; } *il = tmp_ptr; (*il)->allocated = new_allocated; } (*il)->instr[(*il)->entries] = i; (*il)->entries += 1; return 0; } /* also maintain a list of the labels we've seen, indexed back to their instructions. */ /* FIXME: ugh, this could all stand to be rewritten cleaner */ /* these lists could be rearranged to be a lot easier to wrangle and/or maybe use common interfaces */ /* they were thrown together on the fly */ static struct label_list_ *label_list_new(void) { size_t init_size = 256; struct label_list_ *ll = malloc(LL_SIZE(init_size)); if (ll == NULL) { fprintf(stderr, "%s():%s\n", "malloc", strerror(errno)); return NULL; } ll->allocated = init_size; ll->entries = 0; return ll; } /* instr here is index into instruction list */ static int label_list_insert(struct label_list_ **ll, struct instruction_ **instr) { if ((*ll)->entries - 1 == (*ll)->allocated) { size_t new_allocated = (*ll)->allocated + 256; void *tmp_ptr = realloc(*ll, IL_SIZE(new_allocated)); if (tmp_ptr == NULL) { fprintf(stderr, "%s():%s\n", "realloc", strerror(errno)); return -1; } *ll = tmp_ptr; (*ll)->allocated = new_allocated; } DEBUG_PRINTF("TRACE: adding label '%s'\n", (*instr)->label); (*ll)->label[(*ll)->entries].label = (*instr)->label; (*ll)->label[(*ll)->entries].instr = instr; (*ll)->entries += 1; return 0; } /* locate the index of a labelled instruction within the instruction list */ static struct instruction_ **label_list_find_instr(struct label_list_ *ll, char *label) { size_t x; for (x = 0; x < ll->entries; x++) { if (strcmp(ll->label[x].label, label) == 0) return ll->label[x].instr; } return NULL; } /* look up the address of a calculated address */ static int label_list_find_addr(struct label_list_ *ll, char *label, DCPU16_WORD *addr) { size_t x; for (x = 0; x < ll->entries; x++) { if (strcmp(ll->label[x].label, label) == 0) { if (ll->label[x].ready == 1) { *addr = ll->label[x].addr; return 0; } } } return -1; } /* attempt to determine the addresses of labels */ static void label_addr_calculate_(struct instruction_list_ *il, struct label_list_ *ll) { size_t i; /* walk through labels */ for (i = 0; i < ll->entries; i++) { struct instruction_ **instr; unsigned int word_count = 0; if (ll->label[i].ready) continue; /* * walk backwards through the list of instructions * until we get to the start or a known prior label address * update our label */ for (instr = ll->label[i].instr; instr >= il->instr; instr--) { word_count += (*instr)->length; if ((*instr)->label && strcmp((*instr)->label, ll->label[i].label)) { DCPU16_WORD addr; if (label_list_find_addr(ll, (*instr)->label, &addr)) { fprintf(stderr, "internal error: incomplete prior address for '%s' while calculating '%s'\n", (*instr)->label, ll->label[i].label); continue; } word_count += addr; break; } } ll->label[i].addr = word_count; ll->label[i].ready = 1; DEBUG_PRINTF("label '%s' has addr of 0x%04x\n", ll->label[i].label, word_count); } } static void instr_free_(struct instruction_ *i) { if (i->label) free(i->label); if (i->opcode) free(i->opcode); while (i->operands) { struct operand_ *o = i->operands; i->operands = o->next; free(o); } free(i); } /* generate the nibble for a given basic opcode */ static int opcode_bits_(char *opcode) { static struct { char op[4]; char value; } opcodes_lower_nibble[] = { { "JSR", 0x00 }, /* { "future nbi instruction", 0x00 }, */ { "SET", 0x01 }, { "ADD", 0x02 }, { "SUB", 0x03 }, { "MUL", 0x04 }, { "DIV", 0x05 }, { "MOD", 0x06 }, { "SHL", 0x07 }, { "SHR", 0x08 }, { "AND", 0x09 }, { "BOR", 0x0a }, { "XOR", 0x0b }, { "IFE", 0x0c }, { "IFN", 0x0d }, { "IFG", 0x0e }, { "IFB", 0x0f }, { "", 0x00 } }, *o; for (o = opcodes_lower_nibble; o->op[0]; o++) { if (strcasecmp(o->op, opcode) == 0) break; } if (o->op[0] == '\0') { fprintf(stderr, "unknown instruction '%s'\n", opcode); return -1; } return o->value; } /* generate the six bits for a given nbi opcode (aka first operand to opcode 0x00) */ static int nbi_opcode_bits_(char *nbi_opcode) { static struct { char op[4]; char value; } nbi_opcodes_bits[] = { { " ", 0x00 }, /* reserved for future */ { "JSR", 0x01 }, { "", 0x00 } }, *o; for (o = nbi_opcodes_bits; o->op[0]; o++) { if (strcasecmp(o->op, nbi_opcode) == 0) break; } if (o->op[0] == '\0') { fprintf(stderr, "unknown nbi instruction '%s'\n", o->op); return -1; } return o->value; } /* convert register character like 'x' to value like 0x03 */ static inline unsigned int register_enumerate_(char r) { const char regs[] = "AaBbCcXxYyZzIiJj"; const char *x = strchr(regs, r); if (x) return (x - regs)/2; fprintf(stderr, "internal error, unknown register character 0x%02x\n", r); return -1; } /* removes all occurences of chars from buf */ static inline void buf_strip_chars_(char *buf, char *chars) { char *s, *d; for (s = d = buf; *s; s++, d++) { while (*s && strchr(chars, *s)) { s++; } if (!*s) break; *d = *s; } *d = *s; } /* value_bits_ * generate the six bits for a given operand string * returns -1 if it could not parse the operand * returns -2 if it could not parse the operand due to an unresolved label * notes: nextword may be overwritten even if it's not used in final instruction */ static int value_bits_(struct label_list_ *ll, char *operand_orig, DCPU16_WORD *nextword, unsigned int *nextwordused, unsigned int allow_short_labels) { static char *operand = NULL; static size_t operand_sz = 0; unsigned long l; char *o, *ep; /* Our operand working buffer shouldn't ever need to be too big, but DAT might blow that assumption. */ if (operand_sz <= strlen(operand_orig)) { void *tmp_ptr; size_t new_sz = strlen(operand_orig); if (new_sz < 256) new_sz = 256; new_sz += 256; DEBUG_PRINTF("%s: allocating buffer of size %zu\n", __func__, new_sz); tmp_ptr = realloc(operand, new_sz); if (tmp_ptr == NULL) { fprintf(stderr, "%s(%zu):%s\n", "realloc", new_sz, strerror(errno)); return -1; } operand = tmp_ptr; operand_sz = new_sz; } o = strcpy(operand, operand_orig); DEBUG_PRINTF("%s: operand '%s' ", __func__, operand); /* completed later */ /* this is a very stupid parser */ /* first, let's trim all whitespace out of string at once to make parsing easier */ buf_strip_chars_(operand, " \t\n"); /* single character might match a register */ if (strlen(operand) == 1 && strchr("AaBbCcXxYyZzIiJj", *operand)) { DEBUG_PRINTF("is register %c\n", *operand); return register_enumerate_(*operand); } /* easy matches */ if (strcasecmp(operand, "POP") == 0) { DEBUG_PRINTF("is POP\n"); return 0x18; } if (strcasecmp(operand, "PUSH") == 0) { DEBUG_PRINTF("is PUSH\n"); return 0x19; } if (strcasecmp(operand, "PEEK") == 0) { DEBUG_PRINTF("is PEEK\n"); return 0x1a; } if (strcasecmp(operand, "SP") == 0) { DEBUG_PRINTF("is register SP\n"); return 0x1b; } if (strcasecmp(operand, "PC") == 0) { DEBUG_PRINTF("is register PC\n"); return 0x1c; } if (strcasecmp(operand, "O") == 0) { DEBUG_PRINTF("is register O\n"); return 0x1d; } /* is the operand [bracketed]? */ if (operand[0] == '[' && operand[strlen(operand) - 1] == ']') { /* eat the brackets */ operand[strlen(operand) - 1] = '\0'; operand++; /* is it [register]? */ if (strlen(operand) == 1 && strchr("AaBbCcXxYyZzIiJj", *operand)) { DEBUG_PRINTF("is dereferenced register %c\n", *operand); return 0x08 | register_enumerate_(*operand); } /* is it [register+something]? */ if ( (ep = strchr(operand, '+')) ) { char *reg; char *constant; /* eat the plus */ *ep = '\0'; ep++; /* figure out which one is which */ if (strlen(ep) == 1 && strchr("AaBbCcXxYyZzIiJj", *ep)) { reg = ep; constant = operand; } else if (strlen(operand) == 1 && strchr("AaBbCcXxYyZzIiJj", *operand) ) { reg = operand; constant = ep; } else { DEBUG_PRINTF("is unparsable\n"); fprintf(stderr, "couldn't parse operand '%s'\n", operand_orig); return -1; } /* check if something is understandable as a value */ errno = 0; l = strtoul(constant, &ep, 0); if (errno == 0 && (*constant && (*ep == '\0')) ) { /* string conversion went without issue */ /* validate it will fit in a word */ if (l > 0xffff) { DEBUG_PRINTF("is out of range\n"); fprintf(stderr, "constant invalid in operand '%s'\n", operand_orig); return -1; } /* seems fine */ *nextword = l & 0xffff; *nextwordused += 1; DEBUG_PRINTF("is a dereferenced register (%c) + constant (%hu)\n", *reg, *nextword); return 0x10 | register_enumerate_(*reg); } else if (errno) { DEBUG_PRINTF("is out of range\n"); fprintf(stderr, "trouble with operand '%s': %s\n", operand_orig, strerror(errno)); return -1; } /* what? still here? assume it's a label, I guess */ /* try to populate nextword with label address */ if (label_list_find_addr(ll, operand, nextword)) { DEBUG_PRINTF("(deferred label resolution)\n"); *nextwordused += 1; return -2; } DEBUG_PRINTF("is a dereferenced register (%c) + label\n", *reg); *nextwordused += 1; return 0x10 | register_enumerate_(*reg); } /* it must just be a dereferenced literal then */ errno = 0; l = strtoul(operand, &ep, 0); if (errno == 0 && (*operand && (*ep == '\0')) ) { /* string conversion went without issue */ /* validate it will fit in a word */ if (l > 0xffff) { DEBUG_PRINTF("is out of range\n"); fprintf(stderr, "constant invalid in operand '%s'\n", operand_orig); return -1; } DEBUG_PRINTF("is a dereferenced literal value (%hu)\n", *nextword); *nextword = l & 0xffff; *nextwordused += 1; return 0x1e; } else if (errno) { DEBUG_PRINTF("is out of range\n"); fprintf(stderr, "trouble with operand '%s': %s\n", operand_orig, strerror(errno)); } /* not a number? try a label */ if (label_list_find_addr(ll, operand, nextword)) { DEBUG_PRINTF("(deferred label resolution)\n"); *nextwordused += 1; return -2; } DEBUG_PRINTF("is a dereferenced label\n"); *nextwordused += 1; return 0x1e; } /* left with a literal or a label, then */ errno = 0; l = strtoul(operand, &ep, 0); if (errno == 0 || (*operand && (*ep == '\0')) ) { if (l > 0xffff) { DEBUG_PRINTF("is out of range\n"); fprintf(stderr, "constant invalid in operand '%s'\n", operand_orig); return -1; } DEBUG_PRINTF("is literal value (%lu)\n", l); if (l < 0x20) { return l + 0x20; } *nextword = l & 0xffff; *nextwordused += 1; return 0x1f; } /* try to populate nextword with label address */ if (label_list_find_addr(ll, operand, nextword)) { DEBUG_PRINTF("(deferred label resolution)\n"); /* assume non-small literal value */ *nextwordused += 1; return -2; } DEBUG_PRINTF("is label '%s' (0x%02hx)\n", operand, *nextword); if (*nextword < 0x20 && allow_short_labels) { DEBUG_PRINTF("small value label win\n"); return (0x20 + *nextword) & 0x3f; } *nextwordused += 1; return 0x1f; } static inline int instruction_print_(struct instruction_ *i, unsigned int with_label) { struct operand_ *o; int r; if (with_label) r = printf("%-16s %3s", i->label ? i->label : "", i->opcode); else r = printf("%3s", i->opcode); for (o = i->operands; o; o = o->next) r += printf(" %s%s", o->operand, o->next ? "," : ""); return r; } /* parse an instruction out of buf, create new instruction struct if seemingly valid */ /* does not actually check if instruction is valid yet */ /* buf must be 0-terminated */ static int buf_tokenize_(char *buf, struct instruction_ **next_instr) { const char const *sep = " \t\n"; struct instruction_ *instr = NULL; char *label = NULL, *opcode = NULL, *operand = NULL; char *x, *y, *st; assert(buf != NULL); assert(next_instr != NULL); *next_instr = NULL; /* kill comments */ if ((x = strchr(buf, ';')) != NULL) *x = '\0'; /* kill leading whitespace */ buf += strspn(buf, " \t\n"); /* kill trailing whitespace */ if (*buf) { x = buf + strlen(buf); while (strchr(" \t\n", *x)) { *x = '\0'; x--; } } if ((x = strrchr(buf, '\n')) != NULL) *x = '\0'; /* determine if first token is label, opcode, or we just have a blank line to ignore */ x = strtok_r(buf, sep, &st); /* empty line? nothing to do here. */ if (x == NULL) return 0; #ifdef OTHER_LABELS /* labels end with :, otherwise its an opcode */ y = x + strlen(x) - 1; if (*y == ':') { *y = '\0'; label = x; opcode = strtok_r(NULL, sep, &st); } #else /* OTHER_LABELS */ /* labels.. begin? with ':' ? okay, I guess. Whatever. */ /* otherwise, it's an opcode */ if (*x == ':') { label = x + 1; opcode = strtok_r(NULL, sep, &st); } else { label = NULL; opcode = x; } #endif /* OTHER_LABELS */ if (opcode) { operand = st; } /* extra room for assembled words */ instr = calloc(1, 3 + sizeof *instr); if (instr == NULL) { fprintf(stderr, "%s():%s\n", "calloc", strerror(errno)); return -1; } instr->label = label ? strdup(label) : NULL; instr->opcode = opcode ? strdup(opcode) : NULL; if (operand) { struct operand_ **o_next = &instr->operands; for (x = strtok_r(operand, ",", &st); x; x = strtok_r(NULL, ",", &st) ) { *o_next = malloc(3 + sizeof **o_next); /* FIXME: handle this on the fly later */ if (*o_next == NULL) { fprintf(stderr, "%s():%s\n", "calloc", strerror(errno)); instr_free_(instr); return -1; } /* trim */ x += strspn(x, " \t\n"); if (*x) { y = x + strlen(x) - 1; while (strchr(" \t\n", *y)) { *y = '\0'; y--; } } (*o_next)->operand = strdup(x); (*o_next)->next = NULL; o_next = &((*o_next)->next); } } *next_instr = instr; return 0; } /* try to generate bytecode for an instruction */ static int instr_assemble_(struct label_list_ *ll, struct instruction_ *i, unsigned int allow_short_labels) { unsigned int nwu = 0; /* number of words used */ unsigned int incomplete = 0; int bits; struct operand_ *o = i->operands; if (verbose_ > 2) { printf("%s: assembling ", __func__); instruction_print_(i,1); printf("\n"); } if (i->ready) { /* already assembled, nothing to do */ return 0; } /* special case DAT */ if (strncasecmp(i->opcode, "DAT", 3) == 0) { /* just dump operands into words, I guess */ fprintf(stderr, "FIXME unhandled raw data\n"); /* count total length of data.. */ /* realloc instruction */ /* populate words */ return 0; } /* start with opcode bits */ bits = opcode_bits_(i->opcode); if (bits < 0) { fprintf(stderr, "unrecognized instruction '%s%s", i->opcode, i->operands ? " " : ""); for (o = i->operands; o; o = o->next) fprintf(stderr, " %s%s", o->operand, o->next ? "," : ""); fprintf(stderr, "'\n"); return -1; } i->instr_words[0] |= 0x0f & bits; /* in rendered bytecode, all instructions have two operands; nbi instructions take 'first operand' bits. */ if ((bits & 0x0f) == 0) { bits = nbi_opcode_bits_(i->opcode); if (bits < 0) { fprintf(stderr, "INTERNAL ERROR: missing instruction in nbi opcode table\n"); exit(EX_SOFTWARE); } } else { if (o == NULL) { fprintf(stderr, "'%s' requires more operands\n", i->opcode); return -1; } bits = value_bits_(ll, o->operand, i->instr_words + 1, &nwu, allow_short_labels); if (bits == -1) { fprintf(stderr, "couldn't assemble instruction\n"); return -1; } else if (bits == -2) { DEBUG_PRINTF("%s: assembly deferred: unresolved label\n", __func__); /* keep going, but don't finalize until we can calculate label address */ incomplete = 1; bits = 0; } o = o->next; } i->instr_words[0] |= (bits & 0x3f) << 4; if (o == NULL) { fprintf(stderr, "'%s' requires more operands\n", i->opcode); return -1; } bits = value_bits_(ll, o->operand, i->instr_words + nwu + 1, &nwu, allow_short_labels); if (bits == -1) { fprintf(stderr, "couldn't assemble instruction\n"); return -1; } else if (bits == -2) { DEBUG_PRINTF("%s: assembly deferred: unresolved label\n", __func__); /* keep going, but don't finalize until we can calculate label address */ incomplete = 1; bits = 0; } o = o->next; i->instr_words[0] |= (bits & 0x3f) << 10; if (o != NULL) { fprintf(stderr, "too many operands\n"); return -1; } /* counting labels as words, we now know at least the maximum instruction length */ i->length = nwu + 1; DEBUG_PRINTF("instruction words: [%u]", i->length); for (bits = 0; bits <= (int)nwu; bits++) DEBUG_PRINTF(" %04x", i->instr_words[bits]); if (incomplete) { DEBUG_PRINTF(" (preliminary)"); } else { i->ready = 1; } DEBUG_PRINTF("\n"); return 0; } /* parse_stream_ * read lines from stream f * break each line into parts, populate parts into structures */ static int parse_stream_(FILE *f, const char *src, struct instruction_list_ **il, struct label_list_ **ll, unsigned int allow_short_labels) { struct instruction_ *instr, **instr_list_entry; unsigned int line = 0; int retval = 0; char buf[0x4000]; buf[sizeof buf - 1] = '\0'; while (fgets(buf, sizeof buf, f)) { line++; if (buf[sizeof buf - 1] != '\0') { fprintf(stderr, "%s:%u:%s", src, line, "input line too long\n"); retval = -1; break; } if (buf_tokenize_(buf, &instr)) { fprintf(stderr, "%s:%u:%s", src, line, "trouble tokenizing input\n"); retval = -1; break; } if (instr) { /* add to list of instructions */ if (instr_list_insert(il, instr)) { fprintf(stderr, "%s:%u:%s", src, line, "could not populate instruction list\n"); } instr_list_entry = (*il)->instr + (*il)->entries - 1; if (instr->label) { if (label_list_find_instr(*ll, instr->label)) { fprintf(stderr, "%s:%u:%s", src, line, "duplicate label\n"); break; } if (label_list_insert(ll, instr_list_entry)) { fprintf(stderr, "%s:%u:%s", src, line, "could not populate label list\n"); } label_addr_calculate_(*il, *ll); } instr_assemble_(*ll, instr, allow_short_labels); } } if (ferror(f)) { fprintf(stderr, "%s():%s\n", "fgets", strerror(errno)); return -1; } if (! feof(f)) { fprintf(stderr, "parsing aborted\n"); return -1; } return retval; } /* assemble_check_ * make a full pass over instruction list to resolve labels */ static int assemble_check_(struct instruction_list_ *il, struct label_list_ *ll, unsigned int allow_short_labels) { int retval = 0; size_t x; DEBUG_PRINTF(" final pass of assembler...\n"); for (x = 0; x < il->entries; x++) { retval |= instr_assemble_(ll, il->instr[x], allow_short_labels); if (retval) { fprintf(stderr, "instruction failed to assemble\n"); } } VERBOSE_PRINTF("%3s %6s %-32s %-4s\n", "", "_addr_", "_label_", "_instruction_"); for (x = 0; x < ll->entries; x++) { if (! ll->label[x].ready) retval |= -1; if (verbose_) { printf("%3s0x%04x %-32s ", ll->label[x].ready ? "" : "*", ll->label[x].addr, ll->label[x].label); instruction_print_(*(ll->label[x].instr), 0); printf("\n"); } } VERBOSE_PRINTF("\n"); if (retval) fprintf(stderr, "some labels could not be resolved\n"); return retval; } static int output_(struct instruction_list_ *il, const char *filename) { FILE *of = NULL; struct instruction_ *instr; size_t i, r, total_words = 0; size_t x; if (! dryrun_) { of = fopen(filename, "w"); if (of == NULL) { fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno)); return -1; } } for (i = 0; i < il->entries; i++) { instr = il->instr[i]; if (verbose_) { int s; s = instruction_print_(instr, 1); printf("%*s;", (44 - s) > 0 ? (44 - s) : 0, ""); for (x = 0; x < instr->length; x++) { printf(" %04x", instr->instr_words[x]); } printf("\n"); } if (of) { r = fwrite(instr->instr_words, sizeof(DCPU16_WORD), instr->length, of); if (r < instr->length) { fprintf(stderr, "%s():%s\n", "fwrite", strerror(errno)); return -1; } } total_words += instr->length; } fprintf(stderr, "%s 0x%04zx instructions as 0x%04zx words\n", dryrun_ ? "assembled" : "wrote", i, total_words); return 0; } static struct instruction_list_ *il_; static struct label_list_ *ll_; int main(int argc, char *argv[]) { const char *out_filename = NULL; unsigned int allow_short_labels = 0; int c; while ( (c = getopt(argc, argv, "dhsvo:")) != EOF ) { switch (c) { case 'd': dryrun_++; break; case 's': allow_short_labels++; break; case 'o': if (out_filename) { fprintf(stderr, "Sorry, I can only write one file at a time.\n"); exit(EX_CANTCREAT); } out_filename = optarg; break; case 'v': verbose_++; break; case 'h': usage_(argv[0], 1); exit(EX_OK); default: usage_(argv[0], 0); exit(EX_USAGE); } } argc -= optind; argv += optind; if (out_filename == NULL) out_filename = out_filename_default_; /* init tables */ il_ = instr_list_new(); ll_ = label_list_new(); /* if filenames were specified, parse them instead of stdin */ if (argc) { while (argc) { char *filename = *argv; FILE *f = fopen(filename, "r"); argc--, argv++; if (f == NULL) { fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno)); continue; } VERBOSE_PRINTF("assembling '%s'...\n", filename); parse_stream_(f, filename, &il_, &ll_, allow_short_labels); fclose(f); } } else { VERBOSE_PRINTF("assembling '%s'...\n", "stdin"); parse_stream_(stdin, "-", &il_, &ll_, allow_short_labels); } if (assemble_check_(il_, ll_, allow_short_labels)) { fprintf(stderr, "errors prevented assembly\n"); exit(EX_DATAERR); } if (output_(il_, out_filename)) { fprintf(stderr, "failed to create output\n"); exit(EX_OSERR); } exit(EX_OK); }