Skip to content

feat(stdlib): STEP 4-A binary I/O — Bytes constructor + LE getters/setters (Refs #239, closes standards#326)#507

Open
hyperpolymath wants to merge 1 commit into
mainfrom
claude/step4-a-binary-io
Open

feat(stdlib): STEP 4-A binary I/O — Bytes constructor + LE getters/setters (Refs #239, closes standards#326)#507
hyperpolymath wants to merge 1 commit into
mainfrom
claude/step4-a-binary-io

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Adds the raw binary I/O surface that estate ABI-test ports need (raze-tui's 16-byte RazeEvent record, future tree-sitter-k9 / tree-sitter-a2ml grammar fixtures, half of bofig's contract tests).

Companion to the read-only bytesLength / bytesByteAt / bytesAsciiSlice accessors shipped in affinescript#504 (STEP 3 / standards#242). Together the two PRs give AffineScript first-class binary-buffer support.

What lands

stdlib/Deno.affine (+10 externs)

extern lowers to notes
bytes_new(n) -> Bytes new Uint8Array(n) zeroed
bytes_fill(n, byte) -> Bytes (new Uint8Array(n)).fill(byte & 0xFF) all-byte buffer
bytes_set_u8(b, off, v) -> Int … .setUint8(off, v & 0xFF), 0 returns 0
bytes_set_u16_le(b, off, v) -> Int … .setUint16(off, v & 0xFFFF, true), 0 LE
bytes_set_u32_le(b, off, v) -> Int … .setUint32(off, v >>> 0, true), 0 LE
bytes_set_i32_le(b, off, v) -> Int … .setInt32(off, v | 0, true), 0 LE
bytes_get_u8(b, off) -> Int … .getUint8(off)
bytes_get_u16_le(b, off) -> Int … .getUint16(off, true) LE
bytes_get_u32_le(b, off) -> Int … .getUint32(off, true) LE
bytes_get_i32_le(b, off) -> Int … .getInt32(off, true) LE

All multi-byte integer variants are little-endian — the estate's C ABI contracts (raze-tui raze-events.ads, Idris2 Events.idr) are LE-pinned. Setters return Int = 0 so they compose in an expression-statement position; the caller is responsible for the buffer-bounds invariant (an out-of-range offset throws RangeError at the host boundary). Bounds-check via bytesLength from #504.

Tests

tests/codegen-deno/bytes_binary_io.{affine,deno.js,harness.mjs} — round-trips a raze-tui-shaped RazeEvent record (LE i32 + u32 + u8 + u16 × 2) with field-level equality, plus boundary cases:

  • u32 max (0xFFFFFFFF) round-trips
  • i32 -1 preserves sign
  • LE byte order verified byte-by-byte via DataView (0x12345678 writes low byte at offset 4, high byte at offset 7)
  • bytes_fill masks 256 → 0, -1 → 0xFF, 0xFF → 0xFF
  • bytes_new produces a zero-initialised buffer

Verification

step result
dune build bin/main.exe
dune runtest ✅ 353/353
./tools/run_codegen_deno_tests.sh ✅ all harnesses (incl. the new one)

Out of scope

  • Big-endian variants — not needed by any current estate ABI; revisit if/when an external API forces BE.
  • 64-bit getters/setters (*_i64_le) — defer; current STEP 4 candidates max out at 32-bit.

Relation to #504 (STEP 3)

Both this PR and #504 add externs to stdlib/Deno.affine and lowerings to lib/codegen_deno.ml. The two sets are disjoint at the symbol level:

The bytes test fixture uses uniquely-named let _r0 = …, let _r4 = …, etc. instead of the let _ = … form so this PR is independently mergeable from #504; once #504 lands, the fixture could be simplified to use the wildcard pattern.

Refs

🤖 Generated with Claude Code

…tters (Refs #239, closes #326)

Adds the raw binary I/O surface that estate ABI-test ports need
(raze-tui's 16-byte RazeEvent record, future tree-sitter-k9 / tree-
sitter-a2ml grammar fixtures, half of bofig's contract tests).

Stdlib (`stdlib/Deno.affine`):
  + bytes_new(n)              -> new Uint8Array(n)
  + bytes_fill(n, byte)       -> new Uint8Array(n).fill(byte & 0xFF)
  + bytes_set_u8(b, off, v)   -> DataView.setUint8(off, v & 0xFF), 0
  + bytes_set_u16_le(b, o, v) -> DataView.setUint16(o, v & 0xFFFF, true), 0
  + bytes_set_u32_le(b, o, v) -> DataView.setUint32(o, v >>> 0, true), 0
  + bytes_set_i32_le(b, o, v) -> DataView.setInt32(o, v | 0, true), 0
  + bytes_get_u8(b, off)      -> DataView.getUint8(off)
  + bytes_get_u16_le(b, off)  -> DataView.getUint16(off, true)
  + bytes_get_u32_le(b, off)  -> DataView.getUint32(off, true)
  + bytes_get_i32_le(b, off)  -> DataView.getInt32(off, true)

All multi-byte ints are little-endian — the estate's C ABI contracts
(raze-tui `raze-events.ads`, Idris2 `Events.idr`) are LE-pinned.
Setters return `Int = 0` for expression-statement composition; bounds
remain caller's responsibility (out-of-range offsets throw RangeError
at the host boundary). Pair with `bytesLength` from STEP 3 / #504 for
bounds-checks.

Tests:
  + tests/codegen-deno/bytes_binary_io.{affine,deno.js,harness.mjs}
    round-trips a raze-tui-shaped RazeEvent (LE i32 + u32 + u8 +
    u16 × 2) with field-level equality, plus boundary cases
    (u32 max, i32 -1, byte-order byte-by-byte check via DataView,
    bytes_fill masking).
  ✓ All 353 dune-runtest tests pass.
  ✓ All codegen-Deno-ESM harnesses pass.

Out of scope: BE variants (not needed by any estate ABI today);
64-bit getters/setters (defer to `*_i64_le` follow-up if current
STEP 4 candidates exceed 32-bit).

Refs: hyperpolymath/standards#239 (umbrella), hyperpolymath/standards#326

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant