UNPKG

binpat

Version:

Parse binary data using declarative patterns.

341 lines (340 loc) 12.6 kB
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 {};