c05d444d08215e8daf4bc804fbab3454fd29830e
[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 let forthdiv = document.getElementById("forth")
20 if (forthdiv === null) {
21 forthdiv = document.createElement("div")
22 forthdiv.setAttribute("style","height:400px;")
23 document.body.appendChild(forthdiv)
24 }
25 const outframe = document.createElement("div")
26 outframe.setAttribute("style", "overflow-y:hidden;height:40%;")
27 const txtoutput = document.createElement("pre")
28 txtoutput.setAttribute("style", "white-space:pre-wrap;")
29 const txtinput = document.createElement("textarea")
30 let txtinputrows = "1"
31 txtinput.setAttribute("autofocus", "true")
32 txtinput.setAttribute("cols", "60")
33 txtinput.setAttribute("rows", txtinputrows)
34 outframe.appendChild(txtoutput)
35 forthdiv.appendChild(outframe)
36 forthdiv.appendChild(txtinput)
37 const output = {
38 print: (string) => txtoutput.textContent += string
39 }
40 let wasmMem
41 let forth
42
43 /* Input capture */
44 let stdin = ""
45 txtinput.addEventListener('keydown', (event) => {
46 switch (event.key) {
47 case "Enter":
48 if (event.ctrlKey) {
49 txtinput.value += '\n'
50 txtinputrows = (Number.parseInt(txtinputrows, 10) + 1).toString()
51 txtinput.setAttribute("rows", txtinputrows)
52 }
53 else {
54 stdin += txtinput.value
55 txtoutput.textContent += txtinput.value + '\n'
56 txtinput.value = ""
57 txtinputrows = "1"
58 txtinput.setAttribute("rows", txtinputrows)
59 event.preventDefault()
60 event.stopPropagation()
61 forth()
62 txtoutput.textContent += " _ok.\n"
63 outframe.scrollTop = outframe.scrollHeight;
64 }
65 break
66 case "Backspace":
67 if (txtinput.value.charCodeAt(txtinput.value.length - 1) === 10) {
68 txtinputrows = (Number.parseInt(txtinputrows, 10) - 1).toString()
69 txtinput.setAttribute("rows", txtinputrows)
70 }
71 break
72 default:
73 break
74 }
75 })
76 /* document.addEventListener('keydown', (event) => {
77 //console.log(`keydown: ${event.key}`)
78 if (event.key != "F5") {
79 event.preventDefault()
80 event.stopPropagation()
81 switch (event.key) {
82 case "Enter":
83 txtoutput.textContent += '\n'
84 stdin += '\n'
85 forth()
86 output.print(" _ok.")
87 outframe.scrollTop = outframe.scrollHeight;
88 txtoutput.textContent += '\n'
89 break
90 case "Backspace":
91 stdin = stdin.substring(0, stdin.length - 1)
92 txtoutput.textContent = txtoutput.textContent.substring(0, txtoutput.textContent.length - 1)
93 break
94 default:
95 if (event.key.length == 1) {
96 const input = event.ctrlKey ? ` \\\^${event.key} ` : event.key
97 stdin += input
98 output.print(input)
99 }
100 break
101 }
102 }
103 })*/
104
105 const channels = [{
106 read: (writeAddr, maxBytes) => {
107 const maxChars = maxBytes >> 1
108 const bufView = new Uint16Array(wasmMem.buffer, writeAddr, maxChars)
109 let i
110 for (i = 0; i < maxChars && i < stdin.length; i++)
111 bufView[i] = stdin.charCodeAt(i)
112 stdin = stdin.substring(maxChars)
113 return i << 1
114 },
115 write: (readAddr, maxBytes) =>
116 output.print(String.fromCharCode.apply(
117 null,
118 new Uint16Array(wasmMem.buffer, readAddr, maxBytes >> 1)
119 ))
120 }]
121 const simstack = []
122 const rstack = []
123 const dictionary = {
124 ';': 1,
125 'LIT': 2,
126 RINIT: 3,
127 WORD: 16500,
128 KEY: 5,
129 DUP: 6,
130 '+': 7,
131 'NOOP2': 8,
132 '.': 9,
133 '@': 10,
134 '!': 11,
135 EXECUTE: 12,
136 NOOP: 13,
137 'JZ:': 14,
138 'JNZ:': 15,
139 DROP: 16,
140 'WS?': 17,
141 'JMP:': 18,
142 'WPUTC': 19,
143 'WB0': 20,
144 'FIND': 21,
145 'NUMBER': 22,
146 'W!LEN': 23,
147 'J-1:': 24,
148 'BYE': 25,
149 'SWAP': 26,
150 'WORDS': 27,
151 'HERE': 28,
152 'DEFINE': 29,
153 '2DUP': 30,
154 'ROT': 31,
155 '2DROP': 32,
156 ',': 33,
157 '-': 34,
158 'CHANNEL!': 35,
159 'HERE!': 36,
160 '=?': 37,
161 '.S': 38,
162 'STRING-START': 39,
163 'STRING-PUT': 40,
164 'STRING-END': 41,
165 ':': 16800,
166 'MODE': 14336,
167 'EXECUTE-MODE': 16680,
168 'QUIT': 16384,
169 'INTERPRET': 16400
170 }
171 const wasmImport = {
172 env: {
173 pop: () => simstack.pop(),
174 push: (val) => simstack.push(val),
175 rinit: () => rstack.length = 0,
176 rpop: () => rstack.pop(),
177 rpush: (val) => rstack.push(val),
178 sys_write: (channel, addr, u) => channels[channel] === undefined ? 0 : channels[channel].write(addr, u),
179 sys_read: (channel, addr, u) => channels[channel] === undefined ? 0 : channels[channel].read(addr, u),
180 sys_listen: (reqAddr, cbAddr) => {
181 //TODO: call into the module to wasm fn "event" to push an event
182 //to forth, which pushes esi to the return stack and queues the
183 //callback function provided from listen. reqaddr could be
184 //"fetch", in which case no channel is used. otherwise very
185 //similar to sys_fetch.
186 },
187 sys_fetch: (channel, reqAddr) => {
188 //TODO: map to fetch promise, write to channel buffer,
189 //javascript "fetch" as fallback, explicit handles for
190 //"textEntry" or any third party protocols like activitypub
191 console.log(`fetch ${channel} ${reqAddr}`)
192 },
193 sys_echo: (val) => output.print(`${val} `),
194 sys_echochar: (val) => output.print(String.fromCharCode(val)),
195 sys_reflect: (addr) => {
196 console.log(`reflect: ${addr}: ${
197 new DataView(wasmMem.buffer, addr, 4)
198 .getUint32(0,true)
199 }`)
200 },
201 vocab_get: (addr, u) => {
202 const word = String.fromCharCode.apply(
203 null,
204 new Uint16Array(wasmMem.buffer, addr, u >> 1)
205 )
206 const answer = dictionary[word.toUpperCase()]
207 if (answer === undefined)
208 return 0
209 return answer
210 },
211 vocab_set: (addr, u, num) => {
212 const word = String.fromCharCode.apply(
213 null,
214 new Uint16Array(wasmMem.buffer, addr, u >> 1)
215 )
216 dictionary[word.toUpperCase()] = num
217 return 0
218 },
219 is_whitespace: (key) => /\s/.test(String.fromCharCode(key)),
220 sys_stack: () => console.log(`[${simstack}]`),
221 sys_parsenum: (addr, u, base) => {
222 const word = String.fromCharCode.apply(
223 null,
224 new Uint16Array(wasmMem.buffer, addr, u >> 1)
225 )
226 const answer = Number.parseInt(word, base)
227 if (Number.isNaN(answer))
228 return -1
229 new DataView(wasmMem.buffer, addr, 4).setUint32(0,answer,true)
230 return 0
231 },
232 sys_words: () => {
233 output.print(Object.getOwnPropertyNames(dictionary).toString().split(',').join(' '))
234 }
235 }
236 }
237 initialize.then((results) => {
238 stdin = results[0]
239 WebAssembly.instantiate(results[1], wasmImport).then((module) => {
240 wasmMem = module.instance.exports.memory
241 forth = module.instance.exports.main
242 console.log('wasm loaded')
243 forth()
244 })
245 })
246 }