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