vm-dcpu16 shell rewritten to be use command table
[dcpu16] / vm-dcpu16.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <errno.h>
6 #include <assert.h>
7 #include <sysexits.h>
8
9 #include <readline/readline.h>
10
11 #include "dcpu16.h"
12
13 /*
14 * cli driver for dcpu16 core
15 *
16 * Justin Wind <justin.wind@gmail.com>
17 * 2012 04 10 - implementation started
18 *
19 */
20
21 static const char * const src_id_ = "$Id$";
22
23 /* global invocation options */
24 struct options {
25 unsigned int verbose;
26 } opt_ = {
27 .verbose = 0,
28 };
29
30 #define VERBOSE_PRINTF(...) do { if (opt_.verbose) printf(__VA_ARGS__); } while (0)
31
32 static void usage_(char *prog, unsigned int full) {
33 FILE *f = full ? stdout : stderr;
34 char *x = strrchr(prog, '/');
35
36 if (x && *(x + 1))
37 prog = x + 1;
38
39 if (full)
40 fprintf(f, "%s -- \n\n",
41 prog);
42
43 fprintf(f, "Usage: %s [file]\n",
44 prog);
45
46 if (full) {
47 fprintf(f, "\nOptions:\n"
48 "\t [file] -- ram image to load initially\n"
49 "\t -h -- this screen\n"
50 "\t -v -- verbose execution tracing\n");
51
52 fprintf(f, "\n%78s\n", src_id_);
53 }
54 }
55
56 /* simplified strtoul with range checking */
57 static
58 int str_to_word_(char *s) {
59 unsigned long l;
60 char *ep;
61
62 assert(s);
63
64 errno = 0;
65 l = strtoul(s, &ep, 0);
66
67 if (errno
68 || !(*s && *ep == '\0') ) {
69 /* out of range of conversion, or invalid character encountered */
70 return -1;
71 }
72
73 if (l >= DCPU16_RAM) {
74 /* out of range for our needs */
75 errno = ERANGE;
76 return -1;
77 }
78
79 return l;
80 }
81
82 /* clears the instance and loads an image into ram starting at addr */
83 static
84 int file_load_(struct dcpu16 *vm, char *filename, DCPU16_WORD addr) {
85 FILE *f;
86 size_t r;
87
88 assert(addr < DCPU16_RAM);
89
90 dcpu16_reset(vm);
91
92 f = fopen(filename, "rb");
93 if (f == NULL) {
94 fprintf(stderr, "%s('%s'):%s\n", "fopen", filename, strerror(errno));
95 return -1;
96 }
97
98 r = fread(vm->ram + addr, sizeof(DCPU16_WORD), DCPU16_RAM - addr, f);
99 VERBOSE_PRINTF("read %zu words", r);
100 if (addr) VERBOSE_PRINTF(" starting at 0x%04x", addr);
101 VERBOSE_PRINTF("\n");
102
103 if (ferror(f))
104 fprintf(stderr, "%s('%s'):%s\n", "fread", filename, strerror(errno));
105
106 fclose(f);
107 return 0;
108 }
109
110 /* the commands the vm shell can execute */
111
112 struct command_ {
113 char *name;
114 int args_min;
115 int args_max;
116 int (*func)(struct dcpu16 *, int c, char **v);
117 void (*help)(FILE *f, unsigned int);
118 };
119
120 #define COMMAND_IMPL(x) static int command_##x##_(struct dcpu16 *vm, int token_count, char **token_vector)
121 #define COMMAND_HELP(x) static void command_##x##_help_(FILE *f, unsigned int summary)
122 #define COMMAND_ENTRY(x, y, z) { #x, y, z, command_##x##_, command_##x##_help_ }
123
124
125 COMMAND_IMPL(quit) {
126 (void)vm, (void)token_count, (void)token_vector;
127 VERBOSE_PRINTF("done\n");
128 return -1;
129 }
130 COMMAND_HELP(quit) {
131 fprintf(f, "quit\n");
132 if (summary) return;
133
134 fprintf(f, "\tExits the emulator.\n");
135 }
136
137
138 COMMAND_IMPL(load) {
139 int addr = 0;
140
141 if (token_count > 1) {
142 addr = str_to_word_(token_vector[1]);
143 if (addr < 0) {
144 fprintf(stderr, "address '%s' is not a valid word: %s\n", token_vector[1], strerror(errno));
145 return 0;
146 }
147 }
148
149 if (file_load_(vm, token_vector[0], addr)) {
150 fprintf(stderr, "failed to load '%s'\n", token_vector[0]);
151 return 0;
152 }
153 printf("loaded '%s'", token_vector[0]);
154 if (addr) printf(" starting at 0x%04x", addr);
155 printf("\n");
156
157 return 0;
158 }
159 COMMAND_HELP(load) {
160 fprintf(f, "load file [addr]\n");
161 if (summary) return;
162
163 fprintf(f, "Usage: load file [addr]\n"
164 "\tAttempts to load binary image from 'file' at addr.\n");
165 }
166
167
168 COMMAND_IMPL(dump) {
169 int addr[2];
170 int i;
171
172 for (i = 0; i < token_count; i++) {
173 addr[i] = str_to_word_(token_vector[i]);
174 if (addr[i] < 0) {
175 fprintf(stderr, "address '%s' is not a valid word: %s\n", token_vector[i], strerror(errno));
176 return 0;
177 }
178 }
179 if (token_count < 1) addr[0] = vm->pc;
180 if (token_count < 2) addr[1] = addr[0];
181
182 if (addr[1] < addr[0]) {
183 fprintf(stderr, "\t'addr_start' must be before addr_end\n");
184 return 0;
185 }
186
187 dcpu16_dump_ram(vm, addr[0], addr[1]);
188
189 return 0;
190 }
191 COMMAND_HELP(dump) {
192 fprintf(f, "dump [addr_start [addr_end]]\n");
193 if (summary) return;
194
195 fprintf(f, "\tDisplays contents of ram from addr_start to addr_end.\n");
196 }
197
198
199 COMMAND_IMPL(disassemble) {
200 int addr[2];
201 int i;
202
203 for (i = 0; i < token_count; i++) {
204 addr[i] = str_to_word_(token_vector[i]);
205 if (addr[i] < 0) {
206 fprintf(stderr, "address '%s' is not a valid word: %s\n", token_vector[i], strerror(errno));
207 return 0;
208 }
209 }
210 if (token_count < 1) addr[0] = vm->pc;
211 if (token_count < 2) addr[1] = addr[0];
212
213 if (addr[1] < addr[0]) {
214 fprintf(stderr, "\t'addr_start' must be before addr_end\n");
215 return 0;
216 }
217
218 for (i = addr[0]; i <= addr[1]; i++)
219 dcpu16_disassemble_print(vm, i);
220
221 return 0;
222 }
223 COMMAND_HELP(disassemble) {
224 fprintf(f, "disassemble [addr_start [addr_end]]\n");
225 if (summary) return;
226
227 fprintf(f, "\tDisplays contents of ram parsed into instructions.\n");
228 }
229
230
231 COMMAND_IMPL(step) {
232 unsigned long count;
233 char *ep;
234
235 (void)token_count;
236
237 errno = 0;
238 count = strtoul(token_vector[0], &ep, 0);
239 if (errno
240 || !(*token_vector[0] && *ep == '\0') ) {
241 fprintf(stderr, "count '%s' is not a valid number: %s\n", token_vector[0], strerror(errno));
242 return 0;
243 }
244
245 if (count <= 0) {
246 fprintf(stderr, "count must be positive\n");
247 return 0;
248 }
249
250 while (count--) {
251 VERBOSE_PRINTF("executing next cycle, instruction: ");
252 dcpu16_disassemble_print(vm, vm->pc), printf("\n");
253
254 dcpu16_step(vm);
255
256 if (opt_.verbose)
257 dcpu16_state_print(vm);
258 }
259
260 return 0;
261 }
262 COMMAND_HELP(step) {
263 fprintf(f, "step [count]\n");
264 if (summary) return;
265
266 fprintf(f, "\tExecutes the next instruction, or the next count instructions.\n");
267 }
268
269
270 /* catch sigint while running, stop running */
271 static volatile unsigned int running_ = 0;
272 static
273 void sigint_handler_(int sig) {
274 (void)sig;
275 running_ = 0;
276 }
277 COMMAND_IMPL(run) {
278 sig_t osig;
279 (void)token_count, (void)token_vector;
280
281 running_ = 1;
282
283 /* install our new interrupt signal handler */
284 if ( (osig = signal(SIGINT, sigint_handler_)) ) {
285 fprintf(stderr, "%s():%s\n", "signal", strerror(errno));
286 return -1;
287 }
288
289 while(running_) {
290 dcpu16_step(vm);
291 if (opt_.verbose)
292 dcpu16_state_print(vm);
293 }
294
295 /* restore the old interrupt signal handler */
296 if (signal(SIGINT, osig) == SIG_ERR) {
297 fprintf(stderr, "%s():%s\n", "sigaction", strerror(errno));
298 return -1;
299 }
300
301 VERBOSE_PRINTF("interrupted...\n");
302
303 return 0;
304 }
305 COMMAND_HELP(run) {
306 fprintf(f, "run\n");
307 if (summary) return;
308
309 fprintf(f, "\tBegins executing continuously.\n");
310 }
311
312 /* gather all these together into a searchable table */
313 /* help command gets some assistance in declarations */
314 COMMAND_IMPL(help);
315 COMMAND_HELP(help);
316
317 static struct command_ command_table_[] = {
318 COMMAND_ENTRY(help, 0, 1),
319 COMMAND_ENTRY(quit, 0, -1),
320 COMMAND_ENTRY(load, 1, 2),
321 COMMAND_ENTRY(dump, 0, 2),
322 COMMAND_ENTRY(disassemble, 0, 2),
323 COMMAND_ENTRY(step, 0, 1),
324 COMMAND_ENTRY(run, 0, 0),
325 { NULL, 0, 0, NULL, NULL }
326 };
327
328 COMMAND_IMPL(help) {
329 struct command_ *c;
330 (void)vm;
331
332 if (token_count) {
333 while (token_count) {
334 for (c = command_table_; c->func; c++) {
335 if (strcasecmp(*token_vector, c->name) == 0) {
336 if (c->help)
337 c->help(stdout, 0);
338 break;
339 }
340 }
341 token_count--;
342 token_vector++;
343 }
344 return 0;
345 }
346
347 for (c = command_table_; c->func; c++) {
348 if (c->help)
349 c->help(stdout, 1);
350 }
351 return 0;
352 }
353 COMMAND_HELP(help) {
354 if (summary) {
355 fprintf(f, "help [command]\n");
356 return;
357 }
358
359 fprintf(f, "Usage: help [command]\n"
360 "\tDisplays a list of available commands, or help on a specific command.\n");
361 }
362
363 int main(int argc, char **argv) {
364 int c;
365 char *line, *line_prev;
366 struct dcpu16 *vm;
367 char prompt[32];
368 const char prompt_fmt[] = "PC:%04x> ";
369
370 while ( (c = getopt(argc, argv, "hv")) != EOF) {
371 switch (c) {
372 case 'v':
373 opt_.verbose++;
374 break;
375
376 case 'h':
377 usage_(argv[0], 1);
378 exit(EX_OK);
379
380 default:
381 usage_(argv[0], 0);
382 exit(EX_USAGE);
383 }
384 }
385 if (opt_.verbose < 1) {
386 dcpu16_warn_cb_set(NULL);
387 dcpu16_trace_cb_set(NULL);
388 } else if (opt_.verbose < 2) {
389 dcpu16_trace_cb_set(NULL);
390 }
391 argc -= optind;
392 argv += optind;
393
394 if ((vm = dcpu16_new()) == NULL) {
395 fprintf(stderr, "could not allocate new dcpu instance\n");
396 exit(EX_UNAVAILABLE);
397 }
398
399 if (argc) {
400 file_load_(vm, *argv, 0);
401 }
402
403 /* show state, read commands */
404 for (line_prev = NULL,
405 snprintf(prompt, sizeof prompt, prompt_fmt, vm->pc),
406 dcpu16_state_print(vm);
407
408 (line = readline(prompt));
409
410 printf("\n"),
411 snprintf(prompt, sizeof prompt, prompt_fmt, vm->pc),
412 dcpu16_state_print(vm)) {
413 const char whitespace[] = " \t";
414 char *rest, *line_start, *command;
415 struct command_ *c;
416 int token_count;
417 char *token_vector[] = { NULL, NULL };
418 int r = 0;
419
420 /* skip whitespaces */
421 line_start = line + strspn(line, whitespace);
422
423 if (*line_start) {
424 /* a new command, it will be the prior command now */
425 free(line_prev);
426 line_prev = line;
427 } else {
428 /* empty command, read another line if there's no prior command to repeat */
429 if (line_prev == NULL || *line_prev == '\0') {
430 continue;
431 }
432
433 /* otherwise discard new line and repeat prior */
434 free(line);
435 line_start = line_prev + strspn(line, whitespace);
436 VERBOSE_PRINTF("repeating previous command '%s'\n", line_start);
437 }
438
439 /* first word */
440 command = strtok_r(line_start, whitespace, &rest);
441
442 /* look up command */
443 /* FIXME: tokenize 'rest' into proper argv */
444 token_count = 0;
445 if (rest)
446 token_count++, token_vector[0] = rest;
447 for (c = command_table_; c->name; c++) {
448 if (strcasecmp(command, c->name) == 0) {
449 if (c->args_min > token_count) {
450 fprintf(stderr, "%s: not enough arguments\n", c->name);
451 c->help(stderr, 1);
452 break;
453 }
454
455 if (c->args_max > 0
456 && token_count > c->args_max) {
457 fprintf(stderr, "%s: too many arguments\n", c->name);
458 c->help(stderr, 1);
459 break;
460 }
461
462 r = c->func(vm, token_count, token_vector);
463 break;
464 }
465 }
466 if (r)
467 break;
468
469 if (!c->func)
470 fprintf(stderr, "didn't recognize '%s'\n", command);
471 }
472
473 printf("\nfinished\n");
474
475 dcpu16_delete(&vm);
476
477 exit(EX_OK);
478 }