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