Added stackrotatereverse function to VVS stdlib to complement stackrotate.
[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;
84c650ed 121 if (c == '\t') label++;
1adfc4f4 122 }
8d17aa41
AT
123 return label;
124}
125
126uint16_t
127check_label(size_t * labels, uint16_t label, size_t * pc)
128{
439d5659
AT
129 if (!labels[label]) {
130 fprintf(stderr, "Trying to process label 0x%X.\n", label);
131 ws_die(pc, "uninitialized label (forgot an include?)");
132 }
1adfc4f4
AT
133 return label;
134}
135
bf43fa3f 136void
eea0db3f 137populate_labels(size_t * labels, uint8_t * code, size_t code_size)
bf43fa3f 138{
9686c901
AT
139 size_t cp = 0;
140 while (cp <= code_size) {
84c650ed 141 if (code[cp++] == '\v') {
9686c901
AT
142 uint16_t temp_label = parse_label(code, &cp);
143 labels[temp_label] = cp;
144 }
9686c901 145 }
bf43fa3f
AT
146}
147
1adfc4f4 148void
eea0db3f 149process_imp_stack(uint8_t * code, size_t * pc, int64_t ** sp)
1adfc4f4
AT
150{
151 switch (next_code_byte(code,pc)) {
152 case ' ':
153 /* Push number onto TOS. */
154 {
155 /* First, pick off the sign */
eea0db3f 156 int64_t sign = 0;
1adfc4f4
AT
157 switch (next_code_byte(code,pc)) {
158 case ' ' : sign = 1; break;
159 case '\t': sign = -1; break;
bf43fa3f 160 default : ws_die(pc, "expected sign"); break;
1adfc4f4
AT
161 }
162
163 /* Now, construct the number and push to TOS. */
164 /* I'm assuming the numbers are read MSb first. */
eea0db3f
AT
165 int64_t number = 0;
166 uint8_t temp;
1adfc4f4 167 while ((temp = next_code_byte(code,pc)) != '\n') {
bf43fa3f 168 if (temp == '\v') ws_die(pc, "non-binary digit in number");
1adfc4f4
AT
169 number <<= 1;
170 if (temp == '\t') number++;
171 }
172 stack_push(sp, number*sign);
173 }
174 break;
175 case '\n':
176 /* Stack sub-command */
177 {
178 switch (next_code_byte(code,pc)) {
179 /* Duplicate the TOS. */
180 case ' ':
181 stack_push(sp, stack_peek(sp,0));
182 break;
183 /* Swap TOS and NOS. */
184 case '\t':
185 {
eea0db3f
AT
186 int64_t t1 = stack_pop(sp);
187 int64_t t2 = stack_pop(sp);
1adfc4f4
AT
188 stack_push(sp, t1);
189 stack_push(sp, t2);
190 }
191 break;
192 /* Discard TOS. */
193 case '\n':
194 stack_pop(sp);
195 break;
bf43fa3f
AT
196 default:
197 ws_die(pc, "malformed stack IMP");
198 break;
1adfc4f4
AT
199 }
200 }
201 break;
bf43fa3f 202 default: ws_die(pc, "malformed stack IMP"); break;
1adfc4f4
AT
203 }
204}
205
206void
eea0db3f 207process_imp_arithmetic(uint8_t * code, size_t * pc, int64_t ** sp)
1adfc4f4 208{
eea0db3f 209 int64_t temp;
1adfc4f4
AT
210 switch (next_code_byte(code,pc)) {
211 case ' ':
212 {
213 switch (next_code_byte(code,pc)) {
214 case ' ':
215 /* Addition */
216 stack_push(sp, stack_pop(sp)+stack_pop(sp));
217 break;
218 case '\t':
219 /* Subtraction */
220 temp = stack_pop(sp);
221 stack_push(sp, stack_pop(sp)-temp);
222 break;
223 case '\n':
224 /* Multiplication */
225 stack_push(sp, stack_pop(sp)*stack_pop(sp));
226 break;
bf43fa3f
AT
227 default:
228 ws_die(pc, "malformed arithmetic IMP");
229 break;
1adfc4f4
AT
230 }
231 }
232 break;
233 case '\t':
234 {
235 switch (next_code_byte(code,pc)) {
236 case ' ':
237 /* Division */
238 temp = stack_pop(sp);
239 stack_push(sp, stack_pop(sp)/temp);
240 break;
241 case '\t':
242 /* Modulo */
243 temp = stack_pop(sp);
244 stack_push(sp, stack_pop(sp)%temp);
245 break;
bf43fa3f 246 default: ws_die(pc, "malformed arithmetic IMP"); break;
1adfc4f4
AT
247 }
248 }
249 break;
bf43fa3f 250 default: ws_die(pc, "malformed arithmetic IMP"); break;
1adfc4f4
AT
251 }
252}
253
254void
eea0db3f
AT
255process_imp_flowcontrol(uint8_t * code, size_t * pc, int64_t ** sp, size_t * labels,
256 size_t ** rsp)
1adfc4f4 257{
84c650ed 258 size_t temp_pc;
1adfc4f4
AT
259 switch (next_code_byte(code,pc)) {
260 case '\n':
261 /* Technically another LF is required but we ignore it. */
1adfc4f4 262 fflush(stdout);
a2664738 263 unset_terminal_mode();
1adfc4f4
AT
264 exit(EXIT_SUCCESS);
265 case ' ':
266 {
267 switch (next_code_byte(code,pc)) {
268 case ' ':
269 /* Mark a location in the program. */
9686c901
AT
270 if (next_code_byte(code,pc) != '\v') ws_die(pc,"expected vtab, "
271 "perhaps a whitespace program, rather than vvhitespace?");
272 /* Jump to next instruction since labels were parsed during startup. */
1e574f5b 273 parse_label(code, pc);
1adfc4f4
AT
274 break;
275 case '\t':
276 /* Call a subroutine. */
8d17aa41 277 temp_pc = labels[check_label(labels, parse_label(code, pc), pc)];
84c650ed
AT
278 *((*rsp)++) = *pc;
279 *pc = temp_pc;
1adfc4f4
AT
280 break;
281 case '\n':
282 /* Jump unconditionally to a label. */
8d17aa41 283 *pc = labels[check_label(labels, parse_label(code, pc), pc)];
1adfc4f4 284 break;
bf43fa3f
AT
285 default:
286 ws_die(pc, "malformed flow control IMP");
287 break;
1adfc4f4
AT
288 }
289 }
290 break;
291 case '\t':
292 {
293 switch (next_code_byte(code,pc)) {
294 case ' ':
295 /* Jump to a label if TOS == 0 */
8d17aa41 296 temp_pc = labels[check_label(labels, parse_label(code, pc), pc)];
84c650ed 297 if (stack_pop(sp) == 0) *pc = temp_pc;
1adfc4f4
AT
298 break;
299 case '\t':
300 /* Jump to a label if TOS < 0. */
8d17aa41 301 temp_pc = labels[check_label(labels, parse_label(code, pc), pc)];
84c650ed 302 if (stack_pop(sp) < 0) *pc = temp_pc;
1adfc4f4
AT
303 break;
304 case '\n':
305 /* Return from subroutine. */
306 *pc = *(--(*rsp));
307 break;
bf43fa3f
AT
308 default:
309 ws_die(pc, "malformed flow control IMP");
310 break;
1adfc4f4
AT
311 }
312 }
313 break;
bf43fa3f
AT
314 default:
315 ws_die(pc, "malformed flow control IMP");
316 break;
1adfc4f4
AT
317 }
318}
319
320void
eea0db3f 321process_imp_heap(uint8_t * code, size_t * pc, int64_t ** sp, int64_t ** hp)
1adfc4f4
AT
322{
323 switch (next_code_byte(code,pc)) {
aea85838
AT
324 case ' ' :
325 /* Store to heap */
326 {
eea0db3f
AT
327 int64_t value = stack_pop(sp);
328 int64_t addr = stack_pop(sp);
aea85838
AT
329 *(*hp + addr) = value;
330 }
331 break;
332 case '\t':
333 /* Retrieve from heap */
334 stack_push(sp, *(*hp + stack_pop(sp)));
335 break;
336 default:
337 ws_die(pc, "malformed heap IMP");
338 break;
1adfc4f4
AT
339 }
340}
341
342void
eea0db3f 343process_imp_io(uint8_t * code, size_t * pc, int64_t ** sp, int64_t ** hp)
1adfc4f4
AT
344{
345 switch (next_code_byte(code,pc)) {
346 case ' ':
347 /* Output */
348 {
349 switch (next_code_byte(code,pc)) {
eea0db3f
AT
350 case ' ' : /* Output char from TOS */ printf("%c", (uint8_t) stack_pop(sp)); break;
351 case '\t': /* Output digit from TOS */ printf("%c", (uint8_t) stack_pop(sp)+'0'); break;
9686c901 352 default : ws_die(pc, "malformed output IMP"); break;
1adfc4f4
AT
353 }
354 fflush(stdout);
355 }
356 break;
357 case '\t':
358 /* Input */
359 {
360 while (stdin_empty()) continue;
361 char c = getchar();
362 switch (next_code_byte(code,pc)) {
363 case '\t': /* Input digit */ c -= '0'; /* fallthrough */
a2664738 364 case ' ' : /* Input character */ *(*hp + *(--(*sp))) = c; break;
9686c901 365 default : ws_die(pc, "malformed input IMP"); break;
1adfc4f4
AT
366 }
367 }
368 break;
bf43fa3f 369 default: ws_die(pc, "malformed i/o IMP"); break;
1adfc4f4
AT
370 }
371}
372
1adfc4f4
AT
373int
374main(int argc, char ** argv)
375{
376 /*
377 * Process command line arguments
378 */
379 int c;
380 FILE * input = NULL;
381 while ((c = getopt(argc,argv,"i:h")) != -1) {
382 switch (c) {
383 case 'i':
384 if ((input = fopen(optarg, "r")) == NULL) {
385 fprintf(stderr, "ERROR: %s: %s\n", optarg, strerror(errno));
386 }
387 break;
388 case 'h':
389 print_usage(argv);
390 exit(EXIT_SUCCESS);
391 break;
392 default:
393 break;
394 }
395 }
396 if (input == NULL) {
bf43fa3f 397 fprintf(stderr, "ERROR: Must specify a VVhitespace source file with -f flag.\n");
1adfc4f4
AT
398 print_usage(argv);
399 exit(EXIT_FAILURE);
400 }
401
402 /*
eea0db3f 403 * Read just the VVhitespace source code into memory, stripping comment characters.
1adfc4f4
AT
404 * We will use the array indices as addresses for the virtual PC when jumping to labels.
405 */
406 size_t ws_code_size = 0;
407 uint8_t temp_byte;
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_size++;
411 }
1adfc4f4
AT
412 }
413 rewind(input);
414 uint8_t * ws_code_space = malloc(ws_code_size);
415 ws_code_size = 0;
416 while (fread(&temp_byte, 1, 1, input)) {
bf43fa3f
AT
417 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n' || temp_byte == '\v') {
418 ws_code_space[ws_code_size++] = temp_byte;
419 }
1adfc4f4
AT
420 }
421 fclose(input);
422
423 /*
424 * Setup a stack and heap.
1adfc4f4 425 */
eea0db3f
AT
426 int64_t * hp = malloc(HEAPSIZE*sizeof(int64_t));
427 int64_t * sp = malloc(DATASTACKSIZE*sizeof(int64_t));
1adfc4f4
AT
428
429 /*
430 * Setup the return stack and the label array.
431 */
eea0db3f
AT
432 size_t * rsp = malloc(RETURNSTACKSIZE*sizeof(size_t));
433 size_t labels[65536] = {0}; /* 65536 = 2^16 => Holds all possible labels. */
434 /* Default value of zero indicates an uninitialized label. */
bf43fa3f 435 populate_labels(labels, ws_code_space, ws_code_size);
1adfc4f4
AT
436
437 /*
438 * Main Loop
439 */
a2664738 440 set_terminal_mode();
1adfc4f4
AT
441 size_t pc = 0; /* Virtual program counter. Operates in the ws_code_space[] address space. */
442 while (1) {
443 if (pc >= ws_code_size) {
bf43fa3f
AT
444 fprintf(stderr, "SIM_ERROR: PC Overrun\n Requested PC: %lu\n Max Address: %lu\n",
445 pc, ws_code_size-1);
1adfc4f4 446 exit(EXIT_FAILURE);
a2664738 447 unset_terminal_mode();
1adfc4f4
AT
448 }
449
450 /* Decode the IMPs */
451 switch (ws_code_space[pc++]) {
452 case ' ':
453 /* Stack Manipulation */
454 process_imp_stack(ws_code_space, &pc, &sp);
455 break;
456 case '\n':
457 /* Flow Control */
458 process_imp_flowcontrol(ws_code_space, &pc, &sp, labels, &rsp);
459 break;
460 case '\t':
461 /* Arithmetic, Heap Access, or I/O */
462 {
463 switch (ws_code_space[pc++]) {
464 case ' ':
465 /* Arithmetic */
466 process_imp_arithmetic(ws_code_space, &pc, &sp);
467 break;
468 case '\t':
469 /* Heap Access */
470 process_imp_heap(ws_code_space, &pc, &sp, &hp);
471 break;
472 case '\n':
473 /* I/O */
474 process_imp_io(ws_code_space, &pc, &sp, &hp);
475 break;
476 }
477 }
478 break;
eea0db3f 479 default: ws_die(&pc, "unexpected byte"); break;
1adfc4f4
AT
480 }
481 }
1adfc4f4 482}