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