recommit of old milestones, cleanup pass
[watForth.git] / forth.js
index 7e2f5a3..c05d444 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').then((re) => re.text()),
+  fetch('forth.wasm').then(re => re.arrayBuffer())
+])
+window.onload = () => {
+  let forthdiv = document.getElementById("forth")
+  if (forthdiv === null) {
+    forthdiv = document.createElement("div")
+    forthdiv.setAttribute("style","height:400px;")
+    document.body.appendChild(forthdiv)
+  }
+  const outframe = document.createElement("div")
+  outframe.setAttribute("style", "overflow-y:hidden;height:40%;")
+  const txtoutput = document.createElement("pre")
+  txtoutput.setAttribute("style", "white-space:pre-wrap;")
+  const txtinput = document.createElement("textarea")
+  let txtinputrows = "1"
+  txtinput.setAttribute("autofocus", "true")  
+  txtinput.setAttribute("cols", "60")
+  txtinput.setAttribute("rows", txtinputrows)
+  outframe.appendChild(txtoutput)
+  forthdiv.appendChild(outframe)
+  forthdiv.appendChild(txtinput)
+  const output = {
+    print: (string) => txtoutput.textContent += string
+  }
+  let wasmMem
+  let forth
 
-/* Input capture */
-let stdin = ""
-document.addEventListener('keydown', (event) => {
-  console.log(`keydown: ${event.key}`)
-  if (event.key != "F5") {
-    event.preventDefault()
-    event.stopPropagation()
+  /* 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) {
+        txtinput.value += '\n'
+        txtinputrows = (Number.parseInt(txtinputrows, 10) + 1).toString()
+        txtinput.setAttribute("rows", txtinputrows)
+      }
+      else {
+        stdin += txtinput.value
+        txtoutput.textContent += txtinput.value + '\n'
+        txtinput.value = ""
+        txtinputrows = "1"
+        txtinput.setAttribute("rows", txtinputrows)
+        event.preventDefault()
+        event.stopPropagation()
+        forth()
+        txtoutput.textContent += " _ok.\n"
+        outframe.scrollTop = outframe.scrollHeight;
+      }
       break
     case "Backspace":
-      stdin = stdin.substring(0, stdin.length - 1)
-      txtdiv.textContent = txtdiv.textContent.substring(0, txtdiv.textContent.length - 1)
+      if (txtinput.value.charCodeAt(txtinput.value.length - 1) === 10) {
+        txtinputrows = (Number.parseInt(txtinputrows, 10) - 1).toString()
+        txtinput.setAttribute("rows", txtinputrows)
+      }
       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)
+  })
+/*  document.addEventListener('keydown', (event) => {
+    //console.log(`keydown: ${event.key}`)
+    if (event.key != "F5") {
+      event.preventDefault()
+      event.stopPropagation()
+      switch (event.key) {
+      case "Enter":
+        txtoutput.textContent += '\n'
+        stdin += '\n'
+        forth()
+        output.print(" _ok.")
+        outframe.scrollTop = outframe.scrollHeight;
+        txtoutput.textContent += '\n'
+        break
+      case "Backspace":
+        stdin = stdin.substring(0, stdin.length - 1)
+        txtoutput.textContent = txtoutput.textContent.substring(0, txtoutput.textContent.length - 1)
+        break
+      default:
+        if (event.key.length == 1) {
+          const input = event.ctrlKey ? ` \\\^${event.key} ` : event.key
+          stdin += input
+          output.print(input)
+        }
+        break
       }
-      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)
+      ))
+  }]
+  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,
+    'STRING-START': 39,
+    'STRING-PUT': 40,
+    'STRING-END': 41,
+    ':': 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) => 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) => output.print(`${val} `),
+      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) => {
+        const word = String.fromCharCode.apply(
+         null,
+         new Uint16Array(wasmMem.buffer, addr, u >> 1)
+        )
+        const answer = dictionary[word.toUpperCase()]
+        if (answer === undefined)
+         return 0
+        return answer
+      },
+      vocab_set: (addr, u, num) => {
+        const word = String.fromCharCode.apply(
+         null,
+         new Uint16Array(wasmMem.buffer, addr, u >> 1)
+        )
+        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)
+        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()
+    })
   })
-})
+}