Skip to content

hqtsm/struct

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HQTSM: Struct

Binary structures

binary struct union pointer array endian dataview arraybuffer

JSR npm CI

Features

  • 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

Usage

Fixed Endianness

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');

Dynamic Endianness

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');

Extending / Inheritance

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');

Child Structures

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');

Pointer

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');

Array

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');

Union

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');

Alignment / Padding

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.

Private / Protected

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');