ΞΌEforth
Graphics and Sound
on the Web
-- Redux!
π
December 17, 2022
Now
with
Working
DEMOS!
ΞΌEforth
β¨ β¨ β¨
β’ Multi-platform eForth based Forth
β’ One codebase targets:
- Windows 32-bit and 64-bit
- Linux & WSL
- ESP32 / ESP32-S2/S3 / ESP32-C3 / ESP-CAM
(known as ESP32forth)
- Web!
Why the Web?
πΈ π πΈ
β’ Portable, powerful,
relatively simple I/O APIs.
β’ Asm.js, WebAssembly, and JITs
mean it can go fast!
β’ It's easy to show people.
β’ It let's people run my code
without trusting me. π
Β΅EForth Approach
β
β’ Each opcodes in 1-2 lines of C code
β’ Define "system variables" in a C structure
β’ Define 5 "core" opcodes
in larger C functions
β₯ These could be built from opcodes
β₯ But avoid needing to "assemble" loops
β’ Boot from Forth code in a string
: QUIT
BEGIN
EVALUATE1
AGAIN ;
S>NUMBER? π‘ BASE
PARSE π‘ 'TIB #TIB >IN
CREATE π‘ CURRENT
FIND π‘ CONTEXT
EVALUATE1 π‘ STATE 'NOTFOUND
Design Choices
π
β’ Indirect Threaded
β₯ Single cell VM opcodes
β₯ Simpler SEE / DOES>
β₯ Computed goto in C avoids assembly
β₯ Other models might be faster?
β’ Unlimited stacks
β’ 32-bit floats only
β’ No counted strings
How to bring it to the Web?
β΅ π πΈ
β’ Convert to Asm.js
β₯ Hand convert 5 "core" words
β₯ Automate converting opcodes
.. with Regexes π ..
Asm.js
β΅π±
β’ Best hack ever
β’ Embed C in JavaScript
β’ Converted by Chrome
to WebAssembly
Asm.js
β΅π±
a = a | 0; // a is an int
a = fround(a); // a is a float32
a = (a + 1) | 0; // ++a;
a = (a + b) | 0; // a = a + b;
a = imul32(a, b); // a = a * b;
a = i32[p>>2] | 0; // a = *p; (32-bit)
a = u8[p] | 0; // a = *p; (8-bit)
return a | 0; // function returns an int
function Match(sep, ch) {
return sep == ch || (sep == 32 && (ch == 9 || ch == 10 || ch == 13));
}
function Parse(sep, ret) {
if (sep == 32) {
while (i32[g_sys_tin>>2] < i32[g_sys_ntib>>2] &&
Match(sep, u8[i32[g_sys_tib>>2] + i32[g_sys_tin>>2]])) { ++i32[g_sys_tin>>2];
}
}
var start = i32[g_sys_tin>>2];
while (i32[g_sys_tin>>2] < i32[g_sys_ntib>>2] &&
!Match(sep, u8[i32[g_sys_tib>>2] + i32[g_sys_tin>>2]])) { ++i32[g_sys_tin>>2]; }
var len = i32[g_sys_tin>>2] - start;
if (i32[g_sys_tin>>2] < i32[g_sys_ntib>>2]) { ++i32[g_sys_tin>>2]; }
i32[ret>>2] = i32[g_sys_tib>>2] + start;
if (DEBUGGING) { console.log(βPARSE: [β + GetString(i32[ret>>2], len) + β]β); }
return len;
}
X("0=", ZEQUAL, tos = !tos ? -1 : 0) \
X("0<", ZLESS, tos = (tos|0) < 0 ? -1 : 0) \
X("+", PLUS, tos += *sp--) \
Y(LSHIFT, tos = (*sp << tos); --sp) \
Y(RSHIFT, tos = (((ucell_t) *sp) >> tos); --sp) \
Y(ARSHIFT, tos = (*sp >> tos); --sp) \
Y(AND, tos &= *sp--) \
Y(OR, tos |= *sp--) \
Y(XOR, tos ^= *sp--) \
X("DUP", ALTDUP, DUP) \
Y(SWAP, w = tos; tos = *sp; *sp = w) \
Y(OVER, DUP; tos = sp[-1]) \
X("DROP", ALTDROP, DROP) \
X("@", AT, tos = *(cell_t *) tos) \
cases = ReplaceAll(cases, '*(cell_t *) tos = ', 'i32[tos>>2] = ');
cases = ReplaceAll(cases, '((cell_t *) tos)[1] = ', 'i32[(tos + 4)>>2] = ');
cases = ReplaceAll(cases, '*(int32_t *) tos = ', 'i32[tos>>2] = ');
cases = ReplaceAll(cases, '*(int16_t *) tos = ', 'i16[tos>>1] = ');
cases = ReplaceAll(cases, '*(uint8_t *) tos = ', 'u8[tos] = ');
cases = ReplaceAll(cases, '*(float *) tos = ', 'f32[tos>>2] = ');
cases = ReplaceAll(cases, '*(cell_t *) tos', '(i32[tos>>2]|0)');
cases = ReplaceAll(cases, '((cell_t *) tos)[1]', '(i32[(tos + 4)>>2]|0)');
cases = ReplaceAll(cases, '*(int32_t *) tos', '(i32[tos>>2]|0)');
cases = ReplaceAll(cases, '*(uint32_t *) tos', '(i32[tos>>2]>>>0)');
cases = ReplaceAll(cases, '*(int16_t *) tos', '(i16[tos>>1]|0)');
cases = ReplaceAll(cases, '*(uint16_t *) tos', '(i16[tos>>1]>>>0)');
cases = ReplaceAll(cases, '*(uint8_t *) tos', '(u8[tos]|0)');
cases = ReplaceAll(cases, '*(float *) tos', 'f32[tos>>2]');
cases = ReplaceAll(cases, '*(float *) ip', 'f32[ip>>2]');
Bindings to JavaScript
βββ
call ( slot -- )
jseval! ( a n slot -- )
jseval ( a n -- )
X("CALL", CALL, DUP; sp = Call(sp|0)|0; DROP) \
function(sp) {
var tos = i32[sp>>2]; sp -= 4;
// do something with tos
return sp;
}
function SetEval(sp) {
var index = i32[sp>>2]; sp -= 4;
var n = i32[sp>>2]; sp -= 4;
var a = i32[sp>>2]; sp -= 4;
objects[index] = eval(GetString(a, n));
return sp;
}
var objects = [SetEval];
function Call(sp) {
var op = i32[sp>>2]; sp -= 4;
return objects[op](sp);
}
r~
// Run a bunch of JavaScript!
~ jseval
Bindings to JavaScript
βββ
JSWORD: myword { }
// Do something in JS
~
Arguments to JavaScript
π π
JSWORD: my+ { a b -- n }
return a + b;
~
Multiple Return
πΆπΆπΆ
JSWORD: myswap { a b -- n n }
return [b, a];
~
Graphics
π·π·
gr ( -- ) Enter graphics mode
text ( -- ) Text mode
show-text ( f -- ) Show/hide text
viewport@ ( -- w h )
window ( w h -- )
Input
π
mouse ( -- x y )
button ( -- b )
Color
π π‘ π’ π£
color! ( rgb -- )
$ff0000 constant red
$ffff00 constant yellow
$0000ff constant blue
Drawing
π¨ π¨
box ( x y w h -- )
line ( x1 y1 x2 y2 -- )
font ( a n -- )
fillText ( str str# x y -- )
Paths
π§ π§
lineWidth ( w -- )
beginPath ( -- )
moveTo ( x y -- )
lineTo ( x y -- )
quarticCurveTo ( cx cy x y -- )
stroke ( -- )
fill ( -- )
Transforms
π€π€π€
translate ( x y -- )
scale ( sx sy div -- )
rotate ( angle div -- )
gpush ( -- ) Push transform stack
gpop ( -- ) Push transform stack
Sound
π π
tone ( note[midi] volume[0-100] voice[0-7] )
silence ( -- )
DEMO
eforth.appspot.com
QUESTIONS?
π΅
Thank you!