feat(Struct)
This commit is contained in:
47
src/ConstArray.js
Normal file
47
src/ConstArray.js
Normal file
@ -0,0 +1,47 @@
|
||||
import { limits, parse, serialize, sizeofHead, Type } from "."
|
||||
|
||||
export function ConstArray(size) {
|
||||
const obj = { _size: size }
|
||||
Object.setPrototypeOf(obj, ConstArray.prototype)
|
||||
obj.new(ConstArray, arguments)
|
||||
return obj
|
||||
}
|
||||
ConstArray.prototype.serialize = function(dv, src, ...inner_types) {
|
||||
const item_size = sizeofHead(src[0])
|
||||
let size = this._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
|
||||
}
|
||||
ConstArray.prototype.parse = function(dv, ...inner_types) {
|
||||
const size = this._size
|
||||
const item_size = sizeofHead(...inner_types)
|
||||
const array = Array(size)
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
const item_frame = new DataView(dv.buffer, dv.byteOffset + item_size * i)
|
||||
array[i] = parse(item_frame, ...inner_types)
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
ConstArray.prototype.isHeadless = function() {
|
||||
return false
|
||||
}
|
||||
ConstArray.prototype.sizeof = function(...inner_types) {
|
||||
return sizeofHead(...inner_types) * this._size
|
||||
}
|
||||
Object.setPrototypeOf(ConstArray.prototype, Type.prototype)
|
||||
Object.freeze(ConstArray.prototype)
|
||||
33
src/ConstString.js
Normal file
33
src/ConstString.js
Normal file
@ -0,0 +1,33 @@
|
||||
import { memcpy } from "./mem"
|
||||
import { Type } from "./Type"
|
||||
|
||||
export function ConstString(size) {
|
||||
const obj = { _size: size }
|
||||
Object.setPrototypeOf(obj, ConstString.prototype)
|
||||
obj.new(ConstString, arguments)
|
||||
return obj
|
||||
}
|
||||
ConstString.prototype.serialize = function(dv, src) {
|
||||
const encoder = new TextEncoder('utf-8')
|
||||
const encoded = new DataView(encoder.encode(src).buffer, 0, this._size)
|
||||
|
||||
if (dv.byteLength < encoded.byteLength) {
|
||||
throw new Error('too small buffer')
|
||||
}
|
||||
|
||||
memcpy(dv, encoded)
|
||||
return
|
||||
}
|
||||
ConstString.prototype.parse = function(dv) {
|
||||
const frame = new DataView(dv.buffer, dv.byteOffset, this._size)
|
||||
const decoder = new TextDecoder('utf-8')
|
||||
return decoder.decode(frame)
|
||||
}
|
||||
ConstString.prototype.isHeadless = function() {
|
||||
return false
|
||||
}
|
||||
ConstString.prototype.sizeof = function() {
|
||||
return this._size
|
||||
}
|
||||
Object.setPrototypeOf(ConstString.prototype, Type.prototype)
|
||||
Object.freeze(ConstString.prototype)
|
||||
93
src/Struct.js
Normal file
93
src/Struct.js
Normal file
@ -0,0 +1,93 @@
|
||||
import { isHeadless, parse, serialize, sizeof, sizeofHead, Type } from "."
|
||||
|
||||
export function Struct(type_obj) {
|
||||
const obj = {}
|
||||
Object.setPrototypeOf(obj, Struct.prototype)
|
||||
|
||||
obj._info_by_key = new Map
|
||||
obj._headless = false
|
||||
|
||||
const arg = {}
|
||||
let offset = 0
|
||||
|
||||
for (const [key, value] of Object.entries(type_obj)) {
|
||||
let types
|
||||
if (Array.isArray(value)) {
|
||||
types = value
|
||||
} else {
|
||||
types = [value]
|
||||
}
|
||||
|
||||
arg[key] = types.map(type => type.name)
|
||||
|
||||
const headless = isHeadless(...types)
|
||||
obj._info_by_key.set(key, {
|
||||
offset,
|
||||
types,
|
||||
headless,
|
||||
})
|
||||
obj._headless |= headless
|
||||
offset += sizeofHead(...types)
|
||||
}
|
||||
|
||||
obj._size = offset
|
||||
obj.new(Struct, [arg])
|
||||
|
||||
return obj
|
||||
}
|
||||
Struct.prototype.serialize = function(dv, src) {
|
||||
let data_offset = this._size
|
||||
|
||||
for (const [key, value] of Object.entries(src)) {
|
||||
const info = this._info_by_key.get(key)
|
||||
|
||||
if (info.headless) {
|
||||
dv.setUint32(info.offset, data_offset)
|
||||
const frame = new DataView(dv.buffer, dv.byteOffset + data_offset)
|
||||
serialize(frame, value, ...info.types)
|
||||
data_offset += sizeof(value, ...info.types)
|
||||
} else {
|
||||
const frame = new DataView(dv.buffer, dv.byteOffset + info.offset)
|
||||
serialize(frame, value, ...info.types)
|
||||
}
|
||||
}
|
||||
}
|
||||
Struct.prototype.parse = function(dv) {
|
||||
const res = {}
|
||||
|
||||
for (const [key, info] of this._info_by_key.entries()) {
|
||||
if (info.headless) {
|
||||
const data_offset = dv.getUint32(info.offset)
|
||||
const frame = new DataView(dv.buffer, dv.byteOffset + data_offset)
|
||||
res[key] = parse(frame, ...info.types)
|
||||
} else {
|
||||
const frame = new DataView(dv.buffer, dv.byteOffset + info.offset)
|
||||
res[key] = parse(frame, ...info.types)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
Struct.prototype.isHeadless = function() {
|
||||
return this._headless
|
||||
}
|
||||
Struct.prototype.sizeof = function(arg) {
|
||||
if (this._headless) {
|
||||
if (arg === undefined) {
|
||||
throw new Error('unknown size of ' + this)
|
||||
}
|
||||
let size = this._size
|
||||
|
||||
for (const [key, info] of this._info_by_key.entries()) {
|
||||
if (info.headless) {
|
||||
size += sizeof(arg[key], ...info.types)
|
||||
}
|
||||
}
|
||||
|
||||
return size
|
||||
} else {
|
||||
return this._size
|
||||
}
|
||||
}
|
||||
Object.setPrototypeOf(Struct.prototype, Type.prototype)
|
||||
Object.freeze(Struct.prototype)
|
||||
22
src/Type.js
Normal file
22
src/Type.js
Normal file
@ -0,0 +1,22 @@
|
||||
export function Type() {
|
||||
const obj = {}
|
||||
Object.setPrototypeOf(obj, Type.prototype)
|
||||
return obj
|
||||
}
|
||||
Type.prototype.new = function(func, args) {
|
||||
this._name = func.name
|
||||
if (args !== undefined) {
|
||||
const str_args = Array.from(args).map(arg => JSON.stringify(arg))
|
||||
this._name += '(' + str_args.join(', ') + ')'
|
||||
}
|
||||
}
|
||||
Type.prototype.toString = function() {
|
||||
return this._name
|
||||
}
|
||||
Type.prototype.serialize = function() {
|
||||
throw new Error('should be overloaded')
|
||||
}
|
||||
Type.prototype.parse = Type.prototype.serialize
|
||||
Type.prototype.isHeadless = Type.prototype.serialize
|
||||
Type.prototype.sizeof = Type.prototype.serialize
|
||||
Object.freeze(Type.prototype)
|
||||
103
src/index.js
103
src/index.js
@ -1,8 +1,11 @@
|
||||
import { limits } from "./limits"
|
||||
import { memcpy } from "./mem"
|
||||
import { ConstArray, ConstString } from "./type"
|
||||
import { Type } from "./Type"
|
||||
import { ConstString } from './ConstString'
|
||||
import { ConstArray } from "./ConstArray"
|
||||
import { Struct } from './Struct'
|
||||
|
||||
export { limits, memcpy, ConstString, ConstArray}
|
||||
export { limits, memcpy, Type, ConstString, ConstArray, Struct }
|
||||
|
||||
export function serialize(dv, src, ...types) {
|
||||
const [type, ...inner_types] = types
|
||||
@ -14,20 +17,13 @@ export function serialize(dv, src, ...types) {
|
||||
dv.setFloat64(0, src)
|
||||
return
|
||||
}
|
||||
if ((type == String || type._func == ConstString) && typeof src == 'string') {
|
||||
if (type == String && typeof src == 'string') {
|
||||
const encoder = new TextEncoder('utf-8')
|
||||
let encoded
|
||||
if (type == String) {
|
||||
encoded = new DataView(encoder.encode(src).buffer)
|
||||
let 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')
|
||||
}
|
||||
@ -38,22 +34,14 @@ export function serialize(dv, src, ...types) {
|
||||
memcpy(frame, encoded)
|
||||
return
|
||||
}
|
||||
if ((type == Array || type._func == ConstArray) && Array.isArray(src)) {
|
||||
if (type == Array && Array.isArray(src)) {
|
||||
|
||||
const item_size = sizeofHead(src[0])
|
||||
let size
|
||||
const size = src.length
|
||||
|
||||
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')
|
||||
}
|
||||
@ -61,11 +49,15 @@ export function serialize(dv, src, ...types) {
|
||||
dv.setUint32(0, size)
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
const item_frame = new DataView(dv.buffer, dv.byteOffset + item_size * i)
|
||||
const item_frame = new DataView(dv.buffer, dv.byteOffset + 4 + item_size * i)
|
||||
serialize(item_frame, src[i], ...inner_types)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (type instanceof Type) {
|
||||
type.serialize(dv, src, ...inner_types)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export function parse(dv, ...types) {
|
||||
@ -74,46 +66,38 @@ export function parse(dv, ...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 size = dv.getUint32(0)
|
||||
const frame = new DataView(dv.buffer, 4 + 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 size = dv.getUint32(0)
|
||||
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)
|
||||
const item_frame = new DataView(dv.buffer, dv.byteOffset + 4 + item_size * i)
|
||||
array[i] = parse(item_frame, ...inner_types)
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
if (type instanceof Type) {
|
||||
return type.parse(dv, ...inner_types)
|
||||
}
|
||||
}
|
||||
|
||||
export function isHeadless(...args) {
|
||||
const [first_arg] = args
|
||||
return first_arg == Array ||
|
||||
first_arg == String ||
|
||||
Array.isArray(first_arg) ||
|
||||
typeof first_arg == 'string'
|
||||
const [arg, ...inner_args] = args
|
||||
if (arg instanceof Type) {
|
||||
return arg.isHeadless(...inner_args)
|
||||
}
|
||||
return arg == Array ||
|
||||
arg == String ||
|
||||
Array.isArray(arg) ||
|
||||
typeof arg == 'string'
|
||||
}
|
||||
|
||||
export function sizeofHead(...args) {
|
||||
@ -125,23 +109,24 @@ export function sizeofHead(...args) {
|
||||
}
|
||||
|
||||
export function sizeof(...args) {
|
||||
const [first_arg, ...remain_args] = args
|
||||
const [arg, ...inner_args] = args
|
||||
|
||||
if (first_arg == Number || typeof first_arg == 'number') {
|
||||
if (arg == Number || typeof 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') {
|
||||
if (typeof arg == 'string') {
|
||||
const encoder = new TextEncoder('utf-8')
|
||||
return 4 + encoder.encode(first_arg).byteLength
|
||||
return 4 + encoder.encode(arg).byteLength
|
||||
}
|
||||
if (Array.isArray(first_arg)) {
|
||||
return 4 + sizeofHead(first_arg[0]) * first_arg.length
|
||||
if (Array.isArray(arg)) {
|
||||
return 4 + sizeofHead(arg[0]) * arg.length
|
||||
}
|
||||
throw new Error('unknown size of ' + args)
|
||||
if (arg instanceof Type) {
|
||||
return arg.sizeof(...inner_args)
|
||||
}
|
||||
const [arg2, ...inner_args2] = inner_args
|
||||
if (arg2 instanceof Type) {
|
||||
return arg2.sizeof(arg, ...inner_args2)
|
||||
}
|
||||
throw new Error('unknown size of ' + arg)
|
||||
}
|
||||
|
||||
18
src/type.js
18
src/type.js
@ -1,18 +0,0 @@
|
||||
export class Type {
|
||||
constructor (func, obj) {
|
||||
this._func = func
|
||||
Object.assign(this, obj)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this._func.name
|
||||
}
|
||||
}
|
||||
|
||||
export function ConstString(size) {
|
||||
return new Type(ConstString, { _size: size })
|
||||
}
|
||||
|
||||
export function ConstArray(size) {
|
||||
return new Type(ConstArray, { _size: size })
|
||||
}
|
||||
34
test/ConstArray.test.js
Normal file
34
test/ConstArray.test.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { ConstArray, parse, serialize, sizeof, sizeofHead } from "../src";
|
||||
import { filledDataView, sizedDataView } from ".";
|
||||
|
||||
describe(ConstArray.name, () => {
|
||||
test('serialize, Number', () => {
|
||||
const expected = filledDataView([
|
||||
0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
])
|
||||
let dv = sizedDataView(15)
|
||||
expect(() => serialize(dv, [1, 2], ConstArray(2), Number)).toThrow()
|
||||
|
||||
dv = sizedDataView(sizeof(ConstArray(2), Number))
|
||||
expect(16).toEqual(dv.byteLength)
|
||||
|
||||
serialize(dv, [1, 2], ConstArray(2), Number)
|
||||
expect(dv).toEqual(expected)
|
||||
})
|
||||
|
||||
test('parse, Number', () => {
|
||||
const expected = [1, 2]
|
||||
const dv = filledDataView([
|
||||
0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
])
|
||||
const actual = parse(dv, ConstArray(2), Number)
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
test('sizeof', () => {
|
||||
expect(sizeofHead(ConstArray(2), Number)).toEqual(16)
|
||||
})
|
||||
})
|
||||
27
test/ConstString.test.js
Normal file
27
test/ConstString.test.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { ConstString, parse, serialize, sizeofHead } from "../src";
|
||||
import { filledDataView, sizedDataView } from ".";
|
||||
|
||||
describe(ConstString.name, () => {
|
||||
test('serialize', () => {
|
||||
const expected = filledDataView([0x68, 0x65, 0x6C, 0x00, 0x00])
|
||||
let dv = sizedDataView(4)
|
||||
expect(() => serialize(dv, 'hello', ConstString(5))).toThrow()
|
||||
|
||||
dv = sizedDataView(5)
|
||||
|
||||
serialize(dv, 'hello', ConstString(3))
|
||||
expect(dv).toEqual(expected)
|
||||
})
|
||||
|
||||
test('parse', () => {
|
||||
const expected = 'hel'
|
||||
const dv = filledDataView([0x68, 0x65, 0x6C, 0x6C, 0x6F])
|
||||
const actual = parse(dv, ConstString(3))
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
test('sizeof', () => {
|
||||
expect(sizeofHead(ConstString(2))).toEqual(2)
|
||||
})
|
||||
})
|
||||
70
test/Struct.test.js
Normal file
70
test/Struct.test.js
Normal file
@ -0,0 +1,70 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { parse, serialize, sizeof, Struct } from "../src";
|
||||
import { filledDataView, sizedDataView } from ".";
|
||||
|
||||
describe(Struct.name, () => {
|
||||
|
||||
const User = Struct({ age: Number, name: String })
|
||||
const user = { age: 1, name: 'hello' }
|
||||
const user_dv = filledDataView([
|
||||
0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0C,
|
||||
0x00, 0x00, 0x00, 0x05,
|
||||
0x68, 0x65, 0x6C, 0x6C, 0x6F,
|
||||
])
|
||||
|
||||
test('serialize, headless', () => {
|
||||
expect(() => sizeof(User)).toThrow()
|
||||
|
||||
const dv = sizedDataView(sizeof(user, User))
|
||||
expect(21).toEqual(dv.byteLength)
|
||||
|
||||
serialize(dv, user, User)
|
||||
expect(dv).toEqual(user_dv)
|
||||
})
|
||||
|
||||
test('parse, headless', () => {
|
||||
expect(parse(user_dv, User)).toEqual(user)
|
||||
})
|
||||
|
||||
const Vector2 = Struct({ x: Number, y: Number })
|
||||
const vector2 = { x: 1, y: 2 }
|
||||
const vector2_dv = filledDataView([
|
||||
0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
])
|
||||
|
||||
test('serialize, non-headless', () => {
|
||||
const dv = sizedDataView(sizeof(Vector2))
|
||||
expect(16).toEqual(dv.byteLength)
|
||||
|
||||
serialize(dv, vector2, Vector2)
|
||||
expect(dv).toEqual(vector2_dv)
|
||||
})
|
||||
|
||||
test('parse, non-headless', () => {
|
||||
expect(parse(vector2_dv, Vector2)).toEqual(vector2)
|
||||
})
|
||||
|
||||
const Nested = Struct({ user: User, vector2: [Vector2] })
|
||||
const nested = { user, vector2 }
|
||||
const nested_dv = filledDataView([
|
||||
0x00, 0x00, 0x00, 0x14,
|
||||
0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x0C,
|
||||
0x00, 0x00, 0x00, 0x05,
|
||||
0x68, 0x65, 0x6C, 0x6C, 0x6F,
|
||||
])
|
||||
|
||||
test('serialize, nested', () => {
|
||||
const dv = sizedDataView(sizeof(nested, Nested))
|
||||
serialize(dv, nested, Nested)
|
||||
expect(dv).toEqual(nested_dv)
|
||||
})
|
||||
|
||||
test('parse, nested', () => {
|
||||
expect(parse(nested_dv, Nested)).toEqual(nested)
|
||||
})
|
||||
})
|
||||
@ -1,7 +1,6 @@
|
||||
import { describe, test, expect } from 'vitest'
|
||||
import { filledDataView, sizedDataView } from '.'
|
||||
import { parse, serialize, sizeof, sizeofHead } from '../src'
|
||||
import { ConstArray, ConstString } from '../src/type'
|
||||
|
||||
describe('serialize', () => {
|
||||
test('Number', () => {
|
||||
@ -15,16 +14,6 @@ describe('serialize', () => {
|
||||
serialize(dv, 1532.625, Number)
|
||||
expect(dv).toEqual(expected)
|
||||
})
|
||||
test('ConstString', () => {
|
||||
const expected = filledDataView([0x68, 0x65, 0x6C, 0x00, 0x00])
|
||||
let dv = sizedDataView(4)
|
||||
expect(() => serialize(dv, 'hello', ConstString(5))).toThrow()
|
||||
|
||||
dv = sizedDataView(5)
|
||||
|
||||
serialize(dv, 'hello', ConstString(3))
|
||||
expect(dv).toEqual(expected)
|
||||
})
|
||||
test('String', () => {
|
||||
const expected = filledDataView([
|
||||
0x00, 0x00, 0x00, 0x05,
|
||||
@ -39,20 +28,6 @@ describe('serialize', () => {
|
||||
serialize(dv, 'hello', String)
|
||||
expect(dv).toEqual(expected)
|
||||
})
|
||||
test('ConstArray, Number', () => {
|
||||
const expected = filledDataView([
|
||||
0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
])
|
||||
let dv = sizedDataView(15)
|
||||
expect(() => serialize(dv, [1, 2], ConstArray(2), Number)).toThrow()
|
||||
|
||||
dv = sizedDataView(sizeof(ConstArray(2), Number))
|
||||
expect(16).toEqual(dv.byteLength)
|
||||
|
||||
serialize(dv, [1, 2], ConstArray(2), Number)
|
||||
expect(dv).toEqual(expected)
|
||||
})
|
||||
test('Array, Number', () => {
|
||||
const expected = filledDataView([
|
||||
0x00, 0x00, 0x00, 0x02,
|
||||
@ -77,12 +52,6 @@ describe('parse', () => {
|
||||
const actual = parse(dv, Number)
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
test('ConstString', () => {
|
||||
const expected = 'hel'
|
||||
const dv = filledDataView([0x68, 0x65, 0x6C, 0x6C, 0x6F])
|
||||
const actual = parse(dv, ConstString(3))
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
test('String', () => {
|
||||
const expected = 'hello'
|
||||
const dv = filledDataView([
|
||||
@ -92,15 +61,6 @@ describe('parse', () => {
|
||||
const actual = parse(dv, String)
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
test('ConstArray, Number', () => {
|
||||
const expected = [1, 2]
|
||||
const dv = filledDataView([
|
||||
0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
])
|
||||
const actual = parse(dv, ConstArray(2), Number)
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
test('Array, Number', () => {
|
||||
const expected = [1, 2]
|
||||
const dv = filledDataView([
|
||||
@ -116,21 +76,15 @@ describe('parse', () => {
|
||||
describe('sizeofHead', () => {
|
||||
test('Number', () => {
|
||||
expect(sizeofHead(Number)).toEqual(8)
|
||||
expect(sizeofHead(1)).toEqual(8)
|
||||
})
|
||||
test('ConstString', () => {
|
||||
expect(sizeofHead(ConstString(2))).toEqual(2)
|
||||
expect(sizeofHead(1, Number)).toEqual(8)
|
||||
})
|
||||
test('String', () => {
|
||||
expect(sizeofHead(String)).toEqual(4)
|
||||
expect(sizeofHead('s')).toEqual(4)
|
||||
})
|
||||
test('ConstArray', () => {
|
||||
expect(sizeofHead(ConstArray(2), Number)).toEqual(16)
|
||||
expect(sizeofHead('s', String)).toEqual(4)
|
||||
})
|
||||
test('Array', () => {
|
||||
expect(sizeofHead(Array, Number)).toEqual(4)
|
||||
expect(sizeofHead([1])).toEqual(4)
|
||||
expect(sizeofHead([1], Array, Number)).toEqual(4)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
8
types/ConstArray.d.ts
vendored
Normal file
8
types/ConstArray.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import { Type } from ".";
|
||||
|
||||
/**
|
||||
* constructs type of array with constant byte size
|
||||
* @param {number} size number of items
|
||||
* @returns {Type}
|
||||
*/
|
||||
export function ConstArray(size: number): Type;
|
||||
9
types/type.d.ts → types/ConstString.d.ts
vendored
9
types/type.d.ts → types/ConstString.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
export type Type = unknown
|
||||
import { Type } from ".";
|
||||
|
||||
/**
|
||||
* constructs type of utf8-string with constant byte size
|
||||
@ -6,10 +6,3 @@ export type Type = unknown
|
||||
* @returns {Type}
|
||||
*/
|
||||
export function ConstString(byte_size: number): Type;
|
||||
|
||||
/**
|
||||
* constructs type of array with constant byte size
|
||||
* @param {number} size number of items
|
||||
* @returns {Type}
|
||||
*/
|
||||
export function ConstArray(size: number): Type;
|
||||
7
types/Struct.d.ts
vendored
Normal file
7
types/Struct.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import { SerializableType, Type } from "."
|
||||
|
||||
/**
|
||||
* constructs type of c-like structure. if field is headless, inside of structure will be stored u32 offset, outside of structure will be stored value of field
|
||||
* @returns {Type}
|
||||
*/
|
||||
export function Struct(type_obj: Record<string, SerializableType | SerializableType[]>): Type;
|
||||
1
types/Type.d.ts
vendored
Normal file
1
types/Type.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export type Type = unknown
|
||||
24
types/index.d.ts
vendored
24
types/index.d.ts
vendored
@ -1,10 +1,11 @@
|
||||
import { memcpy } from "./mem";
|
||||
import { limits } from "./limits";
|
||||
import { ConstString, ConstArray } from "./type";
|
||||
import { Type } from "./Type"
|
||||
import { ConstString } from "./ConstString"
|
||||
import { ConstArray } from "./ConstArray";
|
||||
import { Struct } from "./Struct";
|
||||
|
||||
export { memcpy, limits, ConstString, ConstArray }
|
||||
|
||||
import { Type } from "./type";
|
||||
export { memcpy, limits, Type, ConstString, ConstArray, Struct }
|
||||
|
||||
export type SerializableType = NumberConstructor | StringConstructor | ArrayConstructor | Type
|
||||
export type Serializable = number | string | array
|
||||
@ -28,13 +29,14 @@ export function parse(dv: DataView, ...types: SerializableType[]): Serializable;
|
||||
/**
|
||||
* some types, like Array, String, has no fixed size. So in Structure they are stored as u32 offset, which points to their beginning
|
||||
* @param {Serializable} obj to check
|
||||
* @param {SerializableType[]} ...types primary and inner types. eg: Array, Number
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isHeadless(obj: Serializable): boolean;
|
||||
export function isHeadless(obj: Serializable, ...types: SerializableType[]): boolean;
|
||||
|
||||
/**
|
||||
* some types, like Array, String, has no fixed size. So in Structure they are stored as u32 offset, which points to their beginning
|
||||
* @param {SerializableType[]} ...args primary and inner types. eg: Array, Number
|
||||
* @param {SerializableType[]} ...types primary and inner types. eg: Array, Number
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isHeadless(...types: SerializableType[]): boolean;
|
||||
@ -42,25 +44,27 @@ export function isHeadless(...types: SerializableType[]): boolean;
|
||||
/**
|
||||
* if obj has no fixed size, return 4 (sizeof u32 offset)
|
||||
* @param {Serializable} obj to check
|
||||
* @param {SerializableType[]} ...types primary and inner types. eg: Array, Number
|
||||
* @returns {number}
|
||||
*/
|
||||
export function sizeofHead(obj: Serializable): number;
|
||||
export function sizeofHead(obj: Serializable, ...types: SerializableType[]): number;
|
||||
|
||||
/**
|
||||
* if obj has no fixed size, return 4 (sizeof u32 offset)
|
||||
* @param {SerializableType[]} ...args primary and inner types. eg: Array, Number
|
||||
* @param {SerializableType[]} ...types primary and inner types. eg: Array, Number
|
||||
* @returns {number}
|
||||
*/
|
||||
export function sizeofHead(...types: SerializableType[]): number;
|
||||
|
||||
/**
|
||||
* @param {Serializable} obj to check
|
||||
* @param {SerializableType[]} ...types primary and inner types. eg: Array, Number
|
||||
* @returns {number}
|
||||
*/
|
||||
export function sizeof(obj: Serializable): number;
|
||||
export function sizeof(obj: Serializable, ...types: SerializableType[]): number;
|
||||
|
||||
/**
|
||||
* @param {SerializableType[]} ...args primary and inner types. eg: Array, Number
|
||||
* @param {SerializableType[]} ...types primary and inner types. eg: Array, Number
|
||||
* @returns {number}
|
||||
* @throws {Error} if passed Array or String type (unknown sizeof)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user