Updated VVS interpreter to 64 bit word. Also misc cleanup.
[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
eea0db3f
AT
18#define HEAPSIZE 1024 /* Size of heap in words */
19#define DATASTACKSIZE 1024 /* Size of stack in words */
20#define RETURNSTACKSIZE 1024 /* Max subroutine call depth */
1adfc4f4
AT
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
eea0db3f 86stack_push(int64_t ** sp, int64_t word)
1adfc4f4
AT
87{
88 *((*sp)++) = word;
89}
90
eea0db3f
AT
91int64_t
92stack_pop(int64_t ** sp)
1adfc4f4
AT
93{
94 return *(--(*sp));
95}
96
eea0db3f
AT
97int64_t
98stack_peek(int64_t ** sp, size_t offset)
1adfc4f4
AT
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 128void
eea0db3f 129populate_labels(size_t * labels, uint8_t * code, size_t code_size)
bf43fa3f 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 141void
eea0db3f 142process_imp_stack(uint8_t * code, size_t * pc, int64_t ** sp)
1adfc4f4
AT
143{
144 switch (next_code_byte(code,pc)) {
145 case ' ':
146 /* Push number onto TOS. */
147 {
148 /* First, pick off the sign */
eea0db3f 149 int64_t sign = 0;
1adfc4f4
AT
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. */
eea0db3f
AT
158 int64_t number = 0;
159 uint8_t temp;
1adfc4f4 160 while ((temp = next_code_byte(code,pc)) != '\n') {
bf43fa3f 161 if (temp == '\v') ws_die(pc, "non-binary digit in number");
1adfc4f4
AT
162 number <<= 1;
163 if (temp == '\t') number++;
164 }
165 stack_push(sp, number*sign);
166 }
167 break;
168 case '\n':
169 /* Stack sub-command */
170 {
171 switch (next_code_byte(code,pc)) {
172 /* Duplicate the TOS. */
173 case ' ':
174 stack_push(sp, stack_peek(sp,0));
175 break;
176 /* Swap TOS and NOS. */
177 case '\t':
178 {
eea0db3f
AT
179 int64_t t1 = stack_pop(sp);
180 int64_t t2 = stack_pop(sp);
1adfc4f4
AT
181 stack_push(sp, t1);
182 stack_push(sp, t2);
183 }
184 break;
185 /* Discard TOS. */
186 case '\n':
187 stack_pop(sp);
188 break;
bf43fa3f
AT
189 default:
190 ws_die(pc, "malformed stack IMP");
191 break;
1adfc4f4
AT
192 }
193 }
194 break;
bf43fa3f 195 default: ws_die(pc, "malformed stack IMP"); break;
1adfc4f4
AT
196 }
197}
198
199void
eea0db3f 200process_imp_arithmetic(uint8_t * code, size_t * pc, int64_t ** sp)
1adfc4f4 201{
eea0db3f 202 int64_t temp;
1adfc4f4
AT
203 switch (next_code_byte(code,pc)) {
204 case ' ':
205 {
206 switch (next_code_byte(code,pc)) {
207 case ' ':
208 /* Addition */
209 stack_push(sp, stack_pop(sp)+stack_pop(sp));
210 break;
211 case '\t':
212 /* Subtraction */
213 temp = stack_pop(sp);
214 stack_push(sp, stack_pop(sp)-temp);
215 break;
216 case '\n':
217 /* Multiplication */
218 stack_push(sp, stack_pop(sp)*stack_pop(sp));
219 break;
bf43fa3f
AT
220 default:
221 ws_die(pc, "malformed arithmetic IMP");
222 break;
1adfc4f4
AT
223 }
224 }
225 break;
226 case '\t':
227 {
228 switch (next_code_byte(code,pc)) {
229 case ' ':
230 /* Division */
231 temp = stack_pop(sp);
232 stack_push(sp, stack_pop(sp)/temp);
233 break;
234 case '\t':
235 /* Modulo */
236 temp = stack_pop(sp);
237 stack_push(sp, stack_pop(sp)%temp);
238 break;
bf43fa3f 239 default: ws_die(pc, "malformed arithmetic IMP"); break;
1adfc4f4
AT
240 }
241 }
242 break;
bf43fa3f 243 default: ws_die(pc, "malformed arithmetic IMP"); break;
1adfc4f4
AT
244 }
245}
246
247void
eea0db3f
AT
248process_imp_flowcontrol(uint8_t * code, size_t * pc, int64_t ** sp, size_t * labels,
249 size_t ** rsp)
1adfc4f4
AT
250{
251 switch (next_code_byte(code,pc)) {
252 case '\n':
253 /* Technically another LF is required but we ignore it. */
1adfc4f4 254 fflush(stdout);
a2664738 255 unset_terminal_mode();
1adfc4f4
AT
256 exit(EXIT_SUCCESS);
257 case ' ':
258 {
259 switch (next_code_byte(code,pc)) {
260 case ' ':
261 /* Mark a location in the program. */
9686c901
AT
262 if (next_code_byte(code,pc) != '\v') ws_die(pc,"expected vtab, "
263 "perhaps a whitespace program, rather than vvhitespace?");
264 /* Jump to next instruction since labels were parsed during startup. */
1e574f5b 265 parse_label(code, pc);
1adfc4f4
AT
266 break;
267 case '\t':
268 /* Call a subroutine. */
519b6ab4
AT
269 {
270 size_t temp_pc = labels[parse_label(code, pc)];
271 *((*rsp)++) = *pc;
272 *pc = temp_pc;
273 }
1adfc4f4
AT
274 break;
275 case '\n':
276 /* Jump unconditionally to a label. */
277 *pc = labels[parse_label(code, pc)];
278 break;
bf43fa3f
AT
279 default:
280 ws_die(pc, "malformed flow control IMP");
281 break;
1adfc4f4
AT
282 }
283 }
284 break;
285 case '\t':
286 {
287 switch (next_code_byte(code,pc)) {
288 case ' ':
289 /* Jump to a label if TOS == 0 */
140262a9 290 if (stack_pop(sp) == 0) *pc = labels[parse_label(code, pc)];
1adfc4f4
AT
291 break;
292 case '\t':
293 /* Jump to a label if TOS < 0. */
140262a9 294 if (stack_pop(sp) < 0) *pc = labels[parse_label(code, pc)];
1adfc4f4
AT
295 break;
296 case '\n':
297 /* Return from subroutine. */
298 *pc = *(--(*rsp));
299 break;
bf43fa3f
AT
300 default:
301 ws_die(pc, "malformed flow control IMP");
302 break;
1adfc4f4
AT
303 }
304 }
305 break;
bf43fa3f
AT
306 default:
307 ws_die(pc, "malformed flow control IMP");
308 break;
1adfc4f4
AT
309 }
310}
311
312void
eea0db3f 313process_imp_heap(uint8_t * code, size_t * pc, int64_t ** sp, int64_t ** hp)
1adfc4f4
AT
314{
315 switch (next_code_byte(code,pc)) {
aea85838
AT
316 case ' ' :
317 /* Store to heap */
318 {
eea0db3f
AT
319 int64_t value = stack_pop(sp);
320 int64_t addr = stack_pop(sp);
aea85838
AT
321 *(*hp + addr) = value;
322 }
323 break;
324 case '\t':
325 /* Retrieve from heap */
326 stack_push(sp, *(*hp + stack_pop(sp)));
327 break;
328 default:
329 ws_die(pc, "malformed heap IMP");
330 break;
1adfc4f4
AT
331 }
332}
333
334void
eea0db3f 335process_imp_io(uint8_t * code, size_t * pc, int64_t ** sp, int64_t ** hp)
1adfc4f4
AT
336{
337 switch (next_code_byte(code,pc)) {
338 case ' ':
339 /* Output */
340 {
341 switch (next_code_byte(code,pc)) {
eea0db3f
AT
342 case ' ' : /* Output char from TOS */ printf("%c", (uint8_t) stack_pop(sp)); break;
343 case '\t': /* Output digit from TOS */ printf("%c", (uint8_t) stack_pop(sp)+'0'); break;
9686c901 344 default : ws_die(pc, "malformed output IMP"); break;
1adfc4f4
AT
345 }
346 fflush(stdout);
347 }
348 break;
349 case '\t':
350 /* Input */
351 {
352 while (stdin_empty()) continue;
353 char c = getchar();
354 switch (next_code_byte(code,pc)) {
355 case '\t': /* Input digit */ c -= '0'; /* fallthrough */
a2664738 356 case ' ' : /* Input character */ *(*hp + *(--(*sp))) = c; break;
9686c901 357 default : ws_die(pc, "malformed input IMP"); break;
1adfc4f4
AT
358 }
359 }
360 break;
bf43fa3f 361 default: ws_die(pc, "malformed i/o IMP"); break;
1adfc4f4
AT
362 }
363}
364
1adfc4f4
AT
365int
366main(int argc, char ** argv)
367{
368 /*
369 * Process command line arguments
370 */
371 int c;
372 FILE * input = NULL;
373 while ((c = getopt(argc,argv,"i:h")) != -1) {
374 switch (c) {
375 case 'i':
376 if ((input = fopen(optarg, "r")) == NULL) {
377 fprintf(stderr, "ERROR: %s: %s\n", optarg, strerror(errno));
378 }
379 break;
380 case 'h':
381 print_usage(argv);
382 exit(EXIT_SUCCESS);
383 break;
384 default:
385 break;
386 }
387 }
388 if (input == NULL) {
bf43fa3f 389 fprintf(stderr, "ERROR: Must specify a VVhitespace source file with -f flag.\n");
1adfc4f4
AT
390 print_usage(argv);
391 exit(EXIT_FAILURE);
392 }
393
394 /*
eea0db3f 395 * Read just the VVhitespace source code into memory, stripping comment characters.
1adfc4f4
AT
396 * We will use the array indices as addresses for the virtual PC when jumping to labels.
397 */
398 size_t ws_code_size = 0;
399 uint8_t temp_byte;
400 while (fread(&temp_byte, 1, 1, input)) {
bf43fa3f
AT
401 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n' || temp_byte == '\v') {
402 ws_code_size++;
403 }
1adfc4f4
AT
404 }
405 rewind(input);
406 uint8_t * ws_code_space = malloc(ws_code_size);
407 ws_code_size = 0;
408 while (fread(&temp_byte, 1, 1, input)) {
bf43fa3f
AT
409 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n' || temp_byte == '\v') {
410 ws_code_space[ws_code_size++] = temp_byte;
411 }
1adfc4f4
AT
412 }
413 fclose(input);
414
415 /*
416 * Setup a stack and heap.
1adfc4f4 417 */
eea0db3f
AT
418 int64_t * hp = malloc(HEAPSIZE*sizeof(int64_t));
419 int64_t * sp = malloc(DATASTACKSIZE*sizeof(int64_t));
1adfc4f4
AT
420
421 /*
422 * Setup the return stack and the label array.
423 */
eea0db3f
AT
424 size_t * rsp = malloc(RETURNSTACKSIZE*sizeof(size_t));
425 size_t labels[65536] = {0}; /* 65536 = 2^16 => Holds all possible labels. */
426 /* Default value of zero indicates an uninitialized label. */
bf43fa3f 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;
eea0db3f 471 default: ws_die(&pc, "unexpected byte"); break;
1adfc4f4
AT
472 }
473 }
1adfc4f4 474}