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