Skip to content

Instantly share code, notes, and snippets.

@ikasoba
Created March 25, 2023 17:31
Show Gist options
  • Save ikasoba/cc1da5dd90f44157daa6e8369011c608 to your computer and use it in GitHub Desktop.
Save ikasoba/cc1da5dd90f44157daa6e8369011c608 to your computer and use it in GitHub Desktop.
The one that encodes json to binary
const BinaryTypes = {
EOO: 0,
Null: 1,
NumberF64: 2,
NumberF32: 3,
NumberI32: 4,
NumberU32: 5,
BoolTrue: 6,
BoolFalse: 7,
String: 8,
Object: 9,
Array: 10
}
/**
* @template T
* @typedef {{
* is(value: T): boolean
* serialize(value: T): number[] | Error
* parse(src: Uint8Array, i: number): [T, number] | Error
* }} Schema
*/
class Jbon {
/** @returns {Schema<null>} */
static null(){
return {
is(value){
return value === null
},
serialize(_){
return [BinaryTypes.Null]
},
parse(src, i){
if (src[i] == BinaryTypes.Null){
i++
return [null, i]
}else return new Error("invalid Type\nexpect Null")
}
}
}
/** @returns {Schema<number>} */
static number(){
return {
is(value){
return typeof value == "number"
},
serialize(value){
if ((value - Math.floor(value)) > 0 || value > 2**32 - 1){
if (value <= 3.4028235e38 && value >= -1.7976931348623157e308){
return [BinaryTypes.NumberF32, ...new Uint8Array(new Float32Array([value]).buffer)]
}else{
return [BinaryTypes.NumberF64, ...new Uint8Array(new Float64Array([value]).buffer)]
}
}else{
if (value <= Math.floor((2**32 - 1) / 2)){
return [BinaryTypes.NumberI32, ...new Uint8Array(new Int32Array([value]).buffer)]
}else{
return [BinaryTypes.NumberU32, ...new Uint8Array(new Uint32Array([value]).buffer)]
}
}
},
parse(src, i){
if (src[i] == BinaryTypes.NumberF64){
i++
return [new Float64Array(src.slice(i, i += 8).buffer)[0], i]
}else if (src[i] == BinaryTypes.NumberF32){
i++
return [new Float32Array(src.slice(i, i += 4).buffer)[0], i]
}else if (src[i] == BinaryTypes.NumberI32){
i++
return [new Int32Array(src.slice(i, i += 4).buffer)[0], i]
}else if (src[i] == BinaryTypes.NumberU32){
i++
return [new Uint32Array(src.slice(i, i += 4).buffer)[0], i]
}
return new Error("invalid Type\nexpect Number")
}
}
}
/** @returns {Schema<boolean>} */
static bool(){
return {
is(value){
return typeof value == "boolean"
},
serialize(value){
return value ? [BinaryTypes.BoolTrue] : [BinaryTypes.BoolFalse]
},
parse(src, i){
if (src[i] == BinaryTypes.BoolTrue){
i++
return [true, i]
}else if (src[i] == BinaryTypes.BoolFalse){
i++
return [false, i]
}else return new Error("invalid Type\nexpect Bool")
}
}
}
/** @returns {Schema<string>} */
static string(){
return {
is(value){
return typeof value == "string"
},
serialize(value){
const bytes = new TextEncoder().encode(value)
const res = [BinaryTypes.String]
res.push(...new Uint8Array(new Uint16Array([bytes.length]).buffer))
res.push(...bytes)
return res
},
parse(src, i){
if (src[i] == BinaryTypes.String){
i++
const length = new Uint16Array(src.slice(i, i += 2).buffer)[0]
const res = new TextDecoder().decode(src.slice(i, i += length))
return [res, i]
}else return new Error("invalid Type\nexpect String")
}
}
}
/** @template {[string, Schema<any>][]} S @argument {S} schemas @returns {Schema<{[key: string]: any}>} */
static object(schemas){
return {
is(value){
if (typeof value != "object")return false;
for (const [k, s] of schemas){
if (!s.is(value[k])) return false
}
return true
},
serialize(value){
/**
* @type {number[]}
*/
const res = [BinaryTypes.Object]
for (const [k, s] of schemas){
const r = s.serialize(value[k])
if (r instanceof Error) return r;
res.push(...r)
}
return res
},
parse(src, i){
if (src[i] == BinaryTypes.Object){
i++
const res = Object.create(null)
for (const [k, s] of schemas){
const m = s.parse(src, i)
if (m instanceof Error)return m;
i = m[1]
res[k] = m[0]
}
return [res, i]
}else return new Error("invalid Type\nexpect Object")
}
}
}
/** @template T @argument {Schema<T>} schema @returns {Schema<T[]>} */
static array(schema){
return {
is(value){
if (!(value instanceof Array))return false;
return value.every(x => schema.is(x))
},
serialize(value){
const res = [BinaryTypes.Array, ...new Uint8Array(new Uint16Array([value.length]).buffer)]
for (const x of value){
const r = schema.serialize(x)
if (r instanceof Error) return r;
res.push(...r)
}
return res
},
parse(src, i){
if (src[i] == BinaryTypes.Array){
i++
const res = []
const length = new Uint16Array(src.slice(i, i += 2).buffer)[0]
for (let c = 0; c < length; c++){
const m = schema.parse(src, i)
if (m instanceof Error)return m;
i = m[1]
res.push(m[0])
}
return [res, i]
}else return new Error("invalid Type\nexpect Array")
}
}
}
/** @template {Schema<any>[]} S @argument {S} schemas @returns {S[number]} */
static union(...schemas){
return {
is(value){
return schemas.some(x => x.is(value))
},
serialize(value){
for (const schema of schemas){
if (schema.is(value)){
return schema.serialize(value)
}
}
return new Error("Invalid Value Type")
},
parse(src, i){
let lastError
for (const schema of schemas){
const m = schema.parse(src, i)
if (m instanceof Error){
lastError = m
}else{
return m
}
}
return /** @type {Error} */(lastError)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment