// Copyright 2021 Bradley D. Nelson // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. 'use strict'; (function() { const HEAP_SIZE = (1024 * 1024); const STACK_CELLS = 4096; const VOCABULARY_DEPTH = 16; const IMMEDIATE = 1; const SMUDGE = 2; const BUILTIN_FORK = 4; const BUILTIN_MARK = 8; const DEBUGGING = false; {{boot}} var heap = new ArrayBuffer(HEAP_SIZE); var f32 = new Float32Array(heap); var i32 = new Int32Array(heap); var u16 = new Uint16Array(heap); var u8 = new Uint8Array(heap); var builtins = []; var opcodes = {}; var objects = [SetEval]; var context = {}; // For later use by platform. {{sys}} 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; } function Call(sp) { var op = i32[sp>>2]; sp -= 4; return objects[op](sp); } function Load(addr, content) { for (var i = 0; i < content.length; ++i) { u8[addr++] = content.charCodeAt(i); } return addr; } function GetString(a, n) { var ret = ''; for (var i = 0; i < n; ++i) { ret += String.fromCharCode(u8[a + i]); } return ret; } function CELL_ALIGNED(n) { return (n + 3) & ~3; } function UPPER(ch) { return ch >= 'a'.charCodeAt(0) && ch <= 'z'.charCodeAt(0) ? (ch & 0x5F) : ch; } function TOFLAGS(xt) { return xt - 1 * 4; } function TONAMELEN(xt) { return TOFLAGS(xt) + 1; } function TOPARAMS(xt) { return TOFLAGS(xt) + 2; } function TOSIZE(xt) { return CELL_ALIGNED(u8[TONAMELEN(xt)>>2]) + 4 * i32[TOPARAMS(xt)>>2]; } function TOLINK(xt) { return xt - 2 * 4; } function TONAME(xt) { return (u8[TOFLAGS(xt)>>2] & BUILTIN_MARK) ? i32[TOLINK(xt)] : TOLINK(xt) - CELL_ALIGNED(u8[TONAMELEN(xt)]); } function TOBODY(xt) { return xt + (i32[xt>>2] === OP_DOCREATE || i32[xt>>2] === OP_DODOES ? 2 : 1) * 4; } function DOES(ip) { i32[i32[i32[g_sys_current>>2]>>2]>>2] = OP_DODOES; i32[(i32[i32[g_sys_current>>2]>>2] + 4)>>2] = ip; } function BUILTIN_ITEM(i) { return i32[g_sys_builtins>>2] + 4 * 3 * i; } function BUILTIN_NAME(i) { return i32[(BUILTIN_ITEM(i) + 0 * 4)>>2]; } function BUILTIN_FLAGS(i) { return u8[BUILTIN_ITEM(i) + 1 * 4 + 0]; } function BUILTIN_NAMELEN(i) { return u8[BUILTIN_ITEM(i) + 1 * 4 + 1]; } function BUILTIN_VOCAB(i) { return u16[(BUILTIN_ITEM(i) + 1 * 4 + 2)>>1]; } function BUILTIN_CODE(i) { return BUILTIN_ITEM(i) + 2 * 4; } function Find(name) { for (var voc = i32[g_sys_context>>2]; i32[voc>>2]; voc += 4) { var xt = i32[i32[voc>>2]>>2]; while (xt) { if (u8[TOFLAGS(xt)] & BUILTIN_FORK) { var vocab = i32[(TOLINK(xt) + 4 * 3)>>2]; for (var i = 0; BUILTIN_NAME(i); ++i) { if (BUILTIN_VOCAB(i) === vocab && name.length === BUILTIN_NAMELEN(i) && name.toUpperCase() === GetString(BUILTIN_NAME(i), name.length).toUpperCase()) { if (DEBUGGING) { console.log('FOUND: ' + name); } return BUILTIN_CODE(i); } } } if (!(u8[TOFLAGS(xt)] & SMUDGE) && name.length === u8[TONAMELEN(xt)] && name.toUpperCase() === GetString(TONAME(xt), name.length).toUpperCase()) { if (DEBUGGING) { console.log('FOUND REGULAR: ' + name); } return xt; } xt = i32[TOLINK(xt)>>2]; } } if (DEBUGGING) { console.log('NOT FOUND: ' + name); } return 0; } function trace(ip, sp, tos) { var line = '['; for (var i = 0; i < 3; i++) { line += i32[(sp + (i - 2) * 4)>>2] + ' '; } line += tos + '] '; while (line.length < 25) { line += ' '; } line += ip + ': '; for (var i = 0; i < 10; ++i) { var val = i32[(ip + i * 4)>>2]; if (i32[val>>2] && opcodes[i32[val>>2]] !== undefined) { var op = opcodes[i32[val>>2]]; if (op === 'DOCOL') { line += op + '(' + val + ') '; } else { line += op + ' '; } } else { line += val + ' '; } } console.log(line); } function COMMA(value) { i32[i32[g_sys_heap>>2]>>2] = value; i32[g_sys_heap>>2] += 4; } function CCOMMA(value) { u8[i32[g_sys_heap>>2]] = value; i32[g_sys_heap>>2]++; } function SSMOD(sp) { var a = i32[(sp - 8)>>2]; var b = i32[(sp - 4)>>2]; var c = i32[sp>>2]; a *= b; var x = Math.floor(a / c); var m = a - x * c; if (m < 0) { x--; m += c; } i32[(sp - 8)>>2] = m; i32[sp>>2] = x; } function Finish() { if (i32[g_sys_latestxt>>2] && !u16[TOPARAMS(i32[g_sys_latestxt>>2])>>1]) { var sz = i32[g_sys_heap>>2] - (i32[g_sys_latestxt>>2] + 4); sz /= 4; if (sz < 0 || sz > 0xffff) { sz = 0xffff; } u16[TOPARAMS(i32[g_sys_latestxt>>2])>>1] = sz; } } function UNSMUDGE() { u8[TOFLAGS(i32[i32[g_sys_current>>2]>>2])] &= ~SMUDGE; Finish(); } function DOIMMEDIATE() { u8[TOFLAGS(i32[i32[g_sys_current>>2]>>2])] |= IMMEDIATE; } function Create(name, flags, op) { if (DEBUGGING) { console.log('CREATE: ' + name); } Finish(); i32[g_sys_heap>>2] = CELL_ALIGNED(i32[g_sys_heap>>2]); i32[g_sys_heap>>2] = Load(i32[g_sys_heap>>2], name); // name i32[g_sys_heap>>2] = CELL_ALIGNED(i32[g_sys_heap>>2]); COMMA(i32[i32[g_sys_current>>2]>>2]); // link COMMA((name.length << 8) | flags); // flags & length i32[i32[g_sys_current>>2]>>2] = i32[g_sys_heap>>2]; i32[g_sys_latestxt>>2] = i32[g_sys_heap>>2]; COMMA(op); } function Builtin(name, flags, vocab, opcode) { opcodes[opcode] = name; builtins.push([name, flags | BUILTIN_MARK, vocab, opcode]); } function SetupBuiltins() { for (var i = 0; i < builtins.length; ++i) { var name = builtins[i][0]; builtins[i][0] = i32[g_sys_heap>>2]; i32[g_sys_heap>>2] = Load(i32[g_sys_heap>>2], name); // name i32[g_sys_heap>>2] = CELL_ALIGNED(i32[g_sys_heap>>2]); builtins[i][1] |= (name.length << 8); } i32[g_sys_builtins>>2] = i32[g_sys_heap>>2]; for (var i = 0; i < builtins.length; ++i) { COMMA(builtins[i][0]); COMMA(builtins[i][1] | (builtins[i][2] << 16)); COMMA(builtins[i][3]); } COMMA(0); COMMA(0); COMMA(0); } 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; } function Convert(pos, n, base, ret) { i32[ret>>2] = 0; var negate = 0; if (!n) { return 0; } if (u8[pos] == '-'.charCodeAt(0)) { negate = -1; ++pos; --n; } if (u8[pos] == '$'.charCodeAt(0)) { base = 16; ++pos; --n; } for (; n; --n) { var d = UPPER(u8[pos]) - 48; if (d > 9) { d -= 7; if (d < 10) { return 0; } } if (d >= base) { return 0; } i32[ret>>2] = i32[ret>>2] * base + d; ++pos; } if (negate) { i32[ret>>2] = -i32[ret>>2]; } return -1; } function FConvert(pos, n, ret) { f32[ret>>2] = 0; var negate = 0; var has_dot = 0; var exp = 0; var shift = 1; if (!n) { return 0; } if (u8[pos] == '-'.charCodeAt(0)) { negate = -1; ++pos; --n; } for (; n; --n) { if (u8[pos] >= 48 && u8[pos] <= 48 + 9) { if (has_dot) { shift = shift * 0.1; f32[ret>>2] = f32[ret>>2] + (u8[pos] - 48) * shift; } else { f32[ret>>2] = f32[ret>>2] * 10 + (u8[pos] - 48); } } else if (u8[pos] == 'e'.charCodeAt(0) || u8[pos] == 'E'.charCodeAt(0)) { break; } else if (u8[pos] == '.'.charCodeAt(0)) { if (has_dot) { return 0; } has_dot = -1; } else { return 0; } ++pos; } if (!n) { return 0; } // must have E ++pos; --n; if (n) { var tmp = f32[ret>>2]; if (!Convert(pos, n, 10, ret)) { return 0; } exp = i32[ret>>2]; f32[ret>>2] = tmp; } if (exp < -128 || exp > 128) { return 0; } for (; exp < 0; ++exp) { f32[ret>>2] *= 0.1; } for (; exp > 0; --exp) { f32[ret>>2] *= 10.0; } if (negate) { f32[ret>>2] = -f32[ret>>2]; } return -1; } function Evaluate1(rp) { var call = 0; var tos, sp, ip, fp; // UNPARK ip = i32[rp>>2]; rp -= 4; sp = i32[rp>>2]; rp -= 4; fp = i32[rp>>2]; rp -= 4; tos = i32[sp>>2]; sp -= 4; var name = sp + 8; var len = Parse(32, name); if (len == 0) { // ignore empty sp += 4; i32[sp>>2] = tos; tos = 0; // PARK sp += 4; i32[sp>>2] = tos; rp += 4; i32[rp>>2] = fp; rp += 4; i32[rp>>2] = sp; rp += 4; i32[rp>>2] = ip; return rp; } name = i32[name>>2]; var xt = Find(GetString(name, len)); if (xt) { if (i32[g_sys_state>>2] && !(u8[TOFLAGS(xt)] & IMMEDIATE)) { COMMA(xt); } else { call = xt; } } else { if (DEBUGGING) { console.log('CONVERTING: ' + GetString(name, len)); } var n = sp + 16; if (Convert(name, len, i32[g_sys_base>>2], n)) { if (i32[g_sys_state>>2]) { COMMA(i32[g_sys_DOLIT_XT>>2]); COMMA(i32[n>>2]); } else { sp += 4; i32[sp>>2] = tos; tos = i32[n>>2]; } } else { if (FConvert(name, len, n)) { if (i32[g_sys_state>>2]) { COMMA(i32[g_sys_DOFLIT_XT>>2]); f32[i32[g_sys_heap>>2]>>2] = f32[n>>2]; i32[g_sys_heap>>2] += 4; } else { fp += 4; f32[fp>>2] = f32[n>>2]; } } else { if (DEBUGGING) { console.log('CANT FIND: ' + GetString(name, len)); } sp += 4; i32[sp>>2] = tos; tos = name; sp += 4; i32[sp>>2] = tos; tos = len; sp += 4; i32[sp>>2] = tos; tos = -1; call = i32[g_sys_notfound>>2]; } } } sp += 4; i32[sp>>2] = tos; tos = call; // PARK sp += 4; i32[sp>>2] = tos; rp += 4; i32[rp>>2] = fp; rp += 4; i32[rp>>2] = sp; rp += 4; i32[rp>>2] = ip; return rp; } function InitDictionary() { {{dict}} SetupBuiltins(); } function Init() { i32[g_sys_heap_start>>2] = 0; i32[g_sys_heap_size>>2] = HEAP_SIZE; i32[g_sys_stack_cells>>2] = STACK_CELLS; // Start heap after G_SYS area. i32[g_sys_heap>>2] = i32[g_sys_heap_start>>2] + 256; i32[g_sys_heap>>2] += 4; // Allocate stacks. var fp = i32[g_sys_heap>>2] + 4; i32[g_sys_heap>>2] += STACK_CELLS * 4; var rp = i32[g_sys_heap>>2] + 4; i32[g_sys_heap>>2] += STACK_CELLS * 4; var sp = i32[g_sys_heap>>2] + 4; i32[g_sys_heap>>2] += STACK_CELLS * 4; // FORTH worldlist (relocated when vocabularies added). var forth_wordlist = i32[g_sys_heap>>2]; COMMA(0); // Vocabulary stack. i32[g_sys_current>>2] = forth_wordlist; i32[g_sys_context>>2] = i32[g_sys_heap>>2]; i32[g_sys_latestxt>>2] = 0; COMMA(forth_wordlist); for (var i = 0; i < VOCABULARY_DEPTH; ++i) { COMMA(0); } // setup boot text. var source = i32[g_sys_heap>>2]; i32[g_sys_heap>>2] = Load(i32[g_sys_heap>>2], boot); var source_len = i32[g_sys_heap>>2] - source; i32[g_sys_boot>>2] = source; i32[g_sys_boot_size>>2] = source_len; InitDictionary(); i32[g_sys_latestxt>>2] = 0; // So last builtin doesn't get wrong size. i32[g_sys_DOLIT_XT>>2] = Find("DOLIT"); i32[g_sys_DOFLIT_XT>>2] = Find("DOFLIT"); i32[g_sys_DOEXIT_XT>>2] = Find("EXIT"); i32[g_sys_YIELD_XT>>2] = Find("YIELD"); i32[g_sys_notfound>>2] = Find("DROP"); // Init code. var start = i32[g_sys_heap>>2]; COMMA(Find("EVALUATE1")); COMMA(Find("BRANCH")); COMMA(start); i32[g_sys_argc>>2] = 0; i32[g_sys_argv>>2] = 0; i32[g_sys_base>>2] = 10; i32[g_sys_tib>>2] = source; i32[g_sys_ntib>>2] = source_len; i32[g_sys_ntib>>2] = source_len; rp += 4; i32[rp>>2] = fp; rp += 4; i32[rp>>2] = sp; rp += 4; i32[rp>>2] = start; i32[g_sys_rp>>2] = rp; } function VM(stdlib, foreign, heap) { "use asm"; var imul = stdlib.Math.imul; var fround = stdlib.Math.fround; var sqrt = stdlib.Math.sqrt; var sin = stdlib.Math.sin; var cos = stdlib.Math.cos; var atan2 = stdlib.Math.atan2; var floor = stdlib.Math.floor; var exp = stdlib.Math.exp; var log = stdlib.Math.log; var pow = stdlib.Math.pow; var fabs = stdlib.Math.abs; var fmin = stdlib.Math.min; var fmax = stdlib.Math.max; var SSMOD = foreign.SSMOD; var Call = foreign.Call; var COMMA = foreign.COMMA; var CCOMMA = foreign.CCOMMA; var DOES = foreign.DOES; var DOIMMEDIATE = foreign.DOIMMEDIATE; var UNSMUDGE = foreign.UNSMUDGE; var TOBODY = foreign.TOBODY; var create = foreign.create; var find = foreign.find; var parse = foreign.parse; var convert = foreign.convert; var fconvert = foreign.fconvert; var evaluate1 = foreign.evaluate1; var emitlog = foreign.log; var trace = foreign.trace; var u8 = new stdlib.Uint8Array(heap); var i16 = new stdlib.Int16Array(heap); var i32 = new stdlib.Int32Array(heap); var f32 = new stdlib.Float32Array(heap); {{sys}} function memset(dst, ch, n) { dst = dst | 0; ch = ch | 0; n = n | 0; while (n | 0) { u8[dst] = ch; dst = (dst + 1) | 0; n = (n - 1) | 0; } } function memmove(dst, src, n) { dst = dst | 0; src = src | 0; n = n | 0; if ((src | 0) < (dst | 0)) { src = (src + n - 1) | 0; dst = (dst + n - 1) | 0; while (n | 0) { u8[dst] = u8[src]; src = (src - 1) | 0; dst = (dst - 1) | 0; n = (n - 1) | 0; } } else { while (n | 0) { u8[dst] = u8[src]; src = (src + 1) | 0; dst = (dst + 1) | 0; n = (n - 1) | 0; } } } function run() { var tos = 0; var ip = 0; var sp = 0; var rp = 0; var fp = 0; var w = 0; var ir = 0; var ft = fround(0.0); // UNPARK rp = i32[g_sys_rp>>2]|0; ip = i32[rp>>2]|0; rp = (rp - 4)|0; sp = i32[rp>>2]|0; rp = (rp - 4)|0; fp = i32[rp>>2]|0; rp = (rp - 4)|0; tos = i32[sp>>2]|0; sp = (sp - 4)|0; for (;;) { //trace(ip|0, sp|0, tos|0); w = i32[ip>>2]|0; ip = (ip + 4)|0; decode: for (;;) { ir = u8[w]|0; switch (ir&0xff) { {{cases}} default: break; } break; } } } return {run: run}; } var ffi = { 'Call': Call, 'create': function(name, len, flags, op) { Create(GetString(name, len), flags, op); }, 'parse': function(sep, ret) { return Parse(sep, ret); }, 'find': function(a, n) { return Find(GetString(a, n)); }, 'convert': function(pos, n, base, ret) { return Convert(pos, n, base, ret); }, 'fconvert': function(pos, n, ret) { return FConvert(pos, n, ret); }, 'evaluate1': function(rp) { return Evaluate1(rp); }, 'log': function(n) { console.log(n); }, 'trace': function(ip, sp, tos) { trace(ip, sp, tos); }, 'COMMA': function(n) { COMMA(n); }, 'CCOMMA': function(n) { CCOMMA(n); }, 'SSMOD': function(sp) { SSMOD(sp); }, 'TOBODY': function(tos) { return TOBODY(tos); }, 'DOES': function(ip) { DOES(ip); }, 'DOIMMEDIATE': function() { DOIMMEDIATE(); }, 'UNSMUDGE': function() { UNSMUDGE(); }, }; function getGlobalObj() { return (function(g) { return g; })(new Function('return this')()); } var globalObj = getGlobalObj(); var module = VM(globalObj, ffi, heap); Init(); function run() { module.run(); setTimeout(run, 0); } setTimeout(run, 0); })();