add example calling retro from C

FossilOrigin-Name: bd9d8be4b5b523620b4d2b1f7208b1091ca8573fe0eb08a8a7f83fe569571c70
This commit is contained in:
crc 2021-03-29 13:03:10 +00:00
parent e2a139c5fe
commit b43e63ee69
6 changed files with 695 additions and 1 deletions

View file

@ -3681,6 +3681,74 @@ Assuming an Nga built with 524287 cells of memory:
The buffers at the end of memory will resize when specific
variables related to them are altered.
# Calling Retro from C
The C implementation of Retro provides several functions for
interacting with code written in Retro.
## Dictionary
The dictionary is a linked list, with a pointer to the most
recent entry stored in address 2.
You can access the fields for each entry using:
d_link
d_xt
d_class
d_name
Each takes a dictionary header address (the "dictionary token")
and returns a pointer to the Retro address for the desired data.
To find a dictionary token, use `d_lookup`. This takes the address
of the dictionary to search (`memory[2]` in most cases) and the
name of the word to find.
There is also `d_xt_for()` which takes a name and a dictionary
pointer and returns the execution token for the specified word.
## Strings
Like C, Retro uses NUL terminated strings. But, since all
addressing is 32-bit (or 64-bit, depending on your configuration),
some conversion is needed.
To get a C version of a string, use `string_extract()`. This takes
a Retro address and returns a pointer to a C string.
Example:
// Get the name of the most recently defined word
string_extract(d_name(memory[2]));
To push a C string into Retro memory, use `string_inject()`. This
takes a C string and a Retro address.
// Copy a string to the TIB buffer
string_inject("hello", 1024);
## Stack
You can push values to the stack with `stack_push()` and pop them
off with `stack_pop()`.
## Interacting
If you have a word named `hello` that you wish to run:
execute(d_xt_for("hello", memory[2]));
If you want to evaluate a token as if it was typed into a Retro
listener:
string_inject("token", 1024);
stack_push(1024);
execute("interpret", memory[2]);
The `interpret` word handles things like prefixes, so this is
needed if you want to run something that needs those.
# Historical Papers and Notes
## On the Naming of RETRO

View file

@ -0,0 +1,67 @@
# Calling Retro from C
The C implementation of Retro provides several functions for
interacting with code written in Retro.
## Dictionary
The dictionary is a linked list, with a pointer to the most
recent entry stored in address 2.
You can access the fields for each entry using:
d_link
d_xt
d_class
d_name
Each takes a dictionary header address (the "dictionary token")
and returns a pointer to the Retro address for the desired data.
To find a dictionary token, use `d_lookup`. This takes the address
of the dictionary to search (`memory[2]` in most cases) and the
name of the word to find.
There is also `d_xt_for()` which takes a name and a dictionary
pointer and returns the execution token for the specified word.
## Strings
Like C, Retro uses NUL terminated strings. But, since all
addressing is 32-bit (or 64-bit, depending on your configuration),
some conversion is needed.
To get a C version of a string, use `string_extract()`. This takes
a Retro address and returns a pointer to a C string.
Example:
// Get the name of the most recently defined word
string_extract(d_name(memory[2]));
To push a C string into Retro memory, use `string_inject()`. This
takes a C string and a Retro address.
// Copy a string to the TIB buffer
string_inject("hello", 1024);
## Stack
You can push values to the stack with `stack_push()` and pop them
off with `stack_pop()`.
## Interacting
If you have a word named `hello` that you wish to run:
execute(d_xt_for("hello", memory[2]));
If you want to evaluate a token as if it was typed into a Retro
listener:
string_inject("token", 1024);
stack_push(1024);
execute("interpret", memory[2]);
The `interpret` word handles things like prefixes, so this is
needed if you want to run something that needs those.

View file

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head>
<title>.</title>
<style type="text/css">
* { color: #000; background: #fff; max-width: 700px; }
tt, pre { background: #dedede; color: #111; font-family: monospace;
white-space: pre; display: block; width: 100%; }
.indentedcode { margin-left: 2em; margin-right: 2em; }
.codeblock {
background: #dedede; color: #111; font-family: monospace;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
padding: 7px;
display: block;
}
.indentedlist { margin-left: 2em; color: #000; }
span { white-space: pre; }
.text { color: #000; white-space: pre; background: #dedede; }
.colon { color: #000; background: #dedede; }
.note { color: #000; background: #dedede; }
.str { color: #000; text-decoration: underline; background: #dedede; }
.num { color: #000; background: #dedede; font-weight: bold; font-style: italic; }
.fnum { color: #000; font-weight: bold; background: #dedede; }
.ptr { color: #000; font-weight: bold; background: #dedede; }
.fetch { color: #000; font-style: italic; background: #dedede; }
.store { color: #000; font-style: italic; background: #dedede; }
.char { color: #000; background: #dedede; }
.inst { color: #000; background: #dedede; }
.defer { color: #000; background: #dedede; }
.imm { color: #000; font-weight: bold; background: #dedede; }
.prim { color: #000; font-weight: bolder; background: #dedede; }
.tt { white-space: pre; font-family: monospace; background: #dedede; }
.h1, .h2, .h3, .h4 { white-space: normal; }
.h1 { font-size: 125%; }
.h2 { font-size: 120%; }
.h3 { font-size: 115%; }
.h4 { font-size: 110%; }
.hr { display: block; height: 2px; background: #000000; }
</style>
</head><body>
<p><span class="h1">Calling Retro from C</span>
<br/><br/>
The C implementation of Retro provides several functions for
interacting with code written in Retro.
<br/><br/>
<span class="h2">Dictionary</span>
<br/><br/>
The dictionary is a linked list, with a pointer to the most
recent entry stored in address 2.
<br/><br/>
You can access the fields for each entry using:
<br/><br/>
<tt class='indentedcode'>d_link</tt>
<tt class='indentedcode'>d_xt</tt>
<tt class='indentedcode'>d_class</tt>
<tt class='indentedcode'>d_name</tt>
<br/><br/>
Each takes a dictionary header address (the "dictionary token")
and returns a pointer to the Retro address for the desired data.
<br/><br/>
To find a dictionary token, use <span class="tt">d_lookup</span>. This takes the address
of the dictionary to search (<span class="tt">memory[2]</span> in most cases) and the
name of the word to find.
<br/><br/>
There is also <span class="tt">d_xt_for()</span> which takes a name and a dictionary
pointer and returns the execution token for the specified word.
<br/><br/>
<span class="h2">Strings</span>
<br/><br/>
Like C, Retro uses NUL terminated strings. But, since all
addressing is 32-bit (or 64-bit, depending on your configuration),
some conversion is needed.
<br/><br/>
To get a C version of a string, use <span class="tt">string_extract()</span>. This takes
a Retro address and returns a pointer to a C string.
<br/><br/>
Example:
<br/><br/>
<tt class='indentedcode'>//&nbsp;Get&nbsp;the&nbsp;name&nbsp;of&nbsp;the&nbsp;most&nbsp;recently&nbsp;defined&nbsp;word</tt>
<tt class='indentedcode'>string_extract(d_name(memory[2]));</tt>
<br/><br/>
To push a C string into Retro memory, use <span class="tt">string_inject()</span>. This
takes a C string and a Retro address.
<br/><br/>
<tt class='indentedcode'>//&nbsp;Copy&nbsp;a&nbsp;string&nbsp;to&nbsp;the&nbsp;TIB&nbsp;buffer</tt>
<tt class='indentedcode'>string_inject("hello",&nbsp;1024);</tt>
<br/><br/>
<span class="h2">Stack</span>
<br/><br/>
You can push values to the stack with <span class="tt">stack_push()</span> and pop them
off with <span class="tt">stack_pop()</span>.
<br/><br/>
<span class="h2">Interacting</span>
<br/><br/>
If you have a word named <span class="tt">hello</span> that you wish to run:
<br/><br/>
<tt class='indentedcode'>execute(d_xt_for("hello",&nbsp;memory[2]));</tt>
<br/><br/>
If you want to evaluate a token as if it was typed into a Retro
listener:
<br/><br/>
<tt class='indentedcode'>string_inject("token",&nbsp;1024);</tt>
<tt class='indentedcode'>stack_push(1024);</tt>
<tt class='indentedcode'>execute("interpret",&nbsp;memory[2]);</tt>
<br/><br/>
The <span class="tt">interpret</span> word handles things like prefixes, so this is
needed if you want to run something that needs those.
</p>
</body></html>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,444 @@
/*
* 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;
}
}

View file

@ -52,6 +52,7 @@ This contains the list of files for the chapters in the book.
{ 'Basic_I/O 'internals/io }
{ 'Detailed_I/O 'internals/io-devices }
{ 'Image 'internals/image }
{ 'Calling_Retro_From_C 'internals/calling-retro-from-c }
{ 'Historical_Papers 'tech-notes/historical-papers }
{ 'Metacompiling 'tech-notes/metacompilation }
{ 'Self-Hosting 'tech-notes/self-hosting }