print: (string) => {},
updateViews: () => {}
}
-const channels = []
-let wasmMem, forth
-
-/* Init */
-Promise.all([
- fetch('forth.forth', {credentials: 'include', headers:{'content-type':'text/plain'}}).then((re) => re.text()),
- fetch('forth.wasm', {credentials: 'include', headers:{'content-type':'application/wasm'}}).then(re => re.arrayBuffer())
-]).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])
- })
-})
+let wasmMem, wasmMain
/* Environment functions */
-const wasmString = (addr, u) =>
- String.fromCharCode.apply(
- null,
- new Uint16Array(wasmMem, addr, u >> 1)
- )
+const bufString = (arrayBuf, addr, u) =>
+ String.fromCharCode.apply(null, new Uint16Array(arrayBuf, addr, u >> 1))
+const wasmString = (addr, u) => bufString(wasmMem, addr, u)
+const wasmBase = () => new DataView(wasmMem, 14348, 4).getUint32(0,true)
const strToArrayBuffer = (str) => {
- const buf = new ArrayBuffer(str.length << 1);
- const bufView = new Uint16Array(buf);
+ 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;
+ bufView[i] = str.charCodeAt(i)
+ return buf
}
const bufJoin = (buf1, buf2) => {
const newBuf = new Uint8Array(buf1.byteLength + buf2.byteLength)
}
/* I/O Channel Drivers */
+const channels = []
+const getChannel = (chno) => {
+ const channel = channels[chno]
+ if (!channel)
+ throw new Error(`invalid channel access ${chno}`)
+ return channel
+}
class Channel {
constructor(opt) {
+ opt = opt || Object.create(null)
opt.chno = channels.indexOf(undefined)
if (opt.chno === -1)
opt.chno = channels.length
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)
+ read(writeArray, writeAddr, writeMax) {
+ const bufBytes = Math.min(writeMax, this.buffer.byteLength)
+ new Uint8Array(writeArray).set(new Uint8Array(this.buffer, 0, bufBytes), writeAddr)
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
+ write(readArray, readAddr, readSize) {
+ this.buffer = bufJoin(this.buffer, new Uint8Array(readArray, readAddr, readSize))
+ wasmMain(this.chno)
+ output.updateViews()
}
- 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
- }
+ send(readArray, readAddr, readSize) {
+ this.write(readArray, readAddr, readSize)
}
}
-/* 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 = []
pop: () => simstack.pop(),
push: (val) => simstack.push(val),
rinit: () => rstack.length = 0,
- rpop: () => rstack.length ? rstack.pop() : 16388,
+ rpop: () => rstack.pop(),
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?
+ sys_write: (chno, addr, u) => getChannel(chno).write(wasmMem, addr, u),
+ sys_read: (chno, addr, u) => getChannel(chno).read(wasmMem, addr, u),
+ sys_send: (chno, addr, u) => getChannel(chno).send(wasmMem, addr, u),
+ sys_connect: (addr, u) => {
//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
//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) => {
+ sys_fetch: (chno, addr, u) => {
const str = wasmString(addr, u)
- console.log(`fetching: ${str}`)
+ console.log(`fetching: ${str} || ${addr} ${u}`)
const args = JSON.parse(wasmString(addr, u))
console.log(args)
const url = args.url
delete args.url
- const channel = new Channel()
+ const channel = channels[chno]
+ if (!channel) {
+ console.error(`invalid channel fetch: ${chno}`)
+ return -1
+ }
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))
+ re.text().then((txt) => channel.write(strToArrayBuffer(txt), 0, txt.length << 1))
else {
const reader = new FileReader()
- reader.onload = (evt) => forth(channel.chno, evt.target.result)
+ reader.onload = (evt) => channel.write(evt.target.result, 0, evt.target.result.byteLength)
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
+ return 0
},
- sys_close: (chno) => delete channels[chno],
- sys_echo: (val, base) => output.print(`\\\ => ${val.toString(base)} `),
- sys_echochar: (val) => output.print(String.fromCharCode(val)),
+ sys_open: () => new Channel().chno,
+ sys_close: (chno) => chno < 3 ? 0 : delete channels[chno],
+ sys_echo: (val) => output.print(`\\\ => ${val.toString(wasmBase())}\n`),
+ sys_log: (addr, u) => console.log(`=> ${wasmString(addr, u)}`),
sys_reflect: (addr) => {
console.log(`reflect: ${addr}: ${
new DataView(wasmMem, addr, 4)
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)
+ sys_parsenum: (addr, u) => {
+ const answer = Number.parseInt(wasmString(addr, u), wasmBase())
if (Number.isNaN(answer))
return -1
new DataView(wasmMem, addr, 4).setUint32(0,answer,true)
}
}
+/* Initialization */
+/* 0 STDIN, 1 STDOUT, 2 STDERR */
+new Channel({
+ send(readArray, readAddr, readSize) { output.print(bufString(readArray, readAddr, readSize)) }
+})
+new Channel({
+ write(readArray, readAddr, readSize) { output.print(bufString(readArray, readAddr, readSize)) },
+ send(readArray, readAddr, readSize) { output.print(`\n\\\ => ${bufString(readArray, readAddr, readSize)}\n`) }
+})
+new Channel({
+ write(readArray, readAddr, readSize) { console.error(bufString(readArray, readAddr, readSize)) }
+})
+
+/* Fetch wasm file, and initial forth file */
+Promise.all([
+ fetch('forth.forth', {credentials: 'include', headers:{'content-type':'text/plain'}}).then((re) => re.text()),
+ fetch('forth.wasm', {credentials: 'include', headers:{'content-type':'application/wasm'}}).then(re => re.arrayBuffer())
+]).then((results) => {
+ WebAssembly.instantiate(results[1], wasmImport).then((module) => {
+ wasmMem = module.instance.exports.memory.buffer
+ wasmMain = module.instance.exports.main
+ console.log('wasm loaded')
+ getChannel(0).write(strToArrayBuffer(results[0]))
+ })
+})
+
/* View Logic */
window.onload = () => {
let forthdiv = document.getElementById("forth")
txtinput.value += " "
event.preventDefault()
event.stopPropagation()
- forth(txtinput.value)
+ getChannel(0).write(strToArrayBuffer(txtinput.value))
txtinput.value = ""
}
break
/* Set up output functions */
output.print = (string) => txtoutput.textContent += string,
output.updateViews = () => {
- const base = new DataView(wasmMem, 14348 /* base */, 4).getUint32(0,true)
+ const base = wasmBase()
stackview.textContent = simstack.map((v) => v.toString(base)).join('\n')
// rstackview.textContent = rstack.join('\n')
let cnt = 0;