1 /* RETRO : a personal, minimalistic forth
2
3 This interface layer will create a new binary that
4 bundles the RETRO virtual machine and image file.
5
6 The VM and image are embedded in this as ELF sections.
7 This will extract them, compile code from a file into
8 the image, then embed the image into the VM binary.
9
10 Due to the way this works, it requires a Unix-like OS
11 and the `objcopy` binary in the path.
12
13 Copyright (c) 2016 - 2020, Charles Childers
14 */
15
16
17 #include <stdio.h>
18 #include <stdint.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <string.h>
22 #include <sys/stat.h>
23 #include <limits.h>
24
25 #include "config.h"
26 #ifdef NUM_DEVICES
27 #undef NUM_DEVICES
28 #define NUM_DEVICES 1
29 #endif
30
31 CELL sp, rp, ip;
32 CELL data[STACK_DEPTH];
33 CELL address[ADDRESSES];
34 CELL memory[IMAGE_SIZE + 1];
35 #define TOS data[sp]
36 #define NOS data[sp-1]
37 #define TORS address[rp]
38
39
40 typedef void (*Handler)(void);
41
42 Handler IO_deviceHandlers[NUM_DEVICES + 1];
43 Handler IO_queryHandlers[NUM_DEVICES + 1];
44
45 CELL ngaLoadImage(char *imageFile);
46 void ngaPrepare();
47 void ngaProcessOpcode(CELL opcode);
48 void ngaProcessPackedOpcodes(int opcode);
49 int ngaValidatePackedOpcodes(CELL opcode);
50
51 CELL stack_pop();
52 void stack_push(CELL value);
53 int string_inject(char *str, int buffer);
54 char *string_extract(int at);
55 CELL d_xt_for(char *Name, CELL Dictionary);
56 void execute(int cell);
57 void evaluate(char *s);
58 void read_token(FILE *file, char *token_buffer, int echo);
59
60 /* This assumes some knowledge of the ngaImage format for the
61 Retro language. If things change there, these will need to
62 be adjusted to match. */
63
64 #define TIB 1025
65 #define D_OFFSET_LINK 0
66 #define D_OFFSET_XT 1
67 #define D_OFFSET_CLASS 2
68 #define D_OFFSET_NAME 3
69
70 extern CELL Dictionary, Heap, Compiler;
71 extern CELL notfound;
72
generic_output()73 void generic_output() {
74 putc(stack_pop(), stdout);
75 fflush(stdout);
76 }
77
generic_output_query()78 void generic_output_query() {
79 stack_push(0);
80 stack_push(0);
81 }
82
dump_stack()83 void dump_stack() {
84 CELL i;
85 if (sp == 0)
86 return;
87 printf("\nStack: ");
88 for (i = 1; i <= sp; i++) {
89 if (i == sp)
90 printf("[ TOS: %d ]", data[i]);
91 else
92 printf("%d ", data[i]);
93 }
94 printf("\n");
95 }
96
include_file(char * fname)97 int include_file(char *fname) {
98 FILE *fp;
99 char source[64000];
100 int inBlock = 0;
101 int tokens = 0;
102 fp = fopen(fname, "r");
103 if (fp == NULL)
104 return 0;
105 while (!feof(fp))
106 {
107 read_token(fp, source, 0);
108 if (strcmp(source, "~~~") == 0) {
109 if (inBlock == 0)
110 inBlock = 1;
111 else
112 inBlock = 0;
113 } else {
114 if (inBlock == 1) {
115 evaluate(source);
116 putchar('.');
117 }
118 }
119 tokens++;
120 }
121 fclose(fp);
122 putchar('\n');
123 return tokens;
124 }
125
extract_runtime(char * src)126 void extract_runtime(char *src) {
127 char buffer[4096];
128 snprintf(buffer, 4096, "objcopy -O binary --only-section=.runtime --set-section-flags .runtime=alloc %s a.out", src);
129 system(buffer);
130 }
131
extract_image(char * src)132 void extract_image(char *src) {
133 char buffer[4096];
134 snprintf(buffer, 4096, "objcopy -O binary --only-section=.ngaImage --set-section-flags .ngaImage=alloc %s __ngaImage", src);
135 system(buffer);
136 }
137
generate_turnkey(void)138 void generate_turnkey(void) {
139 system("objcopy --add-section .ngaImage=__ngaImage --set-section-flags .ngaImage=noload,readonly a.out");
140 chmod("a.out", 493);
141 }
142
validate_image(int tokens)143 int validate_image(int tokens) {
144 if (tokens == 0) {
145 unlink("a.out");
146 printf("Error: no tokens in source file!\n");
147 return -1;
148 }
149 if (memory[1] == 0 || memory[1] == -1) {
150 unlink("a.out");
151 printf("Error: entry point not set!\n");
152 return -2;
153 }
154 return 0;
155 }
156
setup()157 void setup() {
158 IO_deviceHandlers[0] = generic_output;
159 IO_queryHandlers[0] = generic_output_query;
160 }
161
write_image()162 void write_image() {
163 FILE *fp;
164 if ((fp = fopen("__ngaImage", "wb")) == NULL) {
165 printf("Unable to save the ngaImage!\n");
166 exit(2);
167 }
168 fwrite(&memory, sizeof(CELL), memory[3] + 1, fp);
169 fclose(fp);
170 }
171
patch_entry(CELL a)172 void patch_entry(CELL a) {
173 memory[1] = a;
174 }
175
main(int argc,char ** argv)176 int main(int argc, char **argv) {
177 int tokens;
178 if (argc < 3) {
179 printf("Missing arguments\n");
180 exit(1);
181 }
182
183 ngaPrepare();
184 extract_runtime(argv[0]);
185 extract_image(argv[0]);
186 ngaLoadImage("__ngaImage");
187 tokens = include_file(argv[1]);
188 patch_entry(d_xt_for(argv[2], Dictionary));
189 write_image();
190 generate_turnkey();
191 unlink("__ngaImage");
192
193 printf("\nFinal image is %d cells\n", memory[3]);
194 return validate_image(tokens);
195 }
196
197 CELL Dictionary, Heap, Compiler;
198 CELL notfound;
199
200 /* Some I/O Parameters */
201
stack_pop()202 CELL stack_pop() {
203 sp--;
204 return data[sp + 1];
205 }
206
stack_push(CELL value)207 void stack_push(CELL value) {
208 sp++;
209 data[sp] = value;
210 }
211
string_inject(char * str,int buffer)212 int string_inject(char *str, int buffer) {
213 int m = strlen(str);
214 int i = 0;
215 while (m > 0) {
216 memory[buffer + i] = (CELL)str[i];
217 memory[buffer + i + 1] = 0;
218 m--; i++;
219 }
220 return buffer;
221 }
222
223 char string_data[8192];
string_extract(int at)224 char *string_extract(int at) {
225 CELL starting = at;
226 CELL i = 0;
227 while(memory[starting] && i < 8192)
228 string_data[i++] = (char)memory[starting++];
229 string_data[i] = 0;
230 return (char *)string_data;
231 }
232
d_xt(CELL dt)233 int d_xt(CELL dt) {
234 return dt + D_OFFSET_XT;
235 }
236
d_name(CELL dt)237 int d_name(CELL dt) {
238 return dt + D_OFFSET_NAME;
239 }
240
d_lookup(CELL Dictionary,char * name)241 int d_lookup(CELL Dictionary, char *name) {
242 CELL dt = 0;
243 CELL i = Dictionary;
244 char *dname;
245 while (memory[i] != 0 && i != 0) {
246 dname = string_extract(d_name(i));
247 if (strcmp(dname, name) == 0) {
248 dt = i;
249 i = 0;
250 } else {
251 i = memory[i];
252 }
253 }
254 return dt;
255 }
256
d_xt_for(char * Name,CELL Dictionary)257 CELL d_xt_for(char *Name, CELL Dictionary) {
258 return memory[d_xt(d_lookup(Dictionary, Name))];
259 }
260
261 /* Retro needs to track a few variables. This function is
262 called as necessary to ensure that the interface stays
263 in sync with the image state. */
264
update_rx()265 void update_rx() {
266 Dictionary = memory[2];
267 Heap = memory[3];
268 Compiler = d_xt_for("Compiler", Dictionary);
269 notfound = d_xt_for("err:notfound", Dictionary);
270 }
271
272
273 /* The `execute` function runs a word in the Retro image.
274 It also handles the additional I/O instructions. */
275
execute(int cell)276 void execute(int cell) {
277 CELL opcode;
278 rp = 1;
279 ip = cell;
280 while (ip < IMAGE_SIZE) {
281 opcode = memory[ip];
282 if (ngaValidatePackedOpcodes(opcode) != 0) {
283 ngaProcessPackedOpcodes(opcode);
284 } else {
285 printf("Invalid instruction!\n");
286 exit(1);
287 }
288 ip++;
289 if (rp == 0)
290 ip = IMAGE_SIZE;
291 }
292 }
293
294 /* The `evaluate` function moves a token into the Retro
295 token buffer, then calls the Retro `interpret` word
296 to process it. */
297
evaluate(char * s)298 void evaluate(char *s) {
299 CELL interpret;
300 if (strlen(s) == 0)
301 return;
302 update_rx();
303 interpret = d_xt_for("interpret", Dictionary);
304 string_inject(s, TIB);
305 stack_push(TIB);
306 execute(interpret);
307 }
308
309
310 /* `read_token` reads a token from the specified file.
311 It will stop on a whitespace or newline. It also
312 tries to handle backspaces, though the success of this
313 depends on how your terminal is configured. */
314
not_eol(int ch)315 int not_eol(int ch) {
316 return (ch != (char)10) && (ch != (char)13) && (ch != (char)32) && (ch != EOF) && (ch != 0);
317 }
318
read_token(FILE * file,char * token_buffer,int echo)319 void read_token(FILE *file, char *token_buffer, int echo) {
320 int ch, count;
321 ch = getc(file);
322 count = 0;
323 while (not_eol(ch))
324 {
325 if ((ch == 8 || ch == 127) && count > 0)
326 count--;
327 else
328 token_buffer[count++] = ch;
329 ch = getc(file);
330 }
331 token_buffer[count] = '\0';
332 }
333
334
335 /* Nga ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
336 Copyright (c) 2008 - 2018, Charles Childers
337 Copyright (c) 2009 - 2010, Luke Parrish
338 Copyright (c) 2010, Marc Simpson
339 Copyright (c) 2010, Jay Skeer
340 Copyright (c) 2011, Kenneth Keating
341 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
342
343 #ifndef NUM_DEVICES
344 #define NUM_DEVICES 0
345 #endif
346
ngaLoadImage(char * imageFile)347 CELL ngaLoadImage(char *imageFile) {
348 FILE *fp;
349 CELL imageSize;
350 long fileLen;
351 CELL i;
352 if ((fp = fopen(imageFile, "rb")) != NULL) {
353 /* Determine length (in cells) */
354 fseek(fp, 0, SEEK_END);
355 fileLen = ftell(fp) / sizeof(CELL);
356 rewind(fp);
357 /* Read the file into memory */
358 imageSize = fread(&memory, sizeof(CELL), fileLen, fp);
359 fclose(fp);
360 }
361 return imageSize;
362 }
363
ngaPrepare()364 void ngaPrepare() {
365 ip = sp = rp = 0;
366 for (ip = 0; ip < IMAGE_SIZE; ip++)
367 memory[ip] = 0; /* NOP */
368 for (ip = 0; ip < STACK_DEPTH; ip++)
369 data[ip] = 0;
370 for (ip = 0; ip < ADDRESSES; ip++)
371 address[ip] = 0;
372 }
373
inst_nop()374 void inst_nop() {
375 }
376
inst_lit()377 void inst_lit() {
378 sp++;
379 ip++;
380 TOS = memory[ip];
381 }
382
inst_dup()383 void inst_dup() {
384 sp++;
385 data[sp] = NOS;
386 }
387
inst_drop()388 void inst_drop() {
389 data[sp] = 0;
390 if (--sp < 0)
391 ip = IMAGE_SIZE;
392 }
393
inst_swap()394 void inst_swap() {
395 CELL a;
396 a = TOS;
397 TOS = NOS;
398 NOS = a;
399 }
400
inst_push()401 void inst_push() {
402 rp++;
403 TORS = TOS;
404 inst_drop();
405 }
406
inst_pop()407 void inst_pop() {
408 sp++;
409 TOS = TORS;
410 rp--;
411 }
412
inst_jump()413 void inst_jump() {
414 ip = TOS - 1;
415 inst_drop();
416 }
417
inst_call()418 void inst_call() {
419 rp++;
420 TORS = ip;
421 ip = TOS - 1;
422 inst_drop();
423 }
424
inst_ccall()425 void inst_ccall() {
426 CELL a, b;
427 a = TOS; inst_drop(); /* False */
428 b = TOS; inst_drop(); /* Flag */
429 if (b != 0) {
430 rp++;
431 TORS = ip;
432 ip = a - 1;
433 }
434 }
435
inst_return()436 void inst_return() {
437 ip = TORS;
438 rp--;
439 }
440
inst_eq()441 void inst_eq() {
442 NOS = (NOS == TOS) ? -1 : 0;
443 inst_drop();
444 }
445
inst_neq()446 void inst_neq() {
447 NOS = (NOS != TOS) ? -1 : 0;
448 inst_drop();
449 }
450
inst_lt()451 void inst_lt() {
452 NOS = (NOS < TOS) ? -1 : 0;
453 inst_drop();
454 }
455
inst_gt()456 void inst_gt() {
457 NOS = (NOS > TOS) ? -1 : 0;
458 inst_drop();
459 }
460
inst_fetch()461 void inst_fetch() {
462 switch (TOS) {
463 case -1: TOS = sp - 1; break;
464 case -2: TOS = rp; break;
465 case -3: TOS = IMAGE_SIZE; break;
466 case -4: TOS = CELL_MIN; break;
467 case -5: TOS = CELL_MAX; break;
468 default: TOS = memory[TOS]; break;
469 }
470 }
471
inst_store()472 void inst_store() {
473 if (TOS <= IMAGE_SIZE && TOS >= 0) {
474 memory[TOS] = NOS;
475 inst_drop();
476 inst_drop();
477 } else {
478 ip = IMAGE_SIZE;
479 }
480 }
481
inst_add()482 void inst_add() {
483 NOS += TOS;
484 inst_drop();
485 }
486
inst_sub()487 void inst_sub() {
488 NOS -= TOS;
489 inst_drop();
490 }
491
inst_mul()492 void inst_mul() {
493 NOS *= TOS;
494 inst_drop();
495 }
496
inst_divmod()497 void inst_divmod() {
498 CELL a, b;
499 a = TOS;
500 b = NOS;
501 TOS = b / a;
502 NOS = b % a;
503 }
504
inst_and()505 void inst_and() {
506 NOS = TOS & NOS;
507 inst_drop();
508 }
509
inst_or()510 void inst_or() {
511 NOS = TOS | NOS;
512 inst_drop();
513 }
514
inst_xor()515 void inst_xor() {
516 NOS = TOS ^ NOS;
517 inst_drop();
518 }
519
inst_shift()520 void inst_shift() {
521 CELL y = TOS;
522 CELL x = NOS;
523 if (TOS < 0)
524 NOS = NOS << (TOS * -1);
525 else {
526 if (x < 0 && y > 0)
527 NOS = x >> y | ~(~0U >> y);
528 else
529 NOS = x >> y;
530 }
531 inst_drop();
532 }
533
inst_zret()534 void inst_zret() {
535 if (TOS == 0) {
536 inst_drop();
537 ip = TORS;
538 rp--;
539 }
540 }
541
inst_halt()542 void inst_halt() {
543 ip = IMAGE_SIZE;
544 }
545
inst_ie()546 void inst_ie() {
547 sp++;
548 TOS = NUM_DEVICES;
549 }
550
inst_iq()551 void inst_iq() {
552 CELL Device = TOS;
553 inst_drop();
554 IO_queryHandlers[Device]();
555 }
556
inst_ii()557 void inst_ii() {
558 CELL Device = TOS;
559 inst_drop();
560 IO_deviceHandlers[Device]();
561 }
562
563 Handler instructions[] = {
564 inst_nop, inst_lit, inst_dup, inst_drop, inst_swap, inst_push, inst_pop,
565 inst_jump, inst_call, inst_ccall, inst_return, inst_eq, inst_neq, inst_lt,
566 inst_gt, inst_fetch, inst_store, inst_add, inst_sub, inst_mul, inst_divmod,
567 inst_and, inst_or, inst_xor, inst_shift, inst_zret, inst_halt, inst_ie,
568 inst_iq, inst_ii
569 };
570
ngaProcessOpcode(CELL opcode)571 void ngaProcessOpcode(CELL opcode) {
572 if (opcode != 0)
573 instructions[opcode]();
574 }
575
ngaValidatePackedOpcodes(CELL opcode)576 int ngaValidatePackedOpcodes(CELL opcode) {
577 CELL raw = opcode;
578 CELL current;
579 int valid = -1;
580 int i;
581 for (i = 0; i < 4; i++) {
582 current = raw & 0xFF;
583 if (!(current >= 0 && current <= 29))
584 valid = 0;
585 raw = raw >> 8;
586 }
587 return valid;
588 }
589
ngaProcessPackedOpcodes(CELL opcode)590 void ngaProcessPackedOpcodes(CELL opcode) {
591 CELL raw = opcode;
592 int i;
593 for (i = 0; i < 4; i++) {
594 ngaProcessOpcode(raw & 0xFF);
595 raw = raw >> 8;
596 }
597 }
598