Skip to content

Commit b2d2536

Browse files
hyperpolymathclaude
andcommitted
feat(stdlib): STEP 4-A binary I/O — Bytes constructor + LE getters/setters (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>
1 parent 83d82d3 commit b2d2536

5 files changed

Lines changed: 516 additions & 0 deletions

File tree

lib/codegen_deno.ml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,19 @@ let () =
464464
(* Recursive file walk — depth-first, returns every file path under
465465
`root`. Filtering by extension is the caller's responsibility. *)
466466
b "walkRecursive" (fun a -> Printf.sprintf "__as_walkRecursive(%s)" (arg 0 a));
467+
(* Bytes I/O — construction + LE getters/setters (campaign #239 STEP
468+
4-A / standards#326). Setters return 0 so they compose in an
469+
expression-statement position. All multi-byte ints are LE. *)
470+
b "bytes_new" (fun a -> Printf.sprintf "new Uint8Array(%s)" (arg 0 a));
471+
b "bytes_fill" (fun a -> Printf.sprintf "(new Uint8Array(%s)).fill((%s) & 0xFF)" (arg 0 a) (arg 1 a));
472+
b "bytes_set_u8" (fun a -> Printf.sprintf "((new DataView((%s).buffer, (%s).byteOffset, (%s).byteLength)).setUint8(%s, (%s) & 0xFF), 0)" (arg 0 a) (arg 0 a) (arg 0 a) (arg 1 a) (arg 2 a));
473+
b "bytes_set_u16_le" (fun a -> Printf.sprintf "((new DataView((%s).buffer, (%s).byteOffset, (%s).byteLength)).setUint16(%s, (%s) & 0xFFFF, true), 0)" (arg 0 a) (arg 0 a) (arg 0 a) (arg 1 a) (arg 2 a));
474+
b "bytes_set_u32_le" (fun a -> Printf.sprintf "((new DataView((%s).buffer, (%s).byteOffset, (%s).byteLength)).setUint32(%s, (%s) >>> 0, true), 0)" (arg 0 a) (arg 0 a) (arg 0 a) (arg 1 a) (arg 2 a));
475+
b "bytes_set_i32_le" (fun a -> Printf.sprintf "((new DataView((%s).buffer, (%s).byteOffset, (%s).byteLength)).setInt32(%s, (%s) | 0, true), 0)" (arg 0 a) (arg 0 a) (arg 0 a) (arg 1 a) (arg 2 a));
476+
b "bytes_get_u8" (fun a -> Printf.sprintf "(new DataView((%s).buffer, (%s).byteOffset, (%s).byteLength)).getUint8(%s)" (arg 0 a) (arg 0 a) (arg 0 a) (arg 1 a));
477+
b "bytes_get_u16_le" (fun a -> Printf.sprintf "(new DataView((%s).buffer, (%s).byteOffset, (%s).byteLength)).getUint16(%s, true)" (arg 0 a) (arg 0 a) (arg 0 a) (arg 1 a));
478+
b "bytes_get_u32_le" (fun a -> Printf.sprintf "(new DataView((%s).buffer, (%s).byteOffset, (%s).byteLength)).getUint32(%s, true)" (arg 0 a) (arg 0 a) (arg 0 a) (arg 1 a));
479+
b "bytes_get_i32_le" (fun a -> Printf.sprintf "(new DataView((%s).buffer, (%s).byteOffset, (%s).byteLength)).getInt32(%s, true)" (arg 0 a) (arg 0 a) (arg 0 a) (arg 1 a));
467480
(* `new RegExp(pat).test(s)` — minimal regex surface. Invalid `pat`
468481
throws at call time (RegExp constructor error). *)
469482
b "regexMatch" (fun a -> Printf.sprintf "__as_regexMatch(%s, %s)" (arg 0 a) (arg 1 a));

stdlib/Deno.affine

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,49 @@ pub extern fn statSize(path: String) -> Int;
7373
/// filter by extension). Throws on a missing root via `Deno.readDirSync`.
7474
pub extern fn walkRecursive(root: String) -> [String];
7575

76+
// ── Bytes I/O (construction + LE getters/setters) ──────────────────
77+
//
78+
// Construction + per-field read/write at byte offsets. Companion to
79+
// the read-only `bytesLength` / `bytesByteAt` / `bytesAsciiSlice`
80+
// accessors (campaign #239 STEP 3 / standards#242). All multi-byte
81+
// integer variants are little-endian — the estate's C ABI contracts
82+
// (raze-tui `raze-events.ads`, Idris2 `Events.idr`) are LE-pinned.
83+
//
84+
// Setters return `Int = 0` so they compose in expression-statement
85+
// position; the caller is responsible for the buffer-bounds invariant
86+
// (an out-of-range offset throws `RangeError` at the host boundary).
87+
// Bounds-check via `bytesLength` from STEP 3.
88+
89+
/// `new Uint8Array(n)` — zeroed buffer of `n` bytes.
90+
pub extern fn bytes_new(n: Int) -> Bytes;
91+
92+
/// `new Uint8Array(n).fill(byte & 0xFF)` — all-`byte` buffer.
93+
pub extern fn bytes_fill(n: Int, byte: Int) -> Bytes;
94+
95+
/// Write `v & 0xFF` to byte `offset`.
96+
pub extern fn bytes_set_u8(b: Bytes, offset: Int, v: Int) -> Int;
97+
98+
/// Write `v & 0xFFFF` to bytes `[offset, offset+2)` as little-endian u16.
99+
pub extern fn bytes_set_u16_le(b: Bytes, offset: Int, v: Int) -> Int;
100+
101+
/// Write `v >>> 0` to bytes `[offset, offset+4)` as little-endian u32.
102+
pub extern fn bytes_set_u32_le(b: Bytes, offset: Int, v: Int) -> Int;
103+
104+
/// Write `v | 0` to bytes `[offset, offset+4)` as little-endian i32.
105+
pub extern fn bytes_set_i32_le(b: Bytes, offset: Int, v: Int) -> Int;
106+
107+
/// Read byte at `offset` (0..255).
108+
pub extern fn bytes_get_u8(b: Bytes, offset: Int) -> Int;
109+
110+
/// Read bytes `[offset, offset+2)` as little-endian u16 (0..65535).
111+
pub extern fn bytes_get_u16_le(b: Bytes, offset: Int) -> Int;
112+
113+
/// Read bytes `[offset, offset+4)` as little-endian u32 (0..4294967295).
114+
pub extern fn bytes_get_u32_le(b: Bytes, offset: Int) -> Int;
115+
116+
/// Read bytes `[offset, offset+4)` as little-endian i32 (-2147483648..2147483647).
117+
pub extern fn bytes_get_i32_le(b: Bytes, offset: Int) -> Int;
118+
76119
// ── Path ───────────────────────────────────────────────────────────
77120

78121
/// Single-segment join with a `/` separator (idempotent on a trailing
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// campaign #239 STEP 4-A — binary I/O smoke (raze-tui RazeEvent shape).
3+
//
4+
// Round-trips the 16-byte event record that estate ABI tests build and
5+
// parse byte-by-byte:
6+
//
7+
// offset 0 size 4 i32_le kind
8+
// offset 4 size 4 u32_le key_code
9+
// offset 8 size 1 u8 modifiers
10+
// offset 9 size 1 pad
11+
// offset 10 size 2 u16_le mouse_x
12+
// offset 12 size 2 u16_le mouse_y
13+
// offset 14 size 2 pad
14+
// Total: 16 bytes
15+
16+
use Deno::{ Bytes, bytes_new, bytes_fill,
17+
bytes_set_u8, bytes_set_u16_le, bytes_set_u32_le, bytes_set_i32_le,
18+
bytes_get_u8, bytes_get_u16_le, bytes_get_u32_le, bytes_get_i32_le };
19+
20+
pub fn build_event(kind: Int, key_code: Int, modifiers: Int, mouse_x: Int, mouse_y: Int) -> Bytes {
21+
// Uniquely-named `let` discards keep the codegen pre-#504-compatible
22+
// (the `PatWildcard` fix from #504 also works, but isn't required).
23+
let b = bytes_new(16);
24+
let _r0 = bytes_set_i32_le(b, 0, kind);
25+
let _r4 = bytes_set_u32_le(b, 4, key_code);
26+
let _r8 = bytes_set_u8(b, 8, modifiers);
27+
let _ra = bytes_set_u16_le(b, 10, mouse_x);
28+
let _rc = bytes_set_u16_le(b, 12, mouse_y);
29+
b
30+
}
31+
32+
pub fn read_kind(b: Bytes) -> Int { bytes_get_i32_le(b, 0) }
33+
pub fn read_key_code(b: Bytes) -> Int { bytes_get_u32_le(b, 4) }
34+
pub fn read_modifiers(b: Bytes) -> Int { bytes_get_u8(b, 8) }
35+
pub fn read_mouse_x(b: Bytes) -> Int { bytes_get_u16_le(b, 10) }
36+
pub fn read_mouse_y(b: Bytes) -> Int { bytes_get_u16_le(b, 12) }
37+
38+
pub fn fill_byte(n: Int, byte: Int) -> Bytes { bytes_fill(n, byte) }
39+
40+
pub fn fill_first(n: Int, byte: Int) -> Int {
41+
let b = bytes_fill(n, byte);
42+
bytes_get_u8(b, 0)
43+
}

0 commit comments

Comments
 (0)