Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/js_dsl/mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions examples/js_dsl/mod.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
19 changes: 16 additions & 3 deletions src/Value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

Expand Down
Loading