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