Part of #239, follow-up to #242 (STEP 3) and pre-req for #243 (STEP 4 per-repo ports).
Why this is needed
The STEP 4 triage on #243 (2026-05-31) found that every estate test repo carrying a binary-layout ABI contract — raze-tui (6 files, 1643L), eventual tree-sitter-k9 / tree-sitter-a2ml ports, half of bofig — builds its test fixtures with:
const buf = new ArrayBuffer(16);
const view = new DataView(buf);
view.setInt32(0, kind, true); // LE
view.setUint16(10, mouse_x, true); // LE
view.setUint8(8, modifiers);
return new Uint8Array(buf);
and parses with the symmetric getInt32(0, true) / getUint16(10, true) / getUint8(8).
#242 / affinescript#504 added the read-only accessors that pure-logic ports of the STEP-2 set needed (bytesLength, bytesByteAt, bytesAsciiSlice). They do NOT cover Bytes construction, per-field write at offset, or multi-byte LE read — which is what binary-ABI tests need.
Required surface
All little-endian (the estate C ABIs are LE-pinned per raze-tui's raze-events.ads + Idris2 Events.idr):
| Extern |
Lowers to |
Notes |
bytes_new(n: Int) -> Bytes |
new Uint8Array(n) |
Zeroed buffer |
bytes_fill(n: Int, byte: Int) -> Bytes |
new Uint8Array(n).fill(byte & 0xFF) |
All-byte buffer |
bytes_set_u8(b, offset, v) -> Int |
(new DataView(b.buffer, b.byteOffset, b.byteLength)).setUint8(offset, v & 0xFF), 0 |
Returns 0 |
bytes_set_u16_le(b, offset, v) -> Int |
… .setUint16(offset, v & 0xFFFF, true), 0 |
|
bytes_set_u32_le(b, offset, v) -> Int |
… .setUint32(offset, v >>> 0, true), 0 |
|
bytes_set_i32_le(b, offset, v) -> Int |
… .setInt32(offset, v | 0, true), 0 |
|
bytes_get_u8(b, offset) -> Int |
(new DataView(b.buffer, b.byteOffset, b.byteLength)).getUint8(offset) |
|
bytes_get_u16_le(b, offset) -> Int |
… .getUint16(offset, true) |
|
bytes_get_u32_le(b, offset) -> Int |
… .getUint32(offset, true) |
|
bytes_get_i32_le(b, offset) -> Int |
… .getInt32(offset, true) |
|
Implementation pattern: same as #504 — extern declarations in stdlib/Deno.affine, lowering table entries in lib/codegen_deno.ml's deno_builtins, smoke test under tests/codegen-deno/.
Out of scope for this issue:
- Big-endian variants (the estate doesn't need them today; revisit if/when an external API forces BE).
- 64-bit getters/setters (
getBigInt64 / setBigInt64) — defer to a *_i64_le follow-up if needed; current STEP 4 candidates max out at 32-bit.
Acceptance
- All 10 externs ship in
stdlib/Deno.affine with docstrings.
tests/codegen-deno/bytes_binary_io.{affine,deno.js,harness.mjs} round-trips a 16-byte raze-tui-shaped record (LE i32 + u32 + u8 + u16 × 2 with explicit pad bytes), with build + parse asserting field-level equality.
dune runtest green; tools/run_codegen_deno_tests.sh green.
Refs
Part of #239, follow-up to #242 (STEP 3) and pre-req for #243 (STEP 4 per-repo ports).
Why this is needed
The STEP 4 triage on #243 (2026-05-31) found that every estate test repo carrying a binary-layout ABI contract — raze-tui (6 files, 1643L), eventual
tree-sitter-k9/tree-sitter-a2mlports, half of bofig — builds its test fixtures with:and parses with the symmetric
getInt32(0, true)/getUint16(10, true)/getUint8(8).#242 / affinescript#504 added the read-only accessors that pure-logic ports of the STEP-2 set needed (
bytesLength,bytesByteAt,bytesAsciiSlice). They do NOT cover Bytes construction, per-field write at offset, or multi-byte LE read — which is what binary-ABI tests need.Required surface
All little-endian (the estate C ABIs are LE-pinned per raze-tui's
raze-events.ads+ Idris2Events.idr):bytes_new(n: Int) -> Bytesnew Uint8Array(n)bytes_fill(n: Int, byte: Int) -> Bytesnew Uint8Array(n).fill(byte & 0xFF)bytebufferbytes_set_u8(b, offset, v) -> Int(new DataView(b.buffer, b.byteOffset, b.byteLength)).setUint8(offset, v & 0xFF), 0bytes_set_u16_le(b, offset, v) -> Int… .setUint16(offset, v & 0xFFFF, true), 0bytes_set_u32_le(b, offset, v) -> Int… .setUint32(offset, v >>> 0, true), 0bytes_set_i32_le(b, offset, v) -> Int… .setInt32(offset, v | 0, true), 0bytes_get_u8(b, offset) -> Int(new DataView(b.buffer, b.byteOffset, b.byteLength)).getUint8(offset)bytes_get_u16_le(b, offset) -> Int… .getUint16(offset, true)bytes_get_u32_le(b, offset) -> Int… .getUint32(offset, true)bytes_get_i32_le(b, offset) -> Int… .getInt32(offset, true)Implementation pattern: same as #504 — extern declarations in
stdlib/Deno.affine, lowering table entries inlib/codegen_deno.ml'sdeno_builtins, smoke test undertests/codegen-deno/.Out of scope for this issue:
getBigInt64/setBigInt64) — defer to a*_i64_lefollow-up if needed; current STEP 4 candidates max out at 32-bit.Acceptance
stdlib/Deno.affinewith docstrings.tests/codegen-deno/bytes_binary_io.{affine,deno.js,harness.mjs}round-trips a 16-byte raze-tui-shaped record (LE i32 + u32 + u8 + u16 × 2 with explicit pad bytes), with build + parse asserting field-level equality.dune runtestgreen;tools/run_codegen_deno_tests.shgreen.Refs