UNPKG

enhance-data-view

Version:

Reactive DataView extension with chainable API for type-safe binary data manipulation.

1,505 lines (1,496 loc) 47.4 kB
'use strict'; /** Unique symbol identifying type definition objects */ const TypeDefinitionSymbol = Symbol("TYPE_DEFINITION"); /** * Type guard for TypeDefinition objects * @param test - Value to check * @returns Whether the value is a valid TypeDefinition */ function isTypeDefinition(test) { if (typeof test !== "object" || test === null) { return false; } if (test.isTypeDefinition === TypeDefinitionSymbol) { return true; } return false; } /** * Reads typed data from DataView * @param view - Source DataView * @param type - Type definition * @param offset - Read offset in bytes * @param littleEndian - Byte order (default: big-endian) * @returns Decoded value * @example * const num = get(view, INT, 0x10, true); */ function get(view, type, offset, littleEndian) { return type.getter({ view, offset, littleEndian }); } /** * Reads array of typed data from DataView * @param view - Source DataView * @param type - Element type definition * @param offset - Starting offset in bytes * @param length - Number of elements to read * @param littleEndian - Byte order (default: big-endian) * @returns Array of decoded values * @example * const coords = getArray(view, FLOAT, 0x20, 3); */ function getArray(view, type, offset, length, littleEndian) { const result = new Array(length); const size = type.size; for (let i = 0; i < length; i++) { result[i] = type.getter({ view, offset: offset + size * i, littleEndian }); } return result; } /** * Writes typed data to DataView * @param view - Target DataView * @param type - Type definition * @param offset - Write offset in bytes * @param value - Value to encode * @param littleEndian - Byte order (default: big-endian) * @example * set(view, FLOAT, 0x08, 3.14159); */ function set(view, type, offset, value, littleEndian) { return type.setter({ view, offset, littleEndian }, value); } /** * Writes array of typed data to DataView * @param view - Target DataView * @param type - Element type definition * @param offset - Starting offset in bytes * @param array - Values to encode * @param littleEndian - Byte order (default: big-endian) * @example * setArray(view, INT, 0x30, [1, 2, 3]); */ function setArray(view, type, offset, array, littleEndian) { const length = array.length; const size = type.size; for (let i = 0; i < length; i++) { type.setter({ view, offset: offset + size * i, littleEndian }, array[i]); } } /** * Converts DataView region to reactive object * @param view - Source DataView * @param type - Type definition * @param offset - Byte offset or offset getter * @param littleEndian - Byte order (default: big-endian) * @returns Reactive proxy object * @remarks Property accesses trigger automatic get/set operations * @example * const player = reactive(view, PlayerStruct, 0x100); * player.health = 80; // Automatically writes to DataView */ function reactive(view, type, offset, littleEndian) { const baseOffset = typeof offset === "function" ? offset : () => offset; return type.reactive({ view, littleEndian, localOffset: 0, baseOffset, cacheGetter: () => void 0 }); } /** * Creates reactive reference for value types * @param view - Source DataView * @param type - Type definition * @param offset - Byte offset or offset getter * @param littleEndian - Byte order (default: big-endian) * @returns Reactive reference object * @remarks Optimized for primitive/value type access * @example * const hp = ref(view, UINT, 0x104); * console.log(hp.value); // Reads from DataView * hp.value = 100; // Writes to DataView */ function ref(view, type, offset, littleEndian) { const baseOffset = typeof offset === "function" ? offset : () => offset; let cachedGetter; return { [OperationRawSymbol]() { return { value: type.getter({ view, offset: baseOffset(), littleEndian }) }; }, get value() { if (cachedGetter) { return cachedGetter(); } return type.reactive({ view, littleEndian, localOffset: 0, baseOffset, cacheGetter: (getter) => cachedGetter = getter }); }, set value(value) { type.setter({ view, offset: baseOffset(), littleEndian }, value); } }; } /** * Symbol for retrieving raw values from reactive objects * @remarks Reactive objects must implement this symbol */ const OperationRawSymbol = Symbol("TYPE_DEFINITION_RAW"); /** * Extracts raw value from reactive objects * @param value - Reactive proxy or raw value * @returns Underlying non-reactive value * @example * const raw = toRaw(reactiveObj); */ function toRaw(value) { const target = value; if (typeof target === "object" && target !== null) { const getter = target[OperationRawSymbol]; if (getter) { return getter(); } } return target; } /** * Creates reference for nested reactive property * @param target - Reactive source object * @param key - Property key to reference * @returns Reactive reference to specified property * @example * const hpRef = toRef(player, 'health'); */ function toRef(target, key) { return { [OperationRawSymbol]() { return { value: target[key] }; }, get value() { return target[key]; }, set value(value) { target[key] = value; } }; } /** * Creates a configurable primitive type definition * @param name - Initial type name (optional) * @returns Mutable primitive type definition * @remarks * - Start with this to define custom binary types * - Chain configuration methods before freezing * @example * const Float32 = definePrimitive<number>('float32') * .setSize(4) * .setGetter(ctx => ctx.view.getFloat32(ctx.offset, ctx.littleEndian)) * .setSetter((ctx, value) => ctx.view.setFloat32(ctx.offset, value, ctx.littleEndian)) * .freeze(); */ function definePrimitive(name) { // Configuration state let _name = name; let _size; let _align; let _getter; let _setter; let _reactive; const setName = (name) => { _name = name; return typeDefinition; }; const setSize = (size) => { _size = size; return typeDefinition; }; const setAlign = (align) => { _align = align; return typeDefinition; }; const setGetter = (getter) => { _getter = getter; return typeDefinition; }; const setSetter = (setter) => { _setter = setter; return typeDefinition; }; const setReactive = (reactive) => { _reactive = reactive; return typeDefinition; }; const freeze = () => { const newDefinition = clone(); return Object.freeze({ isTypeDefinition: TypeDefinitionSymbol, name: newDefinition.name, size: newDefinition.size, align: newDefinition.align, getter: newDefinition.getter, setter: newDefinition.setter, reactive: newDefinition.reactive, clone: newDefinition.clone }); }; const clone = (name) => definePrimitive(name ?? _name) .setSize(_size) .setAlign(_align) .setGetter(_getter) .setSetter(_setter) .setReactive(_reactive); // Default operations (throw if not configured) const getter = () => { throw new Error(`Getter not implemented for type '${_name || "unknown"}'`); }; const setter = () => { throw new Error(`Setter not implemented for type '${_name || "unknown"}'`); }; // Default reactive implementation const reactive = ({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const getter = () => typeDefinition.getter({ view, offset: localOffset + baseOffset(), littleEndian, }); cacheGetter(getter); return getter(); }; // Main definition object with getters const typeDefinition = { isTypeDefinition: TypeDefinitionSymbol, get name() { return _name ?? "unknown"; }, get size() { return Math.max(_size ?? 0, 0); }, get align() { return Math.max(_align ?? _size ?? 1, 1); }, get getter() { return _getter ?? getter; }, get setter() { return _setter ?? setter; }, get reactive() { return _reactive ?? reactive; }, setName, setSize, setAlign, setGetter, setSetter, setReactive, freeze, clone }; return typeDefinition; } /** Unique symbol identifying property definitions */ const PropertyDefinitionSymbol = Symbol("PROPERTY_DEFINITION"); /** * Creates a property definition * @param type - Property type definition * @param options - Configuration options * @returns Property definition object * @example * defineProperty(Uint32, { align: 8, offset: 0x10 }) */ function defineProperty(type, options) { return { isPropertyDefinition: PropertyDefinitionSymbol, type, align: options?.align, offset: options?.offset, order: options?.order }; } /** Type guard for property definitions */ function isPropertyDefinition(test) { if (typeof test !== "object" || test === null) { return false; } if (test.isPropertyDefinition === PropertyDefinitionSymbol) { return true; } return false; } /** Unique symbol identifying padding definitions */ const PaddingDefinitionSymbol = Symbol("STRUCT_DEFINITION"); /** * Creates a padding definition * @param typeOrSize - Type definition or byte size * @param options - Configuration options * @returns Padding definition object * @example * // 8-byte padding with 4-byte alignment * definePadding(8, { align: 4 }) */ function definePadding(typeOrSize, options) { return typeof typeOrSize === "number" ? { isPaddingDefinition: PaddingDefinitionSymbol, size: typeOrSize, align: options?.align ?? 1, offset: options?.offset, order: options?.order } : { isPaddingDefinition: PaddingDefinitionSymbol, size: typeOrSize.size, align: options?.align ?? typeOrSize.align, offset: options?.offset, order: options?.order }; } /** Type guard for padding definitions */ function isPaddingDefinition(test) { if (typeof test !== "object" || test === null) { return false; } if (test.isPaddingDefinition === PaddingDefinitionSymbol) { return true; } return false; } /** * Struct definition implementation * @param param0 - Options object or name * @param param1 - Optional name * @returns Struct definition instance */ function defineStruct(param0, param1) { let _name; let _size; let _sizeCalc = 0; let _align; let _alignCalc = 1; let _keys = new Array(); let _properties = new Map(); let _propertyList = new Array(); let _recordList = new Array(); const setName = (name) => { _name = name; return typeDefinition; }; const setSize = (size) => { _size = size; return typeDefinition; }; const setAlign = (align) => { _align = align; return typeDefinition; }; const updateLayout = () => { const keys = new Array(); const properties = new Map(); const propertyList = new Array(); let maxAlign = 1; let maxOffset = 0; let offset = 0; for (const record of _recordList) { // Get size, align let size; let align; if (record.padding) { size = record.size; align = Math.max(record.align, 1); } else { size = record.type.size; align = Math.max(record.align ?? record.type.align, 1); // Record to map keys.push(record.key); properties.set(record.key, record); propertyList.push(record); } // Static offset const offsetStatic = record.offsetStatic; if (typeof offsetStatic === "number") { record.offset = offsetStatic; offset = offsetStatic + size; } else { // Padding calculation const padding = (align - (offset % align)) % align; // Update offset += padding; record.offset = offset; offset += size; } maxAlign = Math.max(maxAlign, align); maxOffset = Math.max(maxOffset, offset); } // End padding const endPadding = (maxAlign - (maxOffset % maxAlign)) % maxAlign; // Update _alignCalc = maxAlign; _sizeCalc = maxOffset + endPadding; _keys = keys; _properties = properties; _propertyList = propertyList; return typeDefinition; }; const propertyToRecord = (key, definition) => ({ key, type: definition.type, align: definition.align, offset: 0, offsetStatic: definition.offset }); const paddingToRecord = (key, definition) => ({ key, size: definition.size, align: definition.align, offset: 0, offsetStatic: definition.offset, padding: true }); const setProperties = (options) => { const orderList = []; const list = []; for (const [key, option] of Object.entries(options)) { // Pure type if (isTypeDefinition(option)) { list.push({ key, type: option, offset: 0 }); continue; } let property; if (isPropertyDefinition(option)) { property = propertyToRecord(key, option); } else if (isPaddingDefinition(option)) { property = paddingToRecord(key, option); } else { throw new Error(`[${typeDefinition.name}] Unknown option type.`); } if (typeof option.order === "number") { orderList.push({ order: option.order, property, }); } else { list.push(property); } } // Sort by order orderList.sort((a, b) => a.order - b.order); // Update _recordList = orderList.map(x => x.property).concat(list); return updateLayout(); }; const getProperties = () => { let order = 0; const options = {}; for (const record of _recordList) { let option; const definitionOption = { align: record.align, offset: record.offsetStatic, order: order++ }; if (record.padding) { option = definePadding(record.size, definitionOption); } else { option = defineProperty(record.type, definitionOption); } options[record.key] = option; } return options; }; const addRecord = (property) => { // Duplicate Check if (_recordList.find(x => x.key === property.key)) { const keyString = typeof property.key === "symbol" ? `Symbol(${property.key.description || ""})` : property.key; throw new Error(`[${typeDefinition.name}] Repeat adding records with key: ${keyString}`); } // Update _recordList.push(property); return updateLayout(); }; const addProperty = (key, type, options) => { return addRecord(propertyToRecord(key, defineProperty(type, options))); }; const addPadding = (key, typeOrSize, options) => { return addRecord(paddingToRecord(key, definePadding(typeOrSize, options))); }; const updateRecord = (property) => { const index = _recordList.findIndex(x => x.key === property.key); if (index < 0) { const keyString = typeof property.key === "symbol" ? `Symbol(${property.key.description || ""})` : property.key; throw new Error(`[${typeDefinition.name}] There is no record with key: ${keyString}`); } // Update _recordList.splice(index, 1, property); return updateLayout(); }; const updateProperty = (key, type, options) => { return updateRecord(propertyToRecord(key, defineProperty(type, options))); }; const updatePadding = (key, typeOrSize, options) => { return updateRecord(paddingToRecord(key, definePadding(typeOrSize, options))); }; const remove = (key) => { const index = _recordList.findIndex(x => x.key === key); if (index < 0) { const keyString = typeof key === "symbol" ? `Symbol(${key.description || ""})` : key; throw new Error(`[${typeDefinition.name}] There is no record with key: ${keyString}`); } // Delete _recordList.splice(index, 1); return updateLayout(); }; const freeze = () => { const newDefinition = clone(); return Object.freeze({ isTypeDefinition: TypeDefinitionSymbol, name: newDefinition.name, size: newDefinition.size, align: newDefinition.align, keys: Object.freeze(newDefinition.keys), properties: Object.freeze(newDefinition.properties), propertyList: Object.freeze(newDefinition.propertyList), recordList: Object.freeze(newDefinition.recordList), getter: newDefinition.getter, setter: newDefinition.setter, reactive: newDefinition.reactive, clone: newDefinition.clone }); }; const clone = (name) => defineStruct(getProperties(), name ?? _name) .setSize(_size) .setAlign(_align); const getter = ({ view, offset, littleEndian }) => { const structure = {}; for (const property of _propertyList) { structure[property.key] = property.type.getter({ view, offset: offset + property.offset, littleEndian: littleEndian, }); } return structure; }; const setter = ({ view, offset, littleEndian }, value) => { for (const property of _propertyList) { property.type.setter({ view, offset: offset + property.offset, littleEndian: littleEndian, }, value[property.key]); } }; const reactive = ({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const proxyToRaw = () => typeDefinition.getter({ view, offset: baseOffset() + localOffset, littleEndian }); const internal = new Map([ [OperationRawSymbol, proxyToRaw] ]); // Prop getter const getterMap = new Map(); const get = (target, key) => { let getter = getterMap.get(key); if (getter) { return getter(); } const property = _properties.get(key); if (property) { const context = { view, littleEndian, localOffset: localOffset + property.offset, baseOffset, cacheGetter: getter => getterMap.set(property.key, getter) }; getter = () => property.type.reactive(context); getterMap.set(property.key, getter); return getter(); } else { return internal.get(key); } }; // Prop setter const set = (target, key, value) => { const property = _properties.get(key); if (property) { property.type.setter({ view, offset: baseOffset() + localOffset + property.offset, littleEndian }, value); return true; } else { return false; } }; const has = (target, key) => { return _properties.has(key); }; const ownKeys = () => { return _keys; }; const defineProperty = () => false; const deleteProperty = () => false; const proxy = new Proxy({}, { get, set, has, ownKeys, defineProperty, deleteProperty }); cacheGetter(() => proxy); return proxy; }; const typeDefinition = { isTypeDefinition: TypeDefinitionSymbol, get name() { return _name ?? `struct{${_keys.length}}`; }, get size() { return Math.max(_size ?? _sizeCalc, 0); }, get align() { return Math.max(_align ?? _alignCalc, 1); }, get keys() { return _keys; }, get properties() { return _properties; }, get propertyList() { return _propertyList; }, get recordList() { return _recordList; }, getter, setter, reactive, setName, setSize, setAlign, setProperties, addProperty, addPadding, updateProperty, updatePadding, remove, freeze, clone }; if (typeof param0 === "object") { setProperties(param0); setName(param1); } else { setName(param0); } return typeDefinition; } const UNKNOWN = definePrimitive("UNKNOWN").freeze(); ////////// INT ////////// /** * Signed 8-bit integer type descriptor * * @remarks * - Size: 1 byte * - Range: -128 to 127 * - Corresponds to `DataView.getInt8()` and `DataView.setInt8()` */ const INT_8 = definePrimitive("INT_8") .setSize(1) .setGetter(({ view, offset }) => view.getInt8(offset)) .setSetter(({ view, offset }, value) => view.setInt8(offset, value)) .setReactive(({ view, localOffset, baseOffset, cacheGetter }) => { const getter = () => view.getInt8(localOffset + baseOffset()); cacheGetter(getter); return getter(); }) .freeze(); /** * Signed 16-bit integer type descriptor * * @remarks * - Size: 2 bytes * - Range: -32,768 to 32,767 * - Endian-sensitive: Uses littleEndian parameter * - Corresponds to `DataView.getInt16()` and `DataView.setInt16()` */ const INT_16 = definePrimitive("INT_16") .setSize(2) .setGetter(({ view, offset, littleEndian }) => view.getInt16(offset, littleEndian)) .setSetter(({ view, offset, littleEndian }, value) => view.setInt16(offset, value, littleEndian)) .setReactive(({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const getter = () => view.getInt16(localOffset + baseOffset(), littleEndian); cacheGetter(getter); return getter(); }) .freeze(); /** Alias for {@link INT_16} */ const SHORT = INT_16.clone("SHORT").freeze(); /** * Signed 32-bit integer type descriptor * * @remarks * - Size: 4 bytes * - Range: -2,147,483,648 to 2,147,483,647 * - Endian-sensitive: Uses littleEndian parameter * - Corresponds to `DataView.getInt32()` and `DataView.setInt32()` */ const INT_32 = definePrimitive("INT_32") .setSize(4) .setGetter(({ view, offset, littleEndian }) => view.getInt32(offset, littleEndian)) .setSetter(({ view, offset, littleEndian }, value) => view.setInt32(offset, value, littleEndian)) .setReactive(({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const getter = () => view.getInt32(localOffset + baseOffset(), littleEndian); cacheGetter(getter); return getter(); }) .freeze(); /** Alias for {@link INT_32} */ const INT = INT_32.clone("INT").freeze(); /** * Signed 64-bit integer type descriptor * * @remarks * - Size: 8 bytes * - Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 * - Uses JavaScript BigInt type * - Endian-sensitive: Uses littleEndian parameter * - Corresponds to `DataView.getBigInt64()` and `DataView.setBigInt64()` */ const INT_64 = definePrimitive("INT_64") .setSize(8) .setGetter(({ view, offset, littleEndian }) => view.getBigInt64(offset, littleEndian)) .setSetter(({ view, offset, littleEndian }, value) => view.setBigInt64(offset, value, littleEndian)) .setReactive(({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const getter = () => view.getBigInt64(localOffset + baseOffset(), littleEndian); cacheGetter(getter); return getter(); }) .freeze(); /** Alias for {@link INT_64} */ const LONG = INT_64.clone("LONG").freeze(); ////////// UINT ////////// /** * Unsigned 8-bit integer type descriptor * * @remarks * - Size: 1 byte * - Range: 0 to 255 * - Not affected by endianness (single byte) * - Corresponds to `DataView.getUint8()` and `DataView.setUint8()` */ const UINT_8 = definePrimitive("UINT_8") .setSize(1) .setGetter(({ view, offset }) => view.getUint8(offset)) .setSetter(({ view, offset }, value) => view.setUint8(offset, value)) .setReactive(({ view, localOffset, baseOffset, cacheGetter }) => { const getter = () => view.getUint8(localOffset + baseOffset()); cacheGetter(getter); return getter(); }) .freeze(); /** Alias for {@link UINT_8} */ const BYTE = UINT_8.clone("BYTE").freeze(); /** * Single-byte character type descriptor (optimized) * * @remarks * - Size: 1 byte * - Reads/writes a single character from/to a byte * - Uses first character of input string only * - No input validation!!! */ const CHAR = definePrimitive("CHAR") .setSize(1) .setGetter(({ view, offset }) => String.fromCharCode(view.getUint8(offset))) .setSetter(({ view, offset }, value) => view.setUint8(offset, value.charCodeAt(0))) .setReactive(({ view, localOffset, baseOffset, cacheGetter }) => { const getter = () => String.fromCharCode(view.getUint8(localOffset + baseOffset())); cacheGetter(getter); return getter(); }) .freeze(); /** * Unsigned 16-bit integer type descriptor * * @remarks * - Size: 2 bytes * - Range: 0 to 65,535 * - Endian-sensitive: Uses littleEndian parameter * - Corresponds to `DataView.getUint16()` and `DataView.setUint16()` */ const UINT_16 = definePrimitive("UINT_16") .setSize(2) .setGetter(({ view, offset, littleEndian }) => view.getUint16(offset, littleEndian)) .setSetter(({ view, offset, littleEndian }, value) => view.setUint16(offset, value, littleEndian)) .setReactive(({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const getter = () => view.getUint16(localOffset + baseOffset(), littleEndian); cacheGetter(getter); return getter(); }) .freeze(); /** Alias for {@link UINT_16} */ const USHORT = UINT_16.clone("USHORT").freeze(); /** Alias for {@link UINT_16} */ const WORD = UINT_16.clone("WORD").freeze(); /** * Unsigned 32-bit integer type descriptor * * @remarks * - Size: 4 bytes * - Range: 0 to 4,294,967,295 * - Endian-sensitive: Uses littleEndian parameter * - Corresponds to `DataView.getUint32()` and `DataView.setUint32()` */ const UINT_32 = definePrimitive("UINT_32") .setSize(4) .setGetter(({ view, offset, littleEndian }) => view.getUint32(offset, littleEndian)) .setSetter(({ view, offset, littleEndian }, value) => view.setUint32(offset, value, littleEndian)) .setReactive(({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const getter = () => view.getUint32(localOffset + baseOffset(), littleEndian); cacheGetter(getter); return getter(); }) .freeze(); /** Alias for {@link UINT_32} */ const UINT = UINT_32.clone("UINT").freeze(); /** Alias for {@link UINT_32} */ const DWORD = UINT_32.clone("DWORD").freeze(); /** * Unsigned 64-bit integer type descriptor * * @remarks * - Size: 8 bytes * - Range: 0 to 18,446,744,073,709,551,615 * - Uses JavaScript BigInt type * - Endian-sensitive: Uses littleEndian parameter * - Corresponds to `DataView.getBigUint64()` and `DataView.setBigUint64()` */ const UINT_64 = definePrimitive("UINT_64") .setSize(8) .setGetter(({ view, offset, littleEndian }) => view.getBigUint64(offset, littleEndian)) .setSetter(({ view, offset, littleEndian }, value) => view.setBigUint64(offset, value, littleEndian)) .setReactive(({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const getter = () => view.getBigUint64(localOffset + baseOffset(), littleEndian); cacheGetter(getter); return getter(); }) .freeze(); /** Alias for {@link UINT_64} */ const ULONG = UINT_64.clone("ULONG").freeze(); ////////// FLOAT ////////// /** * Half-precision (16-bit) floating-point type descriptor * * @remarks * - Size: 2 bytes * - Range: ±65504 * - Precision: ~3-4 decimal digits * - Endian-sensitive: Uses littleEndian parameter * - Corresponds to `DataView.getFloat16()` and `DataView.setFloat16()` */ const FLOAT_16 = definePrimitive("FLOAT_16") .setSize(2) .setGetter(({ view, offset, littleEndian }) => view.getFloat16(offset, littleEndian)) .setSetter(({ view, offset, littleEndian }, value) => view.setFloat16(offset, value, littleEndian)) .setReactive(({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const getter = () => view.getFloat16(localOffset + baseOffset(), littleEndian); cacheGetter(getter); return getter(); }) .freeze(); /** * Single-precision (32-bit) floating-point type descriptor * * @remarks * - Size: 4 bytes * - Range: ±3.4e38 * - Precision: ~7 decimal digits * - Endian-sensitive: Uses littleEndian parameter * - Corresponds to `DataView.getFloat32()` and `DataView.setFloat32()` */ const FLOAT_32 = definePrimitive("FLOAT_32") .setSize(4) .setGetter(({ view, offset, littleEndian }) => view.getFloat32(offset, littleEndian)) .setSetter(({ view, offset, littleEndian }, value) => view.setFloat32(offset, value, littleEndian)) .setReactive(({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const getter = () => view.getFloat32(localOffset + baseOffset(), littleEndian); cacheGetter(getter); return getter(); }) .freeze(); /** Alias for {@link FLOAT_32} */ const FLOAT = FLOAT_32.clone("FLOAT").freeze(); /** * Double-precision (64-bit) floating-point type descriptor * * @remarks * - Size: 8 bytes * - Range: ±1.8e308 * - Precision: ~15 decimal digits * - Endian-sensitive: Uses littleEndian parameter * - Corresponds to `DataView.getFloat64()` and `DataView.setFloat64()` */ const FLOAT_64 = definePrimitive("FLOAT_64") .setSize(8) .setGetter(({ view, offset, littleEndian }) => view.getFloat64(offset, littleEndian)) .setSetter(({ view, offset, littleEndian }, value) => view.setFloat64(offset, value, littleEndian)) .setReactive(({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const getter = () => view.getFloat64(localOffset + baseOffset(), littleEndian); cacheGetter(getter); return getter(); }) .freeze(); /** Alias for {@link FLOAT_64} */ const DOUBLE = FLOAT_64.clone("DOUBLE").freeze(); var types = /*#__PURE__*/Object.freeze({ __proto__: null, BYTE: BYTE, CHAR: CHAR, DOUBLE: DOUBLE, DWORD: DWORD, FLOAT: FLOAT, FLOAT_16: FLOAT_16, FLOAT_32: FLOAT_32, FLOAT_64: FLOAT_64, INT: INT, INT_16: INT_16, INT_32: INT_32, INT_64: INT_64, INT_8: INT_8, LONG: LONG, SHORT: SHORT, UINT: UINT, UINT_16: UINT_16, UINT_32: UINT_32, UINT_64: UINT_64, UINT_8: UINT_8, ULONG: ULONG, UNKNOWN: UNKNOWN, USHORT: USHORT, WORD: WORD }); /** * Array definition implementation * @param param0 - Element type or name * @param length - Array length * @param name - Array name * @param filler - When using an array setter, * if the length of the incoming data is less than the length of the array, * fill in the remaining positions with values, Default is not filled * @returns Array definition instance */ function defineArray(param0, length, filler, name) { let _name; let _size; let _align; let _element = UNKNOWN; let _filler; let _length = 0; const setName = (name) => { _name = name; return typeDefinition; }; const setSize = (size) => { _size = size; return typeDefinition; }; const setAlign = (align) => { _align = align; return typeDefinition; }; const setElement = (element, filler) => { _element = element ?? UNKNOWN; _filler = filler; return typeDefinition; }; const setLength = (length) => { _length = length ?? 0; return typeDefinition; }; const freeze = () => { const newDefinition = clone(); return Object.freeze({ isTypeDefinition: TypeDefinitionSymbol, name: newDefinition.name, size: newDefinition.size, align: newDefinition.align, element: newDefinition.element, filler: newDefinition.filler, length: newDefinition.length, getter: newDefinition.getter, setter: newDefinition.setter, reactive: newDefinition.reactive, clone: newDefinition.clone }); }; const clone = (name) => defineArray(_element, _length, _filler, name ?? _name) .setSize(_size) .setAlign(_align); const getter = ({ view, offset, littleEndian }) => { const array = new Array(_length); for (let index = 0; index < _length; index++) { array[index] = _element.getter({ view, offset: offset + index * _element.size, littleEndian }); } return array; }; const setter = ({ view, offset, littleEndian }, value) => { const valueLength = value.length; const minLength = Math.min(valueLength, _length); for (let index = 0; index < minLength; index++) { _element.setter({ view, offset: offset + index * _element.size, littleEndian }, value[index]); } if (_filler !== void 0 && valueLength < _length) { for (let index = valueLength; index < _length; index++) { _element.setter({ view, offset: offset + index * _element.size, littleEndian }, _filler); } } }; const reactive = ({ view, littleEndian, localOffset, baseOffset, cacheGetter }) => { const proxyToRaw = () => typeDefinition.getter({ view, offset: baseOffset() + localOffset, littleEndian }); // Element getter const getterMap = new Map(); const getElement = (index) => { let getter = getterMap.get(index); if (getter) { return getter(); } const context = { view, littleEndian, localOffset: localOffset + index * _element.size, baseOffset, cacheGetter: getter => getterMap.set(index, getter) }; getter = () => _element.reactive(context); getterMap.set(index, getter); return getter(); }; // To Array let cachedArray; const getArray = () => { if (cachedArray) { return cachedArray; } cachedArray = new Array(_length); for (let index = 0; index < _length; index++) { cachedArray[index] = getElement(index); } return cachedArray; }; // Common array func proxy const callArray = (key) => { const func = Array.prototype[key]; if (typeof func !== "function") { return void 0; } const caller = (...param) => { const array = getArray(); const copyArray = array.slice(); const result = func.call(copyArray, ...param); typeDefinition.setter({ view, offset: baseOffset() + localOffset, littleEndian }, copyArray.map(x => toRaw(x))); return result; }; internal.set(key, caller); return caller; }; // Symbol.iterator const iterator = function* () { for (let index = 0; index < _length; index++) { yield getElement(index); } }; const forEach = (callback, thisArg) => { for (let index = 0; index < _length; index++) { callback.call(thisArg, getElement(index), index, proxy); } }; const map = (callback, thisArg) => { const result = new Array(_length); for (let index = 0; index < _length; index++) { result[index] = callback.call(thisArg, getElement(index), index, proxy); } return result; }; // Built-in props const internal = new Map([ ["length", _length], ["forEach", forEach], ["map", map], [Symbol.iterator, iterator], [OperationRawSymbol, proxyToRaw] ]); // getter const get = (target, key) => { if (typeof key === "symbol") { return internal.has(key) ? internal.get(key) : callArray(key); } const index = Number(key); if (isNaN(index)) { return internal.has(key) ? internal.get(key) : callArray(key); } if (index < 0 || index >= _length) { return void 0; } return getElement(index); }; // setter const set = (target, key, value) => { if (typeof key === "symbol") { return false; } const index = Number(key); if (isNaN(index)) { return false; } if (index < 0 || index >= _length) { return false; } _element.setter({ view, offset: baseOffset() + localOffset + index * _element.size, littleEndian: littleEndian }, value); return true; }; const has = (target, key) => { if (typeof key === "symbol") { return false; } const index = Number(key); if (isNaN(index)) { return false; } if (index < 0 || index >= _length) { return false; } return true; }; let keys; const ownKeys = () => { if (keys) { return keys; } keys = new Array(_length); for (let index = 0; index < _length; index++) { keys[index] = String(index); } return keys; }; const defineProperty = () => false; const deleteProperty = () => false; const proxy = new Proxy([], { get, set, has, ownKeys, defineProperty, deleteProperty }); cacheGetter(() => proxy); return proxy; }; const typeDefinition = { isTypeDefinition: TypeDefinitionSymbol, get name() { return _name ?? `${_element.name}[${_length}]`; }, get size() { return Math.max(_size ?? _element.size * _length, 0); }, get align() { return Math.max(_align ?? _element.align, 1); }, get element() { return _element; }, get filler() { return _filler; }, get length() { return _length; }, getter, setter, reactive, setName, setSize, setAlign, setElement, setLength, freeze, clone }; if (typeof param0 === "object") { setElement(param0, filler); setLength(length); setName(name); } else { setName(param0); } return typeDefinition; } /** Default UTF-8 encoder/decoder implementation */ const DefaultStringCoder = (() => { const encoder = new TextEncoder(); const decoder = new TextDecoder("utf-8"); return { encode(string) { return encoder.encode(string); }, decode(buffer) { return decoder.decode(buffer); } }; })(); /** * String definition implementation * @param param0 - Size or name * @param filler - Padding byte value * @param name - Type name * @returns String definition instance */ function defineString(param0, filler, name) { let _name; let _size = 0; let _align; let _filler; let _coder = DefaultStringCoder; let _bytes = new Uint8Array(_size); const setName = (name) => { _name = name; return typeDefinition; }; const setSize = (size) => { _size = size ?? 0; _bytes = new Uint8Array(_size); return typeDefinition; }; const setAlign = (align) => { _align = align; return typeDefinition; }; const setFiller = (filler) => { _filler = filler; return typeDefinition; }; const setCoder = (coder) => { _coder = coder ?? DefaultStringCoder; return typeDefinition; }; const freeze = () => { const newDefinition = clone(); return Object.freeze({ isTypeDefinition: TypeDefinitionSymbol, name: newDefinition.name, size: newDefinition.size, align: newDefinition.align, filler: newDefinition.filler, coder: newDefinition.coder, getter: newDefinition.getter, setter: newDefinition.setter, reactive: newDefinition.reactive, clone: newDefinition.clone }); }; const clone = (name) => defineString(_size, _filler, name ?? _name) .setAlign(_align) .setCoder(_coder); const getter = ({ view, offset }) => { let actualLength = _size; for (let index = 0; index < _size; index++) { const byte = view.getUint8(offset + index); _bytes[index] = byte; if (typeof _filler === "number" && byte === _filler) { actualLength = index; break; } } const actualBytes = _bytes.subarray(0, actualLength); return _coder.decode(actualBytes); }; const setter = ({ view, offset }, value) => { const bytes = _coder.encode(value); const writeLength = Math.min(bytes.length, _size); for (let index = 0; index < writeLength; index++) { view.setUint8(offset + index, bytes[index]); } if (typeof _filler === "number" && writeLength < _size) { for (let index = writeLength; index < _size; index++) { view.setUint8(offset + index, _filler); } } }; const reactive = ({ view, localOffset, baseOffset, cacheGetter }) => { const getter = () => { const offset = localOffset + baseOffset(); let actualLength = _size; for (let index = 0; index < _size; index++) { const byte = view.getUint8(offset + index); _bytes[index] = byte; if (typeof _filler === "number" && byte === _filler) { actualLength = index; break; } } const actualBytes = _bytes.subarray(0, actualLength); return _coder.decode(actualBytes); }; cacheGetter(getter); return getter(); }; const typeDefinition = { isTypeDefinition: TypeDefinitionSymbol, get name() { return _name ?? `string(${_size})`; }, get size() { return Math.max(_size, 0); }, get align() { return Math.max(_align ?? 1, 1); }, get filler() { return _filler; }, get coder() { return _coder; }, getter, setter, reactive, setName, setSize, setAlign, setFiller, setCoder, freeze, clone }; if (typeof param0 === "number") { setSize(param0); setFiller(filler); setName(name); } else { setName(param0); } return typeDefinition; } exports.BYTE = BYTE; exports.CHAR = CHAR; exports.DOUBLE = DOUBLE; exports.DWORD = DWORD; exports.DefaultStringCoder = DefaultStringCoder; exports.FLOAT = FLOAT; exports.FLOAT_16 = FLOAT_16; exports.FLOAT_32 = FLOAT_32; exports.FLOAT_64 = FLOAT_64; exports.INT = INT; exports.INT_16 = INT_16; exports.INT_32 = INT_32; exports.INT_64 = INT_64; exports.INT_8 = INT_8; exports.LONG = LONG; exports.OperationRawSymbol = OperationRawSymbol; exports.PaddingDefinitionSymbol = PaddingDefinitionSymbol; exports.PropertyDefinitionSymbol = PropertyDefinitionSymbol; exports.SHORT = SHORT; exports.TypeDefinitionSymbol = TypeDefinitionSymbol; exports.UINT = UINT; exports.UINT_16 = UINT_16; exports.UINT_32 = UINT_32; exports.UINT_64 = UINT_64; exports.UINT_8 = UINT_8; exports.ULONG = ULONG; exports.UNKNOWN = UNKNOWN; exports.USHORT = USHORT; exports.WORD = WORD; exports.defineArray = defineArray; exports.definePadding = definePadding; exports.definePrimitive = definePrimitive; exports.defineProperty = defineProperty; exports.defineString = defineString; exports.defineStruct = defineStruct; exports.get = get; exports.getArray = getArray; exports.isPaddingDefinition = isPaddingDefinition; exports.isPropertyDefinition = isPropertyDefinition; exports.isTypeDefinition = isTypeDefinition; exports.reactive = reactive; exports.ref = ref; exports.set = set; exports.setArray = setArray; exports.toRaw = toRaw; exports.toRef = toRef; exports.types = types;