Skip to content

Commit

Permalink
struct-types: Add datatype to coerce u8/i32 to/from boolean
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Jun 6, 2023
1 parent 4ad1e43 commit b12629d
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 30 deletions.
26 changes: 19 additions & 7 deletions packages/struct-type/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ export type InitType<TType extends Type> = NonNullable<TType[typeof RelatedTypes
/**
* Represents a primitive type.
*/
export interface PrimitiveType<K extends string = string, T extends number | bigint = number | bigint> {
export interface PrimitiveType<K extends string = string, T extends number | bigint | boolean = number | bigint | boolean> {
/**
* Coerce the provided value into a value of this type.
*/
(value: number | bigint): T;
(value: number | bigint | boolean): T;

/**
* The name of the primitive type.
Expand All @@ -56,11 +56,21 @@ export interface PrimitiveType<K extends string = string, T extends number | big
// #region Related Types
[RelatedTypes]?: {
RuntimeType: T;
InitType: number | bigint;
InitType: number | bigint | boolean;
};
// #endregion
}

/**
* A primitive type representing a 1-byte unsigned boolean value.
*/
export const bool8 = primitives.bool8;

/**
* A primitive type representing a 4-byte signed boolean value.
*/
export const bool32 = primitives.bool32;

/**
* A primitive type representing a 1-byte signed integer.
*
Expand Down Expand Up @@ -163,6 +173,8 @@ export {


export type PrimitiveTypes =
| typeof bool8
| typeof bool32
| typeof int8
| typeof int16
| typeof int32
Expand Down Expand Up @@ -215,7 +227,7 @@ export type StructFieldLayout<TDef extends StructDefinition> = {
/**
* Gets or sets a named field of the struct.
*/
-readonly [K in StructPropertyLayoutKeys<TDef>]: RuntimeType<TDef["fields"][K]>;
-readonly [K in StructFieldLayoutKeys<TDef>]: RuntimeType<TDef["fields"][K]>;
};

export type StructElementLayout<TDef extends StructDefinition> = {
Expand All @@ -225,7 +237,7 @@ export type StructElementLayout<TDef extends StructDefinition> = {
-readonly [I in StructElementLayoutIndices<TDef>]: RuntimeType<TDef["fields"][TDef["order"][I]]>;
};

export type StructPropertyLayoutKeys<TDef extends StructDefinition> = keyof TDef["fields"];
export type StructFieldLayoutKeys<TDef extends StructDefinition> = keyof TDef["fields"];
export type StructElementLayoutIndices<TDef extends StructDefinition> = TDef["order"] extends "unspecified" ? never : numstr<keyof TDef["order"]>;

/**
Expand All @@ -250,12 +262,12 @@ export type Struct<TDef extends StructDefinition = StructDefinition> = {
/**
* Gets the value of a named field of this struct.
*/
get<K extends StructPropertyLayoutKeys<TDef>>(key: K): StructFieldLayout<TDef>[K];
get<K extends StructFieldLayoutKeys<TDef>>(key: K): StructFieldLayout<TDef>[K];

/**
* Sets the value of a named field of this struct.
*/
set<K extends StructPropertyLayoutKeys<TDef>>(key: K, value: StructFieldLayout<TDef>[K]): void;
set<K extends StructFieldLayoutKeys<TDef>>(key: K, value: StructFieldLayout<TDef>[K]): void;

/**
* Gets the value of an ordinal field of this struct.
Expand Down
39 changes: 32 additions & 7 deletions packages/struct-type/src/numbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface ArrayBufferViewConstructor {

/* @internal */
export const enum NumberType {
Bool8 = "Bool8",
Bool32 = "Bool32",
Int8 = "Int8",
Int16 = "Int16",
Int32 = "Int32",
Expand All @@ -42,6 +44,8 @@ export const enum NumberType {
}

type AtomicNumberTypes =
| NumberType.Bool8
| NumberType.Bool32
| NumberType.Int8
| NumberType.Int16
| NumberType.Int32
Expand All @@ -51,6 +55,8 @@ type AtomicNumberTypes =

/* @internal */
export interface NumberTypeToType {
[NumberType.Bool8]: boolean;
[NumberType.Bool32]: boolean;
[NumberType.Int8]: number;
[NumberType.Int16]: number;
[NumberType.Int32]: number;
Expand All @@ -65,6 +71,8 @@ export interface NumberTypeToType {

/* @internal */
export interface NumberTypeToTypedArray {
[NumberType.Bool8]: Uint8Array;
[NumberType.Bool32]: Int32Array;
[NumberType.Int8]: Int8Array;
[NumberType.Int16]: Int16Array;
[NumberType.Int32]: Int32Array;
Expand All @@ -82,12 +90,14 @@ type NumberTypeToCoersion<N extends NumberType> = (value: any) => NumberTypeToTy
/* @internal */
export function sizeOf(nt: NumberType): Alignment {
switch (nt) {
case NumberType.Bool8:
case NumberType.Int8:
case NumberType.Uint8:
return 1;
case NumberType.Int16:
case NumberType.Uint16:
return 2;
case NumberType.Bool32:
case NumberType.Int32:
case NumberType.Uint32:
case NumberType.Float32:
Expand All @@ -100,11 +110,13 @@ export function sizeOf(nt: NumberType): Alignment {
}

/* @internal */
export function putValueInView(view: DataView, nt: NumberType, byteOffset: number, value: number | bigint, isLittleEndian?: boolean) {
export function putValueInView(view: DataView, nt: NumberType, byteOffset: number, value: number | bigint | boolean, isLittleEndian?: boolean) {
if (isSharedArrayBuffer(view.buffer) && isAtomic(nt) && typeof value === "number" && ((view.byteOffset + byteOffset) % sizeOf(nt)) === 0) {
return putValueInBuffer(view.buffer, nt, view.byteOffset + byteOffset, value, isLittleEndian);
}
switch (nt) {
case NumberType.Bool8: return view.setUint8(byteOffset, Number(coerceValue(nt, value)));
case NumberType.Bool32: return view.setInt32(byteOffset, Number(coerceValue(nt, value)), isLittleEndian);
case NumberType.Int8: return view.setInt8(byteOffset, coerceValue(nt, value));
case NumberType.Int16: return view.setInt16(byteOffset, coerceValue(nt, value), isLittleEndian);
case NumberType.Int32: return view.setInt32(byteOffset, coerceValue(nt, value), isLittleEndian);
Expand All @@ -121,13 +133,15 @@ export function putValueInView(view: DataView, nt: NumberType, byteOffset: numbe
/* @internal */
export function getValueFromView<N extends NumberType>(view: DataView, nt: N, byteOffset: number, isLittleEndian?: boolean): NumberTypeToType[N];
/* @internal */
export function getValueFromView(view: DataView, nt: NumberType, byteOffset: number, isLittleEndian?: boolean): number | bigint {
export function getValueFromView(view: DataView, nt: NumberType, byteOffset: number, isLittleEndian?: boolean): number | bigint | boolean {
// attempt an atomic read
if (isSharedArrayBuffer(view.buffer) && isAtomic(nt) && ((view.byteOffset + byteOffset) % sizeOf(nt)) === 0) {
return getValueFromBuffer(view.buffer, nt, view.byteOffset + byteOffset, isLittleEndian);
}

switch (nt) {
case NumberType.Bool8: return Boolean(view.getUint8(byteOffset));
case NumberType.Bool32: return Boolean(view.getInt32(byteOffset, isLittleEndian));
case NumberType.Int8: return view.getInt8(byteOffset);
case NumberType.Int16: return view.getInt16(byteOffset, isLittleEndian);
case NumberType.Int32: return view.getInt32(byteOffset, isLittleEndian);
Expand Down Expand Up @@ -268,7 +282,7 @@ const bigInt64ArrayCache = new WeakGenerativeCache<SharedArrayBuffer, BigInt64Ar
const bigUint64ArrayCache = new WeakGenerativeCache<SharedArrayBuffer, BigUint64Array>();

/* @internal */
export function putValueInBuffer(buffer: ArrayBufferLike, nt: NumberType, byteOffset: number, value: number | bigint, isLittleEndian: boolean = false) {
export function putValueInBuffer(buffer: ArrayBufferLike, nt: NumberType, byteOffset: number, value: number | bigint | boolean, isLittleEndian: boolean = false) {
// attempt an atomic write
if (isSharedArrayBuffer(buffer) && isAtomic(nt) && typeof value === "number") {
const size = sizeOf(nt);
Expand All @@ -278,7 +292,7 @@ export function putValueInBuffer(buffer: ArrayBufferLike, nt: NumberType, byteOf
const arrayIndex = byteOffset / size;
const coercedValue = coerceValue(nt, value);
const correctedValue = size === 1 || isLittleEndian === littleEndian ? coercedValue : swapByteOrder(nt, coercedValue);
Atomics.store(array, arrayIndex, correctedValue);
Atomics.store(array, arrayIndex, typeof correctedValue === "boolean" ? Number(correctedValue) : correctedValue);
return;
}
}
Expand All @@ -288,7 +302,7 @@ export function putValueInBuffer(buffer: ArrayBufferLike, nt: NumberType, byteOf
/* @internal */
export function getValueFromBuffer<N extends NumberType>(buffer: ArrayBufferLike, nt: N, byteOffset: number, isLittleEndian?: boolean): NumberTypeToType[N];
/* @internal */
export function getValueFromBuffer<N extends NumberType>(buffer: ArrayBufferLike, nt: N, byteOffset: number, isLittleEndian: boolean = false): number | bigint {
export function getValueFromBuffer<N extends NumberType>(buffer: ArrayBufferLike, nt: N, byteOffset: number, isLittleEndian: boolean = false): number | bigint | boolean {
// attempt an atomic read
if (isSharedArrayBuffer(buffer) && isAtomic(nt)) {
const size = sizeOf(nt);
Expand All @@ -297,6 +311,7 @@ export function getValueFromBuffer<N extends NumberType>(buffer: ArrayBufferLike
const array = getTypedArray(buffer, nt);
const arrayIndex = byteOffset / size;
const value = Atomics.load(array, arrayIndex);
if (nt === NumberType.Bool8 || nt === NumberType.Bool32) return Boolean(value);
return size === 1 || isLittleEndian === littleEndian ? value : swapByteOrder(nt, value);
}
}
Expand All @@ -306,6 +321,8 @@ export function getValueFromBuffer<N extends NumberType>(buffer: ArrayBufferLike
function getTypedArrayConstructor<N extends NumberType>(nt: N): new (buffer: ArrayBufferLike) => NumberTypeToTypedArray[N];
function getTypedArrayConstructor(nt: NumberType) {
switch (nt) {
case NumberType.Bool8: return Uint8Array;
case NumberType.Bool32: return Int32Array;
case NumberType.Int8: return Int8Array;
case NumberType.Int16: return Int16Array;
case NumberType.Int32: return Int32Array;
Expand All @@ -322,6 +339,8 @@ function getTypedArrayConstructor(nt: NumberType) {
function getTypedArrayCache<N extends NumberType>(nt: N): WeakGenerativeCache<SharedArrayBuffer, NumberTypeToTypedArray[N]>;
function getTypedArrayCache(nt: NumberType) {
switch (nt) {
case NumberType.Bool8: return uint8ArrayCache;
case NumberType.Bool32: return int32ArrayCache;
case NumberType.Int8: return int8ArrayCache;
case NumberType.Int16: return int16ArrayCache;
case NumberType.Int32: return int32ArrayCache;
Expand Down Expand Up @@ -357,6 +376,8 @@ function getDataView(buffer: ArrayBufferLike) {

function isAtomic(nt: NumberType): nt is AtomicNumberTypes {
switch (nt) {
case NumberType.Bool8:
case NumberType.Bool32:
case NumberType.Int8:
case NumberType.Int16:
case NumberType.Int32:
Expand All @@ -378,6 +399,9 @@ function swapByteOrder<N extends NumberType>(nt: N, value: NumberTypeToType[N]):
function getTypeCoersion<N extends NumberType>(nt: N): NumberTypeToCoersion<N>;
function getTypeCoersion(nt: NumberType) {
switch (nt) {
case NumberType.Bool8:
case NumberType.Bool32:
return Boolean;
case NumberType.Int8:
case NumberType.Int16:
case NumberType.Int32:
Expand All @@ -396,11 +420,12 @@ function getTypeCoersion(nt: NumberType) {
const sizeCoersionArrays: { [N in NumberType]?: NumberTypeToTypedArray[N]; } = {};

/* @internal */
export function coerceValue<N extends NumberType>(nt: N, value: number | bigint): NumberTypeToType[N];
export function coerceValue<N extends NumberType>(nt: N, value: number | bigint | boolean): NumberTypeToType[N];
/* @internal */
export function coerceValue<N extends NumberType>(nt: N, value: number | bigint): number | bigint {
export function coerceValue<N extends NumberType>(nt: N, value: number | bigint | boolean): number | bigint | boolean {
const typeCoersion = getTypeCoersion(nt);
const coerced = typeCoersion(value);
if (typeof coerced === "boolean") return coerced;
const sizeCoersionArray = sizeCoersionArrays[nt] || (sizeCoersionArrays[nt] = new (getTypedArrayConstructor(nt))(new ArrayBuffer(sizeOf(nt))));
sizeCoersionArray![0] = coerced;
return sizeCoersionArray![0];
Expand Down
6 changes: 6 additions & 0 deletions packages/struct-type/src/primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ function createPrimitiveType<K extends string, N extends NumberType>(name: K, nt
return typeInfo.runtimeType as PrimitiveType<K, NumberTypeToType[N]>;
}

/* @internal */
export const bool8 = createPrimitiveType("bool8", NumberType.Bool8);

/* @internal */
export const bool32 = createPrimitiveType("bool32", NumberType.Bool32);

/* @internal */
export const int8 = createPrimitiveType("int8", NumberType.Int8);

Expand Down
8 changes: 4 additions & 4 deletions packages/struct-type/src/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
limitations under the License.
*/

import type { StructDefinition, StructElementLayout, StructElementLayoutIndices, StructArrayInit, StructObjectInit, StructFieldLayout, StructPropertyLayoutKeys } from './index.js';
import type { StructDefinition, StructElementLayout, StructElementLayoutIndices, StructArrayInit, StructObjectInit, StructFieldLayout, StructFieldLayoutKeys } from './index.js';
import { StructTypeInfo } from './typeInfo.js';

let _getDataView: (struct: Struct) => DataView;
Expand Down Expand Up @@ -118,16 +118,16 @@ abstract class Struct<TDef extends StructDefinition = any> {
get byteOffset() { return this.#byteOffset; }
get byteLength() { return this.#type.size; }

get<K extends StructPropertyLayoutKeys<TDef>>(key: K): StructFieldLayout<TDef>[K];
get<K extends StructPropertyLayoutKeys<TDef>>(key: K) {
get<K extends StructFieldLayoutKeys<TDef>>(key: K): StructFieldLayout<TDef>[K];
get<K extends StructFieldLayoutKeys<TDef>>(key: K) {
const field = this.#type.fieldsByName.get(key as string | symbol);
if (field) {
return field.readFrom(this, this.#dataView);
}
throw new RangeError();
}

set<K extends StructPropertyLayoutKeys<TDef>>(key: K, value: StructFieldLayout<TDef>[K]) {
set<K extends StructFieldLayoutKeys<TDef>>(key: K, value: StructFieldLayout<TDef>[K]) {
const field = this.#type.fieldsByName.get(key as string | symbol);
if (field) {
field.writeTo(this, this.#dataView, field.coerce(value));
Expand Down
18 changes: 9 additions & 9 deletions packages/struct-type/src/typeInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ export abstract class TypeInfo {
}

abstract isCompatibleWith(other: TypeInfo): boolean;
abstract coerce(value: any): number | bigint | Struct | TypedArray<any, number>;
abstract readFrom(view: DataView, offset: number, isLittleEndian?: boolean): number | bigint | Struct | TypedArray<any, number>;
abstract writeTo(view: DataView, offset: number, value: number | bigint | Struct | TypedArray<any, number>, isLittleEndian?: boolean): void;
abstract coerce(value: any): number | bigint | boolean | Struct | TypedArray<any, number>;
abstract readFrom(view: DataView, offset: number, isLittleEndian?: boolean): number | bigint | boolean | Struct | TypedArray<any, number>;
abstract writeTo(view: DataView, offset: number, value: number | bigint | boolean | Struct | TypedArray<any, number>, isLittleEndian?: boolean): void;
}

/* @internal */
Expand Down Expand Up @@ -117,7 +117,7 @@ export class PrimitiveTypeInfo extends TypeInfo {
const size = sizeOf(numberType);
super(numberType, size, size);
this.#numberType = numberType;
this.runtimeType = Object.defineProperties(function (value: number | bigint) { return coerceValue(numberType, value); } as PrimitiveType, {
this.runtimeType = Object.defineProperties(function (value: number | bigint | boolean) { return coerceValue(numberType, value); } as PrimitiveType, {
name: { value: name },
SIZE: { value: size },
});
Expand All @@ -143,7 +143,7 @@ export class PrimitiveTypeInfo extends TypeInfo {
return getValueFromView(view, this.#numberType, offset, isLittleEndian);
}

writeTo(view: DataView, offset: number, value: number | bigint, isLittleEndian?: boolean) {
writeTo(view: DataView, offset: number, value: number | bigint | boolean, isLittleEndian?: boolean) {
putValueInView(view, this.#numberType, offset, value, isLittleEndian);
}

Expand Down Expand Up @@ -198,7 +198,7 @@ export class StructFieldInfo {
return this.typeInfo.readFrom(view, this.byteOffset, isLittleEndian);
}

writeTo(_owner: StructImpl, view: DataView, value: number | bigint | Struct | TypedArray<any, number>, isLittleEndian?: boolean) {
writeTo(_owner: StructImpl, view: DataView, value: number | bigint | boolean | Struct | TypedArray<any, number>, isLittleEndian?: boolean) {
this.typeInfo.writeTo(view, this.byteOffset, value, isLittleEndian);
}
}
Expand Down Expand Up @@ -362,7 +362,7 @@ export class StructTypeInfo extends TypeInfo {
return new this.runtimeType(view.buffer, view.byteOffset + offset);
}

writeTo(view: DataView, offset: number, value: number | bigint | Struct, _isLittleEndian?: boolean) {
writeTo(view: DataView, offset: number, value: number | bigint | boolean | Struct | TypedArray<any, number>, _isLittleEndian?: boolean) {
if (value instanceof this.runtimeType) {
value.writeTo(view.buffer, view.byteOffset + offset);
}
Expand Down Expand Up @@ -419,15 +419,15 @@ export abstract class ArrayTypeInfo extends TypeInfo {
return this.elementTypeInfo.readFrom(view, index * this.bytesPerElement, isLittleEndian);
}

writeElementTo(view: DataView, index: number, value: number | bigint | Struct | TypedArray<any, number>, isLittleEndian?: boolean): void {
writeElementTo(view: DataView, index: number, value: number | bigint | boolean | Struct | TypedArray<any, number>, isLittleEndian?: boolean): void {
this.elementTypeInfo.writeTo(view, index * this.bytesPerElement, value, isLittleEndian);
}

readFrom(view: DataView, offset: number, _isLittleEndian?: boolean) {
return new this.runtimeType(view.buffer, view.byteOffset + offset);
}

writeTo(view: DataView, offset: number, value: number | bigint | Struct | TypedArray<any, number>, _isLittleEndian?: boolean): void {
writeTo(view: DataView, offset: number, value: number | bigint | boolean | Struct | TypedArray<any, number>, _isLittleEndian?: boolean): void {
if (!(value instanceof this.runtimeType)) throw new TypeError();
value.writeTo(view.buffer, view.byteOffset + offset);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/struct-type/src/win32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
limitations under the License.
*/

import { int8, int16, int32, uint8, uint16, uint32, bigint64, biguint64, float32, float64, ArrayType, StructType } from "./index.js";
import { bool8, bool32, int8, int16, int32, uint8, uint16, uint32, bigint64, biguint64, float32, float64, ArrayType, StructType } from "./index.js";

export {
int8 as __int8,
Expand All @@ -37,8 +37,8 @@ export {
uint16 as char16_t,
uint32 as char32_t,
uint16 as wchar_t,
int32 as BOOL,
uint8 as BOOLEAN,
bool32 as BOOL,
bool8 as BOOLEAN,
uint8 as BYTE,
float32 as FLOAT,
int8 as CHAR,
Expand Down

0 comments on commit b12629d

Please sign in to comment.