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