cleanup, compilation semantics
[watForth.git] / forth.js
index 7e2f5a3..9c0e58d 100644 (file)
--- a/forth.js
+++ b/forth.js
     You should have received a copy of the GNU General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 'use strict'
-let txtdiv = document.createElement("div")
-document.body.appendChild(txtdiv)
-const output = {
-  print: (string) => txtdiv.textContent += string
-}
-let wasmMem
-let forth
+const initialize = Promise.all([
+  fetch('forth.forth', {credentials: 'include', headers:{'content-type':'text/plain'}}).then((re) => re.text()),
+  fetch('forth.wasm', {credentials: 'include', headers:{'content-type':'application/wasm'}}).then(re => re.arrayBuffer())
+])
+window.onload = () => {
+  /* Initialize Views */
+  let forthdiv = document.getElementById("forth")
+  if (forthdiv === null) {
+    forthdiv = document.createElement("div")
+    forthdiv.setAttribute("style","height:100%;margin:auto;width:100%;max-width:640px;overflow-x:hidden;")
+    document.body.appendChild(forthdiv)
+  }
+  const outframe = document.createElement("div")
+  outframe.setAttribute("style", "background-color:black;padding-left:6px;padding-right:6px;color:chartreuse;height:268px;resize:vertical;display:flex;align-items:flex-end;flex-flow:row;")
+  const stackview = document.createElement("pre")
+  stackview.setAttribute("style", "white-space:pre-wrap;flex:0 0 8%;")
+  outframe.appendChild(stackview)
+  const txtoutput = document.createElement("pre")
+  txtoutput.setAttribute("style", "white-space:pre-wrap;max-height:256px;overflow-y:scroll;flex:1 0 342px;")
+  outframe.appendChild(txtoutput)
+  /* const rstackview = document.createElement("pre")
+  rstackview.setAttribute("style", "white-space:pre-wrap;flex:0 1 8%;")
+  outframe.appendChild(rstackview) */
+  const memview = document.createElement("pre")
+  memview.setAttribute("style", "padding-left: 8px; white-space:pre-wrap;flex:0 0 88px;")
+  outframe.appendChild(memview)
+  const txtinput = document.createElement("textarea")
+  txtinput.setAttribute("autofocus", "true")
+  txtinput.setAttribute("wrap", "hard")
+  txtinput.setAttribute("style", "resize:none;white-space:pre;margin-left:8%;width:75%;")
+  txtinput.oninput = () => txtinput.setAttribute("rows", ((txtinput.value.match(/[\n]/g) || [1]).length + 1).toString());
+  txtinput.oninput()
+  forthdiv.appendChild(outframe)
+  forthdiv.appendChild(txtinput)
 
-/* Input capture */
-let stdin = ""
-document.addEventListener('keydown', (event) => {
-  console.log(`keydown: ${event.key}`)
-  if (event.key != "F5") {
-    event.preventDefault()
-    event.stopPropagation()
+  /* Initialize State */
+  const simstack = []
+  const rstack = []
+  let wasmMem
+  let forth
+  const dictionary = { EXECUTE: 12 }
+  const doesDictionary = {}
+
+  /* Environment functions */
+  const output = {
+    print: (string) => txtoutput.textContent += `\\\ => ${string} \n`
+  }
+  const wasmString = (addr, u) =>
+        String.fromCharCode.apply(
+          null,
+          new Uint16Array(wasmMem.buffer, addr, u >> 1)
+        )
+  const updateViews = () => {
+    const base = new DataView(wasmMem.buffer, 14348 /* base */, 4).getUint32(0,true)
+    stackview.textContent = simstack.map((v) => v.toString(base)).join('\n')
+    // rstackview.textContent = rstack.join('\n')
+    let cnt = 0;
+    const maxBytes = 64
+    let here = new DataView(wasmMem.buffer, 14340 /* here */, 4).getUint32(0,true)
+    memview.textContent = Array.from(new Uint8Array(wasmMem.buffer, here - maxBytes, maxBytes), (v) => {
+      cnt++;
+      v = ('0' + (v & 0xFF).toString(16)).slice(-2)
+      if (cnt === maxBytes)
+        return v
+      if ((cnt % 16) === 0)
+        return `${v}\n=> ${(here -maxBytes + cnt).toString(base)}\n`
+      if ((cnt % 4) === 0)
+        return `${v}\n`
+      return `${v} `
+    }).join('')
+    outframe.scrollTop = outframe.scrollHeight
+  }
+
+  /* Input capture */
+  let stdin = ""
+  txtinput.addEventListener('keydown', (event) => {
     switch (event.key) {
     case "Enter":
-      txtdiv = document.createElement("div")
-      document.body.appendChild(txtdiv)
-      forth()
-      output.print("ok.")
-      txtdiv = document.createElement("div")
-      document.body.appendChild(txtdiv)
+      if (event.ctrlKey) {
+        const endPos = txtinput.selectionStart + 1
+        txtinput.value =
+          txtinput.value.substring(0, txtinput.selectionStart) +
+          '\n' +
+          txtinput.value.substring(txtinput.selectionEnd, txtinput.value.length)
+        txtinput.setSelectionRange(endPos, endPos)
+        txtinput.oninput()
+      }
+      else {
+        stdin += txtinput.value
+        txtoutput.textContent += txtinput.value
+        if (!/\s/.test(txtinput.value.slice(-1)))
+          txtoutput.textContent += " "
+        txtinput.value = ""
+        event.preventDefault()
+        event.stopPropagation()
+        forth()
+        updateViews()
+        txtoutput.scrollTop = txtoutput.scrollHeight
+      }
       break
     case "Backspace":
-      stdin = stdin.substring(0, stdin.length - 1)
-      txtdiv.textContent = txtdiv.textContent.substring(0, txtdiv.textContent.length - 1)
       break
     default:
-      if (event.key.length == 1) {
-       stdin += event.key
-        output.print(event.key)
-      }
       break
     }
-  }
-})
-
-const channels = [{
-  read: (writeAddr, maxBytes) => {
-    const maxChars = maxBytes >> 1
-    const bufView = new Uint16Array(wasmMem.buffer, writeAddr, maxChars)
-    let i
-    for (i = 0; i < maxChars && i < stdin.length; i++)
-      bufView[i] = stdin.charCodeAt(i)
-    stdin = stdin.substring(maxChars)
-    return i << 1
-  },
-  write: (readAddr, maxBytes) =>
-    output.print(String.fromCharCode.apply(
-      null,
-      new Uint16Array(wasmMem.buffer, readAddr, maxBytes >> 1)
-    ))
-}]
-const simstack = []
-const rstack = []
-const dictionary = {
-  ';': 1,
-  'LIT': 2,
-  RINIT: 3,
-  WORD: 16500,
-  KEY: 5,
-  DUP: 6,
-  '+': 7,
-  'NOOP2': 8,
-  '.': 9,
-  '@': 10,
-  '!': 11,
-  EXECUTE: 12,
-  NOOP: 13,
-  'JZ:': 14,
-  'JNZ:': 15,
-  DROP: 16,
-  'WS?': 17,
-  'JMP:': 18,
-  'WPUTC': 19,
-  'WB0': 20,
-  'FIND': 21,
-  'NUMBER': 22,
-  'W!LEN': 23,
-  'J-1:': 24,
-  'BYE': 25,
-  'SWAP': 26,
-  'WORDS': 27,
-  'HERE': 28,
-  'DEFINE': 29,
-  '2DUP': 30,
-  'ROT': 31,
-  '2DROP': 32,
-  ',': 33,
-  '-': 34,
-  'CHANNEL!': 35,
-  'HERE!': 36,
-  '=?': 37,
-  '.s': 38,
-  ':': 16800,
-  'MODE': 14336,
-  'EXECUTE-MODE': 16680,
-  'QUIT': 16384,
-  'INTERPRET': 16400
-}
-const wasmImport = {
-  env: {
-    pop: () => simstack.pop(),
-    push: (val) => simstack.push(val),
-    rinit: () => rstack.length = 0,
-    rpop: () => rstack.pop(),
-    rpush: (val) => rstack.push(val),
-    sys_write: (channel, addr, u) => {
-      if (channels[channel] === undefined)
-       return
-      console.log(`write ch:${channel} addr:${addr} len:${u}`)
-      channels[channel].write(addr, u)
-    },
-    sys_read: (channel, toBuffer) => {
-      console.log(`read ch:${channel} buf:${toBuffer} current: ${stdin}`)
-      const lenView = new DataView(wasmMem.buffer, toBuffer, 8)
-      const maxBytes = lenView.getUint32(0,true)
-      console.log(`read blen:${lenView.getUint32(0,true)} cstrlen:${lenView.getUint32(4,true)}`)
-      /* If the channel is undefined, or if there isn't enough room in
-       * toBuffer for even one character, then, if there is enough
-       * room for an int write a zero, exit */
-      if (channels[channel] === undefined || maxBytes < 6) {
-       if (maxBytes >= 4)
-         lenView.setUint32(4,0,true)
-      }
-      const numBytes = channels[channel].read(toBuffer + 8, maxBytes - 4)
-      lenView.setUint32(4,numBytes,true);
-      console.log(`read wrote ${lenView.getUint32(4,true)} bytes, remainder: ${stdin}`)
-      console.log(`read blen:${lenView.getUint32(0,true)} cstrlen:${lenView.getUint32(4,true)}`)   },
-    sys_listen: (reqAddr, cbAddr) => {
-      //TODO: call into the module to wasm fn "event" to push an event
-      //to forth, which pushes esi to the return stack and queues the
-      //callback function provided from listen. reqaddr could be
-      //"fetch", in which case no channel is used.  otherwise very
-      //similar to sys_fetch.
-    },
-    sys_fetch: (channel, reqAddr) => {
-      //TODO: map to fetch promise, write to channel buffer,
-      //javascript "fetch" as fallback, explicit handles for
-      //"textEntry" or any third party protocols like activitypub
-      console.log(`fetch ${channel} ${reqAddr}`)
+  })
+  const channels = [{
+    read: (writeAddr, maxBytes) => {
+      const maxChars = maxBytes >> 1
+      const bufView = new Uint16Array(wasmMem.buffer, writeAddr, maxChars)
+      let i
+      for (i = 0; i < maxChars && i < stdin.length; i++)
+        bufView[i] = stdin.charCodeAt(i)
+      stdin = stdin.substring(maxChars)
+      return i << 1
     },
-    sys_echo: (val) => output.print(`${val} `),
-    sys_echochar: (val) => output.print(String.fromCharCode(val)),
-    sys_reflect: (addr) => {
-      console.log(`reflect: ${addr}: ${
+    write: (readAddr, maxBytes) =>
+      output.print(String.fromCharCode.apply(
+        null,
+        new Uint16Array(wasmMem.buffer, readAddr, maxBytes >> 1)
+      ))
+  }]
+
+  /* System runtime */
+  const wasmImport = {
+    env: {
+      pop: () => simstack.pop(),
+      push: (val) => simstack.push(val),
+      rinit: () => rstack.length = 0,
+      rpop: () => rstack.pop(),
+      rpush: (val) => rstack.push(val),
+      sys_write: (channel, addr, u) => channels[channel] === undefined ? 0 : channels[channel].write(addr, u),
+      sys_read: (channel, addr, u) => channels[channel] === undefined ? 0 : channels[channel].read(addr, u),
+      sys_listen: (reqAddr, cbAddr) => {
+        //TODO: call into the module to wasm fn "event" to push an event
+        //to forth, which pushes esi to the return stack and queues the
+        //callback function provided from listen. reqaddr could be
+        //"fetch", in which case no channel is used.  otherwise very
+        //similar to sys_fetch.
+      },
+      sys_fetch: (channel, reqAddr) => {
+        //TODO: map to fetch promise, write to channel buffer,
+        //javascript "fetch" as fallback, explicit handles for
+        //"textEntry" or any third party protocols like activitypub
+        console.log(`fetch ${channel} ${reqAddr}`)
+      },
+      sys_echo: (val, base) => output.print(`${val.toString(base)} `),
+      sys_echochar: (val) => output.print(String.fromCharCode(val)),
+      sys_reflect: (addr) => {
+        console.log(`reflect: ${addr}: ${
         new DataView(wasmMem.buffer, addr, 4)
               .getUint32(0,true)
       }`)
-    },
-    vocab_get: (addr, u) => {
-      const word = String.fromCharCode.apply(
-       null,
-       new Uint16Array(wasmMem.buffer, addr, u >> 1)
-      )
-      const answer = dictionary[word.toUpperCase()]
-      console.log(`vocab_get ${word}: ${answer}`)
-      if (answer === undefined)
-       return 0
-      return answer
-    },
-    vocab_set: (addr, u, num) => {
-      console.log(`vocab set ${addr} ${u} ${num}`)
-      const word = String.fromCharCode.apply(
-       null,
-       new Uint16Array(wasmMem.buffer, addr, u >> 1)
-      )
-      console.log(`vocab_set ${word}: ${num}`)
-      dictionary[word.toUpperCase()] = num
-      return 0
-    },
-    is_whitespace: (key) => /\s/.test(String.fromCharCode(key)),
-    sys_stack: () => console.log(`[${simstack}]`),
-    sys_parsenum: (addr, u, base) => {
-      const word = String.fromCharCode.apply(
-       null,
-       new Uint16Array(wasmMem.buffer, addr, u >> 1)
-      )
-      const answer = Number.parseInt(word, base)
-      console.log(`parsenum: ${addr} ${u} ${base}`)
-      console.log(`parsenum: ${word} ${answer}`)
-      console.log(`parsenum: ${Number.isNaN(answer)}`)
-      if (Number.isNaN(answer))
-        return -1
-      new DataView(wasmMem.buffer, addr, 4).setUint32(0,answer,true)
-      return 0
-    },
-    sys_words: () => {
-      output.print(Object.getOwnPropertyNames(dictionary).toString().split(',').join('  '))
+      },
+      vocab_get: (addr, u) => dictionary[wasmString(addr, u).toUpperCase()] || 0, 
+      vocab_set: (addr, u, v) => dictionary[wasmString(addr, u).toUpperCase()] = v,
+      does_get: (addr, u) => doesDictionary[wasmString(addr, u).toUpperCase()] || 0,
+      does_set: (addr, u, v) => doesDictionary[wasmString(addr, u).toUpperCase()] = v,
+      is_whitespace: (key) => /\s/.test(String.fromCharCode(key)),
+      sys_stack: () => console.log(`[${simstack}][${rstack}]`),
+      sys_parsenum: (addr, u, base) => {
+        const answer = Number.parseInt(wasmString(addr, u), base)
+        if (Number.isNaN(answer))
+          return -1
+        new DataView(wasmMem.buffer, addr, 4).setUint32(0,answer,true)
+        return 0
+      },
+      sys_words: () => {
+        output.print(Object.getOwnPropertyNames(dictionary).toString().split(',').join('  '))
+      }
     }
   }
-}
-
-fetch('forth.forth').then((re) => re.text()).then((txt) => {
-  stdin = txt
-  fetch('forth.wasm')
-  .then(re => re.arrayBuffer())
-  .then(buf => WebAssembly.instantiate(buf, wasmImport))
-  .then(result => {
-    wasmMem = result.instance.exports.memory
-    forth = result.instance.exports.main
-    console.log('wasm loaded');
-    forth()
+  initialize.then((results) => {
+    stdin = results[0]
+    WebAssembly.instantiate(results[1], wasmImport).then((module) => {
+      wasmMem = module.instance.exports.memory
+      forth = module.instance.exports.main
+      console.log('wasm loaded')
+      forth()
+      updateViews()
+    })
   })
-})
+}