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