agssim: allow interactive mode with file
[rofl0r-agsutils.git] / agssim.c
blobef9c9252534d50e2de24253300e8b76f19f4832f
1 #include <stdio.h>
2 #include <ctype.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <assert.h>
6 #include <errno.h>
8 #include "ags_cpu.h"
9 #include "regusage.h"
10 #include "hbmap.h"
11 #include "version.h"
12 #define ADS ":::AGSSim " VERSION " by rofl0r:::"
14 #ifndef MAX
15 #define MAX(a, b) ((a) > (b) ? (a) : (b))
16 #endif
17 #ifndef MIN
18 #define MIN(a, b) ((a) < (b) ? (a) : (b))
19 #endif
21 #ifndef ARRAY_SIZE
22 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
23 #endif
25 static int interactive;
27 static struct text_segment {
28 int *code;
29 size_t len;
30 size_t capa;
31 } text;
33 struct label_ref {
34 char *name;
35 unsigned insno;
37 tglist(struct label_ref) *label_refs;
38 static void add_label_ref(char *name, unsigned insno) {
39 struct label_ref new = {.name = strdup(name), .insno = insno};
40 tglist_add(label_refs, new);
42 static void resolve_label(char* name, unsigned insno) {
43 size_t i;
44 for(i=0; i<tglist_getsize(label_refs); ) {
45 struct label_ref *l = &tglist_get(label_refs, i);
46 if(!strcmp(l->name, name)) {
47 free(l->name);
48 text.code[l->insno] = insno;
49 tglist_delete(label_refs, i);
50 } else ++i;
53 /* label_map */
54 hbmap(char*, unsigned, 32) *label_map;
55 static unsigned *get_label_offset(char* name) {
56 return hbmap_get(label_map, name);
58 static int add_label(char* name, int insno) {
59 char* tmp = strdup(name);
60 return hbmap_insert(label_map, tmp, insno) != -1;
62 static int strptrcmp(const void *a, const void *b) {
63 const char * const *x = a;
64 const char * const *y = b;
65 return strcmp(*x, *y);
67 static unsigned string_hash(const char* s) {
68 uint_fast32_t h = 0;
69 while (*s) {
70 h = 16*h + *s++;
71 h ^= h>>24 & 0xf0;
73 return h & 0xfffffff;
75 static void init_labels() {
76 label_map = hbmap_new(strptrcmp, string_hash, 32);
77 label_refs = tglist_new();
80 /* TODO: move duplicate code from Assembler.c into separate TU */
81 static int get_reg(char* regname) {
82 int i = AR_NULL + 1;
83 for(; i < AR_MAX; i++)
84 if(strcmp(regnames[i], regname) == 0)
85 return i;
86 return AR_NULL;
89 static size_t mnemolen[SCMD_MAX];
90 static int mnemolen_initdone = 0;
92 static void init_mnemolen(void) {
93 size_t i = 0;
94 for(; i< SCMD_MAX; i++)
95 mnemolen[i] = strlen(opcodes[i].mnemonic);
96 mnemolen_initdone = 1;
99 static unsigned find_insn(char* sym) {
100 if(!mnemolen_initdone) init_mnemolen();
101 size_t i = 0, l = strlen(sym);
102 for(; i< SCMD_MAX; i++)
103 if(l == mnemolen[i] && memcmp(sym, opcodes[i].mnemonic, l) == 0)
104 return i;
105 return 0;
108 #include "StringEscape.h"
109 /* expects a pointer to the first char after a opening " in a string,
110 * converts the string into convbuf, and returns the length of that string */
111 static size_t get_length_and_convert(char* x, char* end, char* convbuf, size_t convbuflen) {
112 size_t result = 0;
113 char* e = x + strlen(x);
114 assert(e > x && e < end && *e == 0);
115 e--;
116 while(isspace(*e)) e--;
117 if(*e != '"') return (size_t) -1;
118 *e = 0;
119 result = unescape(x, convbuf, convbuflen);
120 return result;
123 /* sets lets char in arg to 0, and advances pointer till the next argstart */
124 static char* finalize_arg(char **p, char* pend, char* convbuf, size_t convbuflen) {
125 if(**p == '"') {
126 convbuf[0] = '"';
127 size_t l= get_length_and_convert(*p + 1, pend, convbuf+1, convbuflen - 1);
128 if(l == (size_t) -1) return 0;
129 convbuf[l+1] = '"';
130 convbuf[l+2] = 0;
131 *p = 0; /* make it crash if its accessed again, since a string should always be the last arg */
132 return convbuf;
133 } else {
134 char* ret = *p;
135 while(*p < pend && **p != ',' && !isspace(**p)) (*p)++;
136 assert(*p < pend);
137 **p = 0; (*p)++;
138 while(*p < pend && isspace(**p)) (*p)++;
139 assert(*p < pend);
140 return ret;
144 static struct rval {
145 union {
146 int i;
147 float f;
149 enum RegisterUsage ru;
150 } registers[AR_MAX];
152 static unsigned char stack_mem[1000*4];
153 #define memory stack_mem
155 static int canread(int index, int cnt) {
156 return index >= 0 && index+cnt < sizeof(memory)/sizeof(memory[0]);
159 static void grow_text(size_t req) {
160 if(text.len + req > text.capa) {
161 text.code = realloc(text.code, (text.capa+1024)*sizeof(int));
162 text.capa += 1024;
166 static void append_code(int *code, size_t cnt) {
167 grow_text(cnt+1);
168 size_t i;
169 for(i = 0; i < cnt; i++) {
170 text.code[text.len++] = code[i];
172 text.code[text.len] = 0;
175 static void vm_reset_register_usage() {
176 size_t i;
177 for(i = AR_NULL + 1; i < AR_MAX; i++)
178 registers[i].ru = RU_NONE;
181 static void vm_init() {
182 size_t i;
183 /* initialize registers to an easily recognisable junk value */
184 for(i = AR_NULL + 1; i < AR_MAX; i++) {
185 registers[i].i = 2222222222;
187 vm_reset_register_usage();
188 registers[AR_SP].i = 0;
189 registers[AR_NULL].i = 0;
190 int was_null = text.code == 0;
191 /* set up EIP so vm_state() doesn't crash */
192 grow_text(16);
193 /* put NULL insn as first instruction so VM doesn't execute
194 random garbage in mem */
195 if(was_null) text.code[0] = 0;
198 static inline int consume_int(int **eip) {
199 *eip = *eip+1;
200 return **eip;
203 static void change_reg_usage(int regno, enum RegisterAccess ra) {
204 registers[regno].ru = get_reg_usage(regno, registers[regno].ru, ra);
207 static void vm_update_register_usage(int *eip) {
208 const struct regaccess_info *ri = &regaccess_info[*eip];
209 if(ri->ra_reg1) change_reg_usage(eip[1], ri->ra_reg1);
210 if(ri->ra_reg2) change_reg_usage(eip[2], ri->ra_reg2);
211 if(ri->ra_mar) change_reg_usage(AR_MAR, ri->ra_mar);
212 if(ri->ra_sp) change_reg_usage(AR_SP, ri->ra_sp);
215 static void write_mem1(int off, int val) {
216 unsigned char *m = (void*) memory;
217 m[off] = val&0xff;
219 static void write_mem2(int off, int val) {
220 unsigned short *m = (void*) memory;
221 m[off/2] = val&0xffff;
223 static void write_mem(int off, int val) {
224 int *m = (void*) memory;
225 m[off/4] = val;
228 static int read_mem(int off) {
229 int *m = (void*) memory;
230 return m[off/4];
233 static int vm_push(int value) {
234 if(!canread(registers[AR_SP].i, 4)) return 0;
235 write_mem(registers[AR_SP].i, value);
236 registers[AR_SP].i += 4;
237 return 1;
240 static int vm_pop(int *value) {
241 if((int) registers[AR_SP].i >= 4) {
242 registers[AR_SP].i -= 4;
243 *value = read_mem(registers[AR_SP].i);
244 return 1;
246 return 0;
249 static int vm_syscall(int scno) {
250 int ret, arg1, arg2, arg3;
251 /* we follow linux x86_64 syscall numbers for simplicity */
252 switch(scno) {
253 case 0: /* SYS_read (fd, buf, size) */
254 /* fall-through */
255 case 1: /* SYS_write (fd, buf, size) */
256 if(!vm_pop(&arg1) || !vm_pop(&arg2) || !vm_pop(&arg3)) return -EINVAL;
257 if(!canread(arg2, arg3)) return -EFAULT;
258 if(scno == 0)
259 ret = read(arg1, ((char*)memory)+arg2, arg3);
260 else
261 ret = write(arg1, ((char*)memory)+arg2, arg3);
262 if(ret == -1) return -errno;
263 return ret;
264 case 60: /* SYS_exit (exitcode) */
265 if(!vm_pop(&arg1)) arg1 = 1;
266 exit(arg1);
267 default: return -ENOSYS;
271 static int label_check() {
272 if(tglist_getsize(label_refs)) {
273 dprintf(2, "error: unresolved label refs!\n");
274 size_t i; struct label_ref *l;
275 for(i=0; i<tglist_getsize(label_refs); ++i) {
276 l = &tglist_get(label_refs, i);
277 dprintf(2, "%s@%u\n", l->name, l->insno);
279 return 0;
281 return 1;
284 #define CODE_INT(X) eip[X]
285 #define CODE_FLOAT(X) ((float*)eip)[X]
286 #define REGI(X) registers[CODE_INT(X)].i
287 #define REGF(X) registers[CODE_INT(X)].f
289 static int vm_step(int run_context) {
290 if(!run_context && !label_check()) return 0;
291 /* we use register AR_NULL as instruction pointer */
292 #define EIP registers[AR_NULL].i
293 int *eip = &text.code[EIP];
294 int eip_inc = 1 + opcodes[*eip].argcount;
295 int tmp, val;
296 if(!run_context) vm_reset_register_usage();
297 vm_update_register_usage(eip);
299 switch(*eip) {
300 case 0:
301 /* don't modify IP */
302 dprintf(2, "no code at IP %u.\n", EIP);
303 return 0;
304 case SCMD_ADD:
305 REGI(1) += CODE_INT(2);
306 break;
307 case SCMD_SUB:
308 REGI(1) -= CODE_INT(2);
309 break;
310 case SCMD_REGTOREG:
311 REGI(2) = REGI(1);
312 break;
313 case SCMD_LITTOREG:
314 REGI(1) = CODE_INT(2);
315 break;
316 case SCMD_MULREG:
317 REGI(1) *= REGI(2);
318 break;
319 case SCMD_DIVREG:
320 REGI(1) /= REGI(2);
321 break;
322 case SCMD_ADDREG:
323 REGI(1) += REGI(2);
324 break;
325 case SCMD_SUBREG:
326 REGI(1) -= REGI(2);
327 break;
328 case SCMD_BITAND:
329 REGI(1) &= REGI(2);
330 break;
331 case SCMD_BITOR:
332 REGI(1) &= REGI(2);
333 break;
334 case SCMD_ISEQUAL:
335 REGI(1) = !!(REGI(1) == REGI(2));
336 break;
337 case SCMD_NOTEQUAL:
338 REGI(1) = !!(REGI(1) != REGI(2));
339 break;
340 case SCMD_GREATER:
341 REGI(1) = !!(REGI(1) > REGI(2));
342 break;
343 case SCMD_LESSTHAN:
344 REGI(1) = !!(REGI(1) < REGI(2));
345 break;
346 case SCMD_GTE:
347 REGI(1) = !!(REGI(1) >= REGI(2));
348 break;
349 case SCMD_LTE:
350 REGI(1) = !!(REGI(1) <= REGI(2));
351 break;
352 case SCMD_AND:
353 REGI(1) = !!(REGI(1) && REGI(2));
354 break;
355 case SCMD_OR:
356 REGI(1) = !!(REGI(1) || REGI(2));
357 break;
358 case SCMD_LOADSPOFFS:
359 registers[AR_MAR].i = registers[AR_SP].i - CODE_INT(1);
360 break;
361 case SCMD_PUSHREG:
362 if(!vm_push(REGI(1))) goto oob;
363 break;
364 case SCMD_POPREG:
365 if(!vm_pop(&REGI(1))) goto oob;
366 break;
367 case SCMD_MUL:
368 REGI(1) *= CODE_INT(2);
369 break;
370 case SCMD_THISBASE:
371 case SCMD_LINENUM:
372 break;
373 case SCMD_MODREG:
374 REGI(1) %= REGI(2);
375 break;
376 case SCMD_XORREG:
377 REGI(1) ^= REGI(2);
378 break;
379 case SCMD_NOTREG:
380 REGI(1) = !REGI(2);
381 break;
382 case SCMD_SHIFTLEFT:
383 REGI(1) <<= REGI(2);
384 break;
385 case SCMD_SHIFTRIGHT:
386 REGI(1) >>= REGI(2);
387 break;
388 case SCMD_FADD:
389 REGF(1) += CODE_FLOAT(2);
390 break;
391 case SCMD_FSUB:
392 REGF(1) -= CODE_FLOAT(2);
393 break;
394 case SCMD_FMULREG:
395 REGF(1) *= REGF(2);
396 break;
397 case SCMD_FDIVREG:
398 REGF(1) /= REGF(2);
399 break;
400 case SCMD_FADDREG:
401 REGF(1) += REGF(2);
402 break;
403 case SCMD_FSUBREG:
404 REGF(1) -= REGF(2);
405 break;
406 case SCMD_FGREATER:
407 REGI(1) = !!(REGF(1) > REGF(2));
408 break;
409 case SCMD_FLESSTHAN:
410 REGI(1) = !!(REGF(1) < REGF(2));
411 break;
412 case SCMD_FGTE:
413 REGI(1) = !!(REGF(1) >= REGF(2));
414 break;
415 case SCMD_FLTE:
416 REGI(1) = !!(REGF(1) <= REGF(2));
417 break;
418 case SCMD_WRITELIT:
419 tmp = CODE_INT(1);
420 if(tmp <= 0 || tmp > 4 || tmp == 3) {
421 dprintf(2, "VM: invalid memcpy use at IP %u\n", EIP);
422 break;
424 val = CODE_INT(2);
425 goto mwrite;
426 case SCMD_MEMWRITE:
427 tmp = 4;
428 val = REGI(1);
429 goto mwrite;
430 case SCMD_MEMWRITEW:
431 tmp = 2;
432 val = REGI(1);
433 goto mwrite;
434 case SCMD_MEMWRITEB:
435 tmp = 1;
436 val = REGI(1);
437 mwrite:
438 if(canread(registers[AR_MAR].i, tmp)) {
439 switch(tmp) {
440 case 4: write_mem (registers[AR_MAR].i, val); break;
441 case 2: write_mem2(registers[AR_MAR].i, val); break;
442 case 1: write_mem1(registers[AR_MAR].i, val); break;
444 } else {
445 oob:
446 dprintf(2, "info: caught OOB access at IP %u\n", EIP);
448 break;
449 case SCMD_MEMREAD:
450 tmp = 4;
451 goto mread;
452 case SCMD_MEMREADW:
453 tmp = 2;
454 goto mread;
455 case SCMD_MEMREADB:
456 tmp = 1;
457 mread:
458 if(canread(registers[AR_MAR].i, tmp)) {
459 int val = memory[registers[AR_MAR].i];
460 switch(tmp) {
461 case 4: REGI(1) = val; break;
462 case 2: REGI(1) = val & 0xffff; break;
463 case 1: REGI(1) = val & 0xff; break;
465 } else goto oob;
466 break;
467 case SCMD_JZ:
468 if(registers[AR_AX].i == 0) goto jump;
469 break;
470 case SCMD_JNZ:
471 if(registers[AR_AX].i == 0) break;
472 /* fall through */
473 case SCMD_JMP:
474 jump:
475 tmp = CODE_INT(1);
476 jump_tmp:
477 if((unsigned)tmp <= text.len)
478 registers[AR_NULL].i = tmp;
479 else dprintf(2, "error: caught invalid jump to %u at IP %u\n", tmp, EIP);
480 eip_inc = 0;
481 break;
482 case SCMD_CALL:
483 if(!vm_push(registers[AR_NULL].i + eip_inc)) goto oob;
484 tmp = REGI(1);
485 goto jump_tmp;
486 case SCMD_RET:
487 registers[AR_SP].i -= 4;
488 tmp = read_mem(registers[AR_SP].i);
489 goto jump_tmp;
490 case SCMD_CALLAS:
491 /* we re-purpose "callscr" mnemonic to mean syscall,
492 as it is unused in ags-emitted bytecode.
493 using it is unportable, it works only in agssim.
494 syscall number is passed in reg, arguments on the stack. */
495 registers[AR_AX].i = vm_syscall(REGI(1));
496 break;
497 case SCMD_NEWARRAY:
498 case SCMD_DYNAMICBOUNDS:
499 case SCMD_MEMZEROPTRND:
500 case SCMD_LOOPCHECKOFF:
501 case SCMD_CHECKNULLREG:
502 case SCMD_STRINGSNOTEQ:
503 case SCMD_STRINGSEQUAL:
504 case SCMD_CREATESTRING:
505 case SCMD_ZEROMEMORY:
506 case SCMD_CHECKNULL:
507 case SCMD_MEMINITPTR:
508 case SCMD_MEMZEROPTR:
509 case SCMD_MEMREADPTR:
510 case SCMD_MEMWRITEPTR:
511 case SCMD_CHECKBOUNDS:
512 case SCMD_CALLOBJ:
513 case SCMD_NUMFUNCARGS:
514 case SCMD_SUBREALSTACK:
515 case SCMD_PUSHREAL:
516 case SCMD_CALLEXT:
517 default:
518 dprintf(2, "info: %s not implemented yet\n", opcodes[*eip].mnemonic);
520 size_t i, l = opcodes[*eip].argcount;
521 for(i = 0; i < l; i++) ++(*eip);
523 break;
525 registers[AR_NULL].i += eip_inc;
526 return 1;
529 static inline char *int_to_str(int value, char* out) {
530 sprintf(out, "%d", value);
531 return out;
534 static void vm_state() {
535 if(!interactive) return;
536 static const char ru_strings[][3] = {
537 [RU_NONE] = {0},
538 [RU_READ] = {'R', 0},
539 [RU_WRITE] = {'W', 0},
540 [RU_WRITE_AFTER_READ] = {'R', 'W', 0},
542 static const char regorder[] = {
543 0, AR_MAR, AR_OP, AR_SP, -1,
544 AR_AX, AR_BX, AR_CX, AR_DX, -1, -1};
545 size_t i, j;
546 for(j=0; j < ARRAY_SIZE(regorder)-1; ++j) {
547 i = regorder[j];
548 if(i == -1) printf("\n");
549 else {
550 printf("%-3s: %-2s %-11d", i == 0 ? "eip" : regnames[i], ru_strings[registers[i].ru], registers[i].i);
551 if(regorder[j+1] != -1) printf(" ");
555 for( i = MIN(registers[AR_SP].i+2*4, sizeof(stack_mem)/4);
556 i >= MAX(registers[AR_SP].i-2*4, 0);
557 i-=4) {
558 printf("SL %s %3zu %d\n", i == registers[AR_SP].i ? ">" : " ", i, read_mem(i));
559 if(i == 0) break;
562 int *eip = &text.code[registers[AR_NULL].i];
563 char arg1buf[32], arg2buf[32];
564 const char *arg1 = opcodes[*eip].argcount == 0 ? "" : \
565 (opcodes[*eip].regcount > 0 ? regnames[eip[1]] : int_to_str(eip[1], arg1buf));
566 const char *arg2 = opcodes[*eip].argcount < 2 ? "" : \
567 (opcodes[*eip].regcount > 1 ? regnames[eip[2]] : int_to_str(eip[2], arg2buf));
568 printf(" > %s %s %s\n", opcodes[*eip].mnemonic, arg1, arg2);
571 void vm_run(void) {
572 if(!label_check()) return;
573 while(1) {
574 int *eip = &text.code[registers[AR_NULL].i];
575 if(!*eip) break;
576 if(!vm_step(1)) break;
580 static int usage(int fd, char *a0) {
581 dprintf(fd,
582 "%s [OPTIONS] [file.s] - simple ags vm simulator\n"
583 "implements the ALU and a small stack\n"
584 "useful to examine how a chunk of code modifies VM state\n"
585 "OPTIONS:\n"
586 "-i : interpreter mode - don't print anything, run and exit\n"
587 "by default, mode is interactive, sporting the following commands:\n"
588 "!i - reset VM state and IP\n"
589 "!s - single-step\n"
590 "!r - run\n"
591 , a0);
592 return 1;
595 static int lastcommand;
596 enum UserCommand {
597 UC_STEP = 1,
598 UC_RUN,
599 UC_INIT,
600 UC_QUIT,
601 UC_HELP,
603 static void execute_user_command_i(int uc) {
604 switch(uc) {
605 case UC_STEP: vm_step(0); break;
606 case UC_RUN : vm_run(); break;
607 case UC_INIT: vm_init(); break;
608 case UC_QUIT: exit(0); break;
609 case UC_HELP: usage(1, "agssim"); break;
611 lastcommand = uc;
612 vm_state();
614 static void execute_user_command(char *cmd) {
615 int uc = 0;
616 if(0) ;
617 else if(!strcmp(cmd, "s")) uc = UC_STEP;
618 else if(!strcmp(cmd, "r")) uc = UC_RUN;
619 else if(!strcmp(cmd, "i")) uc = UC_INIT;
620 else if(!strcmp(cmd, "q")) uc = UC_QUIT;
621 else if(!strcmp(cmd, "h")) uc = UC_HELP;
622 else {
623 dprintf(2, "unknown command\n");
624 return;
626 execute_user_command_i(uc);
629 int main(int argc, char** argv) {
630 int c;
631 interactive = 1;
632 FILE *in = stdin;
633 while((c = getopt(argc, argv, "i")) != EOF) switch(c) {
634 case 'i': interactive = 0; break;
635 default: return usage(2, argv[0]);
637 if(argv[optind]) in = fopen(argv[optind], "r");
638 if(!in) {
639 dprintf(2, "error opening %s\n", argv[optind]);
640 return 1;
642 char buf[1024], *sym;
643 char convbuf[sizeof(buf)]; /* to convert escaped string into non-escaped version */
644 int lineno = 0;
645 init_labels();
646 vm_init();
647 if(interactive) printf(ADS " - type !h for help\n");
648 mainloop:
649 while(fgets(buf, sizeof buf, in)) {
650 int code[4];
651 size_t pos = 0;
652 lineno++;
653 char* p = buf, *pend = buf + sizeof buf;
654 if(*p == '\n' && lastcommand) {
655 execute_user_command_i(lastcommand);
656 continue;
658 if(*p == '#' || *p == ';') continue;
659 if(*p == '!') {
660 char *n = strchr(p, '\n');
661 if(n) *n = 0;
662 execute_user_command(p+1);
663 continue;
665 while(isspace(*p) && p < pend) p++;
666 assert(p < pend);
667 if(!*p) continue;
668 char* sym = p;
669 while(!isspace(*p) && p < pend) p++;
670 *p = 0; p++;
671 size_t l = strlen(sym);
672 if(l > 1 && sym[l-1] == ':') {
673 // functionstart or label
674 sym[l-1] = 0;
675 resolve_label(sym, text.len);
676 unsigned *loff = get_label_offset(sym);
677 if(loff) dprintf(2, "warning: label %s overwritten\n");
678 add_label(sym, text.len);
679 continue;
681 unsigned instr = find_insn(sym);
682 if(!instr) {
683 dprintf(2, "line %zu: error: unknown instruction '%s'\n", lineno, sym);
684 continue;
686 code[pos++] = instr;
687 size_t arg;
688 for(arg = 0; arg < opcodes[instr].argcount; arg++) {
689 sym = finalize_arg(&p, pend, convbuf, sizeof(convbuf));
690 if(sym == 0) {
691 dprintf(2, "line %zu: error: expected \"\n", lineno);
692 goto loop_footer;
694 int value = 0;
695 if(arg < opcodes[instr].regcount) {
696 value=get_reg(sym);
697 if(value == AR_NULL) {
698 needreg_err:
699 dprintf(2, "line %zu: error: expected register name!\n", lineno);
700 goto loop_footer;
702 if(instr == SCMD_REGTOREG) {
703 /* fix reversed order of arguments */
704 int dst = value;
705 sym = p;
706 while(p < pend && *p != ',' && !isspace(*p)) p++;
707 assert(p < pend);
708 *p = 0;
709 value=get_reg(sym);
710 if(value == AR_NULL) goto needreg_err;
711 code[pos++] = value;
712 code[pos++] = dst;
713 break;
715 } else {
716 switch(instr) {
717 case SCMD_LITTOREG:
718 /* immediate can be function name, string,
719 * variable name, stack fixup, or numeric value */
720 if(sym[0] == '"') {
721 dprintf(2, "error: string handling not implemented\n");
722 goto loop_footer;
723 } else if(sym[0] == '@') {
724 dprintf(2, "error: global variable handling not implemented\n");
725 goto loop_footer;
726 } else if(sym[0] == '.') {
727 if(memcmp(sym+1, "stack", 5)) {
728 dprintf(2, "error: expected stack\n");
729 goto loop_footer;;
731 dprintf(2, "error: stack fixup not implemented\n");
732 goto loop_footer;
733 } else if(isdigit(sym[0]) || sym[0] == '-') {
734 if(sym[0] == '-') assert(isdigit(sym[1]));
735 value = atoi(sym);
736 } else {
737 goto label_ref;
739 break;
740 case SCMD_JMP: case SCMD_JZ: case SCMD_JNZ: {
741 label_ref:;
742 unsigned *loff = get_label_offset(sym);
743 if(!loff) {
744 add_label_ref(sym, text.len+pos);
745 value = -1;
746 } else value = *loff;
747 } break;
748 default:
749 if(!isdigit(sym[0])) {
750 dprintf(2, "error: expected number\n");
751 goto loop_footer;
753 value = atoi(sym);
756 code[pos++] = value;
758 append_code(code, pos);
759 loop_footer: ;
761 if(!interactive) execute_user_command("r");
762 else if(in != stdin) {
763 in = stdin;
764 goto mainloop;
766 return 0;