diff --git a/examples/js_dsl/mod.test.ts b/examples/js_dsl/mod.test.ts index 6d290f9..9df6556 100644 --- a/examples/js_dsl/mod.test.ts +++ b/examples/js_dsl/mod.test.ts @@ -94,6 +94,24 @@ describe("primitive types", () => { expect(value).toEqual(2 ** 63); }); + describe("getValueBigintWords", () => { + it("reads words with null sign_bit (unsigned only)", () => { + expect(mod.bigIntFirstWord(0n)).toEqual(0); + expect(mod.bigIntFirstWord(1n)).toEqual(1); + expect(mod.bigIntFirstWord(0xdeadbeefn)).toEqual(0xdeadbeef); + }); + + it("reads correct sign (non-null sign_bit path)", () => { + expect(mod.bigIntSign(0n)).toEqual(0); + expect(mod.bigIntSign(1n)).toEqual(0); + expect(mod.bigIntSign(0xffffffffffffffffn)).toEqual(0); + + expect(mod.bigIntSign(-1n)).toEqual(1); + expect(mod.bigIntSign(-0xffffffffffffffffn)).toEqual(1); + }); + + }); + it("tomorrow adds one day", () => { const now = new Date("2025-01-01T00:00:00Z"); const result = mod.tomorrow(now); diff --git a/examples/js_dsl/mod.zig b/examples/js_dsl/mod.zig index 0cf16f3..1eef6cd 100644 --- a/examples/js_dsl/mod.zig +++ b/examples/js_dsl/mod.zig @@ -100,6 +100,24 @@ pub fn doubleBigInt(n: BigInt) !BigInt { return BigInt.from(val * 2); } +/// Read a BigInt's first u64 word via `getValueBigintWords` passing `null` for `sign_bit`. +/// +/// Throws if word_count > 1. +pub fn bigIntFirstWord(n: BigInt) !Number { + var words: [1]u64 = .{0}; + const got = try n.toValue().getValueBigintWords(null, &words); + if (got.len > 1) return error.BigIntTooLarge; + return Number.from(words[0]); +} + +/// Read a BigInt's sign as 0/1 via `getValueBigintWords` passing a non-null `sign_bit`. +pub fn bigIntSign(n: BigInt) !Number { + var sign: u1 = 0; + var words: [1]u64 = .{0}; + _ = try n.toValue().getValueBigintWords(&sign, &words); + return Number.from(@as(u32, sign)); +} + /// Add one day (86400000ms) to a Date. pub fn tomorrow(d: Date) Date { const ts = d.assertTimestamp(); diff --git a/src/Value.zig b/src/Value.zig index 534b21b..bd3b206 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -216,12 +216,25 @@ pub fn getValueBigintUint64(self: Value, lossless: ?*bool) NapiError!u64 { return bigint; } -/// https://nodejs.org/api/n-api.html#napi_get_value_bigint_words -pub fn getValueBigintWords(self: Value, sign_bit: *u1, words: []u64) NapiError![]u64 { +/// Reads a JS `BigInt` into `words`. +/// +/// Pass `null` for `sign_bit` to skip getting the sign result. +/// +/// In Ethereum's context, this is useful for big integers defined in the spec to be +/// unsigned. +/// +/// NOTE: napi's C entry takes `int*` (4-byte aligned). Casting a u1 to int* is UB, since that +/// is 4-bytes aligned. We use a local `c_int` for the napi call and narrow back to `u1` for the caller. +/// +/// Source: https://nodejs.org/api/n-api.html#napi_get_value_bigint_words +pub fn getValueBigintWords(self: Value, sign_bit: ?*u1, words: []u64) NapiError![]u64 { var word_count: usize = words.len; + var raw_sign: c_int = 0; try status.check( - c.napi_get_value_bigint_words(self.env, self.value, @alignCast(@ptrCast(sign_bit)), &word_count, @ptrCast(words)), + c.napi_get_value_bigint_words(self.env, self.value, &raw_sign, &word_count, words.ptr), ); + // napi guarantees raw_sign ∈ {0, 1} + if (sign_bit) |s| s.* = @intCast(raw_sign); return words[0..word_count]; }