feat(Sequence)
This commit is contained in:
91
src/Sequence.js
Normal file
91
src/Sequence.js
Normal file
@ -0,0 +1,91 @@
|
||||
import { isHeadless, parse, serialize, sizeof } from '.'
|
||||
import { Type } from './Type'
|
||||
|
||||
export function Sequence(types) {
|
||||
const obj = { _types: types, _headless: false, _sizeof: 0 }
|
||||
Object.setPrototypeOf(obj, Sequence.prototype)
|
||||
obj.new(Sequence, [
|
||||
types.map((arg) => {
|
||||
if (typeof arg == 'function') {
|
||||
return arg.name
|
||||
} else {
|
||||
return arg
|
||||
}
|
||||
}),
|
||||
])
|
||||
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
if (!Array.isArray(types[i])) {
|
||||
types[i] = [types[i]]
|
||||
}
|
||||
obj._headless |= isHeadless(...types[i])
|
||||
if (obj._headless) {
|
||||
obj._sizeof = null
|
||||
} else {
|
||||
obj._sizeof += sizeof(...types[i])
|
||||
}
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
Sequence.prototype._srcValidate = function (src) {
|
||||
if (!Array.isArray(src) || src.length != this._types.length) {
|
||||
throw new Error(
|
||||
this._name +
|
||||
' source object should be array with length ' +
|
||||
this._types.length,
|
||||
)
|
||||
}
|
||||
}
|
||||
Sequence.prototype.serialize = function (dv, src) {
|
||||
if (dv.byteLength < this.sizeof(src)) {
|
||||
throw new Error(this._name + ' too small buffer')
|
||||
}
|
||||
this._srcValidate(src)
|
||||
|
||||
let offset = 0
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
const item = src[i]
|
||||
const item_types = this._types[i]
|
||||
const item_frame = new DataView(dv.buffer, dv.byteOffset + offset)
|
||||
serialize(item_frame, item, ...item_types)
|
||||
offset += sizeof(item, ...item_types)
|
||||
}
|
||||
}
|
||||
Sequence.prototype.parse = function (dv) {
|
||||
const res = new Array(this._types.length)
|
||||
let offset = 0
|
||||
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
const item_types = this._types[i]
|
||||
const item_frame = new DataView(dv.buffer, dv.byteOffset + offset)
|
||||
res[i] = parse(item_frame, ...item_types)
|
||||
offset += sizeof(res[i], ...item_types)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
Sequence.prototype.isHeadless = function () {
|
||||
return this._headless
|
||||
}
|
||||
Sequence.prototype.sizeof = function (src) {
|
||||
if (this._headless) {
|
||||
if (src == undefined) {
|
||||
throw new Error('unknown sizeof ' + this._name)
|
||||
}
|
||||
this._srcValidate(src)
|
||||
|
||||
let size = 0
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
const item = src[i]
|
||||
const item_types = this._types[i]
|
||||
size += sizeof(item, ...item_types)
|
||||
}
|
||||
|
||||
return size
|
||||
} else {
|
||||
return this._sizeof
|
||||
}
|
||||
}
|
||||
Object.setPrototypeOf(Sequence.prototype, Type.prototype)
|
||||
Object.freeze(Sequence.prototype)
|
||||
@ -6,13 +6,20 @@ export function Type() {
|
||||
Type.prototype.new = function (func, args) {
|
||||
this._name = func.name
|
||||
if (args !== undefined) {
|
||||
const str_args = Array.from(args).map((arg) => JSON.stringify(arg))
|
||||
const str_args = Array.from(args).map((arg) => {
|
||||
if (typeof arg == 'function') {
|
||||
return arg.name
|
||||
} else {
|
||||
return JSON.stringify(arg)
|
||||
}
|
||||
})
|
||||
this._name += '(' + str_args.join(', ') + ')'
|
||||
}
|
||||
}
|
||||
Type.prototype.toString = function () {
|
||||
return this._name
|
||||
}
|
||||
Type.prototype.toJSON = Type.prototype.toString
|
||||
Type.prototype.serialize = function () {
|
||||
throw new Error('should be overloaded')
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import { Int } from './Int'
|
||||
import { ConstString } from './ConstString'
|
||||
import { ConstArray } from './ConstArray'
|
||||
import { ConstDataView } from './ConstDataView'
|
||||
import { Sequence } from './Sequence'
|
||||
import { Struct } from './Struct'
|
||||
|
||||
export {
|
||||
@ -15,6 +16,7 @@ export {
|
||||
ConstString,
|
||||
ConstArray,
|
||||
ConstDataView,
|
||||
Sequence,
|
||||
Struct,
|
||||
}
|
||||
|
||||
|
||||
43
test/Sequence.test.js
Normal file
43
test/Sequence.test.js
Normal file
@ -0,0 +1,43 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { ConstString, Int, isHeadless, parse, Sequence, serialize, sizeof } from "../src";
|
||||
import { expectDataViewEqual, filledDataView, sizedDataView } from '.'
|
||||
|
||||
describe(Sequence.name, () => {
|
||||
const Type = Sequence([Int(8, 'unsigned'), String, Boolean])
|
||||
const type = [255, 'hello', true]
|
||||
const type_dv = filledDataView([
|
||||
0xFF,
|
||||
0x00, 0x00, 0x00, 0x05,
|
||||
0x68, 0x65, 0x6C, 0x6C, 0x6F,
|
||||
0x01,
|
||||
])
|
||||
|
||||
test('serialize', () => {
|
||||
let actual = sizedDataView(10)
|
||||
expect(() => serialize(actual, type, Type)).toThrow()
|
||||
|
||||
actual = sizedDataView(sizeof(type, Type))
|
||||
serialize(actual, type, Type)
|
||||
|
||||
expectDataViewEqual(actual, type_dv)
|
||||
})
|
||||
|
||||
test('parse', () => {
|
||||
expect(parse(type_dv, Type)).toEqual(type)
|
||||
})
|
||||
|
||||
test('isHeadless', () => {
|
||||
expect(isHeadless(Sequence([Number, String]))).toBeTruthy()
|
||||
expect(isHeadless(Sequence([Number, ConstString(4)]))).toBeFalsy()
|
||||
})
|
||||
|
||||
test('sizeof', () => {
|
||||
expect(sizeof(Sequence([Number, ConstString(4)]))).toEqual(12)
|
||||
|
||||
expect(() => sizeof(Sequence([Number, String]))).toThrow()
|
||||
expect(() => sizeof([1], Sequence([Number, String]))).toThrow()
|
||||
expect(() => sizeof([1, 'hello', 3], Sequence([Number, String]))).toThrow()
|
||||
|
||||
expect(sizeof([1, 'hello'], Sequence([Number, String]))).toEqual(17)
|
||||
})
|
||||
})
|
||||
7
types/Sequence.d.ts
vendored
Normal file
7
types/Sequence.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import { SerializableType, Type } from '.'
|
||||
|
||||
/**
|
||||
* constructs type of heterogeneous array of arbitrary types. Even if item type is headless, it will be stored directly in the of sequence
|
||||
* @returns {Type}
|
||||
*/
|
||||
export function Sequence(types: (SerializableType | SerializableType[])[]): Type
|
||||
2
types/index.d.ts
vendored
2
types/index.d.ts
vendored
@ -5,6 +5,7 @@ import { Int } from './Int'
|
||||
import { ConstString } from './ConstString'
|
||||
import { ConstArray } from './ConstArray'
|
||||
import { ConstDataView } from './ConstDataView'
|
||||
import { Sequence } from './Sequence'
|
||||
import { Struct } from './Struct'
|
||||
|
||||
export {
|
||||
@ -15,6 +16,7 @@ export {
|
||||
ConstString,
|
||||
ConstArray,
|
||||
ConstDataView,
|
||||
Sequence,
|
||||
Struct,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user