feat(serialize, parse): Number, String, Array, ConstString, ConstArray

This commit is contained in:
2025-08-03 10:54:21 +03:00
commit 26eb3b9068
15 changed files with 2969 additions and 0 deletions

147
src/index.js Normal file
View File

@ -0,0 +1,147 @@
import { limits } from "./limits"
import { memcpy } from "./mem"
import { ConstArray, ConstString } from "./type"
export { limits, memcpy, ConstString, ConstArray}
export function serialize(dv, src, ...types) {
const [type, ...inner_types] = types
if (type == Number && typeof src == 'number') {
if (dv.byteLength < 8) {
throw new Error('too small buffer')
}
dv.setFloat64(0, src)
return
}
if ((type == String || type._func == ConstString) && typeof src == 'string') {
const encoder = new TextEncoder('utf-8')
let encoded
if (type == String) {
encoded = new DataView(encoder.encode(src).buffer)
if (dv.byteLength < 4 + encoded.byteLength) {
throw new Error('too small buffer')
}
} else {
encoded = new DataView(encoder.encode(src).buffer, 0, type._size)
if (dv.byteLength < encoded.byteLength) {
throw new Error('too small buffer')
}
}
if (encoded.byteLength > limits.u32.MAX_VALUE) {
throw new Error('string is too long')
}
dv.setUint32(0, encoded.byteLength)
const frame = new DataView(dv.buffer, dv.byteOffset + 4)
memcpy(frame, encoded)
return
}
if ((type == Array || type._func == ConstArray) && Array.isArray(src)) {
const item_size = sizeofHead(src[0])
let size
if (type == Array) {
size = src.length
if (dv.byteLength < 4 + size * item_size) {
throw new Error('too small buffer')
}
} else {
size = type._size
if (dv.byteLength < size * item_size) {
throw new Error('too small buffer')
}
}
if (size > limits.u32.MAX_VALUE) {
throw new Error('array is too long')
}
dv.setUint32(0, size)
for (let i = 0; i < size; i++) {
const item_frame = new DataView(dv.buffer, dv.byteOffset + item_size * i)
serialize(item_frame, src[i], ...inner_types)
}
return
}
}
export function parse(dv, ...types) {
const [type, ...inner_types] = types
if (type == Number) {
return dv.getFloat64(0)
}
if (type == String || type._func == ConstString) {
let size
let frame
if (type == String) {
size = dv.getUint32(0)
frame = new DataView(dv.buffer, 4 + dv.byteOffset, size)
} else {
size = type._size
frame = new DataView(dv.buffer, dv.byteOffset, size)
}
const decoder = new TextDecoder('utf-8')
return decoder.decode(frame)
}
if (type == Array || type._func == ConstArray) {
let size
let offset = 0
if (type == Array) {
size = dv.getUint32(0)
offset = 4
} else {
size = type._size
}
const item_size = sizeofHead(inner_types[0])
const array = Array(size)
for (let i = 0; i < size; i++) {
const item_frame = new DataView(dv.buffer, dv.byteOffset + offset + item_size * i)
array[i] = parse(item_frame, ...inner_types)
}
return array
}
}
export function isHeadless(...args) {
const [first_arg] = args
return first_arg == Array ||
first_arg == String ||
Array.isArray(first_arg) ||
typeof first_arg == 'string'
}
export function sizeofHead(...args) {
if (isHeadless(...args)) {
return 4
} else {
return sizeof(...args)
}
}
export function sizeof(...args) {
const [first_arg, ...remain_args] = args
if (first_arg == Number || typeof first_arg == 'number') {
return 8
}
if (first_arg._func == ConstArray) {
return sizeofHead(...remain_args) * first_arg._size
}
if (first_arg._func == ConstString) {
return first_arg._size
}
if (typeof first_arg == 'string') {
const encoder = new TextEncoder('utf-8')
return 4 + encoder.encode(first_arg).byteLength
}
if (Array.isArray(first_arg)) {
return 4 + sizeofHead(first_arg[0]) * first_arg.length
}
throw new Error('unknown size of ' + args)
}