binpat
Version:
Parse binary data using declarative patterns.
341 lines (340 loc) • 12.6 kB
TypeScript
export interface BinpatOption {
/** The global endianness. Default value is `big`. */
endian: 'big' | 'little';
}
export interface BinpatContext {
endian: BinpatOption['endian'];
parent?: BinpatContext;
offset: number;
data: Record<string, any>;
}
type BinpatHandler<T> = {
(dv: DataView, ctx: BinpatContext): T;
[BINPAT_TYPE]: string;
};
type BinpatPatternDefinition = BinpatHandler<any> | BinpatPatternObject | BinpatPatternDefinition[] | string | number | boolean;
interface BinpatPatternObject {
[key: string]: BinpatPatternDefinition;
}
type InferOutput<P> = P extends BinpatHandler<infer T> ? T : P extends (infer ElementPattern)[] ? {
[I in keyof P]: InferOutput<P[I]>;
} : P extends BinpatPatternObject ? {
[K in keyof P]: InferOutput<P[K]>;
} : P extends string | number | boolean | bigint | null | undefined ? P : any;
declare const BINPAT_TYPE: unique symbol;
declare const BINPAT_ARRAY: unique symbol;
/**
* Parse binary data using declarative patterns.
*
* @example Usage
* ```js
* import Binpat, { u8 } from 'binpat';
* const binpat = new Binpat({ foo: u8() });
* ```
*/
export default class Binpat<P extends BinpatPatternDefinition> {
#private;
endian: BinpatOption['endian'];
/** Create a new Binpat instance. */
constructor(pattern: P, option?: BinpatOption);
/**
* Executes the pattern against the buffer.
* @param buffer The ArrayBuffer containing the binary data.
*
* @example Usage
* ```js
* import { assertEquals } from '@std/assert';
* import Binpat, { u8 } from 'binpat';
*
* const binpat = new Binpat({ foo: u8() });
* const { buffer } = new Uint8Array([1]);
* assertEquals(binpat.exec(buffer), { foo: 1 });
* ```
*/
exec(buffer: ArrayBuffer): InferOutput<P>;
}
type TypedArrayConstructor = Uint8ArrayConstructor | Uint16ArrayConstructor | Uint32ArrayConstructor | BigUint64ArrayConstructor | Int8ArrayConstructor | Int16ArrayConstructor | Int32ArrayConstructor | BigInt64ArrayConstructor | Float16ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor;
type PrimitiveHandler<T, TC extends TypedArrayConstructor> = () => BinpatHandler<T> & {
[BINPAT_ARRAY]: TC;
};
type PrimitiveHandlerLE<T, TC extends TypedArrayConstructor> = (
/**
* Indicates whether the data is stored in little- or big-endian format.
* If not specified, the global endianness is used.
*/
littleEndian?: boolean) => BinpatHandler<T> & {
[BINPAT_ARRAY]: TC;
};
/** Reads 1 byte and interprets it as an 8-bit unsigned integer. */
export declare const u8: PrimitiveHandler<number, Uint8ArrayConstructor>;
/** Reads 2 bytes and interprets them as a 16-bit unsigned integer. */
export declare const u16: PrimitiveHandlerLE<number, Uint16ArrayConstructor>;
/** Reads 4 bytes and interprets them as a 32-bit unsigned integer. */
export declare const u32: PrimitiveHandlerLE<number, Uint32ArrayConstructor>;
/** Reads 8 bytes and interprets them as a 64-bit unsigned integer. */
export declare const u64: PrimitiveHandlerLE<bigint, BigUint64ArrayConstructor>;
/** Reads 1 byte and interprets it as an 8-bit signed integer. */
export declare const i8: PrimitiveHandler<number, Int8ArrayConstructor>;
/** Reads 2 bytes and interprets them as a 16-bit signed integer. */
export declare const i16: PrimitiveHandlerLE<number, Int16ArrayConstructor>;
/** Reads 4 bytes and interprets them as a 32-bit signed integer. */
export declare const i32: PrimitiveHandlerLE<number, Int32ArrayConstructor>;
/** Reads 8 bytes and interprets them as a 64-bit signed integer. */
export declare const i64: PrimitiveHandlerLE<bigint, BigInt64ArrayConstructor>;
/** Reads 2 bytes and interprets them as a 16-bit floating point number. */
export declare const f16: PrimitiveHandlerLE<number, Float16ArrayConstructor>;
/** Reads 4 bytes and interprets them as a 32-bit floating point number. */
export declare const f32: PrimitiveHandlerLE<number, Float32ArrayConstructor>;
/** Reads 8 bytes and interprets them as a 64-bit floating point number. */
export declare const f64: PrimitiveHandlerLE<number, Float64ArrayConstructor>;
/** Reads 1 byte and and interprets it as a boolean value. */
export declare function bool(): BinpatHandler<boolean>;
/**
* Reads the given number of bytes and decodes to a string.
* @param size number of bytes
* @param encoding same as [TextDecoder.encoding](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/encoding)
*
* @example Usage
* ```js
* import { assertEquals } from '@std/assert';
* import Binpat, { string } from 'binpat';
*
* const binpat = new Binpat({ name: string(6) });
* const { buffer } = new Uint8Array([98, 105, 110, 112, 97, 116]);
* assertEquals(binpat.exec(buffer), { name: 'binpat' });
* ```
*/
export declare function string(size: number | ((ctx: BinpatContext) => number), encoding?: TextDecoderCommon['encoding']): BinpatHandler<string>;
/**
* Reads an array of values, all conforming to the same pattern.
* If the pattern is a primitive type (like u8, u16),
* it will return a TypedArray instance (e.g., Uint8Array).
* Otherwise, it returns a regular array.
* @param pattern The pattern definition for each element in the array.
* @param size The number of elements to read, or a function returning the number based on context.
*
* @example Usage
* ```js
* import Binpat, { array, u16, u32, u8 } from 'binpat';
*
* const binpat = new Binpat({
* // It will return Uint16Array
* foo: array(u16(), 4),
* // pattern can be object
* bar: array({ x: u8(), y: u8() }, 4),
* count: u32(),
* // get size from context
* baz: array(u8(), (ctx) => ctx.data.count),
* });
* ```
*/
export declare function array<P extends BinpatPatternDefinition>(pattern: P, size: number | ((ctx: BinpatContext) => number)): BinpatHandler<P extends {
[BINPAT_ARRAY]: infer TC;
} ? TC extends TypedArrayConstructor ? InstanceType<TC> : InferOutput<P>[] : InferOutput<P>[]>;
type BitfieldTypeUnsigned = {
type: 'u';
size: number;
};
type BitfieldTypeSigned = {
type: 'i';
size: number;
};
type BitfieldTypeBoolean = {
type: 'bool';
size: 1;
};
type BitfieldType = BitfieldTypeUnsigned | BitfieldTypeSigned | BitfieldTypeBoolean;
type BitfieldLayout = {
[name: string]: number | BitfieldType;
};
type BitfieldOption = {
/**
* The endianness of the bitfield. If not specified, it will use the global endianness.
*/
endian?: BinpatOption['endian'];
/**
* Which bit comes first, see [Bit numbering](https://en.wikipedia.org/wiki/Bit_numbering).
* + 'MSb': Most Significant Bit (left-to-right).
* + 'LSb': Least Significant Bit (right-to-left).
*
* If not specified:
* + When endian is `big`, the first bit is `MSb`.
* + When endian is `little`, the first bit is `LSb`.
*/
first?: 'MSb' | 'LSb';
};
type InferBitResult<Def extends number | BitfieldType> = Def extends BitfieldTypeBoolean ? boolean : number;
type InferBitfieldOutput<F extends BitfieldLayout> = {
[K in keyof F as K extends `__BINPAT_OMIT_${string}` ? never : K]: InferBitResult<F[K] extends number ? {
type: 'u';
size: F[K];
} : F[K]>;
};
/**
* Reads a bitfield.
*
* @example Usage
* ```js
* import { assertEquals } from '@std/assert';
* import Binpat, { bitfield, omit } from 'binpat';
*
* const binpat = new Binpat(bitfield({
* a: 3, // unsigned, 3 bits
* b: bitfield.u(3), // unsigned, 3 bits
* [omit('padding')]: 4, // padding, 4 bits
* c: bitfield.i(5), // signed, 5 bits
* d: bitfield.bool(), // boolean, 1 bit
* }));
* const { buffer } = new Uint8Array([0b01010101, 0b11001100]);
* assertEquals(binpat.exec(buffer), {
* a: 2, // 0b010
* b: 5, // 0b101
* // 0b0111 (padding)
* c: 6, // 0b00110
* d: false, // 0b0
* });
* ```
*/
export declare function bitfield<Layout extends BitfieldLayout>(field: Layout, option?: BitfieldOption): BinpatHandler<InferBitfieldOutput<Layout>>;
export declare namespace bitfield {
var u: (size: number) => BitfieldTypeUnsigned;
var i: (size: number) => BitfieldTypeSigned;
var bool: () => BitfieldTypeBoolean;
}
/**
* It works like `condition(ctx) ? truthy : falsy`
* @param condition
* @param truthy
* @param falsy
*
* @example Usage
* ```js
* import { assertEquals } from '@std/assert';
* import Binpat, { ternary, bool, u16, u8 } from 'binpat';
*
* const binpat = new Binpat({
* flag: bool(),
* value: ternary((ctx) => ctx.data.flag, [u8(), u8()], [u16()]),
* });
* assertEquals(binpat.exec(new Uint8Array([1, 0, 0]).buffer), { flag: true, value: [0, 0] });
* assertEquals(binpat.exec(new Uint8Array([0, 0, 0]).buffer), { flag: false, value: [0] });
* ```
*/
export declare function ternary<T extends BinpatPatternDefinition, F extends BinpatPatternDefinition | undefined>(condition: (ctx: BinpatContext) => boolean, truthy: T, falsy?: F): BinpatHandler<InferOutput<T> | (F extends undefined ? never : InferOutput<F>)>;
/**
* Convert the result value with custom function.
* @param pattern
* @param fn
*
* @example Usage
* ```js
* import { assertEquals } from '@std/assert';
* import Binpat, { convert, u16 } from 'binpat';
*
* const binpat = new Binpat({
* type: convert(u16(), (value) => ['', 'ico', 'cur'][value] || 'unknown'),
* });
* assertEquals(binpat.exec(new Uint8Array([0, 1]).buffer), { type: 'ico' });
* assertEquals(binpat.exec(new Uint8Array([0, 2]).buffer), { type: 'cur' });
* assertEquals(binpat.exec(new Uint8Array([0, 0]).buffer), { type: 'unknown' });
* ```
*/
export declare function convert<P extends BinpatPatternDefinition, R>(pattern: P, fn: (value: InferOutput<P>, ctx?: BinpatContext) => R): BinpatHandler<R>;
/**
* Move current offset to the given offset.
* @param offset
*
* @example Usage
* ```js
* import { assertEquals } from '@std/assert';
* import Binpat, { seek, omit, u8 } from 'binpat';
*
* const binpat = new Binpat({
* foo: u8(),
* [omit('padding')]: seek((ctx) => ctx.offset + 4),
* bar: u8(),
* });
* const { buffer } = new Uint8Array([1, 0, 0, 0, 0, 2]);
* assertEquals(binpat.exec(buffer), { foo: 1, bar: 2 });
* ```
*/
export declare function seek(offset: number | ((ctx: BinpatContext) => number)): BinpatHandler<void>;
/**
* Reads pattern from the given offset, and doesn't move current offset.
* @param offset
* @param pattern
*
* @example Usage
* ```js
* import Binpat, { peek, u8 } from 'binpat';
*
* const binpat = new Binpat({
* size: u32(),
* address: u32(),
* data: peek(
* (ctx) => ctx.data.address,
* array(u8(), (ctx) => ctx.data.size),
* ),
* });
* ```
*/
export declare function peek<P extends BinpatPatternDefinition>(offset: number | ((ctx: BinpatContext) => number), pattern: P): BinpatHandler<InferOutput<P>>;
/**
* Move forward with the given offset. `skip(x)` is same as `seek((ctx) => ctx.offset + x)`
* @param offset
*
* @example Usage
* ```js
* import { assertEquals } from '@std/assert';
* import Binpat, { skip, omit, u8 } from 'binpat';
*
* const binpat = new Binpat({
* foo: u8(),
* [omit('padding')]: skip(4),
* bar: u8(),
* });
* const { buffer } = new Uint8Array([1, 0, 0, 0, 0, 2]);
* assertEquals(binpat.exec(buffer), { foo: 1, bar: 2 });
* ```
*/
export declare function skip(offset: number): BinpatHandler<void>;
/**
* Omit the key-value in result.
*
* @example Usage
* ```js
* import { assertEquals } from '@std/assert';
* import Binpat, { omit, u16 } from 'binpat';
*
* const binpat = new Binpat({
* [omit('reserved')]: u16(),
* type: u16(),
* count: u16(),
* });
* const { buffer } = new Uint8Array([0, 0, 0, 1, 0, 1]);
* assertEquals(binpat.exec(buffer), { type: 1, count: 1 });
* ```
*/
export declare function omit(name?: string): `__BINPAT_OMIT_${string}`;
/**
* It works like spread syntax `...`, and usually be used with `ternary()`.
*
* @example Usage
* ```js
* import { assertEquals } from '@std/assert';
* import Binpat, { spread, ternary, bool, u8 } from 'binpat';
*
* const binpat = new Binpat({
* flag: bool(),
* [spread()]: ternary(
* (ctx) => ctx.data.flag,
* { truthy: u8() },
* { falsy: u8() },
* ),
* });
* assertEquals(binpat.exec(new Uint8Array([1, 0]).buffer), { flag: true, truthy: 0 });
* assertEquals(binpat.exec(new Uint8Array([0, 0]).buffer), { flag: false, falsy: 0 });
* ```
*/
export declare function spread(): `__BINPAT_SPREAD_${string}`;
export {};