Binary structures
- Pure TypeScript, run anywhere
- Strong static type checking
- Tree shaking friendly design
- Public interface compatible with
ArrayBufferView
, like native binary types - Support for little-endian, big-endian, and dynamic endian
- Support for structures, unions, pointers, arrays, inheritance, and more
Endianness can be defined for each individual member.
import { int8, Struct, uint16BE, uint16LE } from '@hqtsm/struct';
class Example extends Struct {
declare public alpha: number;
declare public beta: number;
declare public gamma: number;
static {
uint16LE(this, 'alpha');
uint16BE(this, 'beta');
int8(this, 'gamma');
}
}
const data = new Uint8Array(Example.BYTE_LENGTH);
const example = new Example(data.buffer);
example.alpha = 0xABCD;
example.beta = 0xBCDE;
example.gamma = -123;
console.assert(data.join(' ') === '205 171 188 222 133');
Using the endian passed into the constructor, or host endianness.
import { Struct, uint16 } from '@hqtsm/struct';
class Example extends Struct {
declare public alpha: number;
declare public beta: number;
static {
uint16(this, 'alpha');
uint16(this, 'beta');
}
}
const data = new Uint8Array(Example.BYTE_LENGTH);
const exampleLE = new Example(data.buffer, 0, true);
exampleLE.alpha = 0xABCD;
exampleLE.beta = 0xBCDE;
console.assert(data.join(' ') === '205 171 222 188');
const exampleBE = new Example(data.buffer, 0, false);
exampleBE.alpha = 0xABCD;
exampleBE.beta = 0xBCDE;
console.assert(data.join(' ') === '171 205 188 222');
Structures can be extended with new child members.
import { float32, Struct, uint32 } from '@hqtsm/struct';
class Variable extends Struct {
declare public type: number;
static {
uint32(this, 'type');
}
}
class VariableFloat extends Variable {
declare public value: number;
static {
float32(this, 'value');
}
}
const data = new Uint8Array(VariableFloat.BYTE_LENGTH);
const varFloat = new VariableFloat(data.buffer, 0, true);
varFloat.type = 0xF;
varFloat.value = 3.1415;
console.assert(data.join(' ') === '15 0 0 0 86 14 73 64');
Defining a child structure is easy.
import { Arr, array, member, Struct, uint16BE, Uint8Ptr } from '@hqtsm/struct';
class Child extends Struct {
declare public alpha: number;
declare public beta: number;
static {
uint16BE(this, 'alpha');
uint16BE(this, 'beta');
}
}
class Parent extends Struct {
declare public child1: Child;
declare public child2: Child;
static {
member(Child, this, 'child1');
member(Child, this, 'child2');
}
}
const data = new Uint8Array(Parent.BYTE_LENGTH);
const stru = new Parent(data.buffer);
stru.child1.alpha = 97;
stru.child1.beta = 98;
stru.child2.alpha = 65;
stru.child2.beta = 66;
console.assert(data.join(' ') === '0 97 0 98 0 65 0 66');
Comes with pointers for primitives, and a factory for pointers to types.
import { int8, Int8Ptr, pointer, Struct } from '@hqtsm/struct';
const data = new Int8Array(6);
// Starting at an offset.
const i8p = new Int8Ptr(data.buffer, 2);
// Setting values by index.
i8p[0] = 0;
i8p[1] = 1;
i8p[2] = 2;
i8p[3] = 3;
// Negative indexing also works, accessing memory before offset.
i8p[-1] = -1;
i8p[-2] = -2;
console.assert(data.join(' ') === '-2 -1 0 1 2 3');
class XY extends Struct {
declare public x: number;
declare public y: number;
static {
int8(this, 'x');
int8(this, 'y');
}
}
// Pointer for custom type.
const XYPtr = pointer(XY);
const xyp = new XYPtr(data.buffer, 2);
console.assert(xyp[0].x === 0);
console.assert(xyp[0].y === 1);
console.assert(xyp[1].x === 2);
console.assert(xyp[1].y === 3);
console.assert(xyp[-1].x === -2);
console.assert(xyp[-1].y === -1);
// Type memory can also be assigned.
const xy = new XY(new ArrayBuffer(XY.BYTE_LENGTH));
xy.x = 88;
xy.y = 89;
xyp[0] = xy;
console.assert(data.join(' ') === '-2 -1 88 89 2 3');
A pointer extended to a type of a fixed length.
import {
Arr,
array,
int8,
member,
pointer,
Struct,
Uint8Ptr,
} from '@hqtsm/struct';
class XY extends Struct {
declare public x: number;
declare public y: number;
static {
int8(this, 'x');
int8(this, 'y');
}
}
class Example extends Struct {
declare public bytes: Arr<number>;
declare public xys: Arr<XY>;
static {
member(array(Uint8Ptr, 4), this, 'bytes');
member(array(XY, 2), this, 'xys');
}
}
const data = new Uint8Array(Example.BYTE_LENGTH);
const example = new Example(data.buffer);
example.bytes[0] = 10;
example.bytes[1] = 20;
example.bytes[2] = 30;
example.bytes[3] = 40;
example.xys[0].x = 150;
example.xys[0].y = 160;
example.xys[1].x = 170;
example.xys[1].y = 180;
console.assert(data.join(' ') === '10 20 30 40 150 160 170 180');
A union will automatically use overlapping member memory.
import { Arr, array, member, uint32BE, Uint8Ptr, Union } from '@hqtsm/struct';
class FourCC extends Union {
declare public int: number;
declare public chars: Arr<number>;
static {
uint32BE(this, 'int');
member(array(Uint8Ptr, 4), this, 'chars');
}
}
const data = new Uint8Array(FourCC.BYTE_LENGTH);
const four = new FourCC(data.buffer);
four.int = 0x41424344;
console.assert(four.chars[0] === 0x41);
console.assert(String.fromCharCode(...four.chars) === 'ABCD');
By default member memory is sequentially without alignment or padding.
import { pad, Struct, uint32, uint8 } from '@hqtsm/struct';
class Example extends Struct {
declare private alpha: number;
declare protected beta: number;
declare public gamma: number;
public setAlpha(value: number): void {
this.alpha = value;
}
public setBeta(value: number): void {
this.beta = value;
}
static {
uint8(this, 'alpha' as never);
uint32(this, 'beta' as never);
uint8(this, 'gamma');
}
}
console.assert(Example.BYTE_LENGTH === 6);
Padding can be manually added as a property or anonymously.
import { pad, Struct, uint32, uint8 } from '@hqtsm/struct';
class Example extends Struct {
declare private alpha: number;
declare public padding: never;
declare protected beta: number;
declare public gamma: number;
public setAlpha(value: number): void {
this.alpha = value;
}
public setBeta(value: number): void {
this.beta = value;
}
static {
uint8(this, 'alpha' as never);
pad(3, this, 'padding'); // Property.
uint32(this, 'beta' as never);
uint8(this, 'gamma');
pad(3, this); // Anonymous.
}
}
console.assert(Example.BYTE_LENGTH === 12);
Using intergers or arrays for padding also works.
Members can be made private
or protected
but type checking must be relaxed.
Casting the name to never
or any
will pass the type checker.
import { Struct, uint8 } from '@hqtsm/struct';
class Example extends Struct {
declare private alpha: number;
declare protected beta: number;
declare public gamma: number;
public setAlpha(value: number): void {
this.alpha = value;
}
public setBeta(value: number): void {
this.beta = value;
}
static {
uint8(this, 'alpha' as never);
uint8(this, 'beta' as never);
uint8(this, 'gamma');
}
}
const data = new Uint8Array(Example.BYTE_LENGTH);
const example = new Example(data.buffer);
example.setAlpha(65);
example.setBeta(66);
example.gamma = 71;
console.assert(data.join(' ') === '65 66 71');