/** * @module * * Pointer types and factory. */ import { MeekValueMap } from '@hqtsm/meek/valuemap'; import { type Class, constant, toStringTag } from '@hqtsm/class'; import { Endian } from './endian.js'; import type { MemberInfo, MemberInfos } from './members.js'; import type { ArrayBufferType } from './native.js'; import type { Type, TypeConstructor } from './type.js'; import { assignType, parseIndex } from './util.js'; const members = new WeakMap(); const pointers = new WeakMap>>(); const pointerValues = new WeakMap, MeekValueMap>(); const memberBytes = new WeakMap(); const ptrHandler: ProxyHandler> = { deleteProperty(target, key): boolean { let i; return Reflect.has(target, key) || (i = parseIndex(key)) === undefined ? Reflect.deleteProperty(target, key) : i !== i - i % 1; }, get(target, key, receiver: Ptr): unknown | undefined { let i; if (Reflect.has(target, key) || (i = parseIndex(key)) === undefined) { return Reflect.get(target, key, key in target ? target : receiver); } if (i === i - i % 1) { return receiver.get(i); } }, has(target, key): boolean { let i; return ( Reflect.has(target, key) || ((i = parseIndex(key)) !== undefined && i === i - i % 1) ); }, set(target, key, value, receiver: Ptr): boolean { let i; if (Reflect.has(target, key) || (i = parseIndex(key)) === undefined) { return Reflect.set( target, key, value, key in target ? target : receiver, ); } if (i === i - i % 1) { receiver.set(i, value); } return true; }, }; const memberHandler: ProxyHandler> = { get(target, key, receiver): unknown { const i = parseIndex(key); if (i === undefined) { return Reflect.get(target, key, key in target ? target : receiver); } const bpe = memberBytes.get(target)!; if (i === i - i % 1) { return { byteLength: bpe, byteOffset: i * bpe, } satisfies MemberInfo; } }, has(target, key): boolean { return parseIndex(key) !== undefined || Reflect.has(target, key); }, set(target, key, value, receiver): boolean { return ( parseIndex(key) === undefined && Reflect.set(target, key, value, key in target ? target : receiver) ); }, }; function values>(p: T): MeekValueMap { let r = pointerValues.get(p); if (!r) { pointerValues.set(p, r = new MeekValueMap()); } return r; } /** * Pointer to a type. * * @template T Value type. * @template TArrayBuffer Buffer type. */ export class Ptr< T = never, TArrayBuffer extends ArrayBufferType = ArrayBufferType, > extends Endian { /** * Pointer elements. */ [index: number]: T; /** * Create instance for buffer. * * @param buffer Buffer data. * @param byteOffset Byte offset into buffer. * @param littleEndian Host endian, little endian, big endian. */ constructor( buffer: TArrayBuffer, byteOffset?: number, littleEndian?: boolean | null, ) { super(buffer, byteOffset, littleEndian); return new Proxy( this, ptrHandler as ProxyHandler>, ); } /** * Get accessor. * * @param index Pointer index. * @returns Pointer value. */ public get(index: number): T { index = (+index || 0) - (index % 1 || 0); throw new TypeError(`Read from void pointer: ${index}`); } /** * Set accessor. * * @param index Pointer index. * @param value Pointer value. */ public set(index: number, value: T): void { index = (+index || 0) - (index % 1 || 0); void value; throw new TypeError(`Write to void pointer: ${index}`); } /** * Size of each element. */ public static readonly BYTES_PER_ELEMENT: number = 0; /** * Non-overlapping members. */ public static readonly OVERLAPPING: boolean = false; /** * Members infos. */ public static get MEMBERS(): MemberInfos { let r = members.get(this); if (!r) { const MEMBERS = Object.create( Object.getPrototypeOf(this).MEMBERS ?? null, ) as MemberInfos; memberBytes.set(MEMBERS, this.BYTES_PER_ELEMENT); members.set(this, r = new Proxy(MEMBERS, memberHandler)); } return r; } static { toStringTag(this, 'Ptr'); constant(this, 'BYTES_PER_ELEMENT'); constant(this, 'OVERLAPPING'); } } /** * Pointer constructor. * * @template T Pointer type. * @template TArrayBuffer Buffer type. */ export interface PtrConstructor< T extends Ptr = Ptr, TArrayBuffer extends ArrayBufferType = ArrayBufferType, > extends Omit, never> { /** * Create instance for buffer. * * @template _TArrayBuffer Buffer type. * @param buffer Buffer data. * @param byteOffset Byte offset into buffer. * @param littleEndian Host endian, little endian, big endian. */ new <_TArrayBuffer extends TArrayBuffer>( buffer: _TArrayBuffer, byteOffset?: number, littleEndian?: boolean | null, ): T & Ptr; /** * Create instance for buffer. * * @param buffer Buffer data. * @param byteOffset Byte offset into buffer. * @param littleEndian Host endian, little endian, big endian. */ new ( buffer: TArrayBuffer, byteOffset?: number, littleEndian?: boolean | null, ): T & Ptr; /** * Type prototype. */ readonly prototype: T & Ptr; } /** * Pointer class. * * @template T Pointer type. * @template TArrayBuffer Buffer type. */ export type PtrClass< T extends Ptr = Ptr, TArrayBuffer extends ArrayBufferType = ArrayBufferType, > = Class>; /** * Get pointer of type. * * @template T Type. * @param Type Type constructor. * @returns Ptr constructor. */ export function pointer( Type: TypeConstructor, ): PtrConstructor> { let r = pointers.get(Type) as PtrConstructor> | undefined; if (!r) { const name = `Ptr<${Type.name}>`; const tag = `Ptr<${Type.prototype[Symbol.toStringTag]}>`; const bpe = Type.BYTE_LENGTH; pointers.set( Type, r = { [name]: class extends Ptr { public override get(index: number): T { index = (+index || 0) - (index % 1 || 0); const v = values(this); let r = v.get(index); if (!r) { v.set( index, r = new Type( this.buffer, this.byteOffset + index * bpe, this.littleEndian, ), ); } return r; } public override set(index: number, value: T): void { index = (+index || 0) - (index % 1 || 0); const v = values(this); let r = v.get(index); if (!r) { v.set( index, r = new Type( this.buffer, this.byteOffset + index * bpe, this.littleEndian, ), ); } assignType(r, value); } public static override readonly BYTES_PER_ELEMENT = bpe; static { toStringTag(this, tag); constant(this, 'BYTES_PER_ELEMENT'); } }, }[name], ); } return r; }