b59c253ee0700fa561a55ddb221650bb9d9f3632
9 #include <readline/readline.h>
14 * shell-like driver for dcpu16 core
15 * provides a basic interface to control a single emulation instance
17 * Justin Wind <justin.wind@gmail.com>
18 * 2012 04 10 - implementation started
19 * 2012 04 12 - cleanup, better shell loop
22 * handle quotes in shell command parsing
23 * use readline/history.h, since we're using readline anyhow
24 * ncurses windowing or something, for future display capabilities
27 static const char * const src_id_
= "$Id$";
29 /* global invocation options */
36 /* global run state, first sigint caught will drop out of run loop and back into shell */
37 static volatile unsigned int running_
= 0;
39 void sigint_handler_(int sig
) {
44 #define VERBOSE_PRINTF(...) do { if (opt_.verbose) printf(__VA_ARGS__); } while (0)
47 void usage_(char *prog
, unsigned int full
) {
48 FILE *f
= full
? stdout
: stderr
;
49 char *x
= strrchr(prog
, '/');
55 fprintf(f
, "%s -- dcpu16 emulator core shell\n\n",
58 fprintf(f
, "Usage: %s [-v] [file]\n",
62 fprintf(f
, "\nOptions:\n"
63 "\t [file] -- ram image to load initially\n"
64 "\t -v -- displays slightly more information\n"
65 "\t -h -- this screen\n");
67 fprintf(f
, "\n%78s\n", src_id_
);
71 /* simplified strtoul with range checking */
73 int str_to_word_(char *s
) {
80 l
= strtoul(s
, &ep
, 0);
83 || !(*s
&& *ep
== '\0') ) {
84 /* out of range of conversion, or invalid character encountered */
88 if (l
>= DCPU16_RAM
) {
89 /* out of range for our needs */
97 /* flense a buffer into a newly-allocated argument list */
98 /* FIXME: handle quotes */
100 int buf_tok_vect_(char ***v
, int *c
, char *buf
) {
101 const char *sep
= " \t";
102 const size_t v_grow
= 32;
107 *v
= malloc(v_sz
* sizeof **v
);
109 fprintf(stderr
, "%s():%s\n", "malloc", strerror(errno
));
113 for ( (*v
)[*c
] = strtok_r(buf
, sep
, &st
);
115 (*v
)[*c
] = strtok_r(NULL
, sep
, &st
)
119 if ((size_t)(*c
) == v_sz
) {
120 void *tmp_ptr
= realloc(*v
, (v_sz
+ v_grow
) * sizeof **v
);
121 if (tmp_ptr
== NULL
) {
122 fprintf(stderr
, "%s():%s\n", "realloc", strerror(errno
));
135 resets the vm if addr is zero then
136 loads an image from filename into ram starting at addr
139 int file_load_(struct dcpu16
*vm
, char *filename
, DCPU16_WORD addr
) {
146 f
= fopen(filename
, "rb");
148 fprintf(stderr
, "%s('%s'):%s\n", "fopen", filename
, strerror(errno
));
152 r
= fread(vm
->ram
+ addr
, sizeof(DCPU16_WORD
), DCPU16_RAM
- addr
, f
);
153 VERBOSE_PRINTF("read %zu words", r
);
154 if (addr
) VERBOSE_PRINTF(" starting at 0x%04x", addr
);
155 VERBOSE_PRINTF("\n");
158 fprintf(stderr
, "%s('%s'):%s\n", "fread", filename
, strerror(errno
));
165 Here follows the various commands the shell can execute.
167 At invocation, a command function will have already had its
168 number of arguments vetted, but will need command-specific
169 argument verifications done.
171 The arg_vector contains the command as the first entry, and
172 as such, arg_count will always be at least 1.
173 However, the args_min and args_max entries in struct command_
174 only refer to the counts of arguments, not the entries in the
182 int (*func
)(struct dcpu16
*, int c
, char **v
);
183 void (*help
)(FILE *f
, unsigned int);
186 #define COMMAND_IMPL(x) static int command_##x##_(struct dcpu16 *vm, int arg_count, char **arg_vector)
187 #define COMMAND_HELP(x) static void command_##x##_help_(FILE *f, unsigned int summary)
188 #define COMMAND_ENTRY(x, y, z) { #x, y, z, command_##x##_, command_##x##_help_ }
192 (void)vm
, (void)arg_count
, (void)arg_vector
;
197 fprintf(f
, "\tquit\n");
200 fprintf(f
, "Exits the emulator.\n");
204 COMMAND_IMPL(reset
) {
205 (void)arg_count
, (void)arg_vector
;
208 printf("initialized\n");
211 COMMAND_HELP(reset
) {
212 fprintf(f
, "\treset\n");
215 fprintf(f
, "Clears and reinitializes emulator.\n");
223 addr
= str_to_word_(arg_vector
[2]);
225 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[2], strerror(errno
));
230 if (file_load_(vm
, arg_vector
[1], addr
)) {
231 fprintf(stderr
, "failed to load '%s'\n", arg_vector
[1]);
234 printf("loaded '%s'", arg_vector
[1]);
235 if (addr
) printf(" starting at 0x%04x", addr
);
241 fprintf(f
, "\tload file [addr]\n");
244 fprintf(f
, "Load binary image from 'file' into ram.\n");
252 for (i
= 1; i
< arg_count
; i
++) {
253 addr
[i
-1] = str_to_word_(arg_vector
[i
]);
255 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[i
], strerror(errno
));
259 if (arg_count
< 2) addr
[0] = vm
->pc
;
260 if (arg_count
< 3) addr
[1] = addr
[0];
262 if (addr
[1] < addr
[0]) {
263 fprintf(stderr
, "\t'addr_start' must be before addr_end\n");
267 dcpu16_dump_ram(vm
, addr
[0], addr
[1]);
272 fprintf(f
, "\tdump [addr_start [addr_end]]\n");
275 fprintf(f
, "Displays contents of ram from addr_start to addr_end.\n");
279 COMMAND_IMPL(disassemble
) {
283 for (i
= 1; i
< arg_count
; i
++) {
284 addr
[i
-1] = str_to_word_(arg_vector
[i
]);
286 fprintf(stderr
, "address '%s' is not a valid word: %s\n", arg_vector
[i
], strerror(errno
));
290 if (arg_count
< 2) addr
[0] = vm
->pc
;
291 if (arg_count
< 3) addr
[1] = addr
[0];
293 if (addr
[1] < addr
[0]) {
294 fprintf(stderr
, "\t'addr_start' must be before addr_end\n");
298 for (i
= addr
[0]; i
<= addr
[1]; /* */ ) {
299 printf("0x%04x: ", i
);
300 i
+= dcpu16_disassemble_print(vm
, i
);
306 COMMAND_HELP(disassemble
) {
307 fprintf(f
, "\tdisassemble [addr_start [addr_end]]\n");
310 fprintf(f
, "Displays contents of ram parsed into instructions.\n");
315 unsigned long count
= 1;
318 if (arg_count
== 2) {
320 count
= strtoul(arg_vector
[1], &ep
, 0);
322 || !(*arg_vector
[0] && *ep
== '\0') ) {
323 fprintf(stderr
, "count '%s' is not a valid number: %s\n", arg_vector
[1], strerror(errno
));
328 fprintf(stderr
, "count must be positive\n");
334 dcpu16_disassemble_print(vm
, vm
->pc
);
338 if (count
> 1 && opt_
.verbose
)
339 dcpu16_state_print(vm
);
345 fprintf(f
, "\tstep [count]\n");
348 fprintf(f
, "Executes the next instruction, or the next count instructions.\n");
354 (void)arg_count
, (void)arg_vector
;
358 /* install our new interrupt signal handler */
359 if ( (osig
= signal(SIGINT
, sigint_handler_
)) == SIG_ERR
) {
360 fprintf(stderr
, "%s():%s\n", "signal", strerror(errno
));
366 if (opt_
.verbose
> 1)
367 dcpu16_state_print(vm
);
368 else if (opt_
.verbose
) {
369 dcpu16_disassemble_print(vm
, vm
->pc
);
374 /* restore the old interrupt signal handler */
375 if (signal(SIGINT
, osig
) == SIG_ERR
) {
376 fprintf(stderr
, "%s():%s\n", "signal", strerror(errno
));
380 printf("interrupted...\n");
385 fprintf(f
, "\trun\n");
388 fprintf(f
, "Begins executing continuously.\n"
389 "May be interrupted with SIGINT.\n");
392 /* gather all these together into a searchable table */
394 /* help command gets some assistance in declarations */
398 static struct command_ command_table_
[] = {
399 COMMAND_ENTRY(help
, 0, -1),
400 COMMAND_ENTRY(quit
, 0, -1),
401 COMMAND_ENTRY(load
, 1, 2),
402 COMMAND_ENTRY(dump
, 0, 2),
403 COMMAND_ENTRY(disassemble
, 0, 2),
404 COMMAND_ENTRY(step
, 0, 1),
405 COMMAND_ENTRY(run
, 0, 0),
406 COMMAND_ENTRY(reset
, 0, 0),
407 { NULL
, 0, 0, NULL
, NULL
}
414 if (arg_count
== 2) {
415 for (c
= command_table_
; c
->func
; c
++) {
416 if (strcasecmp(arg_vector
[1], c
->name
) == 0) {
425 for (c
= command_table_
; c
->func
; c
++) {
432 fprintf(f
, "\thelp [command]\n");
435 fprintf(f
, "Displays a list of available commands, or detailed help on a specific command.\n");
439 int main(int argc
, char **argv
) {
440 const char prompt_fmt
[] = "PC:%04x> ";
443 char *line
, *line_prev
;
444 char **tok_v
, **tok_v_prev
;
445 int tok_c
, tok_c_prev
;
448 while ( (c
= getopt(argc
, argv
, "hv")) != EOF
) {
463 if (opt_
.verbose
< 1) {
464 dcpu16_warn_cb_set(NULL
);
465 dcpu16_trace_cb_set(NULL
);
466 } else if (opt_
.verbose
< 2) {
467 dcpu16_trace_cb_set(NULL
);
472 if ((vm
= dcpu16_new()) == NULL
) {
473 fprintf(stderr
, "could not allocate new dcpu16 instance\n");
474 exit(EX_UNAVAILABLE
);
478 if (file_load_(vm
, *argv
, 0)) {
479 fprintf(stderr
, "couldn't load '%s'\n", *argv
);
484 /* show state, read commands */
485 for (line
= line_prev
= NULL
,
486 tok_v
= tok_v_prev
= NULL
,
487 tok_c
= tok_c_prev
= 0,
488 snprintf(prompt
, sizeof prompt
, prompt_fmt
, vm
->pc
),
489 dcpu16_state_print(vm
);
491 (line
= readline(prompt
));
494 snprintf(prompt
, sizeof prompt
, prompt_fmt
, vm
->pc
),
495 dcpu16_state_print(vm
)) {
496 const char whitespace
[] = " \t";
501 /* skip whitespaces */
502 line_start
= line
+ strspn(line
, whitespace
);
505 /* a new command, remember previous for possible repetition */
507 /* turn new line into new arg array */
508 if (buf_tok_vect_(&tok_v
, &tok_c
, line_start
)) {
509 fprintf(stderr
, "failed to process command\n");
513 /* and keep track if it all for the next time around */
514 if (line_prev
) free(line_prev
);
517 if (tok_v_prev
) free(tok_v_prev
);
521 /* blank new command, but no prior command to repeat? ask again */
522 if (tok_v_prev
== NULL
|| tok_v_prev
[0] == NULL
|| *(tok_v_prev
[0]) == '\0') {
527 /* otherwise discard new line and promote prior */
534 /* look up command */
535 for (c
= command_table_
; c
->name
; c
++) {
536 if (strcasecmp(tok_v
[0], c
->name
) == 0) {
537 if (c
->args_min
> tok_c
- 1) {
538 fprintf(stderr
, "%s: not enough arguments\n", c
->name
);
544 && tok_c
- 1 > c
->args_max
) {
545 fprintf(stderr
, "%s: too many arguments\n", c
->name
);
550 r
= c
->func(vm
, tok_c
, tok_v
);
558 fprintf(stderr
, "didn't recognize '%s'\n", tok_v
[0]);
561 printf("\nfinished\n");