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