word belts, utf16 strings, interrupt words, compiler modes
[watForth.git] / forth.js
1 'use strict'
2 let txtdiv = document.createElement("div")
3 document.body.appendChild(txtdiv)
4 const output = {
5 print: (string) => txtdiv.textContent += string
6 }
7 let wasmMem
8 let forth
9
10 /* Input capture */
11 let stdin = ""
12 document.addEventListener('keydown', (event) => {
13 console.log(`keydown: ${event.key}`)
14 if (event.key != "F5") {
15 event.preventDefault()
16 event.stopPropagation()
17 switch (event.key) {
18 case "Enter":
19 txtdiv = document.createElement("div")
20 document.body.appendChild(txtdiv)
21 forth()
22 output.print("ok.")
23 txtdiv = document.createElement("div")
24 document.body.appendChild(txtdiv)
25 break
26 case "Backspace":
27 stdin = stdin.substring(0, stdin.length - 1)
28 txtdiv.textContent = txtdiv.textContent.substring(0, txtdiv.textContent.length - 1)
29 break
30 default:
31 if (event.key.length == 1) {
32 stdin += event.key
33 output.print(event.key)
34 }
35 break
36 }
37 }
38 })
39
40 const channels = [{
41 read: (writeAddr, maxBytes) => {
42 const maxChars = maxBytes >> 1
43 const bufView = new Uint16Array(wasmMem.buffer, writeAddr, maxChars)
44 let i
45 for (i = 0; i < maxChars && i < stdin.length; i++)
46 bufView[i] = stdin.charCodeAt(i)
47 stdin = stdin.substring(maxChars)
48 return i << 1
49 },
50 write: (readAddr, maxBytes) =>
51 output.print(String.fromCharCode.apply(
52 null,
53 new Uint16Array(wasmMem.buffer, readAddr, maxBytes)
54 ))
55 }]
56 const simstack = []
57 const rstack = []
58 const dictionary = {
59 ';': 1,
60 'LIT': 2,
61 RINIT: 3,
62 WORD: 16500,
63 KEY: 5,
64 DUP: 6,
65 '+': 7,
66 'NOOP2': 8,
67 '.': 9,
68 '@': 10,
69 '!': 11,
70 EXECUTE: 12,
71 NOOP: 13,
72 'JZ:': 14,
73 'JNZ:': 15,
74 DROP: 16,
75 'WS?': 17,
76 'JMP:': 18,
77 'WPUTC': 19,
78 'WB0': 20,
79 'FIND': 21,
80 'NUMBER': 22,
81 'W!LEN': 23,
82 'J-1:': 24,
83 'BYE': 25,
84 'SWAP': 26,
85 'WORDS': 27,
86 'HERE': 28,
87 'DEFINE': 29,
88 '2DUP': 30,
89 'ROT': 31,
90 '2DROP': 32,
91 ',': 33,
92 '-': 34,
93 'CHANNEL!': 35,
94 'HERE!': 36,
95 '=?': 37,
96 ':': 16800,
97 'MODE': 14336,
98 'EXECUTE-MODE': 16680,
99 'QUIT': 16384,
100 'INTERPRET': 16400
101 }
102 const wasmImport = {
103 env: {
104 pop: () => simstack.pop(),
105 push: (val) => simstack.push(val),
106 rinit: () => rstack.length = 0,
107 rpop: () => rstack.pop(),
108 rpush: (val) => rstack.push(val),
109 sys_write: (channel, fromBuffer) => {
110 if (channels[channel] === undefined)
111 return
112 const maxBytes = new DataView(
113 wasmMem.buffer,
114 fromBuffer,
115 4
116 ).getUint32(0,true)
117 console.log(`write ch:${channel} addr:${fromBuffer} len:${maxBytes}`)
118 channels[channel].write(fromBuffer + 4, maxBytes)
119 },
120 sys_read: (channel, toBuffer) => {
121 console.log(`read ch:${channel} buf:${toBuffer} current: ${stdin}`)
122 const lenView = new DataView(wasmMem.buffer, toBuffer, 8)
123 const maxBytes = lenView.getUint32(0,true)
124 console.log(`read blen:${lenView.getUint32(0,true)} cstrlen:${lenView.getUint32(4,true)}`)
125 /* If the channel is undefined, or if there isn't enough room in
126 * toBuffer for even one character, then, if there is enough
127 * room for an int write a zero, exit */
128 if (channels[channel] === undefined || maxBytes < 6) {
129 if (maxBytes >= 4)
130 lenView.setUint32(4,0,true)
131 }
132 const numBytes = channels[channel].read(toBuffer + 8, maxBytes - 4)
133 lenView.setUint32(4,numBytes,true);
134 console.log(`read wrote ${lenView.getUint32(4,true)} bytes, remainder: ${stdin}`)
135 console.log(`read blen:${lenView.getUint32(0,true)} cstrlen:${lenView.getUint32(4,true)}`) },
136 sys_listen: (reqAddr, cbAddr) => {
137 //TODO: call into the module to wasm fn "event" to push an event
138 //to forth, which pushes esi to the return stack and queues the
139 //callback function provided from listen. reqaddr could be
140 //"fetch", in which case no channel is used. otherwise very
141 //similar to sys_fetch.
142 },
143 sys_fetch: (channel, reqAddr) => {
144 //TODO: map to fetch promise, write to channel buffer,
145 //javascript "fetch" as fallback, explicit handles for
146 //"textEntry" or any third party protocols like activitypub
147 console.log(`fetch ${channel} ${reqAddr}`)
148 },
149 sys_echo: (val) => output.print(`${val} `),
150 sys_echochar: (val) => output.print(String.fromCharCode(val)),
151 sys_reflect: (addr) => {
152 console.log(`reflect: ${addr}: ${
153 new DataView(wasmMem.buffer, addr, 4)
154 .getUint32(0,true)
155 }`)
156 },
157 vocab_get: (addr, u) => {
158 const word = String.fromCharCode.apply(
159 null,
160 new Uint16Array(wasmMem.buffer, addr, u >> 1)
161 )
162 const answer = dictionary[word.toUpperCase()]
163 console.log(`vocab_get ${word}: ${answer}`)
164 if (answer === undefined)
165 return 0
166 return answer
167 },
168 vocab_set: (addr, u, num) => {
169 console.log(`vocab set ${addr} ${u} ${num}`)
170 const word = String.fromCharCode.apply(
171 null,
172 new Uint16Array(wasmMem.buffer, addr, u >> 1)
173 )
174 console.log(`vocab_set ${word}: ${num}`)
175 dictionary[word.toUpperCase()] = num
176 return 0
177 },
178 is_whitespace: (key) => /\s/.test(String.fromCharCode(key)),
179 sys_stack: () => console.log(`[${simstack}]`),
180 sys_parsenum: (addr, u, base) => {
181 const word = String.fromCharCode.apply(
182 null,
183 new Uint16Array(wasmMem.buffer, addr, u >> 1)
184 )
185 const answer = Number.parseInt(word, base)
186 console.log(`parsenum: ${addr} ${u} ${base}`)
187 console.log(`parsenum: ${word} ${answer}`)
188 console.log(`parsenum: ${Number.isNaN(answer)}`)
189 if (Number.isNaN(answer))
190 return -1
191 new DataView(wasmMem.buffer, addr, 4).setUint32(0,answer,true)
192 return 0
193 },
194 sys_words: () => {
195 output.print(Object.getOwnPropertyNames(dictionary).toString().split(',').join(' '))
196 }
197 }
198 }
199
200 fetch('forth.forth').then((re) => re.text()).then((txt) => {
201 stdin = txt
202 fetch('forth.wasm')
203 .then(re => re.arrayBuffer())
204 .then(buf => WebAssembly.instantiate(buf, wasmImport))
205 .then(result => {
206 wasmMem = result.instance.exports.memory
207 forth = result.instance.exports.main
208 console.log('wasm loaded');
209 forth()
210 })
211 })