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