Changed jump-if-tos-is-zero and jump-if-tos-is-negative to consume the 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 */
140262a9 289 if (stack_pop(sp) == 0) *pc = labels[parse_label(code, pc)];
1adfc4f4
AT
290 break;
291 case '\t':
292 /* Jump to a label if TOS < 0. */
140262a9 293 if (stack_pop(sp) < 0) *pc = labels[parse_label(code, pc)];
1adfc4f4
AT
294 break;
295 case '\n':
296 /* Return from subroutine. */
297 *pc = *(--(*rsp));
298 break;
bf43fa3f
AT
299 default:
300 ws_die(pc, "malformed flow control IMP");
301 break;
1adfc4f4
AT
302 }
303 }
304 break;
bf43fa3f
AT
305 default:
306 ws_die(pc, "malformed flow control IMP");
307 break;
1adfc4f4
AT
308 }
309}
310
311void
312process_imp_heap(uint8_t * code, size_t * pc, int32_t ** sp, int32_t ** hp)
313{
314 switch (next_code_byte(code,pc)) {
aea85838
AT
315 case ' ' :
316 /* Store to heap */
317 {
318 int32_t value = stack_pop(sp);
319 int32_t addr = stack_pop(sp);
320 *(*hp + addr) = value;
321 }
322 break;
323 case '\t':
324 /* Retrieve from heap */
325 stack_push(sp, *(*hp + stack_pop(sp)));
326 break;
327 default:
328 ws_die(pc, "malformed heap IMP");
329 break;
1adfc4f4
AT
330 }
331}
332
333void
334process_imp_io(uint8_t * code, size_t * pc, int32_t ** sp, int32_t ** hp)
335{
336 switch (next_code_byte(code,pc)) {
337 case ' ':
338 /* Output */
339 {
340 switch (next_code_byte(code,pc)) {
1e574f5b
AT
341 case ' ' : /* Output char from TOS */ printf("%c", stack_pop(sp)); break;
342 case '\t': /* Output digit from TOS */ printf("%c", stack_pop(sp)+'0'); break;
9686c901 343 default : ws_die(pc, "malformed output IMP"); break;
1adfc4f4
AT
344 }
345 fflush(stdout);
346 }
347 break;
348 case '\t':
349 /* Input */
350 {
351 while (stdin_empty()) continue;
352 char c = getchar();
353 switch (next_code_byte(code,pc)) {
354 case '\t': /* Input digit */ c -= '0'; /* fallthrough */
a2664738 355 case ' ' : /* Input character */ *(*hp + *(--(*sp))) = c; break;
9686c901 356 default : ws_die(pc, "malformed input IMP"); break;
1adfc4f4
AT
357 }
358 }
359 break;
bf43fa3f 360 default: ws_die(pc, "malformed i/o IMP"); break;
1adfc4f4
AT
361 }
362}
363
1adfc4f4
AT
364int
365main(int argc, char ** argv)
366{
367 /*
368 * Process command line arguments
369 */
370 int c;
371 FILE * input = NULL;
372 while ((c = getopt(argc,argv,"i:h")) != -1) {
373 switch (c) {
374 case 'i':
375 if ((input = fopen(optarg, "r")) == NULL) {
376 fprintf(stderr, "ERROR: %s: %s\n", optarg, strerror(errno));
377 }
378 break;
379 case 'h':
380 print_usage(argv);
381 exit(EXIT_SUCCESS);
382 break;
383 default:
384 break;
385 }
386 }
387 if (input == NULL) {
bf43fa3f 388 fprintf(stderr, "ERROR: Must specify a VVhitespace source file with -f flag.\n");
1adfc4f4
AT
389 print_usage(argv);
390 exit(EXIT_FAILURE);
391 }
392
393 /*
bf43fa3f 394 * Read just the VVhitespace source code into memory.
1adfc4f4
AT
395 * We will use the array indices as addresses for the virtual PC when jumping to labels.
396 */
397 size_t ws_code_size = 0;
398 uint8_t temp_byte;
399 while (fread(&temp_byte, 1, 1, input)) {
bf43fa3f
AT
400 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n' || temp_byte == '\v') {
401 ws_code_size++;
402 }
1adfc4f4
AT
403 }
404 rewind(input);
405 uint8_t * ws_code_space = malloc(ws_code_size);
406 ws_code_size = 0;
407 while (fread(&temp_byte, 1, 1, input)) {
bf43fa3f
AT
408 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n' || temp_byte == '\v') {
409 ws_code_space[ws_code_size++] = temp_byte;
410 }
1adfc4f4
AT
411 }
412 fclose(input);
413
414 /*
415 * Setup a stack and heap.
416 * Assume a 32-bit word size.
417 */
bf43fa3f 418 // TODO: Make everything 64-bit.
1adfc4f4
AT
419 int32_t * hp = malloc(HEAPSIZE*4);
420 int32_t * sp = malloc(STACKSIZE*4);
421
422 /*
423 * Setup the return stack and the label array.
424 */
425 uint32_t * rsp = malloc(RETURNSTACKSIZE*4);
bf43fa3f
AT
426 uint32_t labels[65536] = {0};
427 populate_labels(labels, ws_code_space, ws_code_size);
1adfc4f4
AT
428
429 /*
430 * Main Loop
431 */
a2664738 432 set_terminal_mode();
1adfc4f4
AT
433 size_t pc = 0; /* Virtual program counter. Operates in the ws_code_space[] address space. */
434 while (1) {
435 if (pc >= ws_code_size) {
bf43fa3f
AT
436 fprintf(stderr, "SIM_ERROR: PC Overrun\n Requested PC: %lu\n Max Address: %lu\n",
437 pc, ws_code_size-1);
1adfc4f4 438 exit(EXIT_FAILURE);
a2664738 439 unset_terminal_mode();
1adfc4f4
AT
440 }
441
442 /* Decode the IMPs */
443 switch (ws_code_space[pc++]) {
444 case ' ':
445 /* Stack Manipulation */
446 process_imp_stack(ws_code_space, &pc, &sp);
447 break;
448 case '\n':
449 /* Flow Control */
450 process_imp_flowcontrol(ws_code_space, &pc, &sp, labels, &rsp);
451 break;
452 case '\t':
453 /* Arithmetic, Heap Access, or I/O */
454 {
455 switch (ws_code_space[pc++]) {
456 case ' ':
457 /* Arithmetic */
458 process_imp_arithmetic(ws_code_space, &pc, &sp);
459 break;
460 case '\t':
461 /* Heap Access */
462 process_imp_heap(ws_code_space, &pc, &sp, &hp);
463 break;
464 case '\n':
465 /* I/O */
466 process_imp_io(ws_code_space, &pc, &sp, &hp);
467 break;
468 }
469 }
470 break;
9686c901 471 default: ws_die(&pc, "unexpected VTab"); break;
1adfc4f4
AT
472 }
473 }
474
475 printf("\n");
476 printf("Program executed.\n");
477
478 exit(EXIT_SUCCESS);
a2664738 479 unset_terminal_mode();
1adfc4f4 480}