Cleaning up test from previous, premature commit.
[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. */
239 *((*rsp)++) = *pc;
240 *pc = labels[parse_label(code, pc)];
241 break;
242 case '\n':
243 /* Jump unconditionally to a label. */
244 *pc = labels[parse_label(code, pc)];
245 break;
bf43fa3f
AT
246 default:
247 ws_die(pc, "malformed flow control IMP");
248 break;
1adfc4f4
AT
249 }
250 }
251 break;
252 case '\t':
253 {
254 switch (next_code_byte(code,pc)) {
255 case ' ':
256 /* Jump to a label if TOS == 0 */
257 if (stack_peek(sp,0) == 0) *pc = labels[parse_label(code, pc)];
258 break;
259 case '\t':
260 /* Jump to a label if TOS < 0. */
261 if (stack_peek(sp,0) < 0) *pc = labels[parse_label(code, pc)];
262 break;
263 case '\n':
264 /* Return from subroutine. */
265 *pc = *(--(*rsp));
266 break;
bf43fa3f
AT
267 default:
268 ws_die(pc, "malformed flow control IMP");
269 break;
1adfc4f4
AT
270 }
271 }
272 break;
bf43fa3f
AT
273 default:
274 ws_die(pc, "malformed flow control IMP");
275 break;
1adfc4f4
AT
276 }
277}
278
279void
280process_imp_heap(uint8_t * code, size_t * pc, int32_t ** sp, int32_t ** hp)
281{
282 switch (next_code_byte(code,pc)) {
aea85838
AT
283 case ' ' :
284 /* Store to heap */
285 {
286 int32_t value = stack_pop(sp);
287 int32_t addr = stack_pop(sp);
288 *(*hp + addr) = value;
289 }
290 break;
291 case '\t':
292 /* Retrieve from heap */
293 stack_push(sp, *(*hp + stack_pop(sp)));
294 break;
295 default:
296 ws_die(pc, "malformed heap IMP");
297 break;
1adfc4f4
AT
298 }
299}
300
301void
302process_imp_io(uint8_t * code, size_t * pc, int32_t ** sp, int32_t ** hp)
303{
304 switch (next_code_byte(code,pc)) {
305 case ' ':
306 /* Output */
307 {
308 switch (next_code_byte(code,pc)) {
309 case ' ' : /* Output character from TOS */ printf("%c", stack_pop(sp)); break;
310 case '\t': /* Output number from TOS */ printf("%d", stack_pop(sp)); break;
9686c901 311 default : ws_die(pc, "malformed output IMP"); break;
1adfc4f4
AT
312 }
313 fflush(stdout);
314 }
315 break;
316 case '\t':
317 /* Input */
318 {
319 while (stdin_empty()) continue;
320 char c = getchar();
321 switch (next_code_byte(code,pc)) {
322 case '\t': /* Input digit */ c -= '0'; /* fallthrough */
323 case ' ' : /* Input character */ *(*hp + *((*sp)--)) = c; break;
9686c901 324 default : ws_die(pc, "malformed input IMP"); break;
1adfc4f4
AT
325 }
326 }
327 break;
bf43fa3f 328 default: ws_die(pc, "malformed i/o IMP"); break;
1adfc4f4
AT
329 }
330}
331
1adfc4f4
AT
332int
333main(int argc, char ** argv)
334{
335 /*
336 * Process command line arguments
337 */
338 int c;
339 FILE * input = NULL;
340 while ((c = getopt(argc,argv,"i:h")) != -1) {
341 switch (c) {
342 case 'i':
343 if ((input = fopen(optarg, "r")) == NULL) {
344 fprintf(stderr, "ERROR: %s: %s\n", optarg, strerror(errno));
345 }
346 break;
347 case 'h':
348 print_usage(argv);
349 exit(EXIT_SUCCESS);
350 break;
351 default:
352 break;
353 }
354 }
355 if (input == NULL) {
bf43fa3f 356 fprintf(stderr, "ERROR: Must specify a VVhitespace source file with -f flag.\n");
1adfc4f4
AT
357 print_usage(argv);
358 exit(EXIT_FAILURE);
359 }
360
361 /*
bf43fa3f 362 * Read just the VVhitespace source code into memory.
1adfc4f4
AT
363 * We will use the array indices as addresses for the virtual PC when jumping to labels.
364 */
365 size_t ws_code_size = 0;
366 uint8_t temp_byte;
367 while (fread(&temp_byte, 1, 1, input)) {
bf43fa3f
AT
368 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n' || temp_byte == '\v') {
369 ws_code_size++;
370 }
1adfc4f4
AT
371 }
372 rewind(input);
373 uint8_t * ws_code_space = malloc(ws_code_size);
374 ws_code_size = 0;
375 while (fread(&temp_byte, 1, 1, input)) {
bf43fa3f
AT
376 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n' || temp_byte == '\v') {
377 ws_code_space[ws_code_size++] = temp_byte;
378 }
1adfc4f4
AT
379 }
380 fclose(input);
381
382 /*
383 * Setup a stack and heap.
384 * Assume a 32-bit word size.
385 */
bf43fa3f 386 // TODO: Make everything 64-bit.
1adfc4f4
AT
387 int32_t * hp = malloc(HEAPSIZE*4);
388 int32_t * sp = malloc(STACKSIZE*4);
389
390 /*
391 * Setup the return stack and the label array.
392 */
393 uint32_t * rsp = malloc(RETURNSTACKSIZE*4);
bf43fa3f
AT
394 uint32_t labels[65536] = {0};
395 populate_labels(labels, ws_code_space, ws_code_size);
1adfc4f4
AT
396
397 /*
398 * Main Loop
399 */
400
401 size_t pc = 0; /* Virtual program counter. Operates in the ws_code_space[] address space. */
402 while (1) {
403 if (pc >= ws_code_size) {
bf43fa3f
AT
404 fprintf(stderr, "SIM_ERROR: PC Overrun\n Requested PC: %lu\n Max Address: %lu\n",
405 pc, ws_code_size-1);
1adfc4f4
AT
406 exit(EXIT_FAILURE);
407 }
9686c901
AT
408// TODO: Have the SIGTERM signal handler and normal term point return the value
409// on TOS so I can do rudimentary automated tests.
1adfc4f4
AT
410
411 /* Decode the IMPs */
412 switch (ws_code_space[pc++]) {
413 case ' ':
414 /* Stack Manipulation */
415 process_imp_stack(ws_code_space, &pc, &sp);
416 break;
417 case '\n':
418 /* Flow Control */
419 process_imp_flowcontrol(ws_code_space, &pc, &sp, labels, &rsp);
420 break;
421 case '\t':
422 /* Arithmetic, Heap Access, or I/O */
423 {
424 switch (ws_code_space[pc++]) {
425 case ' ':
426 /* Arithmetic */
427 process_imp_arithmetic(ws_code_space, &pc, &sp);
428 break;
429 case '\t':
430 /* Heap Access */
431 process_imp_heap(ws_code_space, &pc, &sp, &hp);
432 break;
433 case '\n':
434 /* I/O */
435 process_imp_io(ws_code_space, &pc, &sp, &hp);
436 break;
437 }
438 }
439 break;
9686c901 440 default: ws_die(&pc, "unexpected VTab"); break;
1adfc4f4
AT
441 }
442 }
443
444 printf("\n");
445 printf("Program executed.\n");
446
447 exit(EXIT_SUCCESS);
448}