Fixing a label I missed when moving functions in the stdlib.
[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 }
8d17aa41
AT
123 return label;
124}
125
126uint16_t
127check_label(size_t * labels, uint16_t label, size_t * pc)
128{
129 if(!labels[label]) ws_die(pc, "uninitialized label (forgot an include?)");
1adfc4f4
AT
130 return label;
131}
132
bf43fa3f 133void
eea0db3f 134populate_labels(size_t * labels, uint8_t * code, size_t code_size)
bf43fa3f 135{
9686c901
AT
136 size_t cp = 0;
137 while (cp <= code_size) {
84c650ed 138 if (code[cp++] == '\v') {
9686c901
AT
139 uint16_t temp_label = parse_label(code, &cp);
140 labels[temp_label] = cp;
141 }
9686c901 142 }
bf43fa3f
AT
143}
144
1adfc4f4 145void
eea0db3f 146process_imp_stack(uint8_t * code, size_t * pc, int64_t ** sp)
1adfc4f4
AT
147{
148 switch (next_code_byte(code,pc)) {
149 case ' ':
150 /* Push number onto TOS. */
151 {
152 /* First, pick off the sign */
eea0db3f 153 int64_t sign = 0;
1adfc4f4
AT
154 switch (next_code_byte(code,pc)) {
155 case ' ' : sign = 1; break;
156 case '\t': sign = -1; break;
bf43fa3f 157 default : ws_die(pc, "expected sign"); break;
1adfc4f4
AT
158 }
159
160 /* Now, construct the number and push to TOS. */
161 /* I'm assuming the numbers are read MSb first. */
eea0db3f
AT
162 int64_t number = 0;
163 uint8_t temp;
1adfc4f4 164 while ((temp = next_code_byte(code,pc)) != '\n') {
bf43fa3f 165 if (temp == '\v') ws_die(pc, "non-binary digit in number");
1adfc4f4
AT
166 number <<= 1;
167 if (temp == '\t') number++;
168 }
169 stack_push(sp, number*sign);
170 }
171 break;
172 case '\n':
173 /* Stack sub-command */
174 {
175 switch (next_code_byte(code,pc)) {
176 /* Duplicate the TOS. */
177 case ' ':
178 stack_push(sp, stack_peek(sp,0));
179 break;
180 /* Swap TOS and NOS. */
181 case '\t':
182 {
eea0db3f
AT
183 int64_t t1 = stack_pop(sp);
184 int64_t t2 = stack_pop(sp);
1adfc4f4
AT
185 stack_push(sp, t1);
186 stack_push(sp, t2);
187 }
188 break;
189 /* Discard TOS. */
190 case '\n':
191 stack_pop(sp);
192 break;
bf43fa3f
AT
193 default:
194 ws_die(pc, "malformed stack IMP");
195 break;
1adfc4f4
AT
196 }
197 }
198 break;
bf43fa3f 199 default: ws_die(pc, "malformed stack IMP"); break;
1adfc4f4
AT
200 }
201}
202
203void
eea0db3f 204process_imp_arithmetic(uint8_t * code, size_t * pc, int64_t ** sp)
1adfc4f4 205{
eea0db3f 206 int64_t temp;
1adfc4f4
AT
207 switch (next_code_byte(code,pc)) {
208 case ' ':
209 {
210 switch (next_code_byte(code,pc)) {
211 case ' ':
212 /* Addition */
213 stack_push(sp, stack_pop(sp)+stack_pop(sp));
214 break;
215 case '\t':
216 /* Subtraction */
217 temp = stack_pop(sp);
218 stack_push(sp, stack_pop(sp)-temp);
219 break;
220 case '\n':
221 /* Multiplication */
222 stack_push(sp, stack_pop(sp)*stack_pop(sp));
223 break;
bf43fa3f
AT
224 default:
225 ws_die(pc, "malformed arithmetic IMP");
226 break;
1adfc4f4
AT
227 }
228 }
229 break;
230 case '\t':
231 {
232 switch (next_code_byte(code,pc)) {
233 case ' ':
234 /* Division */
235 temp = stack_pop(sp);
236 stack_push(sp, stack_pop(sp)/temp);
237 break;
238 case '\t':
239 /* Modulo */
240 temp = stack_pop(sp);
241 stack_push(sp, stack_pop(sp)%temp);
242 break;
bf43fa3f 243 default: ws_die(pc, "malformed arithmetic IMP"); break;
1adfc4f4
AT
244 }
245 }
246 break;
bf43fa3f 247 default: ws_die(pc, "malformed arithmetic IMP"); break;
1adfc4f4
AT
248 }
249}
250
251void
eea0db3f
AT
252process_imp_flowcontrol(uint8_t * code, size_t * pc, int64_t ** sp, size_t * labels,
253 size_t ** rsp)
1adfc4f4 254{
84c650ed 255 size_t temp_pc;
1adfc4f4
AT
256 switch (next_code_byte(code,pc)) {
257 case '\n':
258 /* Technically another LF is required but we ignore it. */
1adfc4f4 259 fflush(stdout);
a2664738 260 unset_terminal_mode();
1adfc4f4
AT
261 exit(EXIT_SUCCESS);
262 case ' ':
263 {
264 switch (next_code_byte(code,pc)) {
265 case ' ':
266 /* Mark a location in the program. */
9686c901
AT
267 if (next_code_byte(code,pc) != '\v') ws_die(pc,"expected vtab, "
268 "perhaps a whitespace program, rather than vvhitespace?");
269 /* Jump to next instruction since labels were parsed during startup. */
1e574f5b 270 parse_label(code, pc);
1adfc4f4
AT
271 break;
272 case '\t':
273 /* Call a subroutine. */
8d17aa41 274 temp_pc = labels[check_label(labels, parse_label(code, pc), pc)];
84c650ed
AT
275 *((*rsp)++) = *pc;
276 *pc = temp_pc;
1adfc4f4
AT
277 break;
278 case '\n':
279 /* Jump unconditionally to a label. */
8d17aa41 280 *pc = labels[check_label(labels, parse_label(code, pc), pc)];
1adfc4f4 281 break;
bf43fa3f
AT
282 default:
283 ws_die(pc, "malformed flow control IMP");
284 break;
1adfc4f4
AT
285 }
286 }
287 break;
288 case '\t':
289 {
290 switch (next_code_byte(code,pc)) {
291 case ' ':
292 /* Jump to a label if TOS == 0 */
8d17aa41 293 temp_pc = labels[check_label(labels, parse_label(code, pc), pc)];
84c650ed 294 if (stack_pop(sp) == 0) *pc = temp_pc;
1adfc4f4
AT
295 break;
296 case '\t':
297 /* Jump to a label if TOS < 0. */
8d17aa41 298 temp_pc = labels[check_label(labels, parse_label(code, pc), pc)];
84c650ed 299 if (stack_pop(sp) < 0) *pc = temp_pc;
1adfc4f4
AT
300 break;
301 case '\n':
302 /* Return from subroutine. */
303 *pc = *(--(*rsp));
304 break;
bf43fa3f
AT
305 default:
306 ws_die(pc, "malformed flow control IMP");
307 break;
1adfc4f4
AT
308 }
309 }
310 break;
bf43fa3f
AT
311 default:
312 ws_die(pc, "malformed flow control IMP");
313 break;
1adfc4f4
AT
314 }
315}
316
317void
eea0db3f 318process_imp_heap(uint8_t * code, size_t * pc, int64_t ** sp, int64_t ** hp)
1adfc4f4
AT
319{
320 switch (next_code_byte(code,pc)) {
aea85838
AT
321 case ' ' :
322 /* Store to heap */
323 {
eea0db3f
AT
324 int64_t value = stack_pop(sp);
325 int64_t addr = stack_pop(sp);
aea85838
AT
326 *(*hp + addr) = value;
327 }
328 break;
329 case '\t':
330 /* Retrieve from heap */
331 stack_push(sp, *(*hp + stack_pop(sp)));
332 break;
333 default:
334 ws_die(pc, "malformed heap IMP");
335 break;
1adfc4f4
AT
336 }
337}
338
339void
eea0db3f 340process_imp_io(uint8_t * code, size_t * pc, int64_t ** sp, int64_t ** hp)
1adfc4f4
AT
341{
342 switch (next_code_byte(code,pc)) {
343 case ' ':
344 /* Output */
345 {
346 switch (next_code_byte(code,pc)) {
eea0db3f
AT
347 case ' ' : /* Output char from TOS */ printf("%c", (uint8_t) stack_pop(sp)); break;
348 case '\t': /* Output digit from TOS */ printf("%c", (uint8_t) stack_pop(sp)+'0'); break;
9686c901 349 default : ws_die(pc, "malformed output IMP"); break;
1adfc4f4
AT
350 }
351 fflush(stdout);
352 }
353 break;
354 case '\t':
355 /* Input */
356 {
357 while (stdin_empty()) continue;
358 char c = getchar();
359 switch (next_code_byte(code,pc)) {
360 case '\t': /* Input digit */ c -= '0'; /* fallthrough */
a2664738 361 case ' ' : /* Input character */ *(*hp + *(--(*sp))) = c; break;
9686c901 362 default : ws_die(pc, "malformed input IMP"); break;
1adfc4f4
AT
363 }
364 }
365 break;
bf43fa3f 366 default: ws_die(pc, "malformed i/o IMP"); break;
1adfc4f4
AT
367 }
368}
369
1adfc4f4
AT
370int
371main(int argc, char ** argv)
372{
373 /*
374 * Process command line arguments
375 */
376 int c;
377 FILE * input = NULL;
378 while ((c = getopt(argc,argv,"i:h")) != -1) {
379 switch (c) {
380 case 'i':
381 if ((input = fopen(optarg, "r")) == NULL) {
382 fprintf(stderr, "ERROR: %s: %s\n", optarg, strerror(errno));
383 }
384 break;
385 case 'h':
386 print_usage(argv);
387 exit(EXIT_SUCCESS);
388 break;
389 default:
390 break;
391 }
392 }
393 if (input == NULL) {
bf43fa3f 394 fprintf(stderr, "ERROR: Must specify a VVhitespace source file with -f flag.\n");
1adfc4f4
AT
395 print_usage(argv);
396 exit(EXIT_FAILURE);
397 }
398
399 /*
eea0db3f 400 * Read just the VVhitespace source code into memory, stripping comment characters.
1adfc4f4
AT
401 * We will use the array indices as addresses for the virtual PC when jumping to labels.
402 */
403 size_t ws_code_size = 0;
404 uint8_t temp_byte;
405 while (fread(&temp_byte, 1, 1, input)) {
bf43fa3f
AT
406 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n' || temp_byte == '\v') {
407 ws_code_size++;
408 }
1adfc4f4
AT
409 }
410 rewind(input);
411 uint8_t * ws_code_space = malloc(ws_code_size);
412 ws_code_size = 0;
413 while (fread(&temp_byte, 1, 1, input)) {
bf43fa3f
AT
414 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n' || temp_byte == '\v') {
415 ws_code_space[ws_code_size++] = temp_byte;
416 }
1adfc4f4
AT
417 }
418 fclose(input);
419
420 /*
421 * Setup a stack and heap.
1adfc4f4 422 */
eea0db3f
AT
423 int64_t * hp = malloc(HEAPSIZE*sizeof(int64_t));
424 int64_t * sp = malloc(DATASTACKSIZE*sizeof(int64_t));
1adfc4f4
AT
425
426 /*
427 * Setup the return stack and the label array.
428 */
eea0db3f
AT
429 size_t * rsp = malloc(RETURNSTACKSIZE*sizeof(size_t));
430 size_t labels[65536] = {0}; /* 65536 = 2^16 => Holds all possible labels. */
431 /* Default value of zero indicates an uninitialized label. */
bf43fa3f 432 populate_labels(labels, ws_code_space, ws_code_size);
1adfc4f4
AT
433
434 /*
435 * Main Loop
436 */
a2664738 437 set_terminal_mode();
1adfc4f4
AT
438 size_t pc = 0; /* Virtual program counter. Operates in the ws_code_space[] address space. */
439 while (1) {
440 if (pc >= ws_code_size) {
bf43fa3f
AT
441 fprintf(stderr, "SIM_ERROR: PC Overrun\n Requested PC: %lu\n Max Address: %lu\n",
442 pc, ws_code_size-1);
1adfc4f4 443 exit(EXIT_FAILURE);
a2664738 444 unset_terminal_mode();
1adfc4f4
AT
445 }
446
447 /* Decode the IMPs */
448 switch (ws_code_space[pc++]) {
449 case ' ':
450 /* Stack Manipulation */
451 process_imp_stack(ws_code_space, &pc, &sp);
452 break;
453 case '\n':
454 /* Flow Control */
455 process_imp_flowcontrol(ws_code_space, &pc, &sp, labels, &rsp);
456 break;
457 case '\t':
458 /* Arithmetic, Heap Access, or I/O */
459 {
460 switch (ws_code_space[pc++]) {
461 case ' ':
462 /* Arithmetic */
463 process_imp_arithmetic(ws_code_space, &pc, &sp);
464 break;
465 case '\t':
466 /* Heap Access */
467 process_imp_heap(ws_code_space, &pc, &sp, &hp);
468 break;
469 case '\n':
470 /* I/O */
471 process_imp_io(ws_code_space, &pc, &sp, &hp);
472 break;
473 }
474 }
475 break;
eea0db3f 476 default: ws_die(&pc, "unexpected byte"); break;
1adfc4f4
AT
477 }
478 }
1adfc4f4 479}