You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
'use strict'
-let txtdiv = document.createElement("div")
-document.body.appendChild(txtdiv)
-const output = {
- print: (string) => txtdiv.textContent += string
-}
-let wasmMem
-let forth
+const initialize = Promise.all([
+ fetch('forth.forth').then((re) => re.text()),
+ fetch('forth.wasm').then(re => re.arrayBuffer())
+])
+window.onload = () => {
+ let forthdiv = document.getElementById("forth")
+ if (forthdiv === null) {
+ forthdiv = document.createElement("div")
+ forthdiv.setAttribute("style","height:400px;")
+ document.body.appendChild(forthdiv)
+ }
+ const outframe = document.createElement("div")
+ outframe.setAttribute("style", "overflow-y:hidden;height:40%;")
+ const txtoutput = document.createElement("pre")
+ txtoutput.setAttribute("style", "white-space:pre-wrap;")
+ const txtinput = document.createElement("textarea")
+ let txtinputrows = "1"
+ txtinput.setAttribute("autofocus", "true")
+ txtinput.setAttribute("cols", "60")
+ txtinput.setAttribute("rows", txtinputrows)
+ outframe.appendChild(txtoutput)
+ forthdiv.appendChild(outframe)
+ forthdiv.appendChild(txtinput)
+ const output = {
+ print: (string) => txtoutput.textContent += string
+ }
+ let wasmMem
+ let forth
-/* Input capture */
-let stdin = ""
-document.addEventListener('keydown', (event) => {
- console.log(`keydown: ${event.key}`)
- if (event.key != "F5") {
- event.preventDefault()
- event.stopPropagation()
+ /* Input capture */
+ let stdin = ""
+ txtinput.addEventListener('keydown', (event) => {
switch (event.key) {
case "Enter":
- txtdiv = document.createElement("div")
- document.body.appendChild(txtdiv)
- forth()
- output.print("ok.")
- txtdiv = document.createElement("div")
- document.body.appendChild(txtdiv)
+ if (event.ctrlKey) {
+ txtinput.value += '\n'
+ txtinputrows = (Number.parseInt(txtinputrows, 10) + 1).toString()
+ txtinput.setAttribute("rows", txtinputrows)
+ }
+ else {
+ stdin += txtinput.value
+ txtoutput.textContent += txtinput.value + '\n'
+ txtinput.value = ""
+ txtinputrows = "1"
+ txtinput.setAttribute("rows", txtinputrows)
+ event.preventDefault()
+ event.stopPropagation()
+ forth()
+ txtoutput.textContent += " _ok.\n"
+ outframe.scrollTop = outframe.scrollHeight;
+ }
break
case "Backspace":
- stdin = stdin.substring(0, stdin.length - 1)
- txtdiv.textContent = txtdiv.textContent.substring(0, txtdiv.textContent.length - 1)
+ if (txtinput.value.charCodeAt(txtinput.value.length - 1) === 10) {
+ txtinputrows = (Number.parseInt(txtinputrows, 10) - 1).toString()
+ txtinput.setAttribute("rows", txtinputrows)
+ }
break
default:
- if (event.key.length == 1) {
- stdin += event.key
- output.print(event.key)
- }
break
}
- }
-})
-
-const channels = [{
- read: (writeAddr, maxBytes) => {
- const maxChars = maxBytes >> 1
- const bufView = new Uint16Array(wasmMem.buffer, writeAddr, maxChars)
- let i
- for (i = 0; i < maxChars && i < stdin.length; i++)
- bufView[i] = stdin.charCodeAt(i)
- stdin = stdin.substring(maxChars)
- return i << 1
- },
- write: (readAddr, maxBytes) =>
- output.print(String.fromCharCode.apply(
- null,
- new Uint16Array(wasmMem.buffer, readAddr, maxBytes >> 1)
- ))
-}]
-const simstack = []
-const rstack = []
-const dictionary = {
- ';': 1,
- 'LIT': 2,
- RINIT: 3,
- WORD: 16500,
- KEY: 5,
- DUP: 6,
- '+': 7,
- 'NOOP2': 8,
- '.': 9,
- '@': 10,
- '!': 11,
- EXECUTE: 12,
- NOOP: 13,
- 'JZ:': 14,
- 'JNZ:': 15,
- DROP: 16,
- 'WS?': 17,
- 'JMP:': 18,
- 'WPUTC': 19,
- 'WB0': 20,
- 'FIND': 21,
- 'NUMBER': 22,
- 'W!LEN': 23,
- 'J-1:': 24,
- 'BYE': 25,
- 'SWAP': 26,
- 'WORDS': 27,
- 'HERE': 28,
- 'DEFINE': 29,
- '2DUP': 30,
- 'ROT': 31,
- '2DROP': 32,
- ',': 33,
- '-': 34,
- 'CHANNEL!': 35,
- 'HERE!': 36,
- '=?': 37,
- '.s': 38,
- ':': 16800,
- 'MODE': 14336,
- 'EXECUTE-MODE': 16680,
- 'QUIT': 16384,
- 'INTERPRET': 16400
-}
-const wasmImport = {
- env: {
- pop: () => simstack.pop(),
- push: (val) => simstack.push(val),
- rinit: () => rstack.length = 0,
- rpop: () => rstack.pop(),
- rpush: (val) => rstack.push(val),
- sys_write: (channel, addr, u) => {
- if (channels[channel] === undefined)
- return
- console.log(`write ch:${channel} addr:${addr} len:${u}`)
- channels[channel].write(addr, u)
- },
- sys_read: (channel, toBuffer) => {
- console.log(`read ch:${channel} buf:${toBuffer} current: ${stdin}`)
- const lenView = new DataView(wasmMem.buffer, toBuffer, 8)
- const maxBytes = lenView.getUint32(0,true)
- console.log(`read blen:${lenView.getUint32(0,true)} cstrlen:${lenView.getUint32(4,true)}`)
- /* If the channel is undefined, or if there isn't enough room in
- * toBuffer for even one character, then, if there is enough
- * room for an int write a zero, exit */
- if (channels[channel] === undefined || maxBytes < 6) {
- if (maxBytes >= 4)
- lenView.setUint32(4,0,true)
+ })
+/* document.addEventListener('keydown', (event) => {
+ //console.log(`keydown: ${event.key}`)
+ if (event.key != "F5") {
+ event.preventDefault()
+ event.stopPropagation()
+ switch (event.key) {
+ case "Enter":
+ txtoutput.textContent += '\n'
+ stdin += '\n'
+ forth()
+ output.print(" _ok.")
+ outframe.scrollTop = outframe.scrollHeight;
+ txtoutput.textContent += '\n'
+ break
+ case "Backspace":
+ stdin = stdin.substring(0, stdin.length - 1)
+ txtoutput.textContent = txtoutput.textContent.substring(0, txtoutput.textContent.length - 1)
+ break
+ default:
+ if (event.key.length == 1) {
+ const input = event.ctrlKey ? ` \\\^${event.key} ` : event.key
+ stdin += input
+ output.print(input)
+ }
+ break
}
- const numBytes = channels[channel].read(toBuffer + 8, maxBytes - 4)
- lenView.setUint32(4,numBytes,true);
- console.log(`read wrote ${lenView.getUint32(4,true)} bytes, remainder: ${stdin}`)
- console.log(`read blen:${lenView.getUint32(0,true)} cstrlen:${lenView.getUint32(4,true)}`) },
- sys_listen: (reqAddr, cbAddr) => {
- //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.
- },
- sys_fetch: (channel, reqAddr) => {
- //TODO: map to fetch promise, write to channel buffer,
- //javascript "fetch" as fallback, explicit handles for
- //"textEntry" or any third party protocols like activitypub
- console.log(`fetch ${channel} ${reqAddr}`)
+ }
+ })*/
+
+ const channels = [{
+ read: (writeAddr, maxBytes) => {
+ const maxChars = maxBytes >> 1
+ const bufView = new Uint16Array(wasmMem.buffer, writeAddr, maxChars)
+ let i
+ for (i = 0; i < maxChars && i < stdin.length; i++)
+ bufView[i] = stdin.charCodeAt(i)
+ stdin = stdin.substring(maxChars)
+ return i << 1
},
- sys_echo: (val) => output.print(`${val} `),
- sys_echochar: (val) => output.print(String.fromCharCode(val)),
- sys_reflect: (addr) => {
- console.log(`reflect: ${addr}: ${
+ write: (readAddr, maxBytes) =>
+ output.print(String.fromCharCode.apply(
+ null,
+ new Uint16Array(wasmMem.buffer, readAddr, maxBytes >> 1)
+ ))
+ }]
+ const simstack = []
+ const rstack = []
+ const dictionary = {
+ ';': 1,
+ 'LIT': 2,
+ RINIT: 3,
+ WORD: 16500,
+ KEY: 5,
+ DUP: 6,
+ '+': 7,
+ 'NOOP2': 8,
+ '.': 9,
+ '@': 10,
+ '!': 11,
+ EXECUTE: 12,
+ NOOP: 13,
+ 'JZ:': 14,
+ 'JNZ:': 15,
+ DROP: 16,
+ 'WS?': 17,
+ 'JMP:': 18,
+ 'WPUTC': 19,
+ 'WB0': 20,
+ 'FIND': 21,
+ 'NUMBER': 22,
+ 'W!LEN': 23,
+ 'J-1:': 24,
+ 'BYE': 25,
+ 'SWAP': 26,
+ 'WORDS': 27,
+ 'HERE': 28,
+ 'DEFINE': 29,
+ '2DUP': 30,
+ 'ROT': 31,
+ '2DROP': 32,
+ ',': 33,
+ '-': 34,
+ 'CHANNEL!': 35,
+ 'HERE!': 36,
+ '=?': 37,
+ '.S': 38,
+ 'STRING-START': 39,
+ 'STRING-PUT': 40,
+ 'STRING-END': 41,
+ ':': 16800,
+ 'MODE': 14336,
+ 'EXECUTE-MODE': 16680,
+ 'QUIT': 16384,
+ 'INTERPRET': 16400
+ }
+ const wasmImport = {
+ env: {
+ pop: () => simstack.pop(),
+ push: (val) => simstack.push(val),
+ rinit: () => rstack.length = 0,
+ rpop: () => rstack.pop(),
+ rpush: (val) => rstack.push(val),
+ sys_write: (channel, addr, u) => channels[channel] === undefined ? 0 : channels[channel].write(addr, u),
+ sys_read: (channel, addr, u) => channels[channel] === undefined ? 0 : channels[channel].read(addr, u),
+ sys_listen: (reqAddr, cbAddr) => {
+ //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.
+ },
+ sys_fetch: (channel, reqAddr) => {
+ //TODO: map to fetch promise, write to channel buffer,
+ //javascript "fetch" as fallback, explicit handles for
+ //"textEntry" or any third party protocols like activitypub
+ console.log(`fetch ${channel} ${reqAddr}`)
+ },
+ sys_echo: (val) => output.print(`${val} `),
+ sys_echochar: (val) => output.print(String.fromCharCode(val)),
+ sys_reflect: (addr) => {
+ console.log(`reflect: ${addr}: ${
new DataView(wasmMem.buffer, addr, 4)
.getUint32(0,true)
}`)
- },
- vocab_get: (addr, u) => {
- const word = String.fromCharCode.apply(
- null,
- new Uint16Array(wasmMem.buffer, addr, u >> 1)
- )
- const answer = dictionary[word.toUpperCase()]
- console.log(`vocab_get ${word}: ${answer}`)
- if (answer === undefined)
- return 0
- return answer
- },
- vocab_set: (addr, u, num) => {
- console.log(`vocab set ${addr} ${u} ${num}`)
- const word = String.fromCharCode.apply(
- null,
- new Uint16Array(wasmMem.buffer, addr, u >> 1)
- )
- console.log(`vocab_set ${word}: ${num}`)
- dictionary[word.toUpperCase()] = num
- return 0
- },
- is_whitespace: (key) => /\s/.test(String.fromCharCode(key)),
- sys_stack: () => console.log(`[${simstack}]`),
- sys_parsenum: (addr, u, base) => {
- const word = String.fromCharCode.apply(
- null,
- new Uint16Array(wasmMem.buffer, addr, u >> 1)
- )
- const answer = Number.parseInt(word, base)
- console.log(`parsenum: ${addr} ${u} ${base}`)
- console.log(`parsenum: ${word} ${answer}`)
- console.log(`parsenum: ${Number.isNaN(answer)}`)
- if (Number.isNaN(answer))
- return -1
- new DataView(wasmMem.buffer, addr, 4).setUint32(0,answer,true)
- return 0
- },
- sys_words: () => {
- output.print(Object.getOwnPropertyNames(dictionary).toString().split(',').join(' '))
+ },
+ vocab_get: (addr, u) => {
+ const word = String.fromCharCode.apply(
+ null,
+ new Uint16Array(wasmMem.buffer, addr, u >> 1)
+ )
+ const answer = dictionary[word.toUpperCase()]
+ if (answer === undefined)
+ return 0
+ return answer
+ },
+ vocab_set: (addr, u, num) => {
+ const word = String.fromCharCode.apply(
+ null,
+ new Uint16Array(wasmMem.buffer, addr, u >> 1)
+ )
+ dictionary[word.toUpperCase()] = num
+ return 0
+ },
+ is_whitespace: (key) => /\s/.test(String.fromCharCode(key)),
+ sys_stack: () => console.log(`[${simstack}]`),
+ sys_parsenum: (addr, u, base) => {
+ const word = String.fromCharCode.apply(
+ null,
+ new Uint16Array(wasmMem.buffer, addr, u >> 1)
+ )
+ const answer = Number.parseInt(word, base)
+ if (Number.isNaN(answer))
+ return -1
+ new DataView(wasmMem.buffer, addr, 4).setUint32(0,answer,true)
+ return 0
+ },
+ sys_words: () => {
+ output.print(Object.getOwnPropertyNames(dictionary).toString().split(',').join(' '))
+ }
}
}
-}
-
-fetch('forth.forth').then((re) => re.text()).then((txt) => {
- stdin = txt
- fetch('forth.wasm')
- .then(re => re.arrayBuffer())
- .then(buf => WebAssembly.instantiate(buf, wasmImport))
- .then(result => {
- wasmMem = result.instance.exports.memory
- forth = result.instance.exports.main
- console.log('wasm loaded');
- forth()
+ initialize.then((results) => {
+ stdin = results[0]
+ WebAssembly.instantiate(results[1], wasmImport).then((module) => {
+ wasmMem = module.instance.exports.memory
+ forth = module.instance.exports.main
+ console.log('wasm loaded')
+ forth()
+ })
})
-})
+}
(type $FUNCSIGiii (func))
(type $FUNCSIGiv (func (param i32 i32) (result i32)))
(type $FUNCSIG$v (func (param i32) (result i32)))
- (type $FUNCSIG$vi (func (param i32 i32) (result i32)))
+ (type $FUNCSIG$vi (func (param i32 i32 i32) (result i32)))
(import "env" "pop" (func $pop (result i32)))
(import "env" "push" (func $push (param i32)))
(import "env" "rinit" (func $rinit))
(import "env" "rpop" (func $rpop (result i32)))
(import "env" "rpush" (func $rpush (param i32)))
- (import "env" "sys_read" (func $sys_read (param i32 i32) (result i32)))
+ (import "env" "sys_read" (func $sys_read (param i32 i32 i32) (result i32)))
(import "env" "sys_fetch" (func $sys_fetch (param i32 i32) (result i32)))
(import "env" "sys_listen" (func $sys_listen (param i32) (result i32)))
(import "env" "sys_write" (func $sys_write (param i32 i32 i32) (result i32)))
(global $inbuf_size i32 (i32.const 12292))
(global $inbuf_data i32 (i32.const 12296))
(global $kvars i32 (i32.const 14336)) ;; 0x3800 Size: 2048
- (data (i32.const 12288) "\fc\07\00\00") ;; 2044 len
+ (data (i32.const 12288) "\f8\07\00\00") ;; 2040 len
(data (i32.const 14336) "\28\41\00\00") ;; MODE
(data (i32.const 14340) "\04\42\00\00") ;; HERE
(data (i32.const 14344) "\00\40\00\00") ;; START
(data (i32.const 16752) "\19\00\00\00") ;; BYE
(; Do Backslash ;)
(data (i32.const 16788) "\05\00\00\00") ;; KEY
- (data (i32.const 16792) "\02\00\00\00") ;; LIT
- (data (i32.const 16796) "\20\00\00\00") ;; 32 (space)
- (data (i32.const 16800) "\25\00\00\00") ;; =?
- (data (i32.const 16804) "\0f\00\00\00") ;; JNZ:
- (data (i32.const 16808) "\bc\41\00\00") ;; addr of keypump
- (data (i32.const 16812) "\cc\40\00\00") ;; WORDLOOP + 1
- (data (i32.const 16816) "\28\41\00\00") ;; EXECUTE-MODE
- (data (i32.const 16820) "\01\00\00\00") ;; RET
+ (data (i32.const 16792) "\11\00\00\00") ;; WS?
+ (data (i32.const 16796) "\0f\00\00\00") ;; JNZ:
+ (data (i32.const 16800) "\c8\41\00\00") ;; addr of keypump + 3
+ (data (i32.const 16804) "\cc\40\00\00") ;; WORDLOOP + 1
+ (data (i32.const 16808) "\28\41\00\00") ;; EXECUTE-MODE
+ (data (i32.const 16812) "\01\00\00\00") ;; RET
+ (; Do Comment ;)
(data (i32.const 16828) "\18\00\00\00") ;; j-1: <-- keypump
(data (i32.const 16832) "\e0\41\00\00") ;; addr of end
(data (i32.const 16836) "\05\00\00\00") ;; KEY
set_local $channel
block $bye
loop $next
- call $sys_stack
- get_local $esi
- call $sys_reflect
get_local $esi
get_local $esi
i32.const 4
block $dictget block $parsenum block $wordfinish block $jneg1 block $swap
block $words block $here block $dictset block $dup2 block $rot block $drop2
block $comma block $subtract block $keychan block $sethere block $eqbool
- block $echostring
+ block $echostring block $strstart block $strput block $strend
get_local $eax
br_table $op0 $ret (;2;)$lit $rinit (;4;)$word $key (;6;)$dup $plus
(;8;)$jmp $emit (;10;)$fetch $set (;12;)$execute $noop (;14;)$jz $jnz
(;16;)$drop $wsbool (;18;)$jmp $wordputc (;20;)$wordstart $dictget
(;22;)$parsenum $wordfinish (;24;)$jneg1 $bye (;26;)$swap $words
(;28;)$here $dictset (;30;)$dup2 $rot (;32;)$drop2 $comma
- (;34;)$subtract $keychan (;36;)$sethere $eqbool (;38;)$echostring $default
+ (;34;)$subtract $keychan (;36;)$sethere $eqbool (;38;)$echostring $strstart
+ (;40;)$strput $strend $default
+ end ;; strend
+ get_local $stringbelt_tail
+ get_local $stringbelt_head
+ get_local $stringbelt_tail
+ i32.const 4
+ i32.add
+ i32.sub
+ tee_local $eax (; n bytes ;)
+ i32.store
+ (; align to 32-bit ;)
+ get_local $stringbelt_head
+ i32.const 3
+ i32.add
+ i32.const 8188
+ i32.and
+ set_local $stringbelt_head
+ (; /align ;)
+ get_local $stringbelt_tail
+ i32.const 4
+ i32.add
+ call $push
+ get_local $eax
+ call $push
+ br $next
+ end ;; strput
+ block $sbhasspace2
+ get_local $stringbelt_head
+ get_global $wordbelt
+ i32.lt_u
+ br_if $sbhasspace2
+ i32.const 0
+ tee_local $stringbelt_head
+ get_local $stringbelt_tail
+ i32.load
+ i32.store
+ get_local $stringbelt_head
+ i32.const 4
+ i32.add
+ set_local $stringbelt_head
+ get_local $stringbelt_tail
+ i32.const 4
+ i32.add
+ set_local $stringbelt_tail
+ loop $copystringtostart
+ get_local $stringbelt_head
+ get_local $stringbelt_tail
+ i32.load16_u
+ i32.store16
+ get_local $stringbelt_head
+ i32.const 2
+ i32.add
+ set_local $stringbelt_head
+ get_local $stringbelt_tail
+ i32.const 2
+ i32.add
+ tee_local $stringbelt_tail
+ get_global $wordbelt
+ i32.le_u
+ br_if $copystringtostart
+ end
+ i32.const 0
+ set_local $stringbelt_tail
+ end
+ get_local $stringbelt_head
+ call $pop
+ i32.store16
+ get_local $stringbelt_head
+ i32.const 2
+ i32.add
+ set_local $stringbelt_head
+ br $next
+ end ;; strstart
+ block $sbhasspace
+ get_local $stringbelt_head
+ get_global $wordbelt
+ i32.const 8
+ i32.sub
+ i32.le_u
+ br_if $sbhasspace
+ i32.const 0
+ set_local $stringbelt_head
+ end
+ get_local $stringbelt_head
+ get_local $stringbelt_head
+ tee_local $stringbelt_tail
+ i32.const 0
+ i32.store
+ i32.const 4
+ i32.add
+ set_local $stringbelt_head
+ br $next
end ;; echostring
get_local $channel
call $pop
get_local $eax
call $push
call $push
- call $sys_stack
br $next
end ;; drop
call $pop
set_local $inbuf_head
br $next
end ;; key_read
+ get_global $inbuf_size
get_local $channel
+ get_global $inbuf_data
get_global $inbuf
+ i32.load
call $sys_read
+ i32.store
block $nullread
get_global $inbuf_size
i32.load
i32.eqz
br_if $nullread
+ get_global $inbuf_data
+ set_local $inbuf_head
br $key_loop
end ;; nullread
i32.const -1 ;; <- keyval sent if sz == 0