+]).then((results) => {
+ WebAssembly.instantiate(results[1], wasmImport).then((module) => {
+ wasmMem = module.instance.exports.memory.buffer
+ forth = (chno, data) => {
+ if (typeof chno !== "number") {
+ data = chno
+ chno = 0
+ }
+ channels[chno].writeEnv(data)
+ if (chno === 0)
+ output.print(data)
+ module.instance.exports.main(chno)
+ output.updateViews()
+ }
+ console.log('wasm loaded')
+ forth(results[0])
+ })
+})
+
+/* Environment functions */
+const wasmString = (addr, u) =>
+ String.fromCharCode.apply(
+ null,
+ new Uint16Array(wasmMem, addr, u >> 1)
+ )
+const strToArrayBuffer = (str) => {
+ const buf = new ArrayBuffer(str.length << 1);
+ const bufView = new Uint16Array(buf);
+ for (let i = 0, strLen = str.length; i < strLen; i++)
+ bufView[i] = str.charCodeAt(i);
+ return buf;
+}
+const bufJoin = (buf1, buf2) => {
+ const newBuf = new Uint8Array(buf1.byteLength + buf2.byteLength)
+ newBuf.set(new Uint8Array(buf1), 0)
+ newBuf.set(new Uint8Array(buf2), buf1.byteLength)
+ return newBuf.buffer
+}
+
+/* I/O Channel Drivers */
+class Channel {
+ constructor(opt) {
+ opt.chno = channels.indexOf(undefined)
+ if (opt.chno === -1)
+ opt.chno = channels.length
+ Object.assign(this, opt)
+ if (this.buffer === undefined)
+ this.buffer = new ArrayBuffer(0)
+ channels[this.chno] = this
+ return this
+ }
+ read(writeAddr, maxBytes) {
+ const wasmView = new Uint8Array(wasmMem, writeAddr, maxBytes)
+ const bufBytes = Math.min(maxBytes, this.buffer.byteLength)
+ const bufView = new Uint8Array(this.buffer, 0, bufBytes)
+ wasmView.set(bufView, 0)
+ this.buffer = this.buffer.slice(bufBytes)
+ return bufBytes
+ }
+ write(readAddr, maxBytes) {
+ const newBuf = new Uint8Array(this.buffer.byteLength + maxBytes)
+ newBuf.set(new Uint8Array(this.buffer), 0)
+ newBuf.set(new Uint8Array(wasmMem, readAddr, maxBytes), this.buffer.byteLength)
+ this.buffer = newBuf
+ }
+ writeEnv(data) {
+ switch (typeof data) {
+ case "string":
+ this.buffer = bufJoin(this.buffer, strToArrayBuffer(data))
+ break
+ case "object" :
+ if (data instanceof ArrayBuffer)
+ this.buffer = bufJoin(this.buffer, data)
+ else
+ this.buffer = bufJoin(this.buffer, strToArrayBuffer(JSON.stringify(data)))
+ break
+ case "number" :
+ const buf = new ArrayBuffer(4)
+ new DataView(buf, 0, 4).setInt32(data)
+ this.buffer = bufJoin(this.buffer, buf)
+ break
+ default :
+ console.error(`environment wrote unhandled object: ${data}`)
+ return
+ }
+ }
+}
+/* 0 STDIN, 1 STDOUT, 2 STDERR */
+new Channel({
+ write(readAddr, maxBytes) {
+ super.write(readAddr, maxBytes)
+ output.print(wasmString(readAddr, maxBytes))
+ }
+})
+new Channel({
+ read(writeAddr, maxBytes) { return 0 },
+ write(readAddr, maxBytes) { output.print(`\\\ => ${wasmString(readAddr, maxBytes)}\n`) }
+})
+new Channel({
+ read(writeAddr, maxBytes) { return 0 },
+ write(readAddr, maxBytes) { console.error(wasmString(readAddr, maxBytes)) }
+})
+
+/* System runtime */
+const simstack = []
+const rstack = []
+const dictionary = { EXECUTE: 12 }
+const doesDictionary = {}
+const wasmImport = {
+ env: {
+ pop: () => simstack.pop(),
+ push: (val) => simstack.push(val),
+ rinit: () => rstack.length = 0,
+ rpop: () => rstack.length ? rstack.pop() : 16388,
+ rpush: (val) => rstack.push(val),
+ sys_write: (chno, addr, u) => channels[chno] === undefined ? 0 : channels[chno].write(addr, u),
+ sys_read: (chno, addr, u) => channels[chno] === undefined ? 0 : channels[chno].read(addr, u),
+ sys_listen: (chno, cbAddr, addr, u) => { //sys_connect?
+ //TODO: call into the module to wasm fn "event" to push an event
+ //to forth, which pushes esi to the return stack and queues the
+ //callback function provided from listen. reqaddr could be
+ //"fetch", in which case no channel is used. otherwise very
+ //similar to sys_fetch.
+
+ //callbacks are events, like listen events
+ //totally event-driven, one listener per event
+ //other listen types: mouse, keyboard, touch, main_loop (10ms interval), wrtc, etc
+ //fetch is different because it offers no "write" interface, doesn't repeat
+ },
+ sys_fetch: (cbAddr, addr, u) => {
+ const str = wasmString(addr, u)
+ console.log(`fetching: ${str}`)
+ const args = JSON.parse(wasmString(addr, u))
+ console.log(args)
+ const url = args.url
+ delete args.url
+ const channel = new Channel()
+ fetch(url, args).then((re) => {
+ if (args.headers === undefined ||
+ args.headers['content-type'] === undefined ||
+ args.headers['content-type'].toLowerCase().indexOf('text/plain') === 0 )
+ re.text().then((txt) => forth(channel.chno, txt))
+ else {
+ const reader = new FileReader()
+ reader.onload = (evt) => forth(channel.chno, evt.target.result)
+ re.blob().then((blob) => reader.readAsArrayBuffer(blob))
+ }
+ }).catch(console.error)
+ //TODO: map to fetch promise, write to channel buffer,
+ //javascript "fetch" as fallback, explicit handles for
+ //"textEntry" or any third party protocols like activitypub
+ return channel.chno
+ },
+ sys_close: (chno) => delete channels[chno],
+ sys_echo: (val, base) => output.print(`\\\ => ${val.toString(base)} `),
+ sys_echochar: (val) => output.print(String.fromCharCode(val)),
+ sys_reflect: (addr) => {
+ console.log(`reflect: ${addr}: ${
+ new DataView(wasmMem, addr, 4)
+ .getUint32(0,true)
+ }`)
+ },
+ vocab_get: (addr, u) => dictionary[wasmString(addr, u).toUpperCase()] || 0,
+ vocab_set: (addr, u, v) => dictionary[wasmString(addr, u).toUpperCase()] = v,
+ does_get: (addr, u) => doesDictionary[wasmString(addr, u).toUpperCase()] || 0,
+ does_set: (addr, u, v) => doesDictionary[wasmString(addr, u).toUpperCase()] = v,
+ is_whitespace: (key) => /\s/.test(String.fromCharCode(key)),
+ sys_stack: () => console.log(`[${simstack}][${rstack}]`),
+ sys_parsenum: (addr, u, base) => {
+ const answer = Number.parseInt(wasmString(addr, u), base)
+ if (Number.isNaN(answer))
+ return -1
+ new DataView(wasmMem, addr, 4).setUint32(0,answer,true)
+ return 0
+ },
+ sys_words: () => {
+ output.print(Object.getOwnPropertyNames(dictionary).toString().split(',').join(' '))
+ }
+ }
+}
+
+/* View Logic */