Select Git revision
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
}