compiler-mode implemented
[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', {credentials: 'include', headers:{'content-type':'text/plain'}}).then((re) => re.text()),
16 fetch('forth.wasm', {credentials: 'include', headers:{'content-type':'application/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:100%;overflow-x:hidden;")
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;flex-flow:row")
30 const stackview = document.createElement("pre")
31 stackview.setAttribute("style", "white-space:pre-wrap;flex:0 0 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 8%;")
41 outframe.appendChild(memview)
42 const txtinput = document.createElement("textarea")
43 txtinput.setAttribute("autofocus", "true")
44 txtinput.setAttribute("wrap", "hard")
45 txtinput.setAttribute("style", "resize:none;white-space:pre;margin-left:8%;width:60%;")
46 txtinput.oninput = () => txtinput.rows = (txtinput.value.match(/[\n]/g) || [1]).length + 1;
47 txtinput.oninput()
48 forthdiv.appendChild(outframe)
49 forthdiv.appendChild(txtinput)
50 let wasmMem
51 let forth
52 const output = {
53 print: (string) => txtoutput.textContent += `\\\ => ${string} \n`
54 }
55 const updateViews = () => {
56 stackview.textContent = simstack.join('\n')
57 rstackview.textContent = rstack.join('\n')
58 let cnt = 0;
59 const maxBytes = 12
60 let here = new DataView(wasmMem.buffer, 14340 /* here */, 4).getUint32(0,true)
61 memview.textContent = Array.from(new Uint8Array(wasmMem.buffer, here - maxBytes, maxBytes), (v) => {
62 cnt++;
63 v = ('0' + (v & 0xFF).toString(16)).slice(-2)
64 if (cnt === maxBytes)
65 return v
66 if ((cnt % 4) === 0)
67 return v + '\n==\n'
68 return v + '\n'
69 }).join('')
70 outframe.scrollTop = outframe.scrollHeight
71 }
72 /* Input capture */
73 let stdin = ""
74 txtinput.addEventListener('keydown', (event) => {
75 switch (event.key) {
76 case "Enter":
77 if (event.ctrlKey) {
78 const endPos = txtinput.selectionStart + 1
79 txtinput.value =
80 txtinput.value.substring(0, txtinput.selectionStart) +
81 '\n' +
82 txtinput.value.substring(txtinput.selectionEnd, txtinput.value.length)
83 txtinput.setSelectionRange(endPos, endPos)
84 txtinput.oninput()
85 }
86 else {
87 stdin += txtinput.value
88 txtoutput.textContent += txtinput.value
89 if (!/\s/.test(txtinput.value.slice(-1)))
90 txtoutput.textContent += " "
91 txtinput.value = ""
92 event.preventDefault()
93 event.stopPropagation()
94 forth()
95 updateViews()
96 }
97 break
98 case "Backspace":
99 break
100 default:
101 break
102 }
103 })
104 const channels = [{
105 read: (writeAddr, maxBytes) => {
106 const maxChars = maxBytes >> 1
107 const bufView = new Uint16Array(wasmMem.buffer, writeAddr, maxChars)
108 let i
109 for (i = 0; i < maxChars && i < stdin.length; i++)
110 bufView[i] = stdin.charCodeAt(i)
111 stdin = stdin.substring(maxChars)
112 return i << 1
113 },
114 write: (readAddr, maxBytes) =>
115 output.print(String.fromCharCode.apply(
116 null,
117 new Uint16Array(wasmMem.buffer, readAddr, maxBytes >> 1)
118 ))
119 }]
120 const dictionary = {
121 EXECUTE: 12
122 }
123 const doesDictionary = {}
124 const wasmImport = {
125 env: {
126 pop: () => simstack.pop(),
127 push: (val) => simstack.push(val),
128 rinit: () => rstack.length = 0,
129 rpop: () => rstack.pop(),
130 rpush: (val) => rstack.push(val),
131 sys_write: (channel, addr, u) => channels[channel] === undefined ? 0 : channels[channel].write(addr, u),
132 sys_read: (channel, addr, u) => channels[channel] === undefined ? 0 : channels[channel].read(addr, u),
133 sys_listen: (reqAddr, cbAddr) => {
134 //TODO: call into the module to wasm fn "event" to push an event
135 //to forth, which pushes esi to the return stack and queues the
136 //callback function provided from listen. reqaddr could be
137 //"fetch", in which case no channel is used. otherwise very
138 //similar to sys_fetch.
139 },
140 sys_fetch: (channel, reqAddr) => {
141 //TODO: map to fetch promise, write to channel buffer,
142 //javascript "fetch" as fallback, explicit handles for
143 //"textEntry" or any third party protocols like activitypub
144 console.log(`fetch ${channel} ${reqAddr}`)
145 },
146 sys_echo: (val) => output.print(`${val} `),
147 sys_echochar: (val) => output.print(String.fromCharCode(val)),
148 sys_reflect: (addr) => {
149 console.log(`reflect: ${addr}: ${
150 new DataView(wasmMem.buffer, addr, 4)
151 .getUint32(0,true)
152 }`)
153 },
154 vocab_get: (addr, u) => {
155 const word = String.fromCharCode.apply(
156 null,
157 new Uint16Array(wasmMem.buffer, addr, u >> 1)
158 )
159 const answer = dictionary[word.toUpperCase()]
160 if (answer === undefined)
161 return 0
162 return answer
163 },
164 vocab_set: (addr, u, num) => {
165 const word = String.fromCharCode.apply(
166 null,
167 new Uint16Array(wasmMem.buffer, addr, u >> 1)
168 )
169 dictionary[word.toUpperCase()] = num
170 return 0
171 },
172 does_get: (u) => doesDictionary[u] || 0,
173 does_set: (u, v) => doesDictionary[u] = v,
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 if (Number.isNaN(answer))
183 return -1
184 new DataView(wasmMem.buffer, addr, 4).setUint32(0,answer,true)
185 return 0
186 },
187 sys_words: () => {
188 output.print(Object.getOwnPropertyNames(dictionary).toString().split(',').join(' '))
189 }
190 }
191 }
192 initialize.then((results) => {
193 stdin = results[0]
194 WebAssembly.instantiate(results[1], wasmImport).then((module) => {
195 wasmMem = module.instance.exports.memory
196 forth = module.instance.exports.main
197 console.log('wasm loaded')
198 forth()
199 updateViews()
200 })
201 })
202 }