ΞΌ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!