formatting/prettiness
[watForth.git] / forth.js
1 /* This program is free software: you can redistribute it and/or modify
2 it under the terms of the GNU General Public License as published by
3 the Free Software Foundation, either version 3 of the License, or
4 (at your option) any later version.
5
6 This program is distributed in the hope that it will be useful,
7 but WITHOUT ANY WARRANTY; without even the implied warranty of
8 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 GNU General Public License for more details.
10
11 You should have received a copy of the GNU General Public License
12 along with this program. If not, see <http://www.gnu.org/licenses/>. */
13 'use strict'
14 const initialize = Promise.all([
15 fetch('forth.forth').then((re) => re.text()),
16 fetch('forth.wasm').then(re => re.arrayBuffer())
17 ])
18 window.onload = () => {
19 const simstack = []
20 const rstack = []
21
22 let forthdiv = document.getElementById("forth")
23 if (forthdiv === null) {
24 forthdiv = document.createElement("div")
25 forthdiv.setAttribute("style","height:100%;margin:auto;width:80%;")
26 document.body.appendChild(forthdiv)
27 }
28 const outframe = document.createElement("div")
29 outframe.setAttribute("style", "background-color:black;padding-left:6px;padding-right:6px;color:chartreuse;height:256px;resize:vertical;display:flex;align-items:flex-end;")
30 const stackview = document.createElement("pre")
31 stackview.setAttribute("style", "white-space:pre-wrap;flex:0 1 8%;")
32 outframe.appendChild(stackview)
33 const txtoutput = document.createElement("pre")
34 txtoutput.setAttribute("style", "white-space:pre-wrap;overflow-y:scroll;flex:1 0 342px;")
35 outframe.appendChild(txtoutput)
36 const rstackview = document.createElement("pre")
37 rstackview.setAttribute("style", "white-space:pre-wrap;flex:0 1 8%;")
38 outframe.appendChild(rstackview)
39 const memview = document.createElement("pre")
40 memview.setAttribute("style", "white-space:pre-wrap;flex:0 0 356px;")
41 outframe.appendChild(memview)
42 const txtinput = document.createElement("textarea")
43 txtinput.setAttribute("autofocus", "true")
44 txtinput.setAttribute("rows", "1")
45 txtinput.setAttribute("style", "white-space:pre;margin-left:8%;width:60%;")
46 txtinput.oninput = () => txtinput.rows = (txtinput.value.match(/[\n]/g) || [1]).length;
47 forthdiv.appendChild(outframe)
48 forthdiv.appendChild(txtinput)
49 let wasmMem
50 let forth
51 const output = {
52 print: (string) => txtoutput.textContent += `\\\ => ${string} \n`
53 }
54 const updateViews = () => {
55 stackview.textContent = simstack.join('\n')
56 rstackview.textContent = rstack.join('\n')
57 let cnt = 0;
58 const maxBytes = 256
59 let here = new DataView(wasmMem.buffer, 14340 /* here */, 4).getUint32(0,true)
60 memview.textContent = Array.from(new Uint8Array(wasmMem.buffer, here - maxBytes, maxBytes), (v) => {
61 cnt++;
62 v = ('0' + (v & 0xFF).toString(16)).slice(-2)
63 if (cnt === maxBytes)
64 return v
65 if ((cnt % 16) === 0)
66 return v + '\n'
67 if ((cnt % 4) === 0)
68 return v + ', '
69 return v + ' '
70 }).join('')
71 outframe.scrollTop = outframe.scrollHeight
72 }
73 /* Input capture */
74 let stdin = ""
75 txtinput.addEventListener('keydown', (event) => {
76 switch (event.key) {
77 case "Enter":
78 if (event.ctrlKey) {
79 txtinput.value += '\n'
80 txtinput.oninput()
81 }
82 else {
83 stdin += txtinput.value
84 txtoutput.textContent += txtinput.value
85 if (!/\s/.test(txtinput.value.slice(-1)))
86 txtoutput.textContent += " "
87 txtinput.value = ""
88 event.preventDefault()
89 event.stopPropagation()
90 forth()
91 updateViews()
92 }
93 break
94 case "Backspace":
95 break
96 default:
97 break
98 }
99 })
100 const channels = [{
101 read: (writeAddr, maxBytes) => {
102 const maxChars = maxBytes >> 1
103 const bufView = new Uint16Array(wasmMem.buffer, writeAddr, maxChars)
104 let i
105 for (i = 0; i < maxChars && i < stdin.length; i++)
106 bufView[i] = stdin.charCodeAt(i)
107 stdin = stdin.substring(maxChars)
108 return i << 1
109 },
110 write: (readAddr, maxBytes) =>
111 output.print(String.fromCharCode.apply(
112 null,
113 new Uint16Array(wasmMem.buffer, readAddr, maxBytes >> 1)
114 ))
115 }]
116 const dictionary = {
117 ';': 1,
118 'LIT': 2,
119 RINIT: 3,
120 WORD: 16500,
121 KEY: 5,
122 DUP: 6,
123 '+': 7,
124 'NOOP2': 8,
125 '.': 9,
126 '@': 10,
127 '!': 11,
128 EXECUTE: 12,
129 NOOP: 13,
130 'JZ:': 14,
131 'JNZ:': 15,
132 DROP: 16,
133 'WS?': 17,
134 'JMP:': 18,
135 'WPUTC': 19,
136 'WB0': 20,
137 'FIND': 21,
138 'NUMBER': 22,
139 'W!LEN': 23,
140 'J-1:': 24,
141 'BYE': 25,
142 'SWAP': 26,
143 'WORDS': 27,
144 'HERE': 28,
145 'DEFINE': 29,
146 '2DUP': 30,
147 'ROT': 31,
148 '2DROP': 32,
149 ',': 33,
150 '-': 34,
151 'CHANNEL!': 35,
152 'HERE!': 36,
153 '=?': 37,
154 '.S': 38,
155 'STRING-START': 39,
156 'STRING-PUT': 40,
157 'STRING-END': 41,
158 ':': 16800,
159 'MODE': 14336,
160 'EXECUTE-MODE': 16680,
161 'QUIT': 16384,
162 'INTERPRET': 16400
163 }
164 const wasmImport = {
165 env: {
166 pop: () => simstack.pop(),
167 push: (val) => simstack.push(val),
168 rinit: () => rstack.length = 0,
169 rpop: () => rstack.pop(),
170 rpush: (val) => rstack.push(val),
171 sys_write: (channel, addr, u) => channels[channel] === undefined ? 0 : channels[channel].write(addr, u),
172 sys_read: (channel, addr, u) => channels[channel] === undefined ? 0 : channels[channel].read(addr, u),
173 sys_listen: (reqAddr, cbAddr) => {
174 //TODO: call into the module to wasm fn "event" to push an event
175 //to forth, which pushes esi to the return stack and queues the
176 //callback function provided from listen. reqaddr could be
177 //"fetch", in which case no channel is used. otherwise very
178 //similar to sys_fetch.
179 },
180 sys_fetch: (channel, reqAddr) => {
181 //TODO: map to fetch promise, write to channel buffer,
182 //javascript "fetch" as fallback, explicit handles for
183 //"textEntry" or any third party protocols like activitypub
184 console.log(`fetch ${channel} ${reqAddr}`)
185 },
186 sys_echo: (val) => output.print(`${val} `),
187 sys_echochar: (val) => output.print(String.fromCharCode(val)),
188 sys_reflect: (addr) => {
189 console.log(`reflect: ${addr}: ${
190 new DataView(wasmMem.buffer, addr, 4)
191 .getUint32(0,true)
192 }`)
193 },
194 vocab_get: (addr, u) => {
195 const word = String.fromCharCode.apply(
196 null,
197 new Uint16Array(wasmMem.buffer, addr, u >> 1)
198 )
199 const answer = dictionary[word.toUpperCase()]
200 if (answer === undefined)
201 return 0
202 return answer
203 },
204 vocab_set: (addr, u, num) => {
205 const word = String.fromCharCode.apply(
206 null,
207 new Uint16Array(wasmMem.buffer, addr, u >> 1)
208 )
209 dictionary[word.toUpperCase()] = num
210 return 0
211 },
212 is_whitespace: (key) => /\s/.test(String.fromCharCode(key)),
213 sys_stack: () => console.log(`[${simstack}]`),
214 sys_parsenum: (addr, u, base) => {
215 const word = String.fromCharCode.apply(
216 null,
217 new Uint16Array(wasmMem.buffer, addr, u >> 1)
218 )
219 const answer = Number.parseInt(word, base)
220 if (Number.isNaN(answer))
221 return -1
222 new DataView(wasmMem.buffer, addr, 4).setUint32(0,answer,true)
223 return 0
224 },
225 sys_words: () => {
226 output.print(Object.getOwnPropertyNames(dictionary).toString().split(',').join(' '))
227 }
228 }
229 }
230 initialize.then((results) => {
231 stdin = results[0]
232 WebAssembly.instantiate(results[1], wasmImport).then((module) => {
233 wasmMem = module.instance.exports.memory
234 forth = module.instance.exports.main
235 console.log('wasm loaded')
236 forth()
237 updateViews()
238 })
239 })
240 }