Skip to content

Commit

Permalink
feat: Untagged union (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
MierenManz authored Jul 12, 2024
1 parent f7952bd commit 8a52ae9
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/compound/tagged_union.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type FindDiscriminant<V, D extends number | string> = (variant: V) => D;

type Keys<T> = Exclude<keyof T, symbol>;

/** Union for when the inner type's don't write their own discriminant */
export class TaggedUnion<
T extends Record<string | number, UnsizedType<unknown>>,
V extends ValueOf<{ [K in keyof T]: InnerType<T[K]> }> = ValueOf<
Expand Down
4 changes: 2 additions & 2 deletions src/compound/tagged_union_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ Deno.test({
dt.setBigUint64(0, 0n);

await t.step("Write Packed", () => {
type.write(32, dt);
type.writePacked(32, dt);
assertEquals(
new Uint8Array(ab).subarray(0, 5),
Uint8Array.of(0, 0, 0, 0, 32),
Uint8Array.of(0, 32, 0, 0, 0),
);
});

Expand Down
67 changes: 67 additions & 0 deletions src/compound/union.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { u8 } from "../primitives/mod.ts";
import {
type InnerType,
type Options,
UnsizedType,
type ValueOf,
} from "../types/mod.ts";
import { getBiggestAlignment } from "../util.ts";

type FindDiscriminant<V, D extends number> = (variant: V) => D;

type Keys<T> = Exclude<keyof T, symbol | string>;

/** Union for when the inner type's do write their own discriminant */
export class Union<
T extends Record<number, UnsizedType<unknown>>,
V extends ValueOf<{ [K in keyof T]: InnerType<T[K]> }>,
> extends UnsizedType<V> {
#record: T;
#variantFinder: FindDiscriminant<V, Keys<T>>;
#discriminant = u8;

constructor(
input: T,
variantFinder: FindDiscriminant<V, Keys<T>>,
) {
super(getBiggestAlignment(input));
this.#record = input;
this.#variantFinder = variantFinder;
}

readPacked(dt: DataView, options: Options = { byteOffset: 0 }): V {
const discriminant = this.#discriminant.readPacked(dt, {
byteOffset: options.byteOffset,
});
const codec = this.#record[discriminant];
if (!codec) throw new TypeError("Unknown discriminant");
return codec.readPacked(dt, options) as V;
}

read(dt: DataView, options: Options = { byteOffset: 0 }): V {
const discriminant = this.#discriminant.read(dt, {
byteOffset: options.byteOffset,
});
const codec = this.#record[discriminant];
if (!codec) throw new TypeError("Unknown discriminant");
return codec.readPacked(dt, options) as V;
}

writePacked(
variant: V,
dt: DataView,
options: Options = { byteOffset: 0 },
): void {
const discriminant = this.#variantFinder(variant);
const codec = this.#record[discriminant];
if (!codec) throw new TypeError("Unknown discriminant");
codec.writePacked(variant, dt, options);
}

write(variant: V, dt: DataView, options: Options = { byteOffset: 0 }): void {
const discriminant = this.#variantFinder(variant);
const codec = this.#record[discriminant];
if (!codec) throw new TypeError("Unknown discriminant");
codec.write(variant, dt, options);
}
}
57 changes: 57 additions & 0 deletions src/compound/union_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { u32le, u8 } from "../mod.ts";
import { assertEquals, assertThrows } from "../../test_deps.ts";
import { Union } from "./union.ts";

Deno.test({
name: "Union",
fn: async (t) => {
const ab = new ArrayBuffer(8);
const dt = new DataView(ab);
const type = new Union({
0: u32le,
1: u8,
2: u8,
}, (a) => a === 32 ? 0 : 1);

await t.step("Read", () => {
dt.setUint8(0, 1);
dt.setUint8(1, 11);
dt.setUint8(2, 22);
dt.setUint8(4, 33);
const result = type.read(dt);
assertEquals(result, 1);
});

await t.step("Read Packed", () => {
dt.setUint8(0, 1);
dt.setUint8(1, 11);
dt.setUint8(2, 22);
dt.setUint8(4, 33);
const result = type.readPacked(dt);
assertEquals(result, 1);
});

dt.setBigUint64(0, 0n);

await t.step("Write", () => {
type.write(32, dt);
assertEquals(new Uint32Array(ab), Uint32Array.of(32, 0));
});

dt.setBigUint64(0, 0n);

await t.step("Write Packed", () => {
type.writePacked(32, dt);
assertEquals(
new Uint8Array(ab).subarray(0, 5),
Uint8Array.of(32, 0, 0, 0, 0),
);
});

await t.step("OOB Read", () => {
assertThrows(() => {
type.read(dt, { byteOffset: 9 });
}, RangeError);
});
},
});

0 comments on commit 8a52ae9

Please sign in to comment.