38d77ce6c6e497d293834a0bcd8d5687205ead2c
[dcpu16] / as-dcpu16.c
1 #include <stdlib.h>
2 #include <unistd.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <errno.h>
6 #include <sysexits.h>
7 #include <assert.h>
8
9 #include "as-dcpu16.h"
10
11 /*
12 * quick and dirty assembler for dcpu16
13 *
14 */
15
16 static const char * const src_id_ = "$Id$";
17
18 const char const out_filename_default_[] = "a.out";
19 unsigned int verbose_ = 0;
20 unsigned int dryrun_ = 0;
21
22
23 #define DEBUG_NOTIFY(...) do { if (verbose_ > 2) fprintf(stderr, __VA_ARGS__); } while (0)
24 #define VERBOSE_NOTIFY(...) do { if (verbose_) printf(__VA_ARGS__); } while (0)
25
26
27 static
28 void usage_(char *prog, unsigned int full) {
29 FILE *f = full ? stdout : stderr;
30 char *x = strrchr(prog, '/');
31
32 if (x && *(x + 1))
33 prog = x + 1;
34
35 if (full)
36 fprintf(f, "%s -- \n\n",
37 prog);
38
39 fprintf(f, "Usage: %s [-h] [-v] [-s] [-o file] file [file [...]]\n",
40 prog);
41
42 if (full) {
43 fprintf(f, "\nOptions:\n"
44 "\t-h -- this screen\n"
45 "\t-o <file> -- output to <file> [default: %s]\n"
46 "\t-s -- allow short labels in instruction words\n"
47 "\t-d -- dry run, print results, do not write to file\n"
48 "\t-v -- verbose output\n",
49 out_filename_default_);
50
51 fprintf(f, "\n%78s\n",
52 src_id_);
53 }
54 }
55
56
57 /* maintain an array of the instructions we have parsed */
58 static
59 struct instruction_list_ *instr_list_new(void) {
60 size_t init_size = 1024;
61 struct instruction_list_ *il = malloc(IL_SIZE(init_size));
62 if (il == NULL) {
63 fprintf(stderr, "%s():%s\n", "malloc", strerror(errno));
64 return NULL;
65 }
66 il->allocated = init_size;
67 il->entries = 0;
68 return il;
69 }
70
71 static
72 int instr_list_insert(struct instruction_list_ **il, struct instruction_ *i) {
73 /* make room make room */
74 if ((*il)->entries - 1 == (*il)->allocated) {
75 size_t new_allocated = (*il)->allocated + 1024;
76 void *tmp_ptr = realloc(*il, IL_SIZE(new_allocated));
77 if (tmp_ptr == NULL) {
78 fprintf(stderr, "%s():%s\n", "realloc", strerror(errno));
79 return -1;
80 }
81 *il = tmp_ptr;
82 (*il)->allocated = new_allocated;
83 }
84
85 (*il)->instr[(*il)->entries] = i;
86 (*il)->entries += 1;
87 return 0;
88 }
89
90 /* also maintain a list of the labels we've seen, indexed back to their instructions. */
91 /* FIXME: ugh, this could all stand to be rewritten cleaner */
92 /* these lists could be rearranged to be a lot easier to wrangle and/or maybe use common interfaces */
93 /* they were thrown together on the fly */
94 static
95 struct label_list_ *label_list_new(void) {
96 size_t init_size = 256;
97 struct label_list_ *ll = malloc(LL_SIZE(init_size));
98 if (ll == NULL) {
99 fprintf(stderr, "%s():%s\n", "malloc", strerror(errno));
100 return NULL;
101 }
102 ll->allocated = init_size;
103 ll->entries = 0;
104 return ll;
105 }
106
107 /* instr here is index into instruction list */
108 static
109 int label_list_insert(struct label_list_ **ll, struct instruction_ **instr) {
110 if ((*ll)->entries - 1 == (*ll)->allocated) {
111 size_t new_allocated = (*ll)->allocated + 256;
112 void *tmp_ptr = realloc(*ll, IL_SIZE(new_allocated));
113 if (tmp_ptr == NULL) {
114 fprintf(stderr, "%s():%s\n", "realloc", strerror(errno));
115 return -1;
116 }
117 *ll = tmp_ptr;
118 (*ll)->allocated = new_allocated;
119 }
120
121 DEBUG_NOTIFY("TRACE: adding label '%s'\n", (*instr)->label);
122
123 (*ll)->label[(*ll)->entries].label = (*instr)->label;
124 (*ll)->label[(*ll)->entries].instr = instr;
125 (*ll)->entries += 1;
126 return 0;
127 }
128
129 /* locate the index of a labelled instruction within the instruction list */
130 static
131 struct instruction_ **label_list_find_instr(struct label_list_ *ll, char *label) {
132 size_t x;
133
134 for (x = 0; x < ll->entries; x++) {
135 if (strcmp(ll->label[x].label, label) == 0)
136 return ll->label[x].instr;
137 }
138 return NULL;
139 }
140
141 /* look up the address of a calculated address */
142 static
143 int label_list_find_addr(struct label_list_ *ll, char *label, DCPU16_WORD *addr) {
144 size_t x;
145
146 for (x = 0; x < ll->entries; x++) {
147 if (strcmp(ll->label[x].label, label) == 0) {
148 if (ll->label[x].ready == 1) {
149 *addr = ll->label[x].addr;
150 return 0;
151 }
152 }
153 }
154 return -1;
155 }
156
157 /* attempt to determine the addresses of labels */
158 static
159 void label_addr_calculate_(struct instruction_list_ *il, struct label_list_ *ll) {
160 size_t i;
161
162 /* walk through labels */
163 for (i = 0; i < ll->entries; i++) {
164 struct instruction_ **instr;
165 unsigned int word_count = 0;
166
167 if (ll->label[i].ready)
168 continue;
169
170 /*
171 * walk backwards through the list of instructions
172 * until we get to the start or a known prior label address
173 * update our label
174 */
175 for (instr = ll->label[i].instr; instr >= il->instr; instr--) {
176
177 word_count += (*instr)->length;
178
179 if ((*instr)->label
180 && strcmp((*instr)->label, ll->label[i].label)) {
181 DCPU16_WORD addr;
182
183 if (label_list_find_addr(ll, (*instr)->label, &addr)) {
184 fprintf(stderr, "internal error: incomplete prior address for '%s' while calculating '%s'\n",
185 (*instr)->label,
186 ll->label[i].label);
187 continue;
188 }
189 word_count += addr;
190 break;
191 }
192 }
193 ll->label[i].addr = word_count;
194 ll->label[i].ready = 1;
195 DEBUG_NOTIFY("label '%s' has addr of 0x%04x\n", ll->label[i].label, word_count);
196 }
197 }
198
199 static
200 void instr_free_(struct instruction_ *i) {
201 if (i->label)
202 free(i->label);
203 if (i->opcode)
204 free(i->opcode);
205 while (i->operands) {
206 struct operand_ *o = i->operands;
207
208 i->operands = o->next;
209 free(o);
210 }
211
212 free(i);
213 }
214
215 /* generate the nibble for a given basic opcode */
216 static
217 int opcode_bits_(char *opcode) {
218 static struct {
219 char op[4];
220 char value;
221 } opcodes_lower_nibble[] = {
222 { "JSR", 0x00 },
223 /* { "future nbi instruction", 0x00 }, */
224 { "SET", 0x01 },
225 { "ADD", 0x02 },
226 { "SUB", 0x03 },
227 { "MUL", 0x04 },
228 { "DIV", 0x05 },
229 { "MOD", 0x06 },
230 { "SHL", 0x07 },
231 { "SHR", 0x08 },
232 { "AND", 0x09 },
233 { "BOR", 0x0a },
234 { "XOR", 0x0b },
235 { "IFE", 0x0c },
236 { "IFN", 0x0d },
237 { "IFG", 0x0e },
238 { "IFB", 0x0f },
239 { "", 0x00 }
240 }, *o;
241
242 for (o = opcodes_lower_nibble; o->op[0]; o++) {
243 if (strcasecmp(o->op, opcode) == 0)
244 break;
245 }
246
247 if (o->op[0] == '\0') {
248 fprintf(stderr, "unknown instruction '%s'\n", opcode);
249 return -1;
250 }
251
252 return o->value;
253 }
254
255 /* generate the six bits for a given nbi opcode (aka first operand to opcode 0x00) */
256 static
257 int nbi_opcode_bits_(char *nbi_opcode) {
258 static struct {
259 char op[4];
260 char value;
261 } nbi_opcodes_bits[] = {
262 { " ", 0x00 }, /* reserved for future */
263 { "JSR", 0x01 },
264 { "", 0x00 }
265 }, *o;
266
267 for (o = nbi_opcodes_bits; o->op[0]; o++) {
268 if (strcasecmp(o->op, nbi_opcode) == 0)
269 break;
270 }
271
272 if (o->op[0] == '\0') {
273 fprintf(stderr, "unknown nbi instruction '%s'\n", o->op);
274 return -1;
275 }
276
277 return o->value;
278 }
279
280 /* convert register character like 'x' to value like 0x03 */
281 static inline
282 unsigned int register_enumerate_(char r) {
283 const char regs[] = "AaBbCcXxYyZzIiJj";
284 const char *x = strchr(regs, r);
285
286 if (x)
287 return (x - regs)/2;
288
289 fprintf(stderr, "internal error, unknown register character 0x%02x\n", r);
290 return -1;
291 }
292
293 /* generate the six bits for a given operand */
294 /* FIXME: MAEK BETTR */
295 /* notes: nextword may be rewritten even if it's not used in final instruction */
296 static
297 int value_bits_(struct label_list_ *ll, char *operand_orig, DCPU16_WORD *nextword, unsigned int *nextwordused, unsigned int allow_short_labels) {
298 unsigned int retval = -1;
299 unsigned long l;
300 char *operand, *o, *ep;
301
302 operand = o = strdup(operand_orig);
303
304 DEBUG_NOTIFY("TRACE: operand '%s' is ", operand);
305
306 if (strlen(operand) == 1) {
307 if ( (strchr("ABCXYZIJ", *operand))
308 || (strchr("abcxyzij", *operand)) ) {
309 DEBUG_NOTIFY("register\n");
310 retval = register_enumerate_(*operand);
311 goto done;
312 }
313 }
314
315 if (operand[0] == '[' && operand[strlen(operand) - 1] == ']') {
316 operand[strlen(operand) - 1] = '\0';
317 operand++;
318 /* trim whitespaces */
319 while (strchr(" \t\n", *operand)) operand++;
320 ep = operand + strlen(operand) - 1;
321
322 if (strlen(operand) == 1) {
323 DEBUG_NOTIFY("dereferenced register\n");
324 retval = 0x08 | register_enumerate_(*operand);
325 goto done;
326 }
327
328 if ( (ep = strchr(operand, '+')) ) {
329 char reg;
330 char *constant;
331
332 while (strchr("+ \t\n", *ep)) {
333 *ep = '\0';
334 ep++;
335 }
336 if (strlen(ep) == 1) {
337 reg = *ep;
338 constant = operand;
339 } else if (strlen(operand) == 1) {
340 reg = *operand;
341 constant = ep;
342 } else {
343 fprintf(stderr, "couldn't parse operand\n");
344 goto done;
345 }
346
347 if ( strchr("ABCXYZIJ", reg)
348 || strchr("abcxyzij", reg) ) {
349 l = strtoul(constant, &ep, 0);
350 }
351
352 DEBUG_NOTIFY("dereferenced register+constant\n");
353
354 DEBUG_NOTIFY("\tregister_index:%u %c\n", reg, register_enumerate_(reg));
355 DEBUG_NOTIFY("\tconstant:%lu\n", l);
356
357 *nextword = l & 0xffff;
358 *nextwordused += 1;
359 retval = 0x10 | register_enumerate_(reg);
360 goto done;
361 }
362
363 l = strtoul(operand, &ep, 0);
364 DEBUG_NOTIFY("dereferenced literal value %lu...\n", l);
365 *nextword = l & 0xffff;
366 *nextwordused += 1;
367 retval = 0x1e;
368 goto done;
369 }
370
371 if (strcasecmp(operand, "POP") == 0) {
372 DEBUG_NOTIFY("POP\n");
373 retval = 0x18;
374 goto done;
375 }
376 if (strcasecmp(operand, "PUSH") == 0) {
377 DEBUG_NOTIFY("PUSH\n");
378 retval = 0x19;
379 goto done;
380 }
381 if (strcasecmp(operand, "PEEK") == 0) {
382 DEBUG_NOTIFY("PEEK\n");
383 retval = 0x1a;
384 goto done;
385 }
386 if (strcasecmp(operand, "SP") == 0) {
387 DEBUG_NOTIFY("sp register\n");
388 retval = 0x1b;
389 goto done;
390 }
391 if (strcasecmp(operand, "PC") == 0) {
392 DEBUG_NOTIFY("pc register\n");
393 retval = 0x1c;
394 goto done;
395 }
396 if (strcasecmp(operand, "O") == 0) {
397 DEBUG_NOTIFY("o register\n");
398 retval = 0x1d;
399 goto done;
400 }
401
402 l = strtoul(operand, &ep, 0);
403
404 if (operand && *ep == '\0') {
405 DEBUG_NOTIFY("literal value %lu...\n", l);
406 if (l < 0x20) {
407 retval = l + 0x20;
408 goto done;
409 } else {
410 *nextword = l & 0xffff;
411 *nextwordused += 1;
412 retval = 0x1f;
413 goto done;
414 }
415 }
416
417 /* try to populate nextword with label address */
418 if (label_list_find_addr(ll, operand, nextword)) {
419 DEBUG_NOTIFY("currently-unknown label...\n");
420 /* assume non-small literal value */
421 *nextwordused += 1;
422 goto done;
423 }
424
425 DEBUG_NOTIFY("label '%s' 0x%02hx\n", operand, *nextword);
426 if (*nextword < 0x20 && allow_short_labels) {
427 DEBUG_NOTIFY("small value label win\n");
428 retval = (0x20 + *nextword) & 0x3f;
429 goto done;
430 }
431
432 retval = 0x1f;
433 *nextwordused += 1;
434
435 done:
436 free(o);
437 return retval;
438 }
439
440 static inline
441 int instruction_print_(struct instruction_ *i, unsigned int with_label) {
442 struct operand_ *o;
443 int r;
444
445 if (with_label)
446 r = printf("%-16s %3s", i->label ? i->label : "", i->opcode);
447 else
448 r = printf("%3s", i->opcode);
449
450 for (o = i->operands; o; o = o->next)
451 r += printf(" %s%s", o->operand, o->next ? "," : "");
452
453 return r;
454 }
455
456 /* parse an instruction out of buf, create new instruction struct if seemingly valid */
457 /* does not actually check if instruction is valid yet */
458 /* buf must be 0-terminated */
459 static
460 int buf_tokenize_(char *buf, struct instruction_ **next_instr) {
461 const char const *sep = " \t\n";
462 struct instruction_ *instr = NULL;
463 char *label = NULL,
464 *opcode = NULL,
465 *operand = NULL;
466
467 char *x,
468 *y,
469 *st;
470
471 assert(buf != NULL);
472 assert(next_instr != NULL);
473
474 *next_instr = NULL;
475
476 /* kill comments */
477 if ((x = strchr(buf, ';')) != NULL)
478 *x = '\0';
479 /* kill leading whitespace */
480 buf += strspn(buf, " \t\n");
481 /* kill trailing whitespace */
482 if (*buf) {
483 x = buf + strlen(buf);
484 while (strchr(" \t\n", *x)) {
485 *x = '\0';
486 x--;
487 }
488 }
489
490 if ((x = strrchr(buf, '\n')) != NULL)
491 *x = '\0';
492
493 /* determine if first token is label, opcode, or we just have a blank line to ignore */
494 x = strtok_r(buf, sep, &st);
495
496 /* empty line? nothing to do here. */
497 if (x == NULL)
498 return 0;
499
500 #ifdef OTHER_LABELS
501 /* labels end with :, otherwise its an opcode */
502 y = x + strlen(x) - 1;
503 if (*y == ':') {
504 *y = '\0';
505 label = x;
506 opcode = strtok_r(NULL, sep, &st);
507 }
508 #else /* OTHER_LABELS */
509 /* labels.. begin? with ':' ? okay, I guess. Whatever. */
510 /* otherwise, it's an opcode */
511 if (*x == ':') {
512 label = x + 1;
513 opcode = strtok_r(NULL, sep, &st);
514 } else {
515 label = NULL;
516 opcode = x;
517 }
518 #endif /* OTHER_LABELS */
519
520 if (opcode) {
521 operand = st;
522 }
523
524 /* extra room for assembled words */
525 instr = calloc(1, 3 + sizeof *instr);
526 if (instr == NULL) {
527 fprintf(stderr, "%s():%s\n", "calloc", strerror(errno));
528 return -1;
529 }
530
531 instr->label = label ? strdup(label) : NULL;
532 instr->opcode = opcode ? strdup(opcode) : NULL;
533
534 if (operand) {
535 struct operand_ **o_next = &instr->operands;
536
537 for (x = strtok_r(operand, ",", &st);
538 x;
539 x = strtok_r(NULL, ",", &st) ) {
540 *o_next = malloc(3 + sizeof **o_next); /* FIXME: handle this on the fly later */
541
542 if (*o_next == NULL) {
543 fprintf(stderr, "%s():%s\n", "calloc", strerror(errno));
544 instr_free_(instr);
545 return -1;
546 }
547
548 /* trim */
549 x += strspn(x, " \t\n");
550 if (*x) {
551 y = x + strlen(x) - 1;
552 while (strchr(" \t\n", *y)) {
553 *y = '\0';
554 y--;
555 }
556 }
557
558 (*o_next)->operand = strdup(x);
559 (*o_next)->next = NULL;
560 o_next = &((*o_next)->next);
561 }
562 }
563
564 *next_instr = instr;
565
566 return 0;
567 }
568
569 /* try to generate bytecode for an instruction */
570 static
571 void instr_bytecodify_(struct label_list_ *ll, struct instruction_ *i, unsigned int allow_short_labels) {
572 unsigned int nwu = 0; /* number of words used */
573 unsigned int incomplete = 0;
574 int bits;
575 struct operand_ *o = i->operands;
576
577 DEBUG_NOTIFY("TRACE: codifying %s%s'%s'...", i->label ? i->label : "", i->label ? ":" : "", i->opcode);
578
579 if (i->ready) {
580 /* already codified */
581 return;
582 }
583
584 /* special case DAT */
585 if (strncasecmp(i->opcode, "DAT", 3) == 0) {
586 /* just dump operands into words, I guess */
587 fprintf(stderr, "FIXME unhandled raw data\n");
588 /* count total length of data.. */
589 /* realloc instruction */
590 /* populate words */
591 return;
592 }
593
594 /* start with opcode bits */
595 bits = opcode_bits_(i->opcode);
596 if (bits < 0) {
597 fprintf(stderr, "unrecognized instruction '%s'\n", i->opcode);
598 return;
599 }
600 i->instr_words[0] |= 0x0f & bits;
601
602 /* in rendered bytecode, all instructions have two operands; nbi instructions take 'first operand' bits. */
603 if ((bits & 0x0f) == 0) {
604 bits = nbi_opcode_bits_(i->opcode);
605 if (bits < 0) {
606 fprintf(stderr, "internal error: missing instruction in nbi opcode table\n");
607 return;
608 }
609 } else {
610 if (o == NULL) {
611 fprintf(stderr, "'%s' requires more operands\n", i->opcode);
612 return;
613 }
614 bits = value_bits_(ll, o->operand, i->instr_words + 1, &nwu, allow_short_labels);
615 if (bits < 0) {
616 DEBUG_NOTIFY("TRACE: unresolved label\n");
617 /* keep going, but don't finalize until we can calculate label address */
618 incomplete = 1;
619 bits = 0;
620 }
621 o = o->next;
622 }
623 i->instr_words[0] |= (bits & 0x3f) << 4;
624
625 if (o == NULL) {
626 fprintf(stderr, "'%s' requires more operands\n", i->opcode);
627 return;
628 }
629
630 bits = value_bits_(ll, o->operand, i->instr_words + nwu + 1, &nwu, allow_short_labels);
631 if (bits < 0) {
632 DEBUG_NOTIFY("TRACE: unresolved label\n");
633 /* keep going, but don't finalize until we can calculate label address */
634 incomplete = 1;
635 bits = 0;
636 }
637 o = o->next;
638 i->instr_words[0] |= (bits & 0x3f) << 10;
639
640 /* counting labels as words, we now know the maximum instruction length */
641 /* if label is < 0x20, it can take up less space */
642
643 i->length = nwu + 1;
644
645 DEBUG_NOTIFY("instruction words: [%u]", i->length);
646 for (bits = 0; bits <= (int)nwu; bits++)
647 DEBUG_NOTIFY(" 0x%04x", i->instr_words[bits]);
648
649 if (incomplete) {
650 DEBUG_NOTIFY(" (preliminary)");
651 } else {
652 i->ready = 1;
653 }
654
655 DEBUG_NOTIFY("\n");
656 }
657
658 /* thish should grow buffer to fit huge linesh, but I jusht don't care right now, hic */
659 static
660 int parse_stream_(FILE *f, struct instruction_list_ **il, struct label_list_ **ll, unsigned int allow_short_labels) {
661 struct instruction_ *instr, **instr_list_entry;
662 char buf[(1<<14)];
663
664 buf[sizeof buf - 1] = '\0';
665
666 while (fgets(buf, sizeof buf, f)) {
667 if (buf[sizeof buf - 1] != '\0') {
668 fprintf(stderr, "input buffer exhausted\n");
669 break;
670 }
671
672 if (buf_tokenize_(buf, &instr)) {
673 fprintf(stderr, "trouble tokenizing input\n");
674 break;
675 }
676
677 if (instr) {
678 /* add to list of instructions */
679 if (instr_list_insert(il, instr)) {
680 fprintf(stderr, "could not populate instruction list\n");
681 }
682 instr_list_entry = (*il)->instr + (*il)->entries - 1;
683 DEBUG_NOTIFY("TRACE: verify %s == %s\n", (*instr_list_entry)->opcode, instr->opcode);
684 if (instr->label) {
685 if (label_list_find_instr(*ll, instr->label)) {
686 fprintf(stderr, "duplicate label\n");
687 break;
688 }
689 if (label_list_insert(ll, instr_list_entry)) {
690 fprintf(stderr, "could not populate label list\n");
691 }
692 label_addr_calculate_(*il, *ll);
693 }
694
695 instr_bytecodify_(*ll, instr, allow_short_labels);
696 }
697 }
698 if (ferror(f)) {
699 fprintf(stderr, "%s():%s\n", "fgets", strerror(errno));
700 return -1;
701 }
702 if (! feof(f)) {
703 fprintf(stderr, "parsing aborted\n");
704 return -1;
705 }
706
707 return 0;
708 }
709
710 static
711 int assemble_check_(struct instruction_list_ *il, struct label_list_ *ll, unsigned int allow_short_labels) {
712 int retval = 0;
713 size_t x;
714
715 DEBUG_NOTIFY(" final pass of codifier...\n");
716 for (x = 0; x < il->entries; x++) {
717 instr_bytecodify_(ll, il->instr[x], allow_short_labels);
718 }
719
720 VERBOSE_NOTIFY("%3s %6s %-32s %-4s\n", "", "_addr_", "_label_", "_instruction_");
721 for (x = 0; x < ll->entries; x++) {
722 if (! ll->label[x].ready)
723 retval |= -1;
724 VERBOSE_NOTIFY("%3s0x%04x %-32s ",
725 ll->label[x].ready ? "" : "*",
726 ll->label[x].addr,
727 ll->label[x].label);
728 if (verbose_) {
729 instruction_print_(*(ll->label[x].instr), 0);
730 printf("\n");
731 }
732 }
733
734 VERBOSE_NOTIFY("\n");
735
736 if (retval)
737 fprintf(stderr, "some labels could not be resolved\n");
738
739 return retval;
740 }
741
742 static
743 int output_(struct instruction_list_ *il, const char *filename) {
744 FILE *of = NULL;
745 struct instruction_ *instr;
746 size_t i, r, total_words = 0;
747 size_t x;
748
749 if (! dryrun_) {
750 of = fopen(filename, "w");
751 if (of == NULL) {
752 fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno));
753 return -1;
754 }
755 }
756
757 for (i = 0; i < il->entries; i++) {
758 instr = il->instr[i];
759
760 if (verbose_) {
761 int s;
762 s = instruction_print_(instr, 1);
763 printf("%*s;", (44 - s) > 0 ? (44 - s) : 0, "");
764 for (x = 0; x < instr->length; x++) {
765 printf(" %04x", instr->instr_words[x]);
766 }
767 printf("\n");
768 }
769
770 if (of) {
771 r = fwrite(instr->instr_words, sizeof(DCPU16_WORD), instr->length, of);
772 if (r < instr->length) {
773 fprintf(stderr, "%s():%s\n", "fwrite", strerror(errno));
774 return -1;
775 }
776 }
777 total_words += instr->length;
778 }
779
780 fprintf(stderr, "wrote 0x%04zx instructions as 0x%04zx words\n",
781 i,
782 total_words);
783 return 0;
784 }
785
786 static struct instruction_list_ *il_;
787 static struct label_list_ *ll_;
788
789 int main(int argc, char *argv[]) {
790 const char *out_filename = NULL;
791 unsigned int allow_short_labels = 0;
792 int c;
793
794 while ( (c = getopt(argc, argv, "dhsvo:")) != EOF ) {
795 switch (c) {
796 case 'd':
797 dryrun_++;
798 break;
799
800 case 's':
801 allow_short_labels++;
802 break;
803
804 case 'o':
805 if (out_filename) {
806 fprintf(stderr, "Sorry, I can only write one file at a time.\n");
807 exit(EX_CANTCREAT);
808 }
809 out_filename = optarg;
810 break;
811
812 case 'v':
813 verbose_++;
814 break;
815
816 case 'h':
817 usage_(argv[0], 1);
818 exit(EX_OK);
819
820 default:
821 usage_(argv[0], 0);
822 exit(EX_USAGE);
823 }
824 }
825
826 argc -= optind;
827 argv += optind;
828
829 if (out_filename == NULL)
830 out_filename = out_filename_default_;
831
832 /* init tables */
833 il_ = instr_list_new();
834 ll_ = label_list_new();
835
836 /* if filenames were specified, parse them instead of stdin */
837 if (argc) {
838 while (argc) {
839 char *filename = *argv;
840 FILE *f = fopen(filename, "r");
841
842 argc--, argv++;
843
844 if (f == NULL) {
845 fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno));
846 continue;
847 }
848
849 VERBOSE_NOTIFY("assembling '%s'...\n", filename);
850 parse_stream_(f, &il_, &ll_, allow_short_labels);
851
852 fclose(f);
853 }
854 } else {
855 VERBOSE_NOTIFY("assembling '%s'...\n", "stdin");
856 parse_stream_(stdin, &il_, &ll_, allow_short_labels);
857 }
858
859 if (assemble_check_(il_, ll_, allow_short_labels)) {
860 fprintf(stderr, "errors prevented assembly\n");
861 exit(EX_DATAERR);
862 }
863
864 if (output_(il_, out_filename)) {
865 fprintf(stderr, "failed to create output\n");
866 exit(EX_OSERR);
867 }
868
869 exit(EX_OK);
870 }