finished reorg of abstracted module interfaces
[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 associations 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 struct packed_args_ {
167 int argc;
168 char **argv;
169 } parg = { argc, argv };
170
171 fprintf(stderr, "DEBUG: rfbScreens->entries:%zu\n", rfbScreens->entries);
172
173 for (i = 0; i < rfbScreens->entries; i++) {
174 s = (struct rfb_instance_ *)DYNARRAY_ITEM(*rfbScreens, i);
175 if (s->attached_display == NULL)
176 return s;
177 }
178
179 if (dcpu16_hw_module_lem1802.ctl(NULL, "new_rfbScreen", &parg, &new_instance.screen)) {
180 fprintf(stderr, "failed to allocate new rfbScreen");
181 return NULL;
182 }
183
184 new_instance.attached_display = NULL;
185 new_instance.attached_keyboard = NULL;
186 s = dynarray_add(rfbScreens, &new_instance);
187 return s;
188 }
189
190 /* locate or allocate the next display with an un-occupied keyboard */
191 static
192 struct rfb_instance_ *rfbScreen_next_available_keyboard_(struct dynamic_array *rfbScreens, int argc, char *argv[]) {
193 size_t i;
194 struct rfb_instance_ new_instance, *s;
195 struct packed_args_ {
196 int argc;
197 char **argv;
198 } parg = { argc, argv };
199
200 for (i = 0; i < rfbScreens->entries; i++) {
201 s = (struct rfb_instance_ *)DYNARRAY_ITEM(*rfbScreens, i);
202 if (s->attached_keyboard == NULL)
203 return s;
204 }
205
206 if (dcpu16_hw_module_lem1802.ctl(NULL, "new_rfbScreen", &parg, &new_instance.screen)) {
207 fprintf(stderr, "failed to allocate new rfbScreen");
208 return NULL;
209 }
210
211 new_instance.attached_display = NULL;
212 new_instance.attached_keyboard = NULL;
213 s = dynarray_add(rfbScreens, &new_instance);
214 return s;
215 }
216
217 /* begin serving a screen */
218 void rfbScreen_start(rfbScreenInfoPtr s) {
219 rfbInitServer(s);
220 rfbRunEventLoop(s, -1, TRUE);
221 }
222 #endif /* HAVE_LIBVNCSERVER */
223
224 /*
225 Here follows the various commands the shell can execute.
226
227 At invocation, a command function will have already had its
228 number of arguments vetted, but will need command-specific
229 argument verifications done.
230
231 The arg_vector contains the command as the first entry, and
232 as such, arg_count will always be at least 1.
233 However, the args_min and args_max entries in struct command_
234 only refer to the counts of arguments, not the entries in the
235 argv.
236 */
237
238 struct command_ {
239 char *name;
240 int args_min;
241 int args_max;
242 int (*func)(struct dcpu16 *, int c, char **v);
243 void (*help)(FILE *f, unsigned int);
244 };
245
246 #define COMMAND_IMPL(x) static int command_##x##_(struct dcpu16 *vm, int arg_count, char **arg_vector)
247 #define COMMAND_HELP(x) static void command_##x##_help_(FILE *f, unsigned int summary)
248 #define COMMAND_ENTRY(x, y, z) { #x, y, z, command_##x##_, command_##x##_help_ }
249
250
251 COMMAND_IMPL(quit) {
252 (void)vm, (void)arg_count, (void)arg_vector;
253
254 return -1;
255 }
256 COMMAND_HELP(quit) {
257 fprintf(f, "\tquit\n");
258 if (summary) return;
259
260 fprintf(f, "Exits the emulator.\n");
261 }
262
263
264 COMMAND_IMPL(reset) {
265 (void)arg_count, (void)arg_vector;
266
267 dcpu16_reset(vm);
268 printf("initialized\n");
269 return 0;
270 }
271 COMMAND_HELP(reset) {
272 fprintf(f, "\treset\n");
273 if (summary) return;
274
275 fprintf(f, "Clears and reinitializes emulator.\n");
276 }
277
278
279 COMMAND_IMPL(load) {
280 int addr = 0;
281
282 if (arg_count > 2) {
283 addr = str_to_word(arg_vector[2]);
284 if (addr < 0) {
285 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno));
286 return 0;
287 }
288 }
289
290 if (file_load_(vm, arg_vector[1], addr)) {
291 fprintf(stderr, "failed to load '%s'\n", arg_vector[1]);
292 return 0;
293 }
294 printf("loaded '%s'", arg_vector[1]);
295 if (addr) printf(" starting at 0x%04x", addr);
296 printf("\n");
297
298 return 0;
299 }
300 COMMAND_HELP(load) {
301 fprintf(f, "\tload file [addr]\n");
302 if (summary) return;
303
304 fprintf(f, "Load binary image from 'file' into ram.\n");
305 }
306
307
308 COMMAND_IMPL(dump) {
309 int addr[2];
310 int i;
311
312 for (i = 1; i < arg_count; i++) {
313 addr[i-1] = str_to_word(arg_vector[i]);
314 if (addr[i-1] < 0) {
315 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno));
316 return 0;
317 }
318 }
319 if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC];
320 if (arg_count < 3) addr[1] = addr[0];
321
322 if (addr[1] < addr[0]) {
323 fprintf(stderr, "\t'addr_start' must be before addr_end\n");
324 return 0;
325 }
326
327 dcpu16_dump_ram(vm, addr[0], addr[1]);
328
329 return 0;
330 }
331 COMMAND_HELP(dump) {
332 fprintf(f, "\tdump [addr_start [addr_end]]\n");
333 if (summary) return;
334
335 fprintf(f, "Displays contents of ram from addr_start to addr_end.\n");
336 }
337
338
339 COMMAND_IMPL(disassemble) {
340 int addr[2];
341 int i;
342
343 for (i = 1; i < arg_count; i++) {
344 addr[i-1] = str_to_word(arg_vector[i]);
345 if (addr[i-1] < 0) {
346 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno));
347 return 0;
348 }
349 }
350 if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC];
351 if (arg_count < 3) addr[1] = addr[0];
352
353 if (addr[1] < addr[0]) {
354 fprintf(stderr, "\t'addr_start' must be before addr_end\n");
355 return 0;
356 }
357
358 for (i = addr[0]; i <= addr[1]; /* */ ) {
359 printf("0x%04x: ", i);
360 i += dcpu16_disassemble_print(vm, i);
361 printf("\n");
362 }
363
364 return 0;
365 }
366 COMMAND_HELP(disassemble) {
367 fprintf(f, "\tdisassemble [addr_start [addr_end]]\n");
368 if (summary) return;
369
370 fprintf(f, "Displays contents of ram parsed into instructions.\n");
371 }
372
373
374 COMMAND_IMPL(step) {
375 unsigned long count = 1;
376 char *ep;
377
378 if (arg_count == 2) {
379 errno = 0;
380 count = strtoul(arg_vector[1], &ep, 0);
381 if (errno
382 || !(*arg_vector[1] && *ep == '\0') ) {
383 fprintf(stderr, "count '%s' is not a valid number: %s\n", arg_vector[1], strerror(errno));
384 return 0;
385 }
386
387 if (count <= 0) {
388 fprintf(stderr, "count must be positive\n");
389 return 0;
390 }
391 }
392
393 while (count--) {
394 dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]);
395 printf("\n");
396 dcpu16_step(vm);
397
398 if (count > 1 && opt_.verbose)
399 dcpu16_state_print(vm);
400 }
401
402 return 0;
403 }
404 COMMAND_HELP(step) {
405 fprintf(f, "\tstep [count]\n");
406 if (summary) return;
407
408 fprintf(f, "Executes the next instruction, or the next count instructions.\n");
409 }
410
411
412 COMMAND_IMPL(set) {
413 int addr, value;
414 DCPU16_WORD *v;
415
416 (void)arg_count;
417
418 /* check if addr is a register */
419 for (addr = 0; dcpu16_reg_names[addr]; addr++) {
420 if (strcasecmp(arg_vector[1], dcpu16_reg_names[addr]) == 0)
421 break;
422 }
423 if (addr < DCPU16_REG__NUM) {
424 v = vm->reg + addr;
425 } else {
426 addr = str_to_word(arg_vector[1]);
427 if (addr < 0) {
428 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[1], strerror(errno));
429 return 0;
430 }
431 v = vm->ram + addr;
432 }
433
434 value = str_to_word(arg_vector[2]);
435 if (value < 0) {
436 fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno));
437 return 0;
438 }
439
440 *v = value;
441
442 return 0;
443 }
444
445 COMMAND_HELP(set) {
446 fprintf(f, "\tset addr value\n");
447 if (summary) return;
448
449 fprintf(f, "Sets addr to value.");
450 }
451
452 #define MICROSECONDS_PER_CYCLE 10
453 COMMAND_IMPL(run) {
454 struct sigaction act;
455 struct timeval run_start_tv, run_stop_tv;
456 long long run_cycle_start;
457 struct timeval start_tv, now_tv, diff_tv;
458 long long cycle_start, cycles_to_wait;
459 struct timespec sleep_time, rem_time;
460 long long run_usec;
461
462 (void)arg_count, (void)arg_vector;
463
464 running_ = 1;
465 gettimeofday(&run_start_tv, NULL);
466 run_cycle_start = vm->cycle_;
467
468 memset(&act, 0, sizeof act);
469 act.sa_handler = sigint_handler_;
470 act.sa_flags = SA_RESETHAND;
471
472 if (sigaction(SIGINT, &act, NULL)) {
473 fprintf(stderr, "%s():%s\n", "sigaction", strerror(errno));
474 return -1;
475 }
476
477 while (running_) {
478 gettimeofday(&start_tv, NULL);
479 cycle_start = vm->cycle_;
480
481 dcpu16_step(vm);
482 if (opt_.verbose > 1)
483 dcpu16_state_print(vm);
484 else if (opt_.verbose) {
485 dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]);
486 printf("\n");
487 }
488
489 /* how many cycles did this instr use? */
490 cycles_to_wait = vm->cycle_ - cycle_start;
491
492 if (cycles_to_wait == 0)
493 continue;
494
495 /* each cycle wants 10 microseconds */
496
497 /* how much of that did we spend already */
498 gettimeofday(&now_tv, NULL);
499 timeval_subtract(&diff_tv, &now_tv, &start_tv);
500 /* do we have time to kill? */
501 if (cycles_to_wait * MICROSECONDS_PER_CYCLE > diff_tv.tv_usec) {
502 sleep_time.tv_sec = diff_tv.tv_sec;
503 /* this is not accurate.. */
504 sleep_time.tv_nsec = 250 * ( (cycles_to_wait * MICROSECONDS_PER_CYCLE) - diff_tv.tv_usec);
505
506 /* nanosleep doesn't interfere with libvncserver, unlike usleep */
507 while ( nanosleep(&sleep_time, &rem_time) ) {
508 sleep_time = rem_time;
509 fprintf(stderr, "rem:%ld %ld\n", rem_time.tv_sec, rem_time.tv_nsec);
510 }
511 }
512 }
513
514 gettimeofday(&run_stop_tv, NULL);
515 timeval_subtract(&diff_tv, &run_stop_tv, &run_start_tv);
516 run_usec = diff_tv.tv_sec * 1000000;
517 run_usec += diff_tv.tv_usec;
518 fprintf(stderr, "ran %llu cycles in %lds %dus (%lldus)\n",
519 vm->cycle_ - run_cycle_start,
520 diff_tv.tv_sec,
521 diff_tv.tv_usec,
522 run_usec);
523
524 printf("interrupted...\n");
525
526 return 0;
527 }
528 COMMAND_HELP(run) {
529 fprintf(f, "\trun\n");
530 if (summary) return;
531
532 fprintf(f, "Begins executing continuously.\n"
533 "May be interrupted with SIGINT.\n");
534 }
535
536 static const char * const display_filename_default_ =
537 #ifdef HAVE_LIBPNG
538 "dcpu16-display.png"
539 #else /* HAVE_LIBPNG */
540 "dcpu16-display.pnm"
541 #endif /* HAVE_LIBPNG */
542 ;
543 COMMAND_IMPL(display) {
544 struct dcpu16_hw *hw;
545 const char *renderer = arg_vector[1];
546 const char *renderer_arg = NULL;
547 void *renderer_data;
548
549 if (arg_count == 3)
550 renderer_arg = arg_vector[2];
551
552 hw = dcpu16_hw_new(vm, &dcpu16_hw_module_lem1802, NULL);
553 if (hw == NULL) {
554 fprintf(stderr, "failed to initialize new display\n");
555 return 0;
556 }
557
558 /* handle per-renderer setup of data.. */
559 /* FIXME: these are awkward */
560 if (strcmp(renderer, "pnm") == 0) {
561 renderer_data = (void *)(renderer_arg ? renderer_arg : display_filename_default_);
562 }
563
564 #ifdef HAVE_LIBPNG
565 if (strcmp(renderer, "png") == 0) {
566 renderer_data = (void *)(renderer_arg ? renderer_arg : display_filename_default_);
567 }
568 #endif /* HAVE_LIBPNG */
569
570 #ifdef HAVE_LIBVNCSERVER
571 if (strcmp(renderer, "vnc") == 0) {
572 int argc = 1;
573 char *argv[] = { "vm-dcpu16", NULL };
574 struct rfb_instance_ *s;
575
576 s = rfbScreen_next_available_display_(&rfbScreens_, argc, argv);
577 if (s == NULL) {
578 fprintf(stderr, "failed to initialize vnc\n");
579 dcpu16_hw_del(&hw);
580 return 0;
581 }
582
583 if (dcpu16_hw_ctl(hw, "associate_rfbScreen", s->screen, NULL)) {
584 fprintf(stderr, "failed to configure display/vnc");
585 dcpu16_hw_del(&hw);
586 return 0;
587 }
588 s->attached_display = hw;
589 rfbScreen_start(s->screen);
590 renderer_data = s->screen;
591 }
592 #endif /* HAVE_LIBVNCSERVER */
593
594 dcpu16_hw_ctl(hw, "renderer", (char *)renderer, NULL);
595 dcpu16_hw_ctl(hw, "renderer_data", renderer_data, NULL);
596
597 if (dcpu16_hw_attach(vm, hw)) {
598 fprintf(stderr, "failed to attach new display\n");
599 dcpu16_hw_del(&hw);
600 return 0;
601 }
602
603 return 0;
604 }
605 COMMAND_HELP(display) {
606 struct renderer_ {
607 char *name;
608 char *args;
609 } renderer;
610 void *iter;
611
612 fprintf(f, "\tdisplay renderer [renderer data]\n");
613 if (summary) return;
614
615 fprintf(f, "Attaches new display unit, using 'renderer' as back-end output.\n"
616 );
617
618 fprintf(f, "Supported renderers:\n");
619
620 iter = NULL;
621 do {
622 if (dcpu16_hw_module_lem1802.ctl(NULL, "renderers_iter", &iter, &renderer)) {
623 fprintf(stderr, "error fetching next renderer\n");
624 break;
625 }
626 if (iter == NULL || renderer.name == NULL)
627 break;
628
629 fprintf(f, "\t%s %s\n", renderer.name, renderer.args);
630 } while (iter);
631 }
632
633 COMMAND_IMPL(keyboard) {
634 struct dcpu16_hw *hw;
635
636 (void)arg_count, (void)arg_vector;
637
638 hw = dcpu16_hw_new(vm, &dcpu16_hw_module_keyboard, NULL);
639 if (hw == NULL) {
640 fprintf(stderr, "failed to initialize new keyboard\n");
641 return 0;
642 }
643
644 #ifdef HAVE_LIBVNCSERVER
645 struct rfb_instance_ *s;
646 int argc = 1;
647 char *argv[] = { "vm-dcpu16", NULL };
648
649 s = rfbScreen_next_available_keyboard_(&rfbScreens_, argc, argv);
650 if (s == NULL) {
651 fprintf(stderr, "failed to initialize vnc\n");
652 dcpu16_hw_del(&hw);
653 return 0;
654 }
655 if (dcpu16_hw_ctl(hw, "associate_rfbScreen", s->screen, NULL)) {
656 fprintf(stderr, "failed to configure keyboard/vnc\n");
657 dcpu16_hw_del(&hw);
658 return 0;
659 }
660 s->attached_keyboard = hw;
661
662 if (dcpu16_hw_attach(vm, hw)) {
663 fprintf(stderr, "failed to attach new keyboard\n");
664 dcpu16_hw_del(&hw);
665 return 0;
666 }
667 #endif /* HAVE_LIBVNCSERVER */
668
669 return 0;
670 }
671 COMMAND_HELP(keyboard) {
672 fprintf(f, "\tkeyboard\n");
673 if (summary) return;
674
675 fprintf(f, "Attaches new keyboard unit.\n");
676 }
677
678 /* gather all these together into a searchable table */
679
680 /* help command gets some assistance in declarations */
681 COMMAND_IMPL(help);
682 COMMAND_HELP(help);
683
684 static struct command_ command_table_[] = {
685 COMMAND_ENTRY(help, 0, -1),
686 COMMAND_ENTRY(quit, 0, -1),
687 COMMAND_ENTRY(load, 1, 2),
688 COMMAND_ENTRY(dump, 0, 2),
689 COMMAND_ENTRY(disassemble, 0, 2),
690 COMMAND_ENTRY(step, 0, 1),
691 COMMAND_ENTRY(run, 0, 0),
692 COMMAND_ENTRY(set, 2, 2),
693 COMMAND_ENTRY(reset, 0, 0),
694 COMMAND_ENTRY(display, 1, 2),
695 COMMAND_ENTRY(keyboard, 0, 0),
696 { NULL, 0, 0, NULL, NULL }
697 };
698
699 COMMAND_IMPL(help) {
700 struct command_ *c;
701 (void)vm;
702
703 if (arg_count == 2) {
704 for (c = command_table_; c->func; c++) {
705 if (strcasecmp(arg_vector[1], c->name) == 0) {
706 if (c->help)
707 c->help(stdout, 0);
708 break;
709 }
710 }
711 return 0;
712 }
713
714 for (c = command_table_; c->func; c++) {
715 if (c->help)
716 c->help(stdout, 1);
717 }
718 return 0;
719 }
720 COMMAND_HELP(help) {
721 fprintf(f, "\thelp [command]\n");
722 if (summary) return;
723
724 fprintf(f, "Displays a list of available commands, or detailed help on a specific command.\n");
725 }
726
727
728 int main(int argc, char **argv) {
729 const char prompt_fmt[] = "PC:%04x> ";
730 char prompt[32];
731 struct dcpu16 *vm;
732 char *line, *line_prev;
733 char **tok_v, **tok_v_prev;
734 int tok_c, tok_c_prev;
735 int c;
736
737 while ( (c = getopt(argc, argv, "hv")) != EOF) {
738 switch (c) {
739 case 'v':
740 opt_.verbose++;
741 break;
742
743 case 'h':
744 usage_(argv[0], 1);
745 exit(EX_OK);
746
747 default:
748 usage_(argv[0], 0);
749 exit(EX_USAGE);
750 }
751 }
752 if (opt_.verbose < 1) {
753 dcpu16_warn_cb_set(NULL);
754 dcpu16_trace_cb_set(NULL);
755 } else if (opt_.verbose < 2) {
756 dcpu16_trace_cb_set(NULL);
757 }
758 argc -= optind;
759 argv += optind;
760
761 if ((vm = dcpu16_new()) == NULL) {
762 fprintf(stderr, "could not allocate new dcpu16 instance\n");
763 exit(EX_UNAVAILABLE);
764 }
765
766 #ifdef HAVE_LIBVNCSERVER
767 if (dynarray_init(&rfbScreens_, sizeof(struct rfb_instance_), 4)) {
768 fprintf(stderr, "could not allocate rfb container\n");
769 exit(EX_UNAVAILABLE);
770 }
771 #endif /* HAVE_LIBVNCSERVER */
772
773 if (argc) {
774 if (file_load_(vm, *argv, 0)) {
775 fprintf(stderr, "couldn't load '%s'\n", *argv);
776 exit(EX_NOINPUT);
777 }
778 }
779
780 /* show state, read commands */
781 for (line = line_prev = NULL,
782 tok_v = tok_v_prev = NULL,
783 tok_c = tok_c_prev= 0,
784 snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]),
785 dcpu16_state_print(vm);
786
787 (line = readline(prompt));
788
789 printf("\n"),
790 snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]),
791 dcpu16_state_print(vm)) {
792 const char whitespace[] = " \t";
793 char *line_start;
794 struct command_ *c;
795 int r = 0;
796
797 /* skip whitespaces */
798 line_start = line + strspn(line, whitespace);
799
800 if (*line_start) {
801 /* a new command, remember previous for possible repetition */
802
803 /* turn new line into new arg array */
804 if (buf_tok_vect_(&tok_v, &tok_c, line_start)) {
805 fprintf(stderr, "failed to process command\n");
806 continue;
807 }
808
809 /* and keep track if it all for the next time around */
810 if (line_prev) free(line_prev);
811 line_prev = line;
812
813 if (tok_v_prev) free(tok_v_prev);
814 tok_v_prev = tok_v;
815 tok_c_prev = tok_c;
816 } else {
817 /* blank new command, but no prior command to repeat? ask again */
818 if (tok_v_prev == NULL || tok_v_prev[0] == NULL || *(tok_v_prev[0]) == '\0') {
819 free(line);
820 continue;
821 }
822
823 /* otherwise discard new line and promote prior */
824 free(line);
825 tok_v = tok_v_prev;
826 tok_c = tok_c_prev;
827 line = line_prev;
828 }
829
830 /* look up command */
831 for (c = command_table_; c->name; c++) {
832 if (strcasecmp(tok_v[0], c->name) == 0) {
833 if (c->args_min > tok_c - 1) {
834 fprintf(stderr, "%s: not enough arguments\n", c->name);
835 c->help(stderr, 1);
836 break;
837 }
838
839 if (c->args_max > 0
840 && tok_c - 1 > c->args_max) {
841 fprintf(stderr, "%s: too many arguments\n", c->name);
842 c->help(stderr, 1);
843 break;
844 }
845
846 r = c->func(vm, tok_c, tok_v);
847 break;
848 }
849 }
850 if (r)
851 break;
852
853 if (!c->func)
854 fprintf(stderr, "didn't recognize '%s'\n", tok_v[0]);
855 }
856
857 printf("\nfinished\n");
858
859 dcpu16_delete(&vm);
860
861 exit(EX_OK);
862 }