keyboard now processes keys via rfb. framework in place for multiple modules.
[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 = lem1802_new(vm);
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 if (renderer_arg == NULL)
546 renderer_arg = display_filename_default_;
547 renderer_data = (void *)renderer_arg;
548 }
549
550 #ifdef HAVE_LIBPNG
551 if (strcmp(renderer, "png") == 0) {
552 if (renderer_arg == NULL)
553 renderer_arg = display_filename_default_;
554 renderer_data = (void *)renderer_arg;
555 }
556 #endif /* HAVE_LIBPNG */
557
558 #ifdef HAVE_LIBVNCSERVER
559 if (strcmp(renderer, "vnc") == 0) {
560 int argc = 1;
561 char *argv[] = { "vm-dcpu16", NULL };
562 struct rfb_instance_ *s;
563
564 s = rfbScreen_next_available_display_(&rfbScreens_, argc, argv);
565 if (s == NULL) {
566 fprintf(stderr, "failed to initialize vnc\n");
567 lem1802_del(&hw);
568 return 0;
569 }
570
571 lem1802_vnc_associate(hw, s->screen);
572 s->attached_display = hw;
573 rfbScreen_start(s->screen);
574 renderer_data = s->screen;
575 }
576 #endif /* HAVE_LIBVNCSERVER */
577
578 if (lem1802_renderer_set(hw, renderer, renderer_data)) {
579 fprintf(stderr, "failed to set back-end renderer for display\n");
580 lem1802_del(&hw);
581 return 0;
582 }
583
584 if (dcpu16_hw_add(vm, hw)) {
585 fprintf(stderr, "failed to attach new display\n");
586 lem1802_del(&hw);
587 return 0;
588 }
589
590 return 0;
591 }
592 COMMAND_HELP(display) {
593 char *name, *args;
594 void *iter;
595
596 fprintf(f, "\tdisplay renderer [renderer data]\n");
597 if (summary) return;
598
599 fprintf(f, "Attaches new display unit, using 'renderer' as back-end output.\n"
600 );
601
602 fprintf(f, "Supported renderers:\n");
603 iter = NULL;
604 while ( (lem1802_renderers_iter(&iter, &name, &args)) ) {
605 fprintf(f, "\t%s %s\n", name, args);
606 }
607 }
608
609 COMMAND_IMPL(keyboard) {
610 struct dcpu16_hw *hw;
611
612 (void)arg_count, (void)arg_vector;
613
614 hw = keyboard_new(vm);
615 if (hw == NULL) {
616 fprintf(stderr, "failed to initialize new keyboard\n");
617 return 0;
618 }
619
620 #ifdef HAVE_LIBVNCSERVER
621 struct rfb_instance_ *s;
622 int argc = 1;
623 char *argv[] = { "vm-dcpu16", NULL };
624
625 s = rfbScreen_next_available_keyboard_(&rfbScreens_, argc, argv);
626 if (s == NULL) {
627 fprintf(stderr, "failed to initialize vnc\n");
628 keyboard_del(&hw);
629 return 0;
630 }
631 keyboard_vnc_associate(hw, s->screen);
632 s->attached_keyboard = hw;
633
634 if (dcpu16_hw_add(vm, hw)) {
635 fprintf(stderr, "failed to attach new keyboard\n");
636 keyboard_del(&hw);
637 return 0;
638 }
639 #endif /* HAVE_LIBVNCSERVER */
640
641 return 0;
642 }
643 COMMAND_HELP(keyboard) {
644 fprintf(f, "\tkeyboard\n");
645 if (summary) return;
646
647 fprintf(f, "Attaches new keyboard unit.\n");
648 }
649
650 /* gather all these together into a searchable table */
651
652 /* help command gets some assistance in declarations */
653 COMMAND_IMPL(help);
654 COMMAND_HELP(help);
655
656 static struct command_ command_table_[] = {
657 COMMAND_ENTRY(help, 0, -1),
658 COMMAND_ENTRY(quit, 0, -1),
659 COMMAND_ENTRY(load, 1, 2),
660 COMMAND_ENTRY(dump, 0, 2),
661 COMMAND_ENTRY(disassemble, 0, 2),
662 COMMAND_ENTRY(step, 0, 1),
663 COMMAND_ENTRY(run, 0, 0),
664 COMMAND_ENTRY(set, 2, 2),
665 COMMAND_ENTRY(reset, 0, 0),
666 COMMAND_ENTRY(display, 1, 2),
667 COMMAND_ENTRY(keyboard, 0, 0),
668 { NULL, 0, 0, NULL, NULL }
669 };
670
671 COMMAND_IMPL(help) {
672 struct command_ *c;
673 (void)vm;
674
675 if (arg_count == 2) {
676 for (c = command_table_; c->func; c++) {
677 if (strcasecmp(arg_vector[1], c->name) == 0) {
678 if (c->help)
679 c->help(stdout, 0);
680 break;
681 }
682 }
683 return 0;
684 }
685
686 for (c = command_table_; c->func; c++) {
687 if (c->help)
688 c->help(stdout, 1);
689 }
690 return 0;
691 }
692 COMMAND_HELP(help) {
693 fprintf(f, "\thelp [command]\n");
694 if (summary) return;
695
696 fprintf(f, "Displays a list of available commands, or detailed help on a specific command.\n");
697 }
698
699
700 int main(int argc, char **argv) {
701 const char prompt_fmt[] = "PC:%04x> ";
702 char prompt[32];
703 struct dcpu16 *vm;
704 char *line, *line_prev;
705 char **tok_v, **tok_v_prev;
706 int tok_c, tok_c_prev;
707 int c;
708
709 while ( (c = getopt(argc, argv, "hv")) != EOF) {
710 switch (c) {
711 case 'v':
712 opt_.verbose++;
713 break;
714
715 case 'h':
716 usage_(argv[0], 1);
717 exit(EX_OK);
718
719 default:
720 usage_(argv[0], 0);
721 exit(EX_USAGE);
722 }
723 }
724 if (opt_.verbose < 1) {
725 dcpu16_warn_cb_set(NULL);
726 dcpu16_trace_cb_set(NULL);
727 } else if (opt_.verbose < 2) {
728 dcpu16_trace_cb_set(NULL);
729 }
730 argc -= optind;
731 argv += optind;
732
733 if ((vm = dcpu16_new()) == NULL) {
734 fprintf(stderr, "could not allocate new dcpu16 instance\n");
735 exit(EX_UNAVAILABLE);
736 }
737
738 #ifdef HAVE_LIBVNCSERVER
739 if (dynarray_init(&rfbScreens_, sizeof(struct rfb_instance_), 4)) {
740 fprintf(stderr, "could not allocate rfb container\n");
741 exit(EX_UNAVAILABLE);
742 }
743 #endif /* HAVE_LIBVNCSERVER */
744
745 if (argc) {
746 if (file_load_(vm, *argv, 0)) {
747 fprintf(stderr, "couldn't load '%s'\n", *argv);
748 exit(EX_NOINPUT);
749 }
750 }
751
752 /* show state, read commands */
753 for (line = line_prev = NULL,
754 tok_v = tok_v_prev = NULL,
755 tok_c = tok_c_prev= 0,
756 snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]),
757 dcpu16_state_print(vm);
758
759 (line = readline(prompt));
760
761 printf("\n"),
762 snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]),
763 dcpu16_state_print(vm)) {
764 const char whitespace[] = " \t";
765 char *line_start;
766 struct command_ *c;
767 int r = 0;
768
769 /* skip whitespaces */
770 line_start = line + strspn(line, whitespace);
771
772 if (*line_start) {
773 /* a new command, remember previous for possible repetition */
774
775 /* turn new line into new arg array */
776 if (buf_tok_vect_(&tok_v, &tok_c, line_start)) {
777 fprintf(stderr, "failed to process command\n");
778 continue;
779 }
780
781 /* and keep track if it all for the next time around */
782 if (line_prev) free(line_prev);
783 line_prev = line;
784
785 if (tok_v_prev) free(tok_v_prev);
786 tok_v_prev = tok_v;
787 tok_c_prev = tok_c;
788 } else {
789 /* blank new command, but no prior command to repeat? ask again */
790 if (tok_v_prev == NULL || tok_v_prev[0] == NULL || *(tok_v_prev[0]) == '\0') {
791 free(line);
792 continue;
793 }
794
795 /* otherwise discard new line and promote prior */
796 free(line);
797 tok_v = tok_v_prev;
798 tok_c = tok_c_prev;
799 line = line_prev;
800 }
801
802 /* look up command */
803 for (c = command_table_; c->name; c++) {
804 if (strcasecmp(tok_v[0], c->name) == 0) {
805 if (c->args_min > tok_c - 1) {
806 fprintf(stderr, "%s: not enough arguments\n", c->name);
807 c->help(stderr, 1);
808 break;
809 }
810
811 if (c->args_max > 0
812 && tok_c - 1 > c->args_max) {
813 fprintf(stderr, "%s: too many arguments\n", c->name);
814 c->help(stderr, 1);
815 break;
816 }
817
818 r = c->func(vm, tok_c, tok_v);
819 break;
820 }
821 }
822 if (r)
823 break;
824
825 if (!c->func)
826 fprintf(stderr, "didn't recognize '%s'\n", tok_v[0]);
827 }
828
829 printf("\nfinished\n");
830
831 dcpu16_delete(&vm);
832
833 exit(EX_OK);
834 }