Skip to content
Snippets Groups Projects
Select Git revision
  • leo
  • dex
  • pendulum
  • master default protected
  • apfelstruder
  • littlerascal
6 results

typeset.js

Blame
  • typeset.js 20.86 KiB
    // typeset: functional types -> bytes for js -> embedded sys
    // bless up @ modern js https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
    
    let tsdebug = false
    
    // oy,
    
    const checkKey = (type, arr, start) => {
      if (arr[start] !== type.key) {
        console.log('erroneous bytes')
        console.log(arr)
        throw new Error(`mismatched key on phy read: for ${type.name}, find ${arr[start]} instead of ${type.key} ... at ${start} index`)
      }
    }
    
    const checkBoolean = (value) => {
      if (typeof value !== 'boolean')
        throw new Error('cannot cast non-boolean to bool at phy')
    }
    
    const checkNumber = (value) => {
      if (typeof value !== 'number')
        throw new Error(`cannot cast non-number into physical world "${value}", ${typeof value}`)
    }
    
    const checkString = (value) => {
      if (typeof value !== 'string')
        throw new Error(`cannot cast non-string to string at phy! "${value}", "${typeof value}"`)
    }
    
    const checkArray = (thing) => {
      if (!Array.isArray(thing))
        throw new Error('this thing is not an array!')
    }
    
    const checkUnsigned = (value, bits) => {
      if (value > Math.pow(2, bits))
        throw new Error('value out of byte bounds')
    }
    
    const checkSigned = (value, bits) => {
      let comparator = Math.pow(2, bits - 1)
      if (value > comparator || value < -comparator)
        throw new Error('value out of byte bounds')
    }
    
    const findPhy = (type) => {
      let phy = TSET.find((cand) => {
        return cand.name === type
      })
      if (phy === undefined)
        // try this... catch err elsewhere
        throw new Error(`could not find phy for datatype: ${type}`)
      return phy
    }
    
    const bounds = (value, min, max) => {
      return Math.max(min, Math.min(value, max))
    }
    
    const intBounds = (pfloat, min, max) => {
      return Math.round(bounds(pfloat, min, max))
    }
    
    // TYPES:
    /*
    name: string identifier, how we 'specify' what an input / output / state is
    key: byte-code for the network. if this, write, and read, don't exist, link can't have one
    write: serialize into bytes
    read: deserialize
    copy: move about within js,
     copies are 'outgoing' ... so typeA.copy.typeB(value) turns the value from
     typeA -> typeB
     the standard is typeA.copy.typeA(value), this is just a memory shuffle
     for the sake of dereferencing: we can write our own types per item
    */
    
    // TODO: TYPES:
    /*
    we are still not really type-checking on calls to .put(), and should be...
    there is probably a slick, clean way to do this
    also: would really like to work with typedarrays where they are appropriate
    
    .. *could* imagine some hairbrain walk through these things: if we have, i.e., conversion
    from string -> number written, and from number -> uint8, we should be able to
    compose a tree of these on the fly ...
    */
    
    const TSET = [
      {
        name: 'boolean',
        key: 32,
        write: function(value) {
          checkBoolean(value)
          let rtarr = [this.key]
          if (value) {
            rtarr.push(1)
          } else {
            rtarr.push(0)
          }
          return rtarr
        },
        read: function(arr, start) {
          checkKey(this, arr, start)
          let item
          if (arr[start + 1] === 1) {
            item = true
          } else if (arr[start + 1] === 0) {
            item = false
          } else {
            throw new Error(`non std boolean byte, ${arr[start + 1]}`)
          }
          return {item: item, increment: 2}
        },
        copy: {
          boolean: function(bool) {
            return bool // direct: a basic js type, so we just pass it on
          },
          number: function(bool) { // converts boolean -> numbers
            if (bool) {
              return 1
            } else {
              return 0
            }
          }
        }
      }, { // booleanArray: 33,
        name: 'byte',
        key: 34,
        write: function(value) {
          checkNumber(value)
          checkUnsigned(value, 8)
          return [this.key, value]
        },
        read: function(arr, start) {
          checkKey(this, arr, start)
          return {
            item: arr[start + 1],
            increment: 2
          }
        },
        copy: {
          byte: function(byte) {
            return byte
          }
        }
      }, {
        name: 'byteArray',
        key: 35,
        write: function(value) {
          checkArray(value)
          let rtarr = writeLenBytes(value.length).concat(value)
          rtarr.unshift(this.key)
          if (tsdebug)
            console.log('byteArray sanity check:', value, 'written as:', rtarr)
          return rtarr
        },
        read: function(arr, start) {
          checkKey(this, arr, start)
          let lb = readLenBytes(arr, start + 1)
          let narr = new Array()
          for (let i = 0; i < lb.len; i++) {
            narr.push(arr[start + 1 + lb.numBytes + i])
          }
          return {
            item: narr,
            increment: lb.len + lb.numBytes + 1
          }
        },
        copy: { // copy:  into another bytearray
          byteArray: function(arr) {
            // TODO would be making bytearrays into buffers, would speed this up ...
            let ret = new Array(arr.length)
            for (let item in arr) {
              ret[item] = arr[item]
            }
            return ret
          },
          reference: function(arr){
            let ret = new Array(arr.length)
            for (let item in arr) {
              ret[item] = arr[item]
            }
            return ret
          }
        }
      }, { // char 36, string 37,
        name: 'string',
        key: 37,
        write: function(str) {
          checkString(str)
          let rtarr = new Array()
          for (let i = 0; i < str.length; i++) {
            rtarr.push(str.charCodeAt(i))
          }
          // length bytes are 7-bit 'msb for continue' numbers ...
          // the -1 because we are not counting the length of the key
          let lb = writeLenBytes(rtarr.length)
          rtarr = lb.concat(rtarr)
          rtarr.unshift(this.key)
          return rtarr
        },
        read: function(arr, start) {
          checkKey(this, arr, start)
          let lb = readLenBytes(arr, start + 1)
          //console.log('lenbytes', lb)
          let str = new String()
          for (let i = 0; i < lb.len; i++) {
            str += String.fromCharCode(arr[start + 1 + lb.numBytes + i])
          }
          return {
            item: str,
            increment: lb.len + lb.numBytes + 1
          }
        },
        copy: {
          string: function(str) {
            return str
          },
          number: function(str) {
            return parseFloat(str)
          },
          boolean: function(str){
            if(str == true){
              return true
            } else {
              return false
            }
          },
          reference: function(str){
            return str
          }
        }
      }, {
        name: 'uint8',
        key: 38,
        write: function(value) {
          checkNumber(value)
          checkUnsigned(value, 8)
          if (value > 255 || value < 0)
            throw new Error('num too large to represent with cast type, will contencate')
            // dont' need to type-buffer this,
          let rtarr = [this.key, value]
          return rtarr
        },
        read: function(arr, start) {
          // assume we're reading out of an array
          // start[] should === key
          if (arr[start] !== this.key)
            throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`)
          if (arr[start + 1] > 255 || arr[start + 1] < 0)
            throw new Error('whaky read-in on uint8')
          return {
            item: arr[start + 1],
            increment: 2
          }
        },
        copy: {
          uint8: function(uint8){
            // by clamping at the copy, we can reduce complexity at read and write ?
            // and improve clarity elsewhere ...
            return bounds(uint8, 0, 255) // clamp number
          },
          uint16: function(uint8){
            // because we always copy-in before copying-out (to other types)
            // we don't have to clamp here again, that's nice.
            // but would to go down to a smaller value ...
            return uint8
          },
          uint32: function(uint8){
            // this could really get exhaustive,
            return uint8
          },
          number: function(uint8){
            return uint8
          }
        }
      }, { // uint8Array 39
        name: 'uint16',
        key: 40,
        write: function(value) {
          if (typeof value !== 'number')
            throw new Error(`cannot cast non-number into physical world "${value}", ${typeof value}`)
          if (value > 65536 || value < 0)
            throw new Error('num too large to represent with cast type, will contencate')
          let tparr = new Uint16Array(1)
          tparr[0] = value
          let btarr = new Uint8Array(tparr.buffer)
          //place
          let rtarr = Array.from(btarr)
          rtarr.unshift(this.key)
          return rtarr
        },
        read: function(arr, start) {
          // assume we're reading out of an array
          // start[] should === key
          if (arr[start] !== this.key)
            throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`)
          let rdarr = arr.slice(start + 1, start + 3)
          let btarr = Uint8Array.from(rdarr)
          if (tsdebug)
            console.log('bytes on read of uint16 (little eadian)', btarr)
            // now make uint32 view on this ...
          let vlarr = new Uint16Array(btarr.buffer)
          if (tsdebug)
            console.log('vlarr', vlarr)
          return {item: vlarr[0], increment: 3}
        },
        copy: {
          uint16: function(uint16){
            return bounds(uint16, 0, 65535)
          },
          uint8: function(uint16){
            return bounds(uint16, 0, 255)
          },
          number: function(uint16){
            return uint16
          }
        }
      }, { // uint16 array 41
        name: 'uint32',
        key: 42,
        write: function(value) {
          if (typeof value !== 'number')
            throw new Error(`cannot cast non-number into physical world "${value}", ${typeof value}`)
          if (value > 4294967296)
            throw new Error('num too large to represent with cast type, will contencate')
          let tparr = new Uint32Array(1)
          tparr[0] = value
          let btarr = new Uint8Array(tparr.buffer)
          //place
          let rtarr = Array.from(btarr)
          rtarr.unshift(this.key)
          if (tsdebug)
            console.log("UINT32 WRITES ARR: ", rtarr, "FOR: ", value)
          return rtarr
        },
        read: function(arr, start) {
          // assume we're reading out of an array
          // start[] should === key
          if (arr[start] !== this.key) {
            console.log("erroneous bytes:", arr)
            console.log("error at byte:", start, "is", arr[start])
            console.log("expected key:", this.key)
            throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`)
          }
          let rdarr = arr.slice(start + 1, start + 5)
          let btarr = Uint8Array.from(rdarr)
          if (tsdebug)
            console.log('bts on read of uint32', btarr)
            // now make uint32 view on this ...
          let vlarr = new Uint32Array(btarr.buffer)
          if (tsdebug)
            console.log("UINT32 READ ARR: ", vlarr[0], "FROM: ", btarr)
          if (tsdebug)
            console.log('vlarr', vlarr)
          return {item: vlarr[0], increment: 5}
        },
        copy: {
          uint32: function(uint32){
            return bounds(uint32, 0, 4294967295)
          },
          number: function(uint32){
            return uint32
          }
        }
      }, // uint32array 43,
      /*
      uint64 44, uint64array 45,
      int8 46, int8array 47,
      int16 48, int16array 49,
      int32 50, int32array 50,
      */
      {
        name: 'int32',
        key: 50,
        write: function(value) {
          if (typeof value !== 'number')
            throw new Error(`cannot cast non-number into physical world "${value}", ${typeof value}`)
          let tparr = new Int32Array(1)
          tparr[0] = value
          let btarr = new Uint8Array(tparr.buffer)
          //place
          let rtarr = Array.from(btarr)
          rtarr.unshift(this.key)
          if (tsdebug)
            console.log("INT32 WRITES ARR: ", rtarr, "FOR: ", value)
          return rtarr
        },
        read: function(arr, start) {
          // assume we're reading out of an array
          // start[] should === key
          if (arr[start] !== this.key)
            throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`)
          let rdarr = arr.slice(start + 1, start + 5)
          let btarr = Uint8Array.from(rdarr)
          if (tsdebug)
            console.log('bts on read of uint32', btarr)
            // now make uint32 view on this ...
          let vlarr = new Int32Array(btarr.buffer)
          if (tsdebug)
            console.log("UINT32 READ ARR: ", vlarr[0], "FROM: ", btarr)
          if (tsdebug)
            console.log('vlarr', vlarr)
          return {item: vlarr[0], increment: 5}
        },
        copy: {
          int32: function(int32){
            return bounds(int32, -2147483647, 2147483647)
          },
          number: function(int32){
            return int32
          }
        }
      },
      /*
      int64 52, int64array 53,
      float32 54, float32array 55,
      float64 56, float64array 57 (these are === javascript 'numbers') ... how to alias ?
      */
      {
        name: 'number',
        key: 56,
        write: function(value) {
          if (typeof value !== 'number')
            throw new Error(`cannot cast non-number into physical world "${value}", ${typeof value}`)
          let tparr = new Float64Array(1)
          tparr[0] = value
          let btarr = new Uint8Array(tparr.buffer)
          // place
          let rtarr = Array.from(btarr)
          rtarr.unshift(this.key)
          return rtarr
        },
        read: function(arr, start) {
          if (arr[start] !== this.key)
            throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`)
          let rdarr = arr.slice(start + 1, start + 9)
          let btarr = Uint8Array.from(rdarr)
          if (tsdebug)
            console.log('bts on read of float64', btarr)
          let vlarr = new Float64Array(btarr.buffer)
          if (tsdebug)
            console.log('vlarr', vlarr)
          return {item: vlarr[0], increment: 9}
        },
        copy: {
          number: function(num){
            return num
          },
          boolean: function(num){
            if(num > 0){
              return true
            } else {
              return false
            }
          },
          uint8: function(num){
            return intBounds(num, 0, 255)
          },
          uint16: function(num){
            return intBounds(num, 0, 65535)
          },
          uint32: function(num){
            return intBounds(num, 0, 4294967295)
          },
          int32: function(num){
            return intBounds(num, -2147483647, 2147483647)
          }
        }
      },
      { // cuttlefish only, not a real pass
        name: 'reference',
        copy: {
          reference: function(ref){
            let type = typeof ref
            if(type === 'string' || type === 'number' || type === 'boolean'){
              console.error('cannot pass core types as a reference')
              return null
            } else {
              return ref
            }
          }
        }
      },
      {
        name: 'object',
        copy: {
          object: function(obj){
            return JSON.parse(JSON.stringify(obj))
          }
        }
      },
      { // cuttlefish only, so no key, read or write fn's
        // this is : https://developer.mozilla.org/en-US/docs/Web/API/ImageData
        name: 'ImageData',
        copy: {
          ImageData: function(imageData){
            return new ImageData(
              new Uint8ClampedArray(imageData.data),
              imageData.width,
              imageData.height
            ) //
          },
          reference: function(imageData){
            return imageData
          }
        }
      },
      {
        name: 'Float32Array',
        copy: {
          Float32Array: function(float32Array) {
            return float32Array.slice();
          }
        }
      },
      {
        name: 'array',
        copy: {
          array: (arr) => [...arr],
          reference: (arr) => {
            return arr
          }
        }
      },
      {
        name: 'MDmseg',
        key: 88,
        write: function(ms) {
          // ok, bless up, we have:
          /*
          p0: 3-arr
          p1: 3-arr
          t: num
          v0: num
          a: num
          // for simplicity, we should write one typedarray
          */
          let f32arr = Float32Array.from([
            ms.p0[0], ms.p0[1], ms.p0[2],
            ms.p1[0], ms.p1[1], ms.p1[2],
            ms.t, ms.v0, ms.a]
          )
          // ok,
          let btarr = new Uint8Array(f32arr.buffer)
          let rtarr = Array.from(btarr)
          rtarr.unshift(this.key)
          return rtarr
        },
        read: function(arr, start) {
          /*
          if (arr[start] !== this.key)
            throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`)
          let rdarr = arr.slice(start + 1, start + 9)
          let btarr = Uint8Array.from(rdarr)
          if (tsdebug)
            console.log('bts on read of float64', btarr)
          let vlarr = new Float64Array(btarr.buffer)
          if (tsdebug)
            console.log('vlarr', vlarr)
          return {item: vlarr[0], increment: 9}
          */
        },
        copy: {
          MDmseg: (mdmseg) => {
            return {
              p0: mdmseg.p0,
              p1: mdmseg.p1,
              t: mdmseg.t,
              v0: mdmseg.v0,
              a: mdmseg.a
            }
          },
          reference: (mdmseg) => {
            return mdmseg
          }
        }
      }
      // etc
    ] // end TSET
    
    let intTypes = [
      'uint8',
      'uint16',
      'uint32',
      'uint64',
      'int8',
      'int16',
      'int32',
      'int64'
    ]
    
    let floatTypes = ['number']
    
    const isIntType = (type) => {
      for (let t of intTypes) {
        if (type == t)
          return true
      }
      return false
    }
    
    const isFloatType = (type) => {
      for (let t of floatTypes) {
        if (type == t)
          return true
      }
      return false
    }
    
    const isNumType = (type) => {
      if (isIntType(type))
        return true
      if (isFloatType(type))
        return true
      return false
    }
    
    const writeLenBytes = (len) => {
      // return array of len bytes for this number
      let bts = new Array()
      if (len > 65536) {
        throw new Error('cannot write length bytes for len > 2^16')
      } else {
        // this is little eadian ... right ?
        bts.push(len & 255);
        bts.push((len >> 8) & 255);
      }
      // here to check,
      //if(len > 255){
      //  console.log(`LEN > 255: writes len bytes `, bts[0], bts[1], 'for', len)
      //}
      return bts
    }
    
    const readLenBytes = (arr, start) => {
      // need 2 know how many to increment as well,
      let len = (arr[start + 1] << 8) | arr[start]
      // still interested in this,
      //if(len > 255){
      //  console.log(`LEN > 255: reads len bytes `, arr[start], arr[start+1], 'for len', len)
      //}
      return {len: len, numBytes: 2}
    }
    
    // heavy mixin of functional programming
    const MSGS = {
      writeTo: function(bytes, thing, type, debug) {
        let phy = findPhy(type)
        // try some js type conversion,
        // course correction here: sometimes states that are numbers are saved as strings (json)
        // we can unf- this here,
        if (typeof thing === 'string' && isNumType(type)) {
          //console.warn('patching num')
          if (isIntType(type)) {
            thing = parseInt(thing)
          } else {
            thing = parseFloat(thing)
          }
          //console.log('new num val', thing)
        } else if (typeof thing === 'string' && type === 'boolean') {
          // ha! use (?) for 'truthiness'
          //console.warn('patching bool')
          if (thing == 'true') {
            thing = true
          } else {
            thing = false
          }
          //console.log('new bool val', thing)
        }
        let block = phy.write(thing)
        if (debug)
          console.log(`writing for type ${type} and thing '${thing}' the following block of bytes`, block)
          // write-in to msg like this
        // this *must be* slow AF, pls correct
        block.forEach((byte) => {
          bytes.push(byte)
        })
      },
      readFrom: function(bytes, place, type) {
        let phy = findPhy(type)
        // check that type exists at place, rip it oot and return it
        return phy.read(bytes, place)
      },
      readListFrom: function(bytes, place, type) {
        // using this where I expect a lit of values, i.e. the addLink(str,str,str,str) arguments,
        // plucks thru, continuing to pull values as long as the next in the serialized list is of
        // the right type
        let phy = findPhy(type)
        // the list of items,
        let list = new Array()
        while (place < bytes.length) {
          let res = phy.read(bytes, place)
          list.push(res.item)
          place += res.increment
          if (bytes[place] !== phy.key)
            break
            // this could throw us into infinite loops, so
          if (res.increment < 1)
            throw new Error('dangerous increment while reading list')
        }
        if (list.length < 1)
          throw new Error('reading list, found no items...')
        if (tsdebug)
          console.log('read list as', list)
        return list
      }
    }
    
    // typically: call, response expected
    // manager keys
    const MK = {
      // bzzt
      ERR: 254, // (str) message
      // heartbeats, wakeup
      HELLO: 231, // (eom)
      // request a top-level description
      QUERY: 251, // (eom)
      BRIEF: 250, // (str) name of interpreter, # hunks, # links (and then begin firing list back)
      // please show what is available
      REQLISTAVAIL: 249, // (eom)
      LISTOFAVAIL: 248, // (list)(str) names 'dirs/like/this' (includes programs ?) (this might be multiple packets?)
      // business ... we should be able to centralize all control w/i view.js if we can write these
      REQADDHUNK: 247, // (str) name
      REQNAMECHANGE: 246,
      HUNKALIVE: 245, // (hunkdescription): name, id, inputlist, outputlist, statelist
      HUNKREPLACE: 244,
      REQSTATECHANGE: 243,
      HUNKSTATECHANGE: 242,
      REQRMHUNK: 241, // (str) id
      HUNKREMOVED: 240, // (str) id
      REQADDLINK: 239, // (str) id, (str) outname, (str) id, (str) inname
      LINKALIVE: 238, // (str) id, (str) outname, (str) id, (str) inname
      REQRMLINK: 237, // (str) id, (str) outname, (str) id, (str) inname
      LINKREMOVED: 236, // (str) id, (str) outname, (str) id, (str) inname
      // to id,
      MSGID: 235
    }
    
    // hunk description keys,
    const HK = {
      NAME: 253,
      TYPE: 252,
      IND: 251,
      DESCR: 250,
      INPUT: 249,
      OUTPUT: 247,
      CONNECTIONS: 246,
      CONNECTION: 245,
      STATE: 244
    }
    
    // link keys,
    const LK = {
      ACK: 254,
      HELLO: 253 // ~ binary solo ~
    }
    
    // should write out as list of pairs ?
    // or write fn to do key(stringtype)
    
    export {
      TSET,
      MK, // manager keys
      HK, // hunk def keys
      LK, // link keys
      MSGS,
      findPhy,
      isIntType,
      isFloatType,
      isNumType
    }