#include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBVNCSERVER #include "rfb/rfb.h" #endif /* HAVE_LIBVNCSERVER */ #include "dcpu16.h" #include "common.h" #include "hw_lem1802.h" #include "hw_keyboard.h" /* * shell-like driver for dcpu16 core * provides a basic interface to control a single emulation instance * * Justin Wind * 2012 04 10 - implementation started * 2012 04 12 - cleanup, better shell loop * 2012 05 12 - support v1.7 style devices * * TODO * handle quotes in shell command parsing * use readline/history.h, since we're using readline anyhow * ncurses windowing or something, for future display capabilities */ static const char * const src_id_ = "$Id$"; /* global invocation options */ struct options { unsigned int verbose; } opt_ = { .verbose = 0, }; /* global run state, first sigint caught will drop out of run loop and back into shell */ static volatile unsigned int running_ = 0; static void sigint_handler_(int sig) { (void)sig; running_ = 0; } #define VERBOSE_PRINTF(...) do { if (opt_.verbose) printf(__VA_ARGS__); } while (0) #ifdef DEBUG #define DEBUG_PRINTF(...) do { if (opt_.verbose > 4) fprintf(stderr, __VA_ARGS__); } while (0) #else /* DEBUG */ #define DEBUG_PRINTF(...) do { } while (0) #endif /* DEBUG */ static void usage_(char *prog, unsigned int full) { FILE *f = full ? stdout : stderr; char *x = strrchr(prog, '/'); if (x && *(x + 1)) prog = x + 1; if (full) fprintf(f, "%s -- dcpu16 emulator core shell\n\n", prog); fprintf(f, "Usage: %s [-v] [file]\n", prog); if (full) { fprintf(f, "\nOptions:\n" "\t [file] -- ram image to load initially\n" "\t -v -- prints slightly more information while operating\n" "\t -h -- this screen\n"); fprintf(f, "\n%78s\n", src_id_); } } /* flense a buffer into a newly-allocated argument list */ static int buf_tok_vect_(char ***v, int *c, char *buf) { const char *sep = " \t"; const char *quot = "\"'`"; const size_t v_grow = 32; size_t v_sz = 32; char *st, *qt; *c = 0; *v = malloc(v_sz * sizeof **v); if (*v == NULL) { fprintf(stderr, "%s():%s\n", "malloc", strerror(errno)); return -1; } for ( (*v)[*c] = strqtok_r(buf, sep, '\\', quot, &qt, &st); (*v)[*c]; (*v)[*c] = strqtok_r(NULL, sep, '\\', quot, &qt, &st) ) { (*c)++; if ((size_t)(*c) == v_sz) { void *tmp_ptr = realloc(*v, (v_sz + v_grow) * sizeof **v); if (tmp_ptr == NULL) { fprintf(stderr, "%s():%s\n", "realloc", strerror(errno)); free(*v); *v = NULL; return -1; } v_sz += v_grow; } } return 0; } /* resets the vm if addr is zero then loads an image from filename into ram starting at addr */ static int file_load_(struct dcpu16 *vm, char *filename, DCPU16_WORD addr) { FILE *f; size_t r; if (!addr) dcpu16_reset(vm); f = fopen(filename, "rb"); if (f == NULL) { fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno)); return -1; } r = fread(vm->ram + addr, sizeof(DCPU16_WORD), DCPU16_RAM - addr, f); VERBOSE_PRINTF("read %zu words", r); if (addr) VERBOSE_PRINTF(" starting at 0x%04x", addr); VERBOSE_PRINTF("\n"); if (ferror(f)) fprintf(stderr, "%s('%s'):%s\n", "fread", filename, strerror(errno)); fclose(f); return 0; } /* dump_ram_ * print raw ram contents from start to stop */ static void dump_ram_(struct dcpu16 *vm, DCPU16_WORD start, DCPU16_WORD end) { unsigned int i, j; const unsigned int n = 8; /* words per line */ if (!vm) return; for (i = start, j = 0; i <= end; i++, j++) { if (j % n == 0) printf("0x%04x:\t", i); printf(" %04x%s", vm->ram[i], (j % n) == (n - 1) ? "\n" : ""); } if ((j % n) != (n - 1)) printf("\n"); } /* print the current state of the machine shows current cycle count, registers, and next instruction */ static void state_print_(struct dcpu16 *vm) { unsigned int i; if (!vm) return; printf(" "); for (i = 0; i < 8; i++) printf(" %s:0x%04x", dcpu16_reg_names[i], vm->reg[i]); printf("\n"); printf("(0x%08llx) %2s:0x%04x %2s:0x%04x %2s:0x%04x %2s:0x%04x [%2s]:", vm->cycle_, dcpu16_reg_names[DCPU16_REG_EX], vm->reg[DCPU16_REG_EX], dcpu16_reg_names[DCPU16_REG_SP], vm->reg[DCPU16_REG_SP], dcpu16_reg_names[DCPU16_REG_PC], vm->reg[DCPU16_REG_PC], dcpu16_reg_names[DCPU16_REG_IA], vm->reg[DCPU16_REG_IA], "PC"); dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]); printf("\n"); } #ifdef HAVE_LIBVNCSERVER static struct dynamic_array rfbScreens_; /* wups, kbdAddEvent isn't null by default, so I guess track associations externally */ struct rfb_instance_ { rfbScreenInfoPtr screen; struct dcpu16_hw *attached_display; struct dcpu16_hw *attached_keyboard; }; enum rfbscreen_next_what_ { NEXT_DISPLAY, NEXT_KEYBOARD, }; /* locate next rfb not paired to requested type */ static struct rfb_instance_ *rfbScreen_next_available_(enum rfbscreen_next_what_ what, struct dynamic_array *rfbScreens, int argc, char *argv[]) { size_t i; struct rfb_instance_ new_instance, *s; struct packed_args_ { int argc; char **argv; } parg = { argc, argv }; for (i = 0; i < rfbScreens->entries; i++) { struct dcpu16_hw *attached; s = (struct rfb_instance_ *)DYNARRAY_ITEM(*rfbScreens, i); switch (what) { case NEXT_DISPLAY: attached = s->attached_display; break; case NEXT_KEYBOARD: attached = s->attached_keyboard; break; } if (attached == NULL) return s; } /* no available rfb, create new */ if (dcpu16_hw_module_lem1802.ctl(NULL, "new_rfbScreen", &parg, &new_instance.screen)) { fprintf(stderr, "failed to allocate new rfbScreen\n"); return NULL; } new_instance.attached_display = NULL; new_instance.attached_keyboard = NULL; new_instance.screen->port += rfbScreens->entries; new_instance.screen->ipv6port += rfbScreens->entries; DEBUG_PRINTF("%s>> port:%u\n", __func__, new_instance.screen->port); s = dynarray_add(rfbScreens, &new_instance); return s; } /* begin serving a screen */ void rfbScreen_start(rfbScreenInfoPtr s) { rfbInitServer(s); rfbRunEventLoop(s, -1, TRUE); } #endif /* HAVE_LIBVNCSERVER */ /* Here follows the various commands the shell can execute. At invocation, a command function will have already had its number of arguments vetted, but will need command-specific argument verifications done. The arg_vector contains the command as the first entry, and as such, arg_count will always be at least 1. However, the args_min and args_max entries in struct command_ only refer to the counts of arguments, not the entries in the argv. */ struct command_ { char *name; int args_min; int args_max; int (*func)(struct dcpu16 *, int c, char **v); void (*help)(FILE *f, unsigned int); }; #define COMMAND_IMPL(x) static int command_##x##_(struct dcpu16 *vm, int arg_count, char **arg_vector) #define COMMAND_HELP(x) static void command_##x##_help_(FILE *f, unsigned int summary) #define COMMAND_ENTRY(x, y, z) { #x, y, z, command_##x##_, command_##x##_help_ } COMMAND_IMPL(quit) { (void)vm, (void)arg_count, (void)arg_vector; return -1; } COMMAND_HELP(quit) { fprintf(f, "\tquit\n"); if (summary) return; fprintf(f, "Exits the emulator.\n"); } COMMAND_IMPL(reset) { (void)arg_count, (void)arg_vector; dcpu16_reset(vm); printf("initialized\n"); return 0; } COMMAND_HELP(reset) { fprintf(f, "\treset\n"); if (summary) return; fprintf(f, "Clears and reinitializes emulator.\n"); } COMMAND_IMPL(verbosity) { int l; (void)vm, (void)arg_count; l = str_to_word(arg_vector[1]); if (l < 0) { fprintf(stderr, "invalid level\n"); return 0; } opt_.verbose = l; return 0; } COMMAND_HELP(verbosity) { fprintf(f, "\tverbosity level\n"); if (summary) return; fprintf(f, "sets the verbosity level\n"); } COMMAND_IMPL(load) { int addr = 0; if (arg_count > 2) { addr = str_to_word(arg_vector[2]); if (addr < 0) { fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno)); return 0; } } if (file_load_(vm, arg_vector[1], addr)) { fprintf(stderr, "failed to load '%s'\n", arg_vector[1]); return 0; } printf("loaded '%s'", arg_vector[1]); if (addr) printf(" starting at 0x%04x", addr); printf("\n"); return 0; } COMMAND_HELP(load) { fprintf(f, "\tload file [addr]\n"); if (summary) return; fprintf(f, "Load binary image from 'file' into ram.\n"); } COMMAND_IMPL(dump) { int addr[2]; int i; for (i = 1; i < arg_count; i++) { addr[i-1] = str_to_word(arg_vector[i]); if (addr[i-1] < 0) { fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno)); return 0; } } if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC]; if (arg_count < 3) addr[1] = addr[0]; if (addr[1] < addr[0]) { fprintf(stderr, "\t'addr_start' must be before addr_end\n"); return 0; } dump_ram_(vm, addr[0], addr[1]); return 0; } COMMAND_HELP(dump) { fprintf(f, "\tdump [addr_start [addr_end]]\n"); if (summary) return; fprintf(f, "Displays contents of ram from addr_start to addr_end.\n"); } COMMAND_IMPL(disassemble) { int addr[2]; int i; for (i = 1; i < arg_count; i++) { addr[i-1] = str_to_word(arg_vector[i]); if (addr[i-1] < 0) { fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[i], strerror(errno)); return 0; } } if (arg_count < 2) addr[0] = vm->reg[DCPU16_REG_PC]; if (arg_count < 3) addr[1] = addr[0]; if (addr[1] < addr[0]) { fprintf(stderr, "\t'addr_start' must be before addr_end\n"); return 0; } for (i = addr[0]; i <= addr[1]; /* */ ) { printf("0x%04x: ", i); i += dcpu16_disassemble_print(vm, i); printf("\n"); } return 0; } COMMAND_HELP(disassemble) { fprintf(f, "\tdisassemble [addr_start [addr_end]]\n"); if (summary) return; fprintf(f, "Displays contents of ram parsed into instructions.\n"); } COMMAND_IMPL(step) { unsigned long count = 1; char *ep; if (arg_count == 2) { errno = 0; count = strtoul(arg_vector[1], &ep, 0); if (errno || !(*arg_vector[1] && *ep == '\0') ) { fprintf(stderr, "count '%s' is not a valid number: %s\n", arg_vector[1], strerror(errno)); return 0; } if (count <= 0) { fprintf(stderr, "count must be positive\n"); return 0; } } while (count--) { dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]); printf("\n"); dcpu16_step(vm); if (count > 1 && opt_.verbose) state_print_(vm); } return 0; } COMMAND_HELP(step) { fprintf(f, "\tstep [count]\n"); if (summary) return; fprintf(f, "Executes the next instruction, or the next count instructions.\n"); } COMMAND_IMPL(set) { int addr, value; DCPU16_WORD *v; (void)arg_count; /* check if addr is a register */ for (addr = 0; dcpu16_reg_names[addr]; addr++) { if (strcasecmp(arg_vector[1], dcpu16_reg_names[addr]) == 0) break; } if (addr < DCPU16_REG__NUM) { v = vm->reg + addr; } else { addr = str_to_word(arg_vector[1]); if (addr < 0) { fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[1], strerror(errno)); return 0; } v = vm->ram + addr; } value = str_to_word(arg_vector[2]); if (value < 0) { fprintf(stderr, "address '%s' is not a valid word: %s\n", arg_vector[2], strerror(errno)); return 0; } *v = value; return 0; } COMMAND_HELP(set) { fprintf(f, "\tset addr value\n"); if (summary) return; fprintf(f, "Sets addr to value."); } #define NANOSECONDS_PER_CYCLE 10000 #define MIN_NANOSLEEP 31000 COMMAND_IMPL(run) { struct sigaction act; long long run_cycle_start, run_cycle_end; long long cycle_start, cycles_to_wait; struct timespec ts_run_start, ts_run_end, ts_run_diff; struct timespec ts_cycle_start, ts_cycle_end_target, ts_cycle_end, ts_cycle_waste, ts_cycle_rem; const struct timespec ts_cycle_time = { .tv_sec = 0, .tv_nsec = NANOSECONDS_PER_CYCLE }; (void)arg_count, (void)arg_vector; running_ = 1; gettimespecofday(&ts_run_start); run_cycle_start = vm->cycle_; memset(&act, 0, sizeof act); act.sa_handler = sigint_handler_; act.sa_flags = SA_RESETHAND; if (sigaction(SIGINT, &act, NULL)) { fprintf(stderr, "%s():%s\n", "sigaction", strerror(errno)); return -1; } while (running_) { gettimespecofday(&ts_cycle_start); ts_cycle_end_target = ts_cycle_start; cycle_start = vm->cycle_; dcpu16_step(vm); if (opt_.verbose > 1) state_print_(vm); else if (opt_.verbose) { dcpu16_disassemble_print(vm, vm->reg[DCPU16_REG_PC]); printf("\n"); } /* how many cycles did this instr use? */ cycles_to_wait = vm->cycle_ - cycle_start; /* each cycle wants to take 10 microseconds */ while (cycles_to_wait--) timespec_add(&ts_cycle_end_target, &ts_cycle_time); /* how much of that did we spend already */ gettimespecofday(&ts_cycle_end); /* do we have time to kill? */ if (timespec_subtract(&ts_cycle_waste, &ts_cycle_end_target, &ts_cycle_end) == 0) { /* nanosleep doesn't interfere with libvncserver, unlike usleep */ if (ts_cycle_waste.tv_sec == 0 && ts_cycle_waste.tv_nsec >= MIN_NANOSLEEP) while ( nanosleep(&ts_cycle_waste, &ts_cycle_rem) ) ts_cycle_waste = ts_cycle_rem; } else { /* negative, we've already blown our time */ DEBUG_PRINTF("cycle time overrun %ld.%09lds\n", ts_cycle_waste.tv_sec, ts_cycle_waste.tv_nsec); } #ifdef DEBUG /* how did we do */ gettimespecofday(&ts_cycle_end); timespec_subtract(&ts_cycle_rem, &ts_cycle_end_target, &ts_cycle_end); DEBUG_PRINTF("projected end: %ld.%09ld actual end: %ld.%09ld diff: %ld.%09ld\n", ts_cycle_end_target.tv_sec, ts_cycle_end_target.tv_nsec, ts_cycle_end.tv_sec, ts_cycle_end.tv_nsec, ts_cycle_rem.tv_sec, ts_cycle_rem.tv_nsec); #endif /* DEBUG */ } run_cycle_end = vm->cycle_; gettimespecofday(&ts_run_end); timespec_subtract(&ts_run_diff, &ts_run_end, &ts_run_start); VERBOSE_PRINTF("ran %lld cycles in %ld.%09lds\n", run_cycle_end - run_cycle_start, ts_run_diff.tv_sec, ts_run_diff.tv_nsec); printf("interrupted...\n"); return 0; } COMMAND_HELP(run) { fprintf(f, "\trun\n"); if (summary) return; fprintf(f, "Begins executing continuously.\n" "May be interrupted with SIGINT.\n"); } static const char * const display_filename_default_ = #ifdef HAVE_LIBPNG "dcpu16-display.png" #else /* HAVE_LIBPNG */ "dcpu16-display.pnm" #endif /* HAVE_LIBPNG */ ; COMMAND_IMPL(display) { struct dcpu16_hw *hw; const char *renderer = arg_vector[1]; const char *renderer_arg = NULL; void *renderer_data; if (arg_count == 3) renderer_arg = arg_vector[2]; hw = dcpu16_hw_new(vm, &dcpu16_hw_module_lem1802, NULL); if (hw == NULL) { fprintf(stderr, "failed to initialize new display\n"); return 0; } /* handle per-renderer setup of data.. */ /* FIXME: these are awkward */ if (strcmp(renderer, "pnm") == 0) { renderer_data = (void *)(renderer_arg ? renderer_arg : display_filename_default_); } #ifdef HAVE_LIBPNG if (strcmp(renderer, "png") == 0) { renderer_data = (void *)(renderer_arg ? renderer_arg : display_filename_default_); } #endif /* HAVE_LIBPNG */ #ifdef HAVE_LIBVNCSERVER if (strcmp(renderer, "vnc") == 0) { int argc = 1; char *argv[] = { "vm-dcpu16", NULL }; struct rfb_instance_ *s; s = rfbScreen_next_available_(NEXT_DISPLAY, &rfbScreens_, argc, argv); if (s == NULL) { fprintf(stderr, "failed to initialize vnc\n"); dcpu16_hw_del(&hw); return 0; } if (dcpu16_hw_ctl(hw, "associate_rfbScreen", s->screen, NULL)) { fprintf(stderr, "failed to configure display/vnc"); dcpu16_hw_del(&hw); return 0; } s->attached_display = hw; rfbScreen_start(s->screen); renderer_data = s->screen; DEBUG_PRINTF("attached display to rfb (frameBuffer:%p)\n", s->screen->frameBuffer); } #endif /* HAVE_LIBVNCSERVER */ dcpu16_hw_ctl(hw, "renderer", (char *)renderer, NULL); dcpu16_hw_ctl(hw, "renderer_data", renderer_data, NULL); if (dcpu16_hw_attach(vm, hw)) { fprintf(stderr, "failed to attach new display\n"); dcpu16_hw_del(&hw); return 0; } return 0; } COMMAND_HELP(display) { struct renderer_ { char *name; char *args; } renderer; void *iter; fprintf(f, "\tdisplay renderer [renderer data]\n"); if (summary) return; fprintf(f, "Attaches new display unit, using 'renderer' as back-end output.\n" ); fprintf(f, "Supported renderers:\n"); iter = NULL; do { if (dcpu16_hw_module_lem1802.ctl(NULL, "renderers_iter", &iter, &renderer)) { fprintf(stderr, "error fetching next renderer\n"); break; } if (iter == NULL || renderer.name == NULL) break; fprintf(f, "\t%s %s\n", renderer.name, renderer.args); } while (iter); } COMMAND_IMPL(keyboard) { struct dcpu16_hw *hw; (void)arg_count, (void)arg_vector; hw = dcpu16_hw_new(vm, &dcpu16_hw_module_keyboard, NULL); if (hw == NULL) { fprintf(stderr, "failed to initialize new keyboard\n"); return 0; } #ifdef HAVE_LIBVNCSERVER struct rfb_instance_ *s; int argc = 1; char *argv[] = { "vm-dcpu16", NULL }; s = rfbScreen_next_available_(NEXT_KEYBOARD, &rfbScreens_, argc, argv); if (s == NULL) { fprintf(stderr, "failed to initialize vnc\n"); dcpu16_hw_del(&hw); return 0; } if (dcpu16_hw_ctl(hw, "associate_rfbScreen", s->screen, NULL)) { fprintf(stderr, "failed to configure keyboard/vnc\n"); dcpu16_hw_del(&hw); return 0; } s->attached_keyboard = hw; if (dcpu16_hw_attach(vm, hw)) { fprintf(stderr, "failed to attach new keyboard\n"); dcpu16_hw_del(&hw); return 0; } #endif /* HAVE_LIBVNCSERVER */ return 0; } COMMAND_HELP(keyboard) { fprintf(f, "\tkeyboard\n"); if (summary) return; fprintf(f, "Attaches new keyboard unit.\n"); } /* gather all these together into a searchable table */ /* help command gets some assistance in declarations */ COMMAND_IMPL(help); COMMAND_HELP(help); static struct command_ command_table_[] = { COMMAND_ENTRY(help, 0, -1), COMMAND_ENTRY(quit, 0, -1), COMMAND_ENTRY(verbosity, 1, 1), COMMAND_ENTRY(load, 1, 2), COMMAND_ENTRY(dump, 0, 2), COMMAND_ENTRY(disassemble, 0, 2), COMMAND_ENTRY(step, 0, 1), COMMAND_ENTRY(run, 0, 0), COMMAND_ENTRY(set, 2, 2), COMMAND_ENTRY(reset, 0, 0), COMMAND_ENTRY(display, 1, 2), COMMAND_ENTRY(keyboard, 0, 0), { NULL, 0, 0, NULL, NULL } }; COMMAND_IMPL(help) { struct command_ *c; (void)vm; if (arg_count == 2) { for (c = command_table_; c->func; c++) { if (strcasecmp(arg_vector[1], c->name) == 0) { if (c->help) c->help(stdout, 0); break; } } return 0; } for (c = command_table_; c->func; c++) { if (c->help) c->help(stdout, 1); } return 0; } COMMAND_HELP(help) { fprintf(f, "\thelp [command]\n"); if (summary) return; fprintf(f, "Displays a list of available commands, or detailed help on a specific command.\n"); } static void msg_verbose_filter_(unsigned int level, char *fmt, ...) { static const char * const levels[] = { "error", "info", "debug" }; FILE *f = (level <= DCPU16_MSG_ERROR) ? stderr : stdout; va_list ap; if (level + 2 > opt_.verbose) return; if (level < sizeof levels / sizeof *levels) fprintf(f, "[%s %s] ", "dcpu16", levels[level]); else fprintf(f, "[%s %u] ", "dcpu16", level); va_start(ap, fmt); vfprintf(f, fmt, ap); va_end(ap); fprintf(f, "\n"); fflush(f); } int main(int argc, char **argv) { const char prompt_fmt[] = "PC:%04x> "; char prompt[32]; struct dcpu16 *vm; char *line, *line_prev; char **tok_v, **tok_v_prev; int tok_c, tok_c_prev; int c; while ( (c = getopt(argc, argv, "hv")) != EOF) { switch (c) { case 'v': opt_.verbose++; break; case 'h': usage_(argv[0], 1); exit(EX_OK); default: usage_(argv[0], 0); exit(EX_USAGE); } } argc -= optind; argv += optind; dcpu16_msg_set_default(msg_verbose_filter_); if ((vm = dcpu16_new()) == NULL) { fprintf(stderr, "could not allocate new dcpu16 instance\n"); exit(EX_UNAVAILABLE); } #ifdef HAVE_LIBVNCSERVER if (dynarray_init(&rfbScreens_, sizeof(struct rfb_instance_), 4)) { fprintf(stderr, "could not allocate rfb container\n"); exit(EX_UNAVAILABLE); } #endif /* HAVE_LIBVNCSERVER */ if (argc) { if (file_load_(vm, *argv, 0)) { fprintf(stderr, "couldn't load '%s'\n", *argv); exit(EX_NOINPUT); } } /* show state, read commands */ for (line = line_prev = NULL, tok_v = tok_v_prev = NULL, tok_c = tok_c_prev= 0, snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]), state_print_(vm); (line = readline(prompt)); printf("\n"), snprintf(prompt, sizeof prompt, prompt_fmt, vm->reg[DCPU16_REG_PC]), state_print_(vm)) { const char whitespace[] = " \t"; char *line_start; struct command_ *c; int r = 0; /* skip whitespaces */ line_start = line + strspn(line, whitespace); if (*line_start) { /* a new command, remember previous for possible repetition */ /* turn new line into new arg array */ if (buf_tok_vect_(&tok_v, &tok_c, line_start)) { fprintf(stderr, "failed to process command\n"); continue; } /* and keep track if it all for the next time around */ if (line_prev) free(line_prev); line_prev = line; if (tok_v_prev) free(tok_v_prev); tok_v_prev = tok_v; tok_c_prev = tok_c; } else { /* blank new command, but no prior command to repeat? ask again */ if (tok_v_prev == NULL || tok_v_prev[0] == NULL || *(tok_v_prev[0]) == '\0') { free(line); continue; } /* otherwise discard new line and promote prior */ free(line); tok_v = tok_v_prev; tok_c = tok_c_prev; line = line_prev; } /* look up command */ for (c = command_table_; c->name; c++) { if (strcasecmp(tok_v[0], c->name) == 0) { if (c->args_min > tok_c - 1) { fprintf(stderr, "%s: not enough arguments\n", c->name); c->help(stderr, 1); break; } if (c->args_max > 0 && tok_c - 1 > c->args_max) { fprintf(stderr, "%s: too many arguments\n", c->name); c->help(stderr, 1); break; } r = c->func(vm, tok_c, tok_v); break; } } if (r) break; if (!c->func) fprintf(stderr, "didn't recognize '%s'\n", tok_v[0]); } printf("\nfinished\n"); dcpu16_delete(&vm); exit(EX_OK); }