Added final tests. All VVhitespace commands now have a basic test.
[vvhitespace] / vv_interpreter.c
CommitLineData
1adfc4f4
AT
1/*
2 * (c) 2019 Aaron Taylor <ataylor at subgeniuskitty dot com>
3 * All rights reserved.
4 */
5
6#include <stdio.h>
7#include <stdlib.h>
8#include <unistd.h>
9#include <string.h>
10#include <errno.h>
11#include <stdint.h>
12#include <sys/select.h>
13#include <getopt.h>
a2664738 14#include <termios.h>
1adfc4f4
AT
15
16#define VERSION 1
17
18#define STACKSIZE 1024
19#define HEAPSIZE 1024
20#define RETURNSTACKSIZE 1024
21
22void
23print_usage(char ** argv)
24{
bf43fa3f 25 printf( "VVhitespace Interpreter v%d (www.subgeniuskitty.com)\n"
1adfc4f4
AT
26 "Usage: %s -i <file>\n"
27 " -h Help (prints this message)\n"
bf43fa3f 28 " -i <file> Specify a VVhitespace source file to interpret.\n"
1adfc4f4
AT
29 , VERSION, argv[0]
30 );
31}
32
33int
34stdin_empty(void)
35{
36 fd_set read_fds;
37 FD_ZERO(&read_fds);
38 FD_SET(STDIN_FILENO, &read_fds);
39
40 struct timeval timeout;
41 timeout.tv_sec = 0;
42 timeout.tv_usec = 0;
43
44 int retval = select(1, &read_fds, NULL, NULL, &timeout);
45 /* retval could be -1. Ignoring that for now. */
46 if (retval > 0) return 0;
47 return 1;
48}
49
a2664738
AT
50void
51set_terminal_mode(void)
52{
53 struct termios options;
54 tcgetattr(STDIN_FILENO, &options);
55 /* Create a cbreak-like environment through the following options. */
56 options.c_lflag &= ~ECHO; /* Disable echoing of input characters. */
57 options.c_lflag &= ~ICANON; /* Disable cooked/line-oriented mode. */
58 options.c_cc[VMIN] = 1;
59 options.c_cc[VTIME] = 0;
60 tcsetattr(STDIN_FILENO, TCSANOW, &options);
61}
62
63void
64unset_terminal_mode(void)
65{
66 struct termios options;
67 tcgetattr(STDIN_FILENO, &options);
68 /* Undo the changes made in set_terminal_mode(). */
69 options.c_lflag |= ECHO; /* Enable echoing of input characters. */
70 options.c_lflag |= ICANON; /* Enable cooked/line-oriented mode. */
71 options.c_cc[VMIN] = 1; /* Default value from /usr/src/sys/sys/ttydefaults.h */
72 options.c_cc[VTIME] = 0; /* Default value from /usr/src/sys/sys/ttydefaults.h */
73 tcsetattr(STDIN_FILENO, TCSANOW, &options);
74}
75
1adfc4f4
AT
76void
77ws_die(size_t * pc, char * msg)
78{
1adfc4f4
AT
79 printf("SIM_ERROR @ PC %lu: %s\n", *pc, msg);
80 fflush(stdout);
a2664738 81 unset_terminal_mode();
1adfc4f4
AT
82 exit(EXIT_FAILURE);
83}
84
85void
86stack_push(int32_t ** sp, int32_t word)
87{
88 *((*sp)++) = word;
89}
90
91int32_t
92stack_pop(int32_t ** sp)
93{
94 return *(--(*sp));
95}
96
97int32_t
98stack_peek(int32_t ** sp, size_t offset)
99/* offset=0 peeks TOS, offset=1 peeks NOS, etc. */
100{
101 return *((*sp)-offset-1);
102}
103
104uint8_t
105next_code_byte(uint8_t * code, size_t * pc)
106{
107 return code[(*pc)++];
108}
109
9686c901
AT
110/*
111 * In addition to returning the parsed label, this function advances the PC to
112 * the next instruction.
113 */
1adfc4f4
AT
114uint16_t
115parse_label(uint8_t * code, size_t * pc)
116{
117 uint16_t label = 0;
118 uint8_t c;
119 while ((c = code[(*pc)++]) != '\n') {
120 label = label << 1;
121 if (c == ' ') label++;
122 }
9686c901
AT
123 // TODO: Where should I handle attempts to access an unitialized label?
124 // For now, leave it undefined in a nasal demon sense.
1adfc4f4
AT
125 return label;
126}
127
bf43fa3f
AT
128void
129populate_labels(uint32_t * labels, uint8_t * code, size_t code_size)
130{
9686c901
AT
131 size_t cp = 0;
132 while (cp <= code_size) {
133 if (code[cp] == '\v') {
134 uint16_t temp_label = parse_label(code, &cp);
135 labels[temp_label] = cp;
136 }
137 cp++;
138 }
bf43fa3f
AT
139}
140
1adfc4f4
AT
141void
142process_imp_stack(uint8_t * code, size_t * pc, int32_t ** sp)
143{
144 switch (next_code_byte(code,pc)) {
145 case ' ':
146 /* Push number onto TOS. */
147 {
148 /* First, pick off the sign */
149 int32_t sign = 0;
150 switch (next_code_byte(code,pc)) {
151 case ' ' : sign = 1; break;
152 case '\t': sign = -1; break;
bf43fa3f 153 default : ws_die(pc, "expected sign"); break;
1adfc4f4
AT
154 }
155
156 /* Now, construct the number and push to TOS. */
157 /* I'm assuming the numbers are read MSb first. */
158 int32_t temp, number = 0;
159 while ((temp = next_code_byte(code,pc)) != '\n') {
bf43fa3f 160 if (temp == '\v') ws_die(pc, "non-binary digit in number");
1adfc4f4
AT
161 number <<= 1;
162 if (temp == '\t') number++;
163 }
164 stack_push(sp, number*sign);
165 }
166 break;
167 case '\n':
168 /* Stack sub-command */
169 {
170 switch (next_code_byte(code,pc)) {
171 /* Duplicate the TOS. */
172 case ' ':
173 stack_push(sp, stack_peek(sp,0));
174 break;
175 /* Swap TOS and NOS. */
176 case '\t':
177 {
178 int32_t t1 = stack_pop(sp);
179 int32_t t2 = stack_pop(sp);
180 stack_push(sp, t1);
181 stack_push(sp, t2);
182 }
183 break;
184 /* Discard TOS. */
185 case '\n':
186 stack_pop(sp);
187 break;
bf43fa3f
AT
188 default:
189 ws_die(pc, "malformed stack IMP");
190 break;
1adfc4f4
AT
191 }
192 }
193 break;
bf43fa3f 194 default: ws_die(pc, "malformed stack IMP"); break;
1adfc4f4
AT
195 }
196}
197
198void
199process_imp_arithmetic(uint8_t * code, size_t * pc, int32_t ** sp)
200{
201 int32_t temp;
202 switch (next_code_byte(code,pc)) {
203 case ' ':
204 {
205 switch (next_code_byte(code,pc)) {
206 case ' ':
207 /* Addition */
208 stack_push(sp, stack_pop(sp)+stack_pop(sp));
209 break;
210 case '\t':
211 /* Subtraction */
212 temp = stack_pop(sp);
213 stack_push(sp, stack_pop(sp)-temp);
214 break;
215 case '\n':
216 /* Multiplication */
217 stack_push(sp, stack_pop(sp)*stack_pop(sp));
218 break;
bf43fa3f
AT
219 default:
220 ws_die(pc, "malformed arithmetic IMP");
221 break;
1adfc4f4
AT
222 }
223 }
224 break;
225 case '\t':
226 {
227 switch (next_code_byte(code,pc)) {
228 case ' ':
229 /* Division */
230 temp = stack_pop(sp);
231 stack_push(sp, stack_pop(sp)/temp);
232 break;
233 case '\t':
234 /* Modulo */
235 temp = stack_pop(sp);
236 stack_push(sp, stack_pop(sp)%temp);
237 break;
bf43fa3f 238 default: ws_die(pc, "malformed arithmetic IMP"); break;
1adfc4f4
AT
239 }
240 }
241 break;
bf43fa3f 242 default: ws_die(pc, "malformed arithmetic IMP"); break;
1adfc4f4
AT
243 }
244}
245
246void
247process_imp_flowcontrol(uint8_t * code, size_t * pc, int32_t ** sp, uint32_t * labels,
248 uint32_t ** rsp)
249{
250 switch (next_code_byte(code,pc)) {
251 case '\n':
252 /* Technically another LF is required but we ignore it. */
1adfc4f4 253 fflush(stdout);
a2664738 254 unset_terminal_mode();
1adfc4f4
AT
255 exit(EXIT_SUCCESS);
256 case ' ':
257 {
258 switch (next_code_byte(code,pc)) {
259 case ' ':
260 /* Mark a location in the program. */
9686c901
AT
261 if (next_code_byte(code,pc) != '\v') ws_die(pc,"expected vtab, "
262 "perhaps a whitespace program, rather than vvhitespace?");
263 /* Jump to next instruction since labels were parsed during startup. */
1e574f5b 264 parse_label(code, pc);
1adfc4f4
AT
265 break;
266 case '\t':
267 /* Call a subroutine. */
519b6ab4
AT
268 {
269 size_t temp_pc = labels[parse_label(code, pc)];
270 *((*rsp)++) = *pc;
271 *pc = temp_pc;
272 }
1adfc4f4
AT
273 break;
274 case '\n':
275 /* Jump unconditionally to a label. */
276 *pc = labels[parse_label(code, pc)];
277 break;
bf43fa3f
AT
278 default:
279 ws_die(pc, "malformed flow control IMP");
280 break;
1adfc4f4
AT
281 }
282 }
283 break;
284 case '\t':
285 {
286 switch (next_code_byte(code,pc)) {
287 case ' ':
288 /* Jump to a label if TOS == 0 */
519b6ab4 289 /* TODO: Does WS pop or peek the TOS? */
1adfc4f4
AT
290 if (stack_peek(sp,0) == 0) *pc = labels[parse_label(code, pc)];
291 break;
292 case '\t':
293 /* Jump to a label if TOS < 0. */
519b6ab4 294 /* TODO: Does WS pop or peek the TOS? */
1adfc4f4
AT
295 if (stack_peek(sp,0) < 0) *pc = labels[parse_label(code, pc)];
296 break;
297 case '\n':
298 /* Return from subroutine. */
299 *pc = *(--(*rsp));
300 break;
bf43fa3f
AT
301 default:
302 ws_die(pc, "malformed flow control IMP");
303 break;
1adfc4f4
AT
304 }
305 }
306 break;
bf43fa3f
AT
307 default:
308 ws_die(pc, "malformed flow control IMP");
309 break;
1adfc4f4
AT
310 }
311}
312
313void
314process_imp_heap(uint8_t * code, size_t * pc, int32_t ** sp, int32_t ** hp)
315{
316 switch (next_code_byte(code,pc)) {
aea85838
AT
317 case ' ' :
318 /* Store to heap */
319 {
320 int32_t value = stack_pop(sp);
321 int32_t addr = stack_pop(sp);
322 *(*hp + addr) = value;
323 }
324 break;
325 case '\t':
326 /* Retrieve from heap */
327 stack_push(sp, *(*hp + stack_pop(sp)));
328 break;
329 default:
330 ws_die(pc, "malformed heap IMP");
331 break;
1adfc4f4
AT
332 }
333}
334
335void
336process_imp_io(uint8_t * code, size_t * pc, int32_t ** sp, int32_t ** hp)
337{
338 switch (next_code_byte(code,pc)) {
339 case ' ':
340 /* Output */
341 {
342 switch (next_code_byte(code,pc)) {
1e574f5b
AT
343 case ' ' : /* Output char from TOS */ printf("%c", stack_pop(sp)); break;
344 case '\t': /* Output digit from TOS */ printf("%c", stack_pop(sp)+'0'); break;
9686c901 345 default : ws_die(pc, "malformed output IMP"); break;
1adfc4f4
AT
346 }
347 fflush(stdout);
348 }
349 break;
350 case '\t':
351 /* Input */
352 {
353 while (stdin_empty()) continue;
354 char c = getchar();
355 switch (next_code_byte(code,pc)) {
356 case '\t': /* Input digit */ c -= '0'; /* fallthrough */
a2664738 357 case ' ' : /* Input character */ *(*hp + *(--(*sp))) = c; break;
9686c901 358 default : ws_die(pc, "malformed input IMP"); break;
1adfc4f4
AT
359 }
360 }
361 break;
bf43fa3f 362 default: ws_die(pc, "malformed i/o IMP"); break;
1adfc4f4
AT
363 }
364}
365
1adfc4f4
AT
366int
367main(int argc, char ** argv)
368{
369 /*
370 * Process command line arguments
371 */
372 int c;
373 FILE * input = NULL;
374 while ((c = getopt(argc,argv,"i:h")) != -1) {
375 switch (c) {
376 case 'i':
377 if ((input = fopen(optarg, "r")) == NULL) {
378 fprintf(stderr, "ERROR: %s: %s\n", optarg, strerror(errno));
379 }
380 break;
381 case 'h':
382 print_usage(argv);
383 exit(EXIT_SUCCESS);
384 break;
385 default:
386 break;
387 }
388 }
389 if (input == NULL) {
bf43fa3f 390 fprintf(stderr, "ERROR: Must specify a VVhitespace source file with -f flag.\n");
1adfc4f4
AT
391 print_usage(argv);
392 exit(EXIT_FAILURE);
393 }
394
395 /*
bf43fa3f 396 * Read just the VVhitespace source code into memory.
1adfc4f4
AT
397 * We will use the array indices as addresses for the virtual PC when jumping to labels.
398 */
399 size_t ws_code_size = 0;
400 uint8_t temp_byte;
401 while (fread(&temp_byte, 1, 1, input)) {
bf43fa3f
AT
402 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n' || temp_byte == '\v') {
403 ws_code_size++;
404 }
1adfc4f4
AT
405 }
406 rewind(input);
407 uint8_t * ws_code_space = malloc(ws_code_size);
408 ws_code_size = 0;
409 while (fread(&temp_byte, 1, 1, input)) {
bf43fa3f
AT
410 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n' || temp_byte == '\v') {
411 ws_code_space[ws_code_size++] = temp_byte;
412 }
1adfc4f4
AT
413 }
414 fclose(input);
415
416 /*
417 * Setup a stack and heap.
418 * Assume a 32-bit word size.
419 */
bf43fa3f 420 // TODO: Make everything 64-bit.
1adfc4f4
AT
421 int32_t * hp = malloc(HEAPSIZE*4);
422 int32_t * sp = malloc(STACKSIZE*4);
423
424 /*
425 * Setup the return stack and the label array.
426 */
427 uint32_t * rsp = malloc(RETURNSTACKSIZE*4);
bf43fa3f
AT
428 uint32_t labels[65536] = {0};
429 populate_labels(labels, ws_code_space, ws_code_size);
1adfc4f4
AT
430
431 /*
432 * Main Loop
433 */
a2664738 434 set_terminal_mode();
1adfc4f4
AT
435 size_t pc = 0; /* Virtual program counter. Operates in the ws_code_space[] address space. */
436 while (1) {
437 if (pc >= ws_code_size) {
bf43fa3f
AT
438 fprintf(stderr, "SIM_ERROR: PC Overrun\n Requested PC: %lu\n Max Address: %lu\n",
439 pc, ws_code_size-1);
1adfc4f4 440 exit(EXIT_FAILURE);
a2664738 441 unset_terminal_mode();
1adfc4f4 442 }
9686c901
AT
443// TODO: Have the SIGTERM signal handler and normal term point return the value
444// on TOS so I can do rudimentary automated tests.
1adfc4f4
AT
445
446 /* Decode the IMPs */
447 switch (ws_code_space[pc++]) {
448 case ' ':
449 /* Stack Manipulation */
450 process_imp_stack(ws_code_space, &pc, &sp);
451 break;
452 case '\n':
453 /* Flow Control */
454 process_imp_flowcontrol(ws_code_space, &pc, &sp, labels, &rsp);
455 break;
456 case '\t':
457 /* Arithmetic, Heap Access, or I/O */
458 {
459 switch (ws_code_space[pc++]) {
460 case ' ':
461 /* Arithmetic */
462 process_imp_arithmetic(ws_code_space, &pc, &sp);
463 break;
464 case '\t':
465 /* Heap Access */
466 process_imp_heap(ws_code_space, &pc, &sp, &hp);
467 break;
468 case '\n':
469 /* I/O */
470 process_imp_io(ws_code_space, &pc, &sp, &hp);
471 break;
472 }
473 }
474 break;
9686c901 475 default: ws_die(&pc, "unexpected VTab"); break;
1adfc4f4
AT
476 }
477 }
478
479 printf("\n");
480 printf("Program executed.\n");
481
482 exit(EXIT_SUCCESS);
a2664738 483 unset_terminal_mode();
1adfc4f4 484}