From 102acc4d6caf863fd90dbccb80d898c6315d2386 Mon Sep 17 00:00:00 2001 From: Skye Date: Thu, 13 Jun 2024 10:50:29 +0200 Subject: [PATCH 1/5] add "untagged" union. --- src/compound/tagged_union.ts | 1 + src/compound/union.ts | 60 ++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/compound/union.ts diff --git a/src/compound/tagged_union.ts b/src/compound/tagged_union.ts index d1f21f7..fa247b2 100644 --- a/src/compound/tagged_union.ts +++ b/src/compound/tagged_union.ts @@ -11,6 +11,7 @@ type FindDiscriminant = (variant: V) => D; type Keys = Exclude; +/** Union for when the inner type's don't write their own discriminant */ export class TaggedUnion< T extends Record>, V extends ValueOf<{ [K in keyof T]: InnerType }> = ValueOf< diff --git a/src/compound/union.ts b/src/compound/union.ts new file mode 100644 index 0000000..26912a5 --- /dev/null +++ b/src/compound/union.ts @@ -0,0 +1,60 @@ +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 = (variant: V) => D; + +type Keys = Exclude; + +/** Union for when the inner type's do write their own discriminant */ +export class Union< + T extends Record>, + V extends ValueOf<{ [K in keyof T]: InnerType }>, +> extends UnsizedType { + + #record: T; + #variantFinder: FindDiscriminant>; + #discriminant = u8; + + constructor( + input: T, + variantFinder: FindDiscriminant>, + ) { + 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) as V; + } + + 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) as V; + } +} From cc1a07e18ebd99b30000c864c39180a69f4d411b Mon Sep 17 00:00:00 2001 From: Skye Date: Tue, 9 Jul 2024 22:52:52 +0200 Subject: [PATCH 2/5] add tests --- src/compound/union_test.ts | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/compound/union_test.ts diff --git a/src/compound/union_test.ts b/src/compound/union_test.ts new file mode 100644 index 0000000..e2a72fc --- /dev/null +++ b/src/compound/union_test.ts @@ -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, 33); + }); + + 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, 11); + }); + + dt.setBigUint64(0, 0n); + + await t.step("Write", () => { + type.write(32, dt); + assertEquals(new Uint32Array(ab), Uint32Array.of(0, 32)); + }); + + dt.setBigUint64(0, 0n); + + await t.step("Write Packed", () => { + type.write(32, dt); + assertEquals( + new Uint8Array(ab).subarray(0, 5), + Uint8Array.of(0, 0, 0, 0, 32), + ); + }); + + await t.step("OOB Read", () => { + assertThrows(() => { + type.read(dt, { byteOffset: 9 }); + }, RangeError); + }); + }, +}); From 3f9d590d93681847bddf4c0ce9989d5165da6d82 Mon Sep 17 00:00:00 2001 From: Skye Date: Thu, 11 Jul 2024 11:52:01 +0200 Subject: [PATCH 3/5] fix broken test --- src/compound/tagged_union_test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compound/tagged_union_test.ts b/src/compound/tagged_union_test.ts index 4a64201..af9b8d3 100644 --- a/src/compound/tagged_union_test.ts +++ b/src/compound/tagged_union_test.ts @@ -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), ); }); From 953c5527b5ce6e5758fdbbb293940788afc043a6 Mon Sep 17 00:00:00 2001 From: Skye Date: Thu, 11 Jul 2024 11:52:19 +0200 Subject: [PATCH 4/5] fix wrong tests --- src/compound/union_test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compound/union_test.ts b/src/compound/union_test.ts index e2a72fc..3505866 100644 --- a/src/compound/union_test.ts +++ b/src/compound/union_test.ts @@ -19,7 +19,7 @@ Deno.test({ dt.setUint8(2, 22); dt.setUint8(4, 33); const result = type.read(dt); - assertEquals(result, 33); + assertEquals(result, 1); }); await t.step("Read Packed", () => { @@ -28,23 +28,23 @@ Deno.test({ dt.setUint8(2, 22); dt.setUint8(4, 33); const result = type.readPacked(dt); - assertEquals(result, 11); + assertEquals(result, 1); }); dt.setBigUint64(0, 0n); await t.step("Write", () => { type.write(32, dt); - assertEquals(new Uint32Array(ab), Uint32Array.of(0, 32)); + assertEquals(new Uint32Array(ab), Uint32Array.of(32, 0)); }); 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(32, 0, 0, 0, 0), ); }); From 297009389ca335f37c341573a056f7016ffd6d87 Mon Sep 17 00:00:00 2001 From: Skye Date: Thu, 11 Jul 2024 11:52:22 +0200 Subject: [PATCH 5/5] fmt --- src/compound/union.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/compound/union.ts b/src/compound/union.ts index 26912a5..ca4dd14 100644 --- a/src/compound/union.ts +++ b/src/compound/union.ts @@ -16,7 +16,6 @@ export class Union< T extends Record>, V extends ValueOf<{ [K in keyof T]: InnerType }>, > extends UnsizedType { - #record: T; #variantFinder: FindDiscriminant>; #discriminant = u8; @@ -31,30 +30,38 @@ export class Union< } readPacked(dt: DataView, options: Options = { byteOffset: 0 }): V { - const discriminant = this.#discriminant.readPacked(dt, { byteOffset: options.byteOffset }); + 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 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 { + 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) as V; + 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) as V; + codec.write(variant, dt, options); } }