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
JavaScript
;
/** 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;