Β΅Eforth π Brad Nelson π January 23, 2021 Introduction π β’ EForth in C has too much C β’ Minimize C code β’ But avoid redundancy β’ Inspired by ColorForth Platform Support π β’ Linux β’ Windows β’ ESP32 Arduino β’ Web (WIP) Platform Packaging π β’ Linux -- includes β’ Windows -- includes β’ ESP32 -- concat script β’ Web (WIP) -- regex script Core Words 0= 0< + U/MOD */MOD AND OR XOR DUP SWAP OVER DROP @ L@ C@ ! L! C! SP@ SP! RP@ RP! >R R> R@ : ; EXIT EXECUTE BRANCH 0BRANCH DONEXT DOLIT ALITERAL CELL DOES> IMMEDIATE 'SYS #define OPCODE_LIST \ X("0=", ZEQUAL, tos = !tos ? -1 : 0) \ X("0<", ZLESS, tos = (tos|0) < 0 ? -1 : 0) \ X("+", PLUS, tos += *sp--) \ #define OPCODE_LIST \ X("0=", ZEQUAL, tos = !tos ? -1 : 0) \ X("0<", ZLESS, tos = (tos|0) < 0 ? -1 : 0) \ X("+", PLUS, tos += *sp--) \ X("U/MOD", USMOD, w = *sp; *sp = (ucell_t) w % (ucell_t) tos; \ tos = (ucell_t) w / (ucell_t) tos) \ X("*/MOD", SSMOD, SSMOD_FUNC) \ X("AND", AND, tos &= *sp--) \ X("OR", OR, tos |= *sp--) \ X("XOR", XOR, tos ^= *sp--) \ X("DUP", DUP, DUP) \ X("SWAP", SWAP, w = tos; tos = *sp; *sp = w) \ X("OVER", OVER, DUP; tos = sp[-1]) \ X("DROP", DROP, DROP) \ X("@", AT, tos = *(cell_t *) tos) \ X("L@", LAT, tos = *(int32_t *) tos) \ X("C@", CAT, tos = *(uint8_t *) tos) \ X("!", STORE, *(cell_t *) tos = *sp--; DROP) \ X("L!", LSTORE, *(int32_t *) tos = *sp--; DROP) \ X("C!", CSTORE, *(uint8_t *) tos = *sp--; DROP) \ X("SP@", SPAT, DUP; tos = (cell_t) sp) \ X("SP!", SPSTORE, sp = (cell_t *) tos; DROP) \ X("RP@", RPAT, DUP; tos = (cell_t) rp) \ X("RP!", RPSTORE, rp = (cell_t *) tos; DROP) \ X(">R", TOR, *++rp = tos; DROP) \ X("R>", FROMR, DUP; tos = *rp; --rp) \ X("R@", RAT, DUP; tos = *rp) \ X("EXECUTE", EXECUTE, w = tos; DROP; JMPW) \ X("BRANCH", BRANCH, ip = (cell_t *) *ip) \ X("0BRANCH", ZBRANCH, if (!tos) ip = (cell_t *) *ip; else ++ip; DROP) \ X("DONEXT", DONEXT, *rp = *rp - 1; \ if (~*rp) ip = (cell_t *) *ip; else (--rp, ++ip)) \ X("DOLIT", DOLIT, DUP; tos = *ip++) \ X("ALITERAL", ALITERAL, COMMA(g_sys.DOLIT_XT); COMMA(tos); DROP) \ X("CELL", CELL, DUP; tos = sizeof(cell_t)) \ X("FIND", FIND, tos = find((const char *) *sp, tos); --sp) \ X("PARSE", PARSE, DUP; tos = parse(tos, sp)) \ X("S>NUMBER?", CONVERT, tos = convert((const char *) *sp, tos, sp); \ if (!tos) --sp) \ X("CREATE", CREATE, DUP; DUP; tos = parse(32, sp); \ create((const char *) *sp, tos, 0, ADDR_DOCREATE); \ COMMA(0); --sp; DROP) \ X("DOES>", DOES, DOES(ip); ip = (cell_t *) *rp; --rp) \ X("IMMEDIATE", IMMEDIATE, IMMEDIATE()) \ X("'SYS", SYS, DUP; tos = (cell_t) &g_sys) \ X("YIELD", YIELD, PARK; return) \ X(":", COLON, DUP; DUP; tos = parse(32, sp); \ create((const char *) *sp, tos, 0, ADDR_DOCOLON); \ g_sys.state = -1; --sp; DROP) \ X("EVALUATE1", EVALUATE1, DUP; sp = evaluate1(sp); w = *sp--; DROP; \ if (w) JMPW) \ X("EXIT", EXIT, ip = (cell_t *) *rp--) \ X(";", SEMICOLON, COMMA(g_sys.DOEXIT_XT); g_sys.state = 0) \ #define JMPW goto **(void **) w #define NEXT w = *ip++; JMPW #define ADDR_DOCOLON && OP_DOCOLON #define ADDR_DOCREATE && OP_DOCREATE #define ADDR_DODOES && OP_DODOES static void ueforth_run(void) { if (!g_sys.ip) { #define X(name, op, code) create(name, sizeof(name) - 1, name[0] == ';', && OP_ ## op); PLATFORM_OPCODE_LIST OPCODE_LIST #undef X return; } register cell_t *ip = g_sys.ip, *rp = g_sys.rp, *sp = g_sys.sp, tos, w; DROP; NEXT; #define X(name, op, code) OP_ ## op: { code; } NEXT; PLATFORM_OPCODE_LIST OPCODE_LIST #undef X OP_DOCOLON: ++rp; *rp = (cell_t) ip; ip = (cell_t *) (w + sizeof(cell_t)); NEXT; OP_DOCREATE: DUP; tos = w + sizeof(cell_t) * 2; NEXT; OP_DODOES: DUP; tos = w + sizeof(cell_t) * 2; ++rp; *rp = (cell_t) ip; ip = (cell_t *) *(cell_t *) (w + sizeof(cell_t)); NEXT; } int main(int argc, char *argv[]) { void *heap = mmap(0, HEAP_SIZE, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); ueforth(argc, argv, heap, boot, sizeof(boot)); return 1; } const char boot[] = ": ( 41 parse drop drop ; immediate\n" "\n" "( Useful Basic Compound Words )\n" ": 2drop ( n n -- ) drop drop ;\n" ": 2dup ( a b -- a b a b ) over over ;\n" ": nip ( a b -- b ) swap drop ;\n" ": rdrop ( r: n n -- ) r> r> drop >r ;\n" ": */ ( n n n -- n ) */mod nip ;\n" ": * ( n n -- n ) 1 */ ;\n" ": /mod ( n n -- n n ) 1 swap */mod ;\n" ": / ( n n -- n ) /mod nip ;\n" ": mod ( n n -- n ) /mod drop ;\n" ": invert ( n -- ~n ) -1 xor ;\n" ": negate ( n -- -n ) invert 1 + ;\n" ": - ( n n -- n ) negate + ;\n" ": rot ( a b c -- c a b ) >r swap r> swap ;\n" ": -rot ( a b c -- b c a ) swap >r swap r> ;\n" ": < ( a b -- a ( a b -- a>b ) swap - 0< ;\n" ": = ( a b -- a!=b ) - 0= ;\n" ": <> ( a b -- a!=b ) = 0= ;\n" ": bl 32 ; : nl 10 ;\n" ": 1+ 1 + ; : 1- 1 - ;\n" ": 2* 2 * ; : 2/ 2 / ;\n" ": 4* 4 * ; : 4/ 4 / ;\n" ": +! ( n a -- ) swap over @ + swap ! ;\n" "\n" Tricky Words PARSE ( ch -- a n ) S>NUMBER? ( a n -- n f | 0 ) CREATE ( "name" -- ) FIND ( a n -- xt | 0 ) EVALUATE1 ( -- ) static struct { const char *tib; cell_t ntib, tin, state, base; cell_t *heap, *last, notfound; int argc; char **argv; cell_t *ip, *sp, *rp; // Parked alternates cell_t DOLIT_XT, DOEXIT_XT, YIELD_XT; } g_sys; PARSE ( ch -- a n ) π 'TIB ---> [...............] | | >IN #TIB static char spacefilter(char ch) { return ch == '\t' || ch == '\n' || ch == '\r' ? ' ' : ch; } static cell_t parse(cell_t sep, cell_t *ret) { while (g_sys.tin < g_sys.ntib && spacefilter(g_sys.tib[g_sys.tin]) == sep) { ++g_sys.tin; } *ret = (cell_t) (g_sys.tib + g_sys.tin); while (g_sys.tin < g_sys.ntib && spacefilter(g_sys.tib[g_sys.tin]) != sep) { ++g_sys.tin; } cell_t len = g_sys.tin - (*ret - (cell_t) g_sys.tib); if (g_sys.tin < g_sys.ntib) { ++g_sys.tin; } return len; } S>NUMBER? ( a n -- n f | 0 ) π BASE static cell_t convert(const char *pos, cell_t n, cell_t *ret) { *ret = 0; cell_t negate = 0; cell_t base = g_sys.base; if (!n) { return 0; } if (pos[0] == '-') { negate = -1; ++pos; --n; } if (pos[0] == '$') { base = 16; ++pos; --n; } for (; n; --n) { uintptr_t d = pos[0] - '0'; if (d > 9) { d = LOWER(d) - 7; if (d < 10) { return 0; } } if (d >= base) { return 0; } *ret = *ret * base + d; ++pos; } if (negate) { *ret = -*ret; } return -1; } CREATE ( "name" -- ) π LAST static void create(const char *name, cell_t length, cell_t flags, void *op) { char *pos = (char *) g_sys.heap; for (cell_t n = length; n; --n) { *pos++ = *name++; } // name g_sys.heap += CELL_LEN(length); *g_sys.heap++ = length; // length *g_sys.heap++ = (cell_t) g_sys.last; // link *g_sys.heap++ = flags; // flags g_sys.last = g_sys.heap; *g_sys.heap++ = (cell_t) op; // code } FIND ( a n -- xt | 0 ) π 'NOTFOUND static cell_t find(const char *name, cell_t len) { cell_t *pos = g_sys.last; cell_t clen = CELL_LEN(len); while (pos) { if (len == pos[-3] && same(name, (const char *) &pos[-3 - clen], len) == 0) { return (cell_t) pos; } pos = (cell_t *) pos[-2]; // Follow link } return 0; } EVALUATE1 ( -- ) π STATE static cell_t *evaluate1(cell_t *sp) { cell_t call = 0; cell_t name; cell_t len = parse(' ', &name); cell_t xt = find((const char *) name, len); if (xt) { if (g_sys.state && !(((cell_t *) xt)[-1] & 1)) { // bit 0 of flags is immediate *g_sys.heap++ = xt; } else { call = xt; } } else { cell_t n; cell_t ok = convert((const char *) name, len, &n); if (ok) { if (g_sys.state) { *g_sys.heap++ = g_sys.DOLIT_XT; *g_sys.heap++ = n; } else { *++sp = n; } } else { *++sp = name; *++sp = len; *++sp = -1; call = g_sys.notfound; } } *++sp = call; return sp; } One other "hard" word */MOD # define SSMOD_FUNC \ dcell_t d = (dcell_t) *sp * (dcell_t) sp[-1]; \ --sp; cell_t a = (cell_t) (d < 0 ? ~(~d / tos) : d / tos); \ *sp = (cell_t) (d - ((dcell_t) a) * tos); tos = a #endif 18 common/calling.h 148 common/core.h 25 common/interp.h 82 common/opcodes.h 23 posix/posix_main.c 296 total 224 common/boot.fs What about I/O? Dynamic Linking #define PLATFORM_OPCODE_LIST \ X("DLSYM", DLSYM, tos = \ (cell_t) dlsym((void *) *sp, (void *) tos); --sp) \ CALLING_OPCODE_LIST \ #define PLATFORM_OPCODE_LIST \ X("GETPROCADDRESS", GETPROCADDRESS, \ tos = (cell_t) GetProcAddress((HMODULE) *sp, (LPCSTR) tos); --sp) \ X("LOADLIBRARYA", LOADLIBRARYA, \ tos = (cell_t) LoadLibraryA((LPCSTR) tos)) \ CALLING_OPCODE_LIST \ #define CALLING_OPCODE_LIST \ X("CALL0", OP_CALL0, tos = ((call_t) tos)()) \ X("CALL1", OP_CALL1, tos = ((call_t) tos)(*sp); --sp) \ X("CALL2", OP_CALL2, tos = ((call_t) tos)(sp[-1], *sp); sp -= 2) \ X("CALL3", OP_CALL3, tos = ((call_t) tos)(sp[-2], sp[-1], *sp); sp -= 3) \ #define CALLING_OPCODE_LIST \ X("CALL0", OP_CALL0, tos = ((call_t) tos)()) \ X("CALL1", OP_CALL1, tos = ((call_t) tos)(*sp); --sp) \ X("CALL2", OP_CALL2, tos = ((call_t) tos)(sp[-1], *sp); sp -= 2) \ X("CALL3", OP_CALL3, tos = ((call_t) tos)(sp[-2], sp[-1], *sp); sp -= 3) \ X("CALL4", OP_CALL4, tos = ((call_t) tos)(sp[-3], sp[-2], sp[-1], *sp); sp -= 4) \ X("CALL5", OP_CALL5, tos = ((call_t) tos)(sp[-4], sp[-3], sp[-2], sp[-1], *sp); sp -= 5) \ X("CALL6", OP_CALL6, tos = ((call_t) tos)(sp[-5], sp[-4], sp[-3], sp[-2], sp[-1], *sp); sp -= 6) \ X("CALL7", OP_CALL7, tos = ((call_t) tos)(sp[-6], sp[-5], sp[-4], sp[-3], sp[-2], sp[-1], *sp); sp -= 7) \ X("CALL8", OP_CALL8, tos = ((call_t) tos)(sp[-7], sp[-6], sp[-5], sp[-4], sp[-3], sp[-2], sp[-1], *sp); sp -= 8) \ X("CALL9", OP_CALL9, tos = ((call_t) tos)(sp[-8], sp[-7], sp[-6], sp[-5], sp[-4], sp[-3], sp[-2], sp[-1], *sp); sp -= 9) \ X("CALL10", OP_CALL10, tos = ((call_t) tos)(sp[-9], sp[-8], sp[-7], sp[-6], sp[-5], sp[-4], sp[-3], sp[-2], sp[-1], *sp); sp -= 10) \ #define PLATFORM_OPCODE_LIST \ /* Serial */ \ X("Serial.begin", SERIAL_BEGIN, Serial.begin(tos); DROP) \ X("Serial.end", SERIAL_END, Serial.end()) \ X("Serial.available", SERIAL_AVAILABLE, DUP; tos = Serial.available()) \ X("Serial.readBytes", SERIAL_READ_BYTES, tos = Serial.readBytes((uint8_t *) *sp, tos); --sp) \ X("Serial.write", SERIAL_WRITE, tos = Serial.write((const uint8_t *) *sp, tos); --sp) \ X("Serial.flush", SERIAL_FLUSH, Serial.flush()) \ Forth Features π β’ FOR..NEXT but also DO..LOOP β’ TASK & Streams β’ DEFER/IS & VALUE/TO LINUX π β’ Start of X11 bindings β’ Nice for testing β’ Maybe use in place of gforth WINDOWS π β’ Win32 console support w/ colors β’ MessageBox β’ Made an icon (using Forth Haiku) @eforth.pngWindows Defender PAIN! π β’ Flagged as a virus / trojan β’ This also has afflicted Win32Forth β’ Switched to Visual Studio helped some β’ Buy a signing certificate may help β’ Big threat to language freedom #define NEXT goto next #define JMPW goto work #define ADDR_DOCOLON ((void *) OP_DOCOLON) #define ADDR_DOCREATE ((void *) OP_DOCREATE) #define ADDR_DODOES ((void *) OP_DODOES) enum { OP_DOCOLON = 0, OP_DOCREATE, OP_DODOES, #define X(name, op, code) OP_ ## op, PLATFORM_OPCODE_LIST OPCODE_LIST #undef X }; static void ueforth_run(void) { if (!g_sys.ip) { #define X(name, op, code) \ create(name, sizeof(name) - 1, name[0] == ';', (void *) OP_ ## op); PLATFORM_OPCODE_LIST OPCODE_LIST #undef X return; } register cell_t *ip = g_sys.ip, *rp = g_sys.rp, *sp = g_sys.sp, tos, w; DROP; for (;;) { next: w = *ip++; work: switch (*(cell_t *) w & 0xff) { #define X(name, op, code) case OP_ ## op: { code; } NEXT; PLATFORM_OPCODE_LIST OPCODE_LIST #undef X case OP_DOCOLON: ++rp; *rp = (cell_t) ip; ip = (cell_t *) (w + sizeof(cell_t)); NEXT; case OP_DOCREATE: DUP; tos = w + sizeof(cell_t) * 2; NEXT; case OP_DODOES: DUP; tos = w + sizeof(cell_t) * 2; ++rp; *rp = (cell_t) ip; ip = (cell_t *) *(cell_t *) (w + sizeof(cell_t)); NEXT; } } } OS SURVEY π Please type the OSes you use in the chat! WEB π β’ Passes asm.js validation -- my own :-( β’ Doesn't run yet β’ Maybe generating WebAssembly would be easier? β’ Dr. Ting's has beat me to the punch :-) case 13: // SWAP w = (tos|0); tos = (i32[sp>>2]|0); i32[sp>>2] = w; break; case 14: // OVER sp = (sp + 4) | 0; i32[sp>>2] = (tos|0); tos = (i32[(sp - 4)>>2]|0); break; ESP32 π β’ Runs on ESP32 and ESP32-CAM! β’ Lots of bindings: β’ malloc, free, realloc β’ Serial & Bluetooth Serial β’ GPIO, I2C β’ Files, SPIFFS, SD_MMC β’ WiFi, mDNS, WebServer, Web UI β’ Camera What's Next? π β’ More ESP32 bindings β’ Write a serial editor eforth.appspot.com β QUESTIONS?