a3920952a75ee86829620d4e84277b010ef1778a
[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 >> 1)
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 '.s': 38,
97 ':': 16800,
98 'MODE': 14336,
99 'EXECUTE-MODE': 16680,
100 'QUIT': 16384,
101 'INTERPRET': 16400
102 }
103 const wasmImport = {
104 env: {
105 pop: () => simstack.pop(),
106 push: (val) => simstack.push(val),
107 rinit: () => rstack.length = 0,
108 rpop: () => rstack.pop(),
109 rpush: (val) => rstack.push(val),
110 sys_write: (channel, addr, u) => {
111 if (channels[channel] === undefined)
112 return
113 console.log(`write ch:${channel} addr:${addr} len:${u}`)
114 channels[channel].write(addr, u)
115 },
116 sys_read: (channel, toBuffer) => {
117 console.log(`read ch:${channel} buf:${toBuffer} current: ${stdin}`)
118 const lenView = new DataView(wasmMem.buffer, toBuffer, 8)
119 const maxBytes = lenView.getUint32(0,true)
120 console.log(`read blen:${lenView.getUint32(0,true)} cstrlen:${lenView.getUint32(4,true)}`)
121 /* If the channel is undefined, or if there isn't enough room in
122 * toBuffer for even one character, then, if there is enough
123 * room for an int write a zero, exit */
124 if (channels[channel] === undefined || maxBytes < 6) {
125 if (maxBytes >= 4)
126 lenView.setUint32(4,0,true)
127 }
128 const numBytes = channels[channel].read(toBuffer + 8, maxBytes - 4)
129 lenView.setUint32(4,numBytes,true);
130 console.log(`read wrote ${lenView.getUint32(4,true)} bytes, remainder: ${stdin}`)
131 console.log(`read blen:${lenView.getUint32(0,true)} cstrlen:${lenView.getUint32(4,true)}`) },
132 sys_listen: (reqAddr, cbAddr) => {
133 //TODO: call into the module to wasm fn "event" to push an event
134 //to forth, which pushes esi to the return stack and queues the
135 //callback function provided from listen. reqaddr could be
136 //"fetch", in which case no channel is used. otherwise very
137 //similar to sys_fetch.
138 },
139 sys_fetch: (channel, reqAddr) => {
140 //TODO: map to fetch promise, write to channel buffer,
141 //javascript "fetch" as fallback, explicit handles for
142 //"textEntry" or any third party protocols like activitypub
143 console.log(`fetch ${channel} ${reqAddr}`)
144 },
145 sys_echo: (val) => output.print(`${val} `),
146 sys_echochar: (val) => output.print(String.fromCharCode(val)),
147 sys_reflect: (addr) => {
148 console.log(`reflect: ${addr}: ${
149 new DataView(wasmMem.buffer, addr, 4)
150 .getUint32(0,true)
151 }`)
152 },
153 vocab_get: (addr, u) => {
154 const word = String.fromCharCode.apply(
155 null,
156 new Uint16Array(wasmMem.buffer, addr, u >> 1)
157 )
158 const answer = dictionary[word.toUpperCase()]
159 console.log(`vocab_get ${word}: ${answer}`)
160 if (answer === undefined)
161 return 0
162 return answer
163 },
164 vocab_set: (addr, u, num) => {
165 console.log(`vocab set ${addr} ${u} ${num}`)
166 const word = String.fromCharCode.apply(
167 null,
168 new Uint16Array(wasmMem.buffer, addr, u >> 1)
169 )
170 console.log(`vocab_set ${word}: ${num}`)
171 dictionary[word.toUpperCase()] = num
172 return 0
173 },
174 is_whitespace: (key) => /\s/.test(String.fromCharCode(key)),
175 sys_stack: () => console.log(`[${simstack}]`),
176 sys_parsenum: (addr, u, base) => {
177 const word = String.fromCharCode.apply(
178 null,
179 new Uint16Array(wasmMem.buffer, addr, u >> 1)
180 )
181 const answer = Number.parseInt(word, base)
182 console.log(`parsenum: ${addr} ${u} ${base}`)
183 console.log(`parsenum: ${word} ${answer}`)
184 console.log(`parsenum: ${Number.isNaN(answer)}`)
185 if (Number.isNaN(answer))
186 return -1
187 new DataView(wasmMem.buffer, addr, 4).setUint32(0,answer,true)
188 return 0
189 },
190 sys_words: () => {
191 output.print(Object.getOwnPropertyNames(dictionary).toString().split(',').join(' '))
192 }
193 }
194 }
195
196 fetch('forth.forth').then((re) => re.text()).then((txt) => {
197 stdin = txt
198 fetch('forth.wasm')
199 .then(re => re.arrayBuffer())
200 .then(buf => WebAssembly.instantiate(buf, wasmImport))
201 .then(result => {
202 wasmMem = result.instance.exports.memory
203 forth = result.instance.exports.main
204 console.log('wasm loaded');
205 forth()
206 })
207 })