retroforth/example/calling-retro-from-c.c
crc b43e63ee69 add example calling retro from C
FossilOrigin-Name: bd9d8be4b5b523620b4d2b1f7208b1091ca8573fe0eb08a8a7f83fe569571c70
2021-03-29 13:03:10 +00:00

444 lines
8.8 KiB
C

/*
* This demonstrates calling RETRO code from C.
*
* Assuming this is named `demo.c`:
*
* (1) Prepare a demo image:
*
* cp ngaImage demoImage
* retro-extend demoImage demo.c
*
* (2) Build:
*
* make demo
*
* (3) Run:
*
* ./demo
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* The next few lines will be compiled into demoImage
* when the `retro-extend` line is run from step 1.
*
~~~
:average (...n-m) [ [ + ] times ] sip / ;
:hello 'hello_world! s:put nl ;
~~~
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
#define CELL int32_t
#define IMAGE_SIZE 242000
#define STACK_DEPTH 256
#define NUM_DEVICES 2
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define TIB 1025 /* Location of TIB */
#define D_OFFSET_LINK 0 /* Dictionary Format Info. Update if */
#define D_OFFSET_XT 1 /* you change the dictionary fields. */
#define D_OFFSET_CLASS 2
#define D_OFFSET_NAME 3
CELL sp, rp, ip;
CELL data[STACK_DEPTH];
CELL address[STACK_DEPTH];
CELL memory[IMAGE_SIZE + 1];
void ngaLoadImage();
void ngaPrepare();
void ngaProcessOpcode(CELL opcode);
void ngaProcessPackedOpcodes(CELL opcode);
int ngaValidatePackedOpcodes(CELL opcode);
char string_data[8192];
/*---------------------------------------------------------------------
Strings are next. RETRO uses C-style NULL terminated strings. So I
can easily inject or extract a string. Injection iterates over the
string, copying it into the image. This also takes care to ensure
that the NULL terminator is added.
---------------------------------------------------------------------*/
CELL string_inject(char *str, CELL buffer) {
CELL m, i;
if (!str) {
memory[buffer] = 0;
return 0;
}
m = strlen(str);
i = 0;
while (m > 0) {
memory[buffer + i] = (CELL)str[i];
memory[buffer + i + 1] = 0;
m--; i++;
}
return buffer;
}
/*---------------------------------------------------------------------
Extracting a string is similar, but I have to iterate over the VM
memory instead of a C string and copy the charaters into a buffer.
This uses a static buffer (`string_data`) as I prefer to avoid using
`malloc()`.
---------------------------------------------------------------------*/
char *string_extract(CELL at) {
CELL starting = at;
CELL i = 0;
while(memory[starting] && i < 8192)
string_data[i++] = (char)memory[starting++];
string_data[i] = 0;
return (char *)string_data;
}
/*---------------------------------------------------------------------
Continuing along, I now define functions to access the dictionary.
RETRO's dictionary is a linked list. Each entry is setup like:
0000 Link to previous entry (NULL if this is the root entry)
0001 Pointer to definition start
0002 Pointer to class handler
0003 Start of a NULL terminated string with the word name
First, functions to access each field. The offsets were defineed at
the start of the file.
---------------------------------------------------------------------*/
CELL d_link(CELL dt) {
return dt + D_OFFSET_LINK;
}
CELL d_xt(CELL dt) {
return dt + D_OFFSET_XT;
}
CELL d_class(CELL dt) {
return dt + D_OFFSET_CLASS;
}
CELL d_name(CELL dt) {
return dt + D_OFFSET_NAME;
}
/*---------------------------------------------------------------------
Next, a more complext word. This will walk through the entries to
find one with a name that matches the specified name. This is *slow*,
but works ok unless you have a really large dictionary. (I've not
run into issues with this in practice).
---------------------------------------------------------------------*/
CELL d_lookup(CELL Dictionary, char *name) {
CELL dt = 0;
CELL i = Dictionary;
char *dname;
while (memory[i] != 0 && i != 0) {
dname = string_extract(d_name(i));
if (strcmp(dname, name) == 0) {
dt = i;
i = 0;
} else {
i = memory[i];
}
}
return dt;
}
/*---------------------------------------------------------------------
My last dictionary related word returns the `xt` pointer for a word.
This is used to help keep various important bits up to date.
---------------------------------------------------------------------*/
CELL d_xt_for(char *Name, CELL Dictionary) {
return memory[d_xt(d_lookup(Dictionary, Name))];
}
CELL stack_pop() {
sp--;
return data[sp + 1];
}
void stack_push(CELL value) {
sp++;
data[sp] = value;
}
void output() {
putc(stack_pop(), stdout);
}
void output_query() {
stack_push(0);
stack_push(0);
}
void input() {
stack_push(getc(stdin));
if (data[sp] == 127) data[sp] = 8;
}
void input_query() {
stack_push(0);
stack_push(1);
}
void execute(CELL cell) {
rp = 1;
ip = cell;
while (ip < IMAGE_SIZE) {
if (ngaValidatePackedOpcodes(memory[ip]) != 0) {
ngaProcessPackedOpcodes(memory[ip]);
} else {
printf("Invalid instruction!\n");
exit(1);
}
ip++;
if (rp == 0)
ip = IMAGE_SIZE;
}
}
int main(int argc, char **argv) {
ngaPrepare();
ngaLoadImage("demoImage");
execute(d_xt_for("hello", memory[2]));
}
typedef void (*Handler)(void);
Handler IO_deviceHandlers[] = { output, input };
Handler IO_queryHandlers[] = { output_query, input_query };
void ngaLoadImage(char *imageFile) {
FILE *fp;
CELL i;
long fileLen;
if ((fp = fopen(imageFile, "rb")) != NULL) {
fseek(fp, 0, SEEK_END);
fileLen = ftell(fp) / sizeof(CELL);
rewind(fp);
fread(&memory, sizeof(CELL), fileLen, fp);
fclose(fp);
}
}
void ngaPrepare() {
for (ip = 0; ip < IMAGE_SIZE; ip++)
memory[ip] = 0;
for (ip = 0; ip < STACK_DEPTH; ip++)
data[ip] = address[ip] = 0;
ip = sp = rp = 0;
}
void inst_nop() {
}
void inst_lit() {
ip++;
stack_push(memory[ip]);
}
void inst_dup() {
sp++;
data[sp] = data[sp-1];
}
void inst_drop() {
data[sp] = 0;
sp--;
}
void inst_swap() {
CELL a;
a = data[sp];
data[sp] = data[sp-1];
data[sp-1] = a;
}
void inst_push() {
rp++;
address[rp] = stack_pop();
}
void inst_pop() {
stack_push(address[rp]);
rp--;
}
void inst_jump() {
ip = stack_pop() - 1;
}
void inst_call() {
rp++;
address[rp] = ip;
ip = stack_pop() - 1;
}
void inst_ccall() {
if (data[sp-1] != 0) {
rp++;
address[rp] = ip;
ip = data[sp] - 1;
}
sp -= 2;
}
void inst_return() {
ip = address[rp];
rp--;
}
void inst_eq() {
data[sp-1] = (data[sp-1] == data[sp]) ? -1 : 0;
inst_drop();
}
void inst_neq() {
data[sp-1] = (data[sp-1] != data[sp]) ? -1 : 0;
inst_drop();
}
void inst_lt() {
data[sp-1] = (data[sp-1] < data[sp]) ? -1 : 0;
inst_drop();
}
void inst_gt() {
data[sp-1] = (data[sp-1] > data[sp]) ? -1 : 0;
inst_drop();
}
void inst_fetch() {
switch (data[sp]) {
case -1: data[sp] = sp - 1; break;
case -2: data[sp] = rp; break;
case -3: data[sp] = IMAGE_SIZE; break;
default: data[sp] = memory[data[sp]]; break;
}
}
void inst_store() {
memory[data[sp]] = data[sp-1];
sp -= 2;
}
void inst_add() {
data[sp-1] += data[sp];
inst_drop();
}
void inst_sub() {
data[sp-1] -= data[sp];
inst_drop();
}
void inst_mul() {
data[sp-1] *= data[sp];
inst_drop();
}
void inst_divmod() {
CELL a, b;
a = data[sp];
b = data[sp-1];
data[sp] = b / a;
data[sp-1] = b % a;
}
void inst_and() {
data[sp-1] = data[sp] & data[sp-1];
inst_drop();
}
void inst_or() {
data[sp-1] = data[sp] | data[sp-1];
inst_drop();
}
void inst_xor() {
data[sp-1] = data[sp] ^ data[sp-1];
inst_drop();
}
void inst_shift() {
CELL y = data[sp];
CELL x = data[sp-1];
if (data[sp] < 0)
data[sp-1] = data[sp-1] << (data[sp] * -1);
else {
if (x < 0 && y > 0)
data[sp-1] = x >> y | ~(~0U >> y);
else
data[sp-1] = x >> y;
}
inst_drop();
}
void inst_zret() {
if (data[sp] == 0) {
inst_drop();
ip = address[rp];
rp--;
}
}
void inst_end() {
ip = IMAGE_SIZE;
}
void inst_ie() {
stack_push(NUM_DEVICES);
}
void inst_iq() {
IO_queryHandlers[data[sp--]]();
}
void inst_ii() {
IO_deviceHandlers[data[sp--]]();
}
Handler instructions[] = {
inst_nop, inst_lit, inst_dup, inst_drop, inst_swap, inst_push, inst_pop,
inst_jump, inst_call, inst_ccall, inst_return, inst_eq, inst_neq, inst_lt,
inst_gt, inst_fetch, inst_store, inst_add, inst_sub, inst_mul, inst_divmod,
inst_and, inst_or, inst_xor, inst_shift, inst_zret, inst_end, inst_ie,
inst_iq, inst_ii
};
void ngaProcessOpcode(CELL opcode) {
if (opcode != 0)
instructions[opcode]();
}
int ngaValidatePackedOpcodes(CELL opcode) {
CELL raw = opcode;
CELL current;
int valid = -1;
for (int i = 0; i < 4; i++) {
current = raw & 0xFF;
if (!(current >= 0 && current <= 29))
valid = 0;
raw = raw >> 8;
}
return valid;
}
void ngaProcessPackedOpcodes(CELL opcode) {
CELL raw = opcode;
for (int i = 0; i < 4; i++) {
ngaProcessOpcode(raw & 0xFF);
raw = raw >> 8;
}
}