/** * @module * * Types and functions for defining a member. */ import type { MemberableClass, MemberableClassKeys, MemberInfo, } from './members.js'; import type { ArrayBufferType } from './native.js'; import type { Type } from './type.js'; import { assignView } from './util.js'; /** * Get next/current/default member offset for type. * * @param Type Type class. * @returns Byte offset. */ export function nextByteOffset(Type: MemberableClass): number { return Type.OVERLAPPING ? 0 : Type.BYTE_LENGTH; } /** * Ensure byte length of type is defined. * * @template T Type class. * @param Type Type class. * @param byteLength Byte length to ensure at-least. * @returns Updated type byte length. */ export function ensureByteLength( Type: T, byteLength = 0, ): number { const { BYTE_LENGTH } = Type; const value = byteLength > BYTE_LENGTH ? byteLength : BYTE_LENGTH; Object.defineProperty(Type, 'BYTE_LENGTH', { value, configurable: true, enumerable: false, writable: false, }); return value; } /** * Define member. * * @template T Type class. * @template M Member type. * @param Type Type class. * @param name Member name. * @param desc Member descriptor. * @returns Updated type byte length. */ export function defineMember( Type: T, name: MemberableClassKeys, byteLength: number, byteOffset: number | null, get: (this: InstanceType & Record) => M, set: (this: InstanceType & Record, value: M) => void, ): number { byteLength = (+byteLength || 0) - (byteLength % 1 || 0); byteOffset ??= nextByteOffset(Type); byteOffset = (+byteOffset || 0) - (byteOffset % 1 || 0); Object.defineProperty(Type.prototype, name, { get, set, configurable: true, enumerable: false, }); Object.defineProperty(Type.MEMBERS, name, { value: { byteOffset, byteLength } satisfies MemberInfo, configurable: true, enumerable: true, writable: false, }); return ensureByteLength(Type, byteOffset + byteLength); } /** * Member constructor. * * @template T Member type. * @template TArrayBuffer Buffer type. */ export interface MemberConstructor< T extends ArrayBufferView = ArrayBufferView, TArrayBuffer extends ArrayBufferType = ArrayBufferType, > { /** * Member constructor. * * @template _TArrayBuffer Buffer type. * @param buffer Buffer data. * @param byteOffset Byte offset. * @param littleEndian Little endian, big endian, or inherit. */ new <_TArrayBuffer extends TArrayBuffer>( buffer: _TArrayBuffer, byteOffset?: number, littleEndian?: boolean | null, ): T & ArrayBufferView<_TArrayBuffer>; /** * Member constructor. * * @param buffer Buffer data. * @param byteOffset Byte offset. * @param littleEndian Little endian, big endian, or inherit. */ new ( buffer: TArrayBuffer, byteOffset?: number, littleEndian?: boolean | null, ): T & ArrayBufferView; /** * Member prototype. */ readonly prototype: T & ArrayBufferView; /** * Byte length. */ readonly BYTE_LENGTH: number; } /** * Member: generic. * * @template T Type class. * @template M Member type. * @param Member Member constructor. * @param Type Type class. * @param name Member name. * @param byteOffset Byte offset. * @param littleEndian Little endian, big endian, or inherit. * @returns Updated type byte length. */ export function member( Member: MemberConstructor>>, Type: T, name: MemberableClassKeys, byteOffset: number | null = null, littleEndian: boolean | null = null, ): number { const members = new WeakMap(); byteOffset ??= nextByteOffset(Type); byteOffset = (+byteOffset || 0) - (byteOffset % 1 || 0); return defineMember( Type, name, Member.BYTE_LENGTH, byteOffset, function (): M { let r = members.get(this); if (!r) { members.set( this, r = new Member( this.buffer, this.byteOffset + byteOffset, littleEndian ?? this.littleEndian, ), ); } return r; }, function (value): void { assignView(this[name], value); }, ); } /** * Member: generic, big endian. * * @template T Type class. * @template M Member type. * @param Member Member constructor. * @param Type Type class. * @param name Member name. * @param byteOffset Byte offset. * @returns Updated type byte length. */ export function memberBE( Member: MemberConstructor>>, Type: T, name: MemberableClassKeys, byteOffset: number | null = null, ): number { return member(Member, Type, name, byteOffset, false); } /** * Member: generic, little endian. * * @template T Type class. * @template M Member type. * @param Member Member constructor. * @param Type Type class. * @param name Member name. * @param byteOffset Byte offset. * @returns Updated type byte length. */ export function memberLE( Member: MemberConstructor>>, Type: T, name: MemberableClassKeys, byteOffset: number | null = null, ): number { return member(Member, Type, name, byteOffset, true); } function padRead(name: PropertyKey): never { throw new TypeError(`Read from padding member: ${String(name)}`); } function padWrite(name: PropertyKey): never { throw new TypeError(`Write to padding member: ${String(name)}`); } /** * Member: pad. * * @template T Type class. * @param byteLength Padding size. * @param Type Type class. * @param name Member name. * @param byteOffset Byte offset. * @returns Updated type byte length. */ export function pad( byteLength: number, Type: T, name: MemberableClassKeys | null = null, byteOffset: number | null = null, ): number { if (name === null) { byteLength = (+byteLength || 0) - (byteLength % 1 || 0); byteOffset ??= nextByteOffset(Type); byteOffset = (+byteOffset || 0) - (byteOffset % 1 || 0); return ensureByteLength(Type, byteLength + byteOffset); } return defineMember( Type, name, byteLength, byteOffset, padRead.bind(null, name), padWrite.bind(null, name), ); }