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