Added VVS stdlib function to dump heap in human readable form.
[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 }
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) {
84c650ed 133 if (code[cp++] == '\v') {
9686c901
AT
134 uint16_t temp_label = parse_label(code, &cp);
135 labels[temp_label] = cp;
136 }
9686c901 137 }
bf43fa3f
AT
138}
139
1adfc4f4 140void
eea0db3f 141process_imp_stack(uint8_t * code, size_t * pc, int64_t ** sp)
1adfc4f4
AT
142{
143 switch (next_code_byte(code,pc)) {
144 case ' ':
145 /* Push number onto TOS. */
146 {
147 /* First, pick off the sign */
eea0db3f 148 int64_t sign = 0;
1adfc4f4
AT
149 switch (next_code_byte(code,pc)) {
150 case ' ' : sign = 1; break;
151 case '\t': sign = -1; break;
bf43fa3f 152 default : ws_die(pc, "expected sign"); break;
1adfc4f4
AT
153 }
154
155 /* Now, construct the number and push to TOS. */
156 /* I'm assuming the numbers are read MSb first. */
eea0db3f
AT
157 int64_t number = 0;
158 uint8_t temp;
1adfc4f4 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 {
eea0db3f
AT
178 int64_t t1 = stack_pop(sp);
179 int64_t t2 = stack_pop(sp);
1adfc4f4
AT
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
eea0db3f 199process_imp_arithmetic(uint8_t * code, size_t * pc, int64_t ** sp)
1adfc4f4 200{
eea0db3f 201 int64_t temp;
1adfc4f4
AT
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
eea0db3f
AT
247process_imp_flowcontrol(uint8_t * code, size_t * pc, int64_t ** sp, size_t * labels,
248 size_t ** rsp)
1adfc4f4 249{
84c650ed 250 size_t temp_pc;
1adfc4f4
AT
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. */
84c650ed
AT
269 temp_pc = labels[parse_label(code, pc)];
270 *((*rsp)++) = *pc;
271 *pc = temp_pc;
1adfc4f4
AT
272 break;
273 case '\n':
274 /* Jump unconditionally to a label. */
275 *pc = labels[parse_label(code, pc)];
276 break;
bf43fa3f
AT
277 default:
278 ws_die(pc, "malformed flow control IMP");
279 break;
1adfc4f4
AT
280 }
281 }
282 break;
283 case '\t':
284 {
285 switch (next_code_byte(code,pc)) {
286 case ' ':
287 /* Jump to a label if TOS == 0 */
84c650ed
AT
288 temp_pc = labels[parse_label(code, pc)];
289 if (stack_pop(sp) == 0) *pc = temp_pc;
1adfc4f4
AT
290 break;
291 case '\t':
292 /* Jump to a label if TOS < 0. */
84c650ed
AT
293 temp_pc = labels[parse_label(code, pc)];
294 if (stack_pop(sp) < 0) *pc = temp_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}