modualarized 'hardware' api changes complete
[dcpu16] / vm-dcpu16.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <strings.h>
6 #include <signal.h>
7 #include <errno.h>
8 #include <assert.h>
9 #include <sysexits.h>
10 #include <time.h>
11 #include <sys/time.h>
12
13 #include <readline/readline.h>
14 #ifdef HAVE_LIBVNCSERVER
15 #include "rfb/rfb.h"
16 #endif /* HAVE_LIBVNCSERVER */
17
18 #include "dcpu16.h"
19 #include "common.h"
20
21 #include "hw_lem1802.h"
22 #include "hw_keyboard.h"
23
24 /*
25 * shell-like driver for dcpu16 core
26 * provides a basic interface to control a single emulation instance
27 *
28 * Justin Wind <justin.wind@gmail.com>
29 * 2012 04 10 - implementation started
30 * 2012 04 12 - cleanup, better shell loop
31 * 2012 05 12 - support v1.7 style devices
32 *
33 * TODO
34 * handle quotes in shell command parsing
35 * use readline/history.h, since we're using readline anyhow
36 * ncurses windowing or something, for future display capabilities
37 */
38
39 static const char * const src_id_ = "$Id$";
40
41 /* global invocation options */
42 struct options {
43 unsigned int verbose;
44 } opt_ = {
45 .verbose = 0,
46 };
47
48 /* global run state, first sigint caught will drop out of run loop and back into shell */
49 static volatile unsigned int running_ = 0;
50 static
51 void sigint_handler_(int sig) {
52 (void)sig;
53 running_ = 0;
54 }
55
56 #define VERBOSE_PRINTF(...) do { if (opt_.verbose) printf(__VA_ARGS__); } while (0)
57
58 static
59 void usage_(char *prog, unsigned int full) {
60 FILE *f = full ? stdout : stderr;
61 char *x = strrchr(prog, '/');
62
63 if (x && *(x + 1))
64 prog = x + 1;
65
66 if (full)
67 fprintf(f, "%s -- dcpu16 emulator core shell\n\n",
68 prog);
69
70 fprintf(f, "Usage: %s [-v] [file]\n",
71 prog);
72
73 if (full) {
74 fprintf(f, "\nOptions:\n"
75 "\t [file] -- ram image to load initially\n"
76 "\t -v -- prints slightly more information while operating\n"
77 "\t -h -- this screen\n");
78
79 fprintf(f, "\n%78s\n", src_id_);
80 }
81 }
82
83
84 /* flense a buffer into a newly-allocated argument list */
85 static
86 int buf_tok_vect_(char ***v, int *c, char *buf) {
87 const char *sep = " \t";
88 const char *quot = "\"'`";
89 const size_t v_grow = 32;
90 size_t v_sz = 32;
91 char *st, *qt;
92
93 *c = 0;
94 *v = malloc(v_sz * sizeof **v);
95 if (*v == NULL) {
96 fprintf(stderr, "%s():%s\n", "malloc", strerror(errno));
97 return -1;
98 }
99
100 for ( (*v)[*c] = strqtok_r(buf, sep, '\\', quot, &qt, &st);
101 (*v)[*c];
102 (*v)[*c] = strqtok_r(NULL, sep, '\\', quot, &qt, &st)
103 ) {
104 (*c)++;
105
106 if ((size_t)(*c) == v_sz) {
107 void *tmp_ptr = realloc(*v, (v_sz + v_grow) * sizeof **v);
108 if (tmp_ptr == NULL) {
109 fprintf(stderr, "%s():%s\n", "realloc", strerror(errno));
110 free(*v);
111 *v = NULL;
112 return -1;
113 }
114 v_sz += v_grow;
115 }
116 }
117
118 return 0;
119 }
120
121 /*
122 resets the vm if addr is zero then
123 loads an image from filename into ram starting at addr
124 */
125 static
126 int file_load_(struct dcpu16 *vm, char *filename, DCPU16_WORD addr) {
127 FILE *f;
128 size_t r;
129
130 if (!addr)
131 dcpu16_reset(vm);
132
133 f = fopen(filename, "rb");
134 if (f == NULL) {
135 fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno));
136 return -1;
137 }
138
139 r = fread(vm->ram + addr, sizeof(DCPU16_WORD), DCPU16_RAM - addr, f);
140 VERBOSE_PRINTF("read %zu words", r);
141 if (addr) VERBOSE_PRINTF(" starting at 0x%04x", addr);
142 VERBOSE_PRINTF("\n");
143
144 if (ferror(f))
145 fprintf(stderr, "%s('%s'):%s\n", "fread", filename, strerror(errno));
146
147 fclose(f);
148 return 0;
149 }
150
151
152 #ifdef HAVE_LIBVNCSERVER
153 static struct dynamic_array rfbScreens_;
154 /* wups, kbdAddEvent isn't null by default, so I guess track things externally */
155 struct rfb_instance_ {
156 rfbScreenInfoPtr screen;
157 struct dcpu16_hw *attached_display;
158 struct dcpu16_hw *attached_keyboard;
159 };
160
161 /* locate or allocate the next display with an un-occupied framebuffer */
162 static
163 struct rfb_instance_ *rfbScreen_next_available_display_(struct dynamic_array *rfbScreens, int argc, char *argv[]) {
164 size_t i;
165 struct rfb_instance_ new_instance, *s;
166
167 fprintf(stderr, "DEBUG: rfbScreens->entries:%zu\n", rfbScreens->entries);
168
169 for (i = 0; i < rfbScreens->entries; i++) {
170 s = (struct rfb_instance_ *)DYNARRAY_ITEM(*rfbScreens, i);
171 if (s->attached_display == NULL)
172 return s;
173 }
174
175 new_instance.screen = lem1802_rfb_new(argc, argv);
176 new_instance.attached_display = NULL;
177 new_instance.attached_keyboard = NULL;
178 s = dynarray_add(rfbScreens, &new_instance);
179 return s;
180 }
181
182 /* locate or allocate the next display with an un-occupied keyboard */
183 static
184 struct rfb_instance_ *rfbScreen_next_available_keyboard_(struct dynamic_array *rfbScreens, int argc, char *argv[]) {
185 size_t i;
186 struct rfb_instance_ new_instance, *s;
187
188 for (i = 0; i < rfbScreens->entries; i++) {
189 s = (struct rfb_instance_ *)DYNARRAY_ITEM(*rfbScreens, i);
190 if (s->attached_keyboard == NULL)
191 return s;
192 }
193
194 new_instance.screen = lem1802_rfb_new(argc, argv);
195 new_instance.attached_display = NULL;
196 new_instance.attached_keyboard = NULL;
197 s = dynarray_add(rfbScreens, &new_instance);
198 return s;
199 }
200
201 /* begin serving a screen */
202 void rfbScreen_start(rfbScreenInfoPtr s) {
203 rfbInitServer(s);
204 rfbRunEventLoop(s, -1, TRUE);
205 }
206 #endif /* HAVE_LIBVNCSERVER */
207
208 /*
209 Here follows the various commands the shell can execute.
210
211 At invocation, a command function will have already had its
212 number of arguments vetted, but will need command-specific
213 argument verifications done.
214
215 The arg_vector contains the command as the first entry, and
216 as such, arg_count will always be at least 1.
217 However, the args_min and args_max entries in struct command_
218 only refer to the counts of arguments, not the entries in the
219 argv.
220 */
221
222 struct command_ {
223 char *name;
224 int args_min;
225 int args_max;
226 int (*func)(struct dcpu16 *, int c, char **v);
227 void (*help)(FILE *f, unsigned int);
228 };
229
230 #define COMMAND_IMPL(x) static int command_##x##_(struct dcpu16 *vm, int arg_count, char **arg_vector)
231 #define COMMAND_HELP(x) static void command_##x##_help_(FILE *f, unsigned int summary)
232 #define COMMAND_ENTRY(x, y, z) { #x, y, z, command_##x##_, command_##x##_help_ }
233
234
235 COMMAND_IMPL(quit) {
236 (void)vm, (void)arg_count, (void)arg_vector;
237
238 return -1;
239 }
240 COMMAND_HELP(quit) {
241 fprintf(f, "\tquit\n");
242 if (summary) return;
243
244 fprintf(f, "Exits the emulator.\n");
245 }
246
247
248 COMMAND_IMPL(reset) {
249 (void)arg_count, (void)arg_vector;
250
251 dcpu16_reset(vm);
252 printf("initialized\n");
253 return 0;
254 }
255 COMMAND_HELP(reset) {
256 fprintf(f, "\treset\n");
257 if (summary) return;
258
259 fprintf(f, "Clears and reinitializes emulator.\n");
260 }
261
262
263 COMMAND_IMPL(load) {
264 int addr = 0;
265
266 if (arg_count > 2) {
267 addr = str_to_word(arg_vector[2]);
268 if (addr < 0) {
269 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno));
270 return 0;
271 }
272 }
273
274 if (file_load_(vm, arg_vector[1], addr)) {
275 fprintf(stderr, "failed to load '%s'\n", arg_vector[1]);
276 return 0;
277 }
278 printf("loaded '%s'", arg_vector[1]);
279 if (addr) printf(" starting at 0x%04x", addr);
280 printf("\n");
281
282 return 0;
283 }
284 COMMAND_HELP(load) {
285 fprintf(f, "\tload file [addr]\n");
286 if (summary) return;
287
288 fprintf(f, "Load binary image from 'file' into ram.\n");
289 }
290
291
292 COMMAND_IMPL(dump) {
293 int addr[2];
294 int i;
295
296 for (i = 1; i < arg_count; i++) {
297 addr[i-1] = str_to_word(arg_vector[i]);
298 if (addr[i-1] < 0) {
299 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno));
300 return 0;
301 }
302 }
303 if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC];
304 if (arg_count < 3) addr[1] = addr[0];
305
306 if (addr[1] < addr[0]) {
307 fprintf(stderr, "\t'addr_start' must be before addr_end\n");
308 return 0;
309 }
310
311 dcpu16_dump_ram(vm, addr[0], addr[1]);
312
313 return 0;
314 }
315 COMMAND_HELP(dump) {
316 fprintf(f, "\tdump [addr_start [addr_end]]\n");
317 if (summary) return;
318
319 fprintf(f, "Displays contents of ram from addr_start to addr_end.\n");
320 }
321
322
323 COMMAND_IMPL(disassemble) {
324 int addr[2];
325 int i;
326
327 for (i = 1; i < arg_count; i++) {
328 addr[i-1] = str_to_word(arg_vector[i]);
329 if (addr[i-1] < 0) {
330 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno));
331 return 0;
332 }
333 }
334 if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC];
335 if (arg_count < 3) addr[1] = addr[0];
336
337 if (addr[1] < addr[0]) {
338 fprintf(stderr, "\t'addr_start' must be before addr_end\n");
339 return 0;
340 }
341
342 for (i = addr[0]; i <= addr[1]; /* */ ) {
343 printf("0x%04x: ", i);
344 i += dcpu16_disassemble_print(vm, i);
345 printf("\n");
346 }
347
348 return 0;
349 }
350 COMMAND_HELP(disassemble) {
351 fprintf(f, "\tdisassemble [addr_start [addr_end]]\n");
352 if (summary) return;
353
354 fprintf(f, "Displays contents of ram parsed into instructions.\n");
355 }
356
357
358 COMMAND_IMPL(step) {
359 unsigned long count = 1;
360 char *ep;
361
362 if (arg_count == 2) {
363 errno = 0;
364 count = strtoul(arg_vector[1], &ep, 0);
365 if (errno
366 || !(*arg_vector[1] && *ep == '\0') ) {
367 fprintf(stderr, "count '%s' is not a valid number: %s\n", arg_vector[1], strerror(errno));
368 return 0;
369 }
370
371 if (count <= 0) {
372 fprintf(stderr, "count must be positive\n");
373 return 0;
374 }
375 }
376
377 while (count--) {
378 dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]);
379 printf("\n");
380 dcpu16_step(vm);
381
382 if (count > 1 && opt_.verbose)
383 dcpu16_state_print(vm);
384 }
385
386 return 0;
387 }
388 COMMAND_HELP(step) {
389 fprintf(f, "\tstep [count]\n");
390 if (summary) return;
391
392 fprintf(f, "Executes the next instruction, or the next count instructions.\n");
393 }
394
395
396 COMMAND_IMPL(set) {
397 int addr, value;
398 DCPU16_WORD *v;
399
400 (void)arg_count;
401
402 /* check if addr is a register */
403 for (addr = 0; dcpu16_reg_names[addr]; addr++) {
404 if (strcasecmp(arg_vector[1], dcpu16_reg_names[addr]) == 0)
405 break;
406 }
407 if (addr < DCPU16_REG__NUM) {
408 v = vm->reg + addr;
409 } else {
410 addr = str_to_word(arg_vector[1]);
411 if (addr < 0) {
412 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[1], strerror(errno));
413 return 0;
414 }
415 v = vm->ram + addr;
416 }
417
418 value = str_to_word(arg_vector[2]);
419 if (value < 0) {
420 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno));
421 return 0;
422 }
423
424 *v = value;
425
426 return 0;
427 }
428
429 COMMAND_HELP(set) {
430 fprintf(f, "\tset addr value\n");
431 if (summary) return;
432
433 fprintf(f, "Sets addr to value.");
434 }
435
436 #define MICROSECONDS_PER_CYCLE 10
437 COMMAND_IMPL(run) {
438 struct sigaction act;
439 struct timeval run_start_tv, run_stop_tv;
440 long long run_cycle_start;
441 struct timeval start_tv, now_tv, diff_tv;
442 long long cycle_start, cycles_to_wait;
443 struct timespec sleep_time, rem_time;
444 long long run_usec;
445
446 (void)arg_count, (void)arg_vector;
447
448 running_ = 1;
449 gettimeofday(&run_start_tv, NULL);
450 run_cycle_start = vm->cycle_;
451
452 memset(&act, 0, sizeof act);
453 act.sa_handler = sigint_handler_;
454 act.sa_flags = SA_RESETHAND;
455
456 if (sigaction(SIGINT, &act, NULL)) {
457 fprintf(stderr, "%s():%s\n", "sigaction", strerror(errno));
458 return -1;
459 }
460
461 while (running_) {
462 gettimeofday(&start_tv, NULL);
463 cycle_start = vm->cycle_;
464
465 dcpu16_step(vm);
466 if (opt_.verbose > 1)
467 dcpu16_state_print(vm);
468 else if (opt_.verbose) {
469 dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]);
470 printf("\n");
471 }
472
473 /* how many cycles did this instr use? */
474 cycles_to_wait = vm->cycle_ - cycle_start;
475
476 if (cycles_to_wait == 0)
477 continue;
478
479 /* each cycle wants 10 microseconds */
480
481 /* how much of that did we spend already */
482 gettimeofday(&now_tv, NULL);
483 timeval_subtract(&diff_tv, &now_tv, &start_tv);
484 /* do we have time to kill? */
485 if (cycles_to_wait * MICROSECONDS_PER_CYCLE > diff_tv.tv_usec) {
486 sleep_time.tv_sec = diff_tv.tv_sec;
487 /* this is not accurate.. */
488 sleep_time.tv_nsec = 250 * ( (cycles_to_wait * MICROSECONDS_PER_CYCLE) - diff_tv.tv_usec);
489
490 /* nanosleep doesn't interfere with libvncserver, unlike usleep */
491 while ( nanosleep(&sleep_time, &rem_time) ) {
492 sleep_time = rem_time;
493 fprintf(stderr, "rem:%ld %ld\n", rem_time.tv_sec, rem_time.tv_nsec);
494 }
495 }
496 }
497
498 gettimeofday(&run_stop_tv, NULL);
499 timeval_subtract(&diff_tv, &run_stop_tv, &run_start_tv);
500 run_usec = diff_tv.tv_sec * 1000000;
501 run_usec += diff_tv.tv_usec;
502 fprintf(stderr, "ran %llu cycles in %lds %dus (%lldus)\n",
503 vm->cycle_ - run_cycle_start,
504 diff_tv.tv_sec,
505 diff_tv.tv_usec,
506 run_usec);
507
508 printf("interrupted...\n");
509
510 return 0;
511 }
512 COMMAND_HELP(run) {
513 fprintf(f, "\trun\n");
514 if (summary) return;
515
516 fprintf(f, "Begins executing continuously.\n"
517 "May be interrupted with SIGINT.\n");
518 }
519
520 static const char * const display_filename_default_ =
521 #ifdef HAVE_LIBPNG
522 "dcpu16-display.png"
523 #else /* HAVE_LIBPNG */
524 "dcpu16-display.pnm"
525 #endif /* HAVE_LIBPNG */
526 ;
527 COMMAND_IMPL(display) {
528 struct dcpu16_hw *hw;
529 const char *renderer = arg_vector[1];
530 const char *renderer_arg = NULL;
531 void *renderer_data;
532
533 if (arg_count == 3)
534 renderer_arg = arg_vector[2];
535
536 hw = dcpu16_hw_new(vm, &dcpu16_hw_module_lem1802, NULL);
537 if (hw == NULL) {
538 fprintf(stderr, "failed to initialize new display\n");
539 return 0;
540 }
541
542 /* handle per-renderer setup of data.. */
543 /* FIXME: these are awkward */
544 if (strcmp(renderer, "pnm") == 0) {
545 renderer_data = (void *)(renderer_arg ? renderer_arg : display_filename_default_);
546 }
547
548 #ifdef HAVE_LIBPNG
549 if (strcmp(renderer, "png") == 0) {
550 renderer_data = (void *)(renderer_arg ? renderer_arg : display_filename_default_);
551 }
552 #endif /* HAVE_LIBPNG */
553
554 #ifdef HAVE_LIBVNCSERVER
555 if (strcmp(renderer, "vnc") == 0) {
556 int argc = 1;
557 char *argv[] = { "vm-dcpu16", NULL };
558 struct rfb_instance_ *s;
559
560 s = rfbScreen_next_available_display_(&rfbScreens_, argc, argv);
561 if (s == NULL) {
562 fprintf(stderr, "failed to initialize vnc\n");
563 dcpu16_hw_del(&hw);
564 return 0;
565 }
566
567 lem1802_vnc_associate(hw, s->screen);
568 s->attached_display = hw;
569 rfbScreen_start(s->screen);
570 renderer_data = s->screen;
571 }
572 #endif /* HAVE_LIBVNCSERVER */
573
574 if (lem1802_renderer_set(hw, renderer, renderer_data)) {
575 fprintf(stderr, "failed to set back-end renderer for display\n");
576 dcpu16_hw_del(&hw);
577 return 0;
578 }
579
580 if (dcpu16_hw_attach(vm, hw)) {
581 fprintf(stderr, "failed to attach new display\n");
582 dcpu16_hw_del(&hw);
583 return 0;
584 }
585
586 return 0;
587 }
588 COMMAND_HELP(display) {
589 char *name, *args;
590 void *iter;
591
592 fprintf(f, "\tdisplay renderer [renderer data]\n");
593 if (summary) return;
594
595 fprintf(f, "Attaches new display unit, using 'renderer' as back-end output.\n"
596 );
597
598 fprintf(f, "Supported renderers:\n");
599 iter = NULL;
600 while ( (lem1802_renderers_iter(&iter, &name, &args)) ) {
601 fprintf(f, "\t%s %s\n", name, args);
602 }
603 }
604
605 COMMAND_IMPL(keyboard) {
606 struct dcpu16_hw *hw;
607
608 (void)arg_count, (void)arg_vector;
609
610 hw = dcpu16_hw_new(vm, &dcpu16_hw_module_keyboard, NULL);
611 if (hw == NULL) {
612 fprintf(stderr, "failed to initialize new keyboard\n");
613 return 0;
614 }
615
616 #ifdef HAVE_LIBVNCSERVER
617 struct rfb_instance_ *s;
618 int argc = 1;
619 char *argv[] = { "vm-dcpu16", NULL };
620
621 s = rfbScreen_next_available_keyboard_(&rfbScreens_, argc, argv);
622 if (s == NULL) {
623 fprintf(stderr, "failed to initialize vnc\n");
624 dcpu16_hw_del(&hw);
625 return 0;
626 }
627 keyboard_vnc_associate(hw, s->screen);
628 s->attached_keyboard = hw;
629
630 if (dcpu16_hw_attach(vm, hw)) {
631 fprintf(stderr, "failed to attach new keyboard\n");
632 dcpu16_hw_del(&hw);
633 return 0;
634 }
635 #endif /* HAVE_LIBVNCSERVER */
636
637 return 0;
638 }
639 COMMAND_HELP(keyboard) {
640 fprintf(f, "\tkeyboard\n");
641 if (summary) return;
642
643 fprintf(f, "Attaches new keyboard unit.\n");
644 }
645
646 /* gather all these together into a searchable table */
647
648 /* help command gets some assistance in declarations */
649 COMMAND_IMPL(help);
650 COMMAND_HELP(help);
651
652 static struct command_ command_table_[] = {
653 COMMAND_ENTRY(help, 0, -1),
654 COMMAND_ENTRY(quit, 0, -1),
655 COMMAND_ENTRY(load, 1, 2),
656 COMMAND_ENTRY(dump, 0, 2),
657 COMMAND_ENTRY(disassemble, 0, 2),
658 COMMAND_ENTRY(step, 0, 1),
659 COMMAND_ENTRY(run, 0, 0),
660 COMMAND_ENTRY(set, 2, 2),
661 COMMAND_ENTRY(reset, 0, 0),
662 COMMAND_ENTRY(display, 1, 2),
663 COMMAND_ENTRY(keyboard, 0, 0),
664 { NULL, 0, 0, NULL, NULL }
665 };
666
667 COMMAND_IMPL(help) {
668 struct command_ *c;
669 (void)vm;
670
671 if (arg_count == 2) {
672 for (c = command_table_; c->func; c++) {
673 if (strcasecmp(arg_vector[1], c->name) == 0) {
674 if (c->help)
675 c->help(stdout, 0);
676 break;
677 }
678 }
679 return 0;
680 }
681
682 for (c = command_table_; c->func; c++) {
683 if (c->help)
684 c->help(stdout, 1);
685 }
686 return 0;
687 }
688 COMMAND_HELP(help) {
689 fprintf(f, "\thelp [command]\n");
690 if (summary) return;
691
692 fprintf(f, "Displays a list of available commands, or detailed help on a specific command.\n");
693 }
694
695
696 int main(int argc, char **argv) {
697 const char prompt_fmt[] = "PC:%04x> ";
698 char prompt[32];
699 struct dcpu16 *vm;
700 char *line, *line_prev;
701 char **tok_v, **tok_v_prev;
702 int tok_c, tok_c_prev;
703 int c;
704
705 while ( (c = getopt(argc, argv, "hv")) != EOF) {
706 switch (c) {
707 case 'v':
708 opt_.verbose++;
709 break;
710
711 case 'h':
712 usage_(argv[0], 1);
713 exit(EX_OK);
714
715 default:
716 usage_(argv[0], 0);
717 exit(EX_USAGE);
718 }
719 }
720 if (opt_.verbose < 1) {
721 dcpu16_warn_cb_set(NULL);
722 dcpu16_trace_cb_set(NULL);
723 } else if (opt_.verbose < 2) {
724 dcpu16_trace_cb_set(NULL);
725 }
726 argc -= optind;
727 argv += optind;
728
729 if ((vm = dcpu16_new()) == NULL) {
730 fprintf(stderr, "could not allocate new dcpu16 instance\n");
731 exit(EX_UNAVAILABLE);
732 }
733
734 #ifdef HAVE_LIBVNCSERVER
735 if (dynarray_init(&rfbScreens_, sizeof(struct rfb_instance_), 4)) {
736 fprintf(stderr, "could not allocate rfb container\n");
737 exit(EX_UNAVAILABLE);
738 }
739 #endif /* HAVE_LIBVNCSERVER */
740
741 if (argc) {
742 if (file_load_(vm, *argv, 0)) {
743 fprintf(stderr, "couldn't load '%s'\n", *argv);
744 exit(EX_NOINPUT);
745 }
746 }
747
748 /* show state, read commands */
749 for (line = line_prev = NULL,
750 tok_v = tok_v_prev = NULL,
751 tok_c = tok_c_prev= 0,
752 snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]),
753 dcpu16_state_print(vm);
754
755 (line = readline(prompt));
756
757 printf("\n"),
758 snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]),
759 dcpu16_state_print(vm)) {
760 const char whitespace[] = " \t";
761 char *line_start;
762 struct command_ *c;
763 int r = 0;
764
765 /* skip whitespaces */
766 line_start = line + strspn(line, whitespace);
767
768 if (*line_start) {
769 /* a new command, remember previous for possible repetition */
770
771 /* turn new line into new arg array */
772 if (buf_tok_vect_(&tok_v, &tok_c, line_start)) {
773 fprintf(stderr, "failed to process command\n");
774 continue;
775 }
776
777 /* and keep track if it all for the next time around */
778 if (line_prev) free(line_prev);
779 line_prev = line;
780
781 if (tok_v_prev) free(tok_v_prev);
782 tok_v_prev = tok_v;
783 tok_c_prev = tok_c;
784 } else {
785 /* blank new command, but no prior command to repeat? ask again */
786 if (tok_v_prev == NULL || tok_v_prev[0] == NULL || *(tok_v_prev[0]) == '\0') {
787 free(line);
788 continue;
789 }
790
791 /* otherwise discard new line and promote prior */
792 free(line);
793 tok_v = tok_v_prev;
794 tok_c = tok_c_prev;
795 line = line_prev;
796 }
797
798 /* look up command */
799 for (c = command_table_; c->name; c++) {
800 if (strcasecmp(tok_v[0], c->name) == 0) {
801 if (c->args_min > tok_c - 1) {
802 fprintf(stderr, "%s: not enough arguments\n", c->name);
803 c->help(stderr, 1);
804 break;
805 }
806
807 if (c->args_max > 0
808 && tok_c - 1 > c->args_max) {
809 fprintf(stderr, "%s: too many arguments\n", c->name);
810 c->help(stderr, 1);
811 break;
812 }
813
814 r = c->func(vm, tok_c, tok_v);
815 break;
816 }
817 }
818 if (r)
819 break;
820
821 if (!c->func)
822 fprintf(stderr, "didn't recognize '%s'\n", tok_v[0]);
823 }
824
825 printf("\nfinished\n");
826
827 dcpu16_delete(&vm);
828
829 exit(EX_OK);
830 }