Importing the Whitespace interpreter as a starting point.
[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{
24 printf( "Whitespace Interpreter v%d (www.subgeniuskitty.com)\n"
25 "Usage: %s -i <file>\n"
26 " -h Help (prints this message)\n"
27 " -i <file> Specify a Whitespace source file to interpret.\n"
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
83uint16_t
84parse_label(uint8_t * code, size_t * pc)
85{
86 uint16_t label = 0;
87 uint8_t c;
88 while ((c = code[(*pc)++]) != '\n') {
89 label = label << 1;
90 if (c == ' ') label++;
91 }
92 return label;
93}
94
95void
96process_imp_stack(uint8_t * code, size_t * pc, int32_t ** sp)
97{
98 switch (next_code_byte(code,pc)) {
99 case ' ':
100 /* Push number onto TOS. */
101 {
102 /* First, pick off the sign */
103 int32_t sign = 0;
104 switch (next_code_byte(code,pc)) {
105 case ' ' : sign = 1; break;
106 case '\t': sign = -1; break;
107 case '\n': ws_die(pc, "expected sign"); break;
108 }
109
110 /* Now, construct the number and push to TOS. */
111 /* I'm assuming the numbers are read MSb first. */
112 int32_t temp, number = 0;
113 while ((temp = next_code_byte(code,pc)) != '\n') {
114 number <<= 1;
115 if (temp == '\t') number++;
116 }
117 stack_push(sp, number*sign);
118 }
119 break;
120 case '\n':
121 /* Stack sub-command */
122 {
123 switch (next_code_byte(code,pc)) {
124 /* Duplicate the TOS. */
125 case ' ':
126 stack_push(sp, stack_peek(sp,0));
127 break;
128 /* Swap TOS and NOS. */
129 case '\t':
130 {
131 int32_t t1 = stack_pop(sp);
132 int32_t t2 = stack_pop(sp);
133 stack_push(sp, t1);
134 stack_push(sp, t2);
135 }
136 break;
137 /* Discard TOS. */
138 case '\n':
139 stack_pop(sp);
140 break;
141 }
142 }
143 break;
144 case '\t': ws_die(pc, "malformed stack IMP"); break;
145 }
146}
147
148void
149process_imp_arithmetic(uint8_t * code, size_t * pc, int32_t ** sp)
150{
151 int32_t temp;
152 switch (next_code_byte(code,pc)) {
153 case ' ':
154 {
155 switch (next_code_byte(code,pc)) {
156 case ' ':
157 /* Addition */
158 stack_push(sp, stack_pop(sp)+stack_pop(sp));
159 break;
160 case '\t':
161 /* Subtraction */
162 temp = stack_pop(sp);
163 stack_push(sp, stack_pop(sp)-temp);
164 break;
165 case '\n':
166 /* Multiplication */
167 stack_push(sp, stack_pop(sp)*stack_pop(sp));
168 break;
169 }
170 }
171 break;
172 case '\t':
173 {
174 switch (next_code_byte(code,pc)) {
175 case ' ':
176 /* Division */
177 temp = stack_pop(sp);
178 stack_push(sp, stack_pop(sp)/temp);
179 break;
180 case '\t':
181 /* Modulo */
182 temp = stack_pop(sp);
183 stack_push(sp, stack_pop(sp)%temp);
184 break;
185 case '\n': ws_die(pc, "malformed arithmetic IMP"); break;
186 }
187 }
188 break;
189 case '\n': ws_die(pc, "malformed arithmetic IMP"); break;
190 }
191}
192
193void
194process_imp_flowcontrol(uint8_t * code, size_t * pc, int32_t ** sp, uint32_t * labels,
195 uint32_t ** rsp)
196{
197 switch (next_code_byte(code,pc)) {
198 case '\n':
199 /* Technically another LF is required but we ignore it. */
200 printf("\n");
201 fflush(stdout);
202 exit(EXIT_SUCCESS);
203 case ' ':
204 {
205 switch (next_code_byte(code,pc)) {
206 case ' ':
207 /* Mark a location in the program. */
208 labels[parse_label(code, pc)] = *pc;
209 break;
210 case '\t':
211 /* Call a subroutine. */
212 *((*rsp)++) = *pc;
213 *pc = labels[parse_label(code, pc)];
214 break;
215 case '\n':
216 /* Jump unconditionally to a label. */
217 *pc = labels[parse_label(code, pc)];
218 break;
219 }
220 }
221 break;
222 case '\t':
223 {
224 switch (next_code_byte(code,pc)) {
225 case ' ':
226 /* Jump to a label if TOS == 0 */
227 if (stack_peek(sp,0) == 0) *pc = labels[parse_label(code, pc)];
228 break;
229 case '\t':
230 /* Jump to a label if TOS < 0. */
231 if (stack_peek(sp,0) < 0) *pc = labels[parse_label(code, pc)];
232 break;
233 case '\n':
234 /* Return from subroutine. */
235 *pc = *(--(*rsp));
236 break;
237 }
238 }
239 break;
240 }
241}
242
243void
244process_imp_heap(uint8_t * code, size_t * pc, int32_t ** sp, int32_t ** hp)
245{
246 switch (next_code_byte(code,pc)) {
247 case ' ' : /* Store to heap */ *(*hp + *((*sp)-1)) = **sp; *sp -= 2; break;
248 case '\t': /* Retrieve from heap */ **sp = *(*hp + **sp); break;
249 case '\n': ws_die(pc, "malformed heap IMP"); break;
250 }
251}
252
253void
254process_imp_io(uint8_t * code, size_t * pc, int32_t ** sp, int32_t ** hp)
255{
256 switch (next_code_byte(code,pc)) {
257 case ' ':
258 /* Output */
259 {
260 switch (next_code_byte(code,pc)) {
261 case ' ' : /* Output character from TOS */ printf("%c", stack_pop(sp)); break;
262 case '\t': /* Output number from TOS */ printf("%d", stack_pop(sp)); break;
263 case '\n': ws_die(pc, "malformed output IMP"); break;
264 }
265 fflush(stdout);
266 }
267 break;
268 case '\t':
269 /* Input */
270 {
271 while (stdin_empty()) continue;
272 char c = getchar();
273 switch (next_code_byte(code,pc)) {
274 case '\t': /* Input digit */ c -= '0'; /* fallthrough */
275 case ' ' : /* Input character */ *(*hp + *((*sp)--)) = c; break;
276 case '\n': ws_die(pc, "malformed input IMP"); break;
277 }
278 }
279 break;
280 case '\n': ws_die(pc, "malformed i/o IMP"); break;
281 }
282}
283
284/* TODO: Continue cleanup here */
285
286int
287main(int argc, char ** argv)
288{
289 /*
290 * Process command line arguments
291 */
292 int c;
293 FILE * input = NULL;
294 while ((c = getopt(argc,argv,"i:h")) != -1) {
295 switch (c) {
296 case 'i':
297 if ((input = fopen(optarg, "r")) == NULL) {
298 fprintf(stderr, "ERROR: %s: %s\n", optarg, strerror(errno));
299 }
300 break;
301 case 'h':
302 print_usage(argv);
303 exit(EXIT_SUCCESS);
304 break;
305 default:
306 break;
307 }
308 }
309 if (input == NULL) {
310 fprintf(stderr, "ERROR: Must specify a Whitespace source file with -f flag.\n");
311 print_usage(argv);
312 exit(EXIT_FAILURE);
313 }
314
315 /*
316 * Read just the Whitespace source code into memory.
317 * We will use the array indices as addresses for the virtual PC when jumping to labels.
318 */
319 size_t ws_code_size = 0;
320 uint8_t temp_byte;
321 while (fread(&temp_byte, 1, 1, input)) {
322 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n') ws_code_size++;
323 }
324 rewind(input);
325 uint8_t * ws_code_space = malloc(ws_code_size);
326 ws_code_size = 0;
327 while (fread(&temp_byte, 1, 1, input)) {
328 if (temp_byte == ' ' || temp_byte == '\t' || temp_byte == '\n') ws_code_space[ws_code_size++] = temp_byte;
329 }
330 fclose(input);
331
332 /*
333 * Setup a stack and heap.
334 * Assume a 32-bit word size.
335 */
336 int32_t * hp = malloc(HEAPSIZE*4);
337 int32_t * sp = malloc(STACKSIZE*4);
338
339 /*
340 * Setup the return stack and the label array.
341 */
342 uint32_t * rsp = malloc(RETURNSTACKSIZE*4);
343 uint32_t labels[65536];
344
345 /*
346 * Main Loop
347 */
348
349 size_t pc = 0; /* Virtual program counter. Operates in the ws_code_space[] address space. */
350 while (1) {
351 if (pc >= ws_code_size) {
352 fprintf(stderr, "SIM_ERROR: PC Overrun\n Requested PC: %lu\n Max Address: %lu\n", pc, ws_code_size-1);
353 exit(EXIT_FAILURE);
354 }
355
356 /* Decode the IMPs */
357 switch (ws_code_space[pc++]) {
358 case ' ':
359 /* Stack Manipulation */
360 process_imp_stack(ws_code_space, &pc, &sp);
361 break;
362 case '\n':
363 /* Flow Control */
364 process_imp_flowcontrol(ws_code_space, &pc, &sp, labels, &rsp);
365 break;
366 case '\t':
367 /* Arithmetic, Heap Access, or I/O */
368 {
369 switch (ws_code_space[pc++]) {
370 case ' ':
371 /* Arithmetic */
372 process_imp_arithmetic(ws_code_space, &pc, &sp);
373 break;
374 case '\t':
375 /* Heap Access */
376 process_imp_heap(ws_code_space, &pc, &sp, &hp);
377 break;
378 case '\n':
379 /* I/O */
380 process_imp_io(ws_code_space, &pc, &sp, &hp);
381 break;
382 }
383 }
384 break;
385 }
386 }
387
388 printf("\n");
389 printf("Program executed.\n");
390
391 exit(EXIT_SUCCESS);
392}