From 3a08f32dc2f5a167e086db9e2756b5911e51e4c6 Mon Sep 17 00:00:00 2001 From: tachytelicdetonation Date: Mon, 21 Sep 2026 02:57:07 -0500 Subject: [PATCH] refactor(io): share file read and write implementations --- AGENTS.md | 2 +- bend2/base.bend | 12 +++--- bend2/effs/file_read.c | 70 ++++++++++++++++++++++++++++++++-- bend2/effs/file_read.js | 29 +++++++++++--- bend2/effs/file_read_at.c | 36 ----------------- bend2/effs/file_read_at.js | 18 --------- bend2/effs/file_read_bytes.c | 35 ----------------- bend2/effs/file_read_bytes.js | 18 --------- bend2/effs/file_write.c | 36 +++++++++++++++++ bend2/effs/file_write.js | 18 ++++++++- bend2/effs/file_write_bytes.c | 46 ---------------------- bend2/effs/file_write_bytes.js | 24 ------------ 12 files changed, 148 insertions(+), 196 deletions(-) delete mode 100644 bend2/effs/file_read_at.c delete mode 100644 bend2/effs/file_read_at.js delete mode 100644 bend2/effs/file_read_bytes.c delete mode 100644 bend2/effs/file_read_bytes.js delete mode 100644 bend2/effs/file_write_bytes.c delete mode 100644 bend2/effs/file_write_bytes.js diff --git a/AGENTS.md b/AGENTS.md index 49b4b42a6..40ef1f08b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,7 +13,7 @@ lines its run must print, and the gates run on the mini cluster. bend2/main.ts the CLI; imported, the .bend loader for bun and node bend2/base.bend the base library bend2/bend.lean the core, mechanized in Lean - bend2/effs/ one file per IO effect, per backend + bend2/effs/ IO effect sources per backend; related effects may share bend2/pack/ package.json, tsconfig.json, bun.lock bend2/docs/ the papers' Typst sources, the film, gen_pins.ts (the record pins on this Mac), gen_charts.ts (the landing diff --git a/bend2/base.bend b/bend2/base.bend index 35475bf56..edae4b3cf 100644 --- a/bend2/base.bend +++ b/bend2/base.bend @@ -266,13 +266,13 @@ def File.read(file: File, max: U32) -> def File.read_bytes(file: File, max: U32) -> IO(File & Result<&1, &1, U32 & String, List<&2, U32>>): - import "./effs/file_read_bytes.c" - import "./effs/file_read_bytes.js" + import "./effs/file_read.c" + import "./effs/file_read.js" def File.read_at(file: File, offset: U32, max: U32) -> IO(File & Result<&1, &1, U32 & String, List<&2, U32>>): - import "./effs/file_read_at.c" - import "./effs/file_read_at.js" + import "./effs/file_read.c" + import "./effs/file_read.js" def File.size(file: File) -> IO(File & Result<&1, &1, U32 & String, U32>): @@ -286,8 +286,8 @@ def File.write(file: File, data: String) -> def File.write_bytes(file: File, data: List<&2, U32>) -> IO(File & Result<&1, &1, U32 & String, Unit>): - import "./effs/file_write_bytes.c" - import "./effs/file_write_bytes.js" + import "./effs/file_write.c" + import "./effs/file_write.js" def File.close(file: File) -> IO(Unit): import "./effs/file_close.c" diff --git a/bend2/effs/file_read.c b/bend2/effs/file_read.c index 004227889..38e0ff3cb 100644 --- a/bend2/effs/file_read.c +++ b/bend2/effs/file_read.c @@ -6,6 +6,16 @@ static void file_read_call(IoWork* w) { w->size = io_sys_end(w, read(fd, w->data, w->word)); } +static Term file_read_start(Term file, U32 max, IoWork* w, + IoCall call, IoPack pack) { + w->hand = (intptr_t)io_hand_v(file); + w->word = max < INT32_MAX ? max : INT32_MAX; + w->data = io_mem(malloc(w->word + 1)); + return io_work(w, call, pack); +} + +#ifdef CID_FILE_READ + static Term file_read_pack(Env e, IoWork* w) { Term r = w->code ? io_fail(e, w->code, NULL) : io_done(e, io_str(e, w->data, w->size)); @@ -14,12 +24,64 @@ static Term file_read_pack(Env e, IoWork* w) { } Term file_read_run(Env e, Term* f, IoWork* w) { - w->hand = (intptr_t)io_hand_v(f[0]); - w->word = f[1] < INT32_MAX ? f[1] : INT32_MAX; - w->data = io_mem(malloc(w->word + 1)); - return io_work(w, file_read_call, file_read_pack); + return file_read_start(f[0], f[1], w, file_read_call, file_read_pack); } static void __attribute__((constructor)) file_read_use(void) { io_eff(CID_FILE_READ, file_read_run, 0); } + +#endif + +#if defined(CID_FILE_READ_BYTES) || defined(CID_FILE_READ_AT) + +// The bytes as they are (0..255), one List cell each; a text reader +// would decode them as UTF-8. +static Term file_read_bytes_pack(Env e, IoWork* w) { + Term r; + if (w->code) { + r = io_fail(e, w->code, NULL); + } else { + Term xs = term_pak(CID_NIL, 0); + for (u64 i = w->size; i > 0; i -= 1) { + xs = io_node(e, CID_CON, ((uint8_t*)w->data)[i - 1], xs); + } + r = io_done(e, xs); + } + free(w->data); + return io_tup(e, io_hand(w->hand), r); +} + +#endif + +#ifdef CID_FILE_READ_BYTES + +Term file_read_bytes_run(Env e, Term* f, IoWork* w) { + return file_read_start(f[0], f[1], w, file_read_call, file_read_bytes_pack); +} + +static void __attribute__((constructor)) file_read_bytes_use(void) { + io_eff(CID_FILE_READ_BYTES, file_read_bytes_run, 0); +} + +#endif + +#ifdef CID_FILE_READ_AT + +// The bytes at an offset, as file_read_bytes gives them; the position of +// the file does not move. +static void file_read_at_call(IoWork* w) { + int fd = (int)w->hand; + w->size = io_sys_end(w, pread(fd, w->data, w->word, (off_t)w->made)); +} + +Term file_read_at_run(Env e, Term* f, IoWork* w) { + w->made = (intptr_t)f[1]; + return file_read_start(f[0], f[2], w, file_read_at_call, file_read_bytes_pack); +} + +static void __attribute__((constructor)) file_read_at_use(void) { + io_eff(CID_FILE_READ_AT, file_read_at_run, 0); +} + +#endif diff --git a/bend2/effs/file_read.js b/bend2/effs/file_read.js index d538c3dd7..f386c8537 100644 --- a/bend2/effs/file_read.js +++ b/bend2/effs/file_read.js @@ -1,14 +1,31 @@ // File // ==== -function file_read(file, max) { +function file_read_with(file, max, offset, pack) { const sys = io_sys(); - const fd = file; const len = Math.min(max, 2147483647); const b = new Uint8Array(Math.max(len, 1)); - const n = Number(sys.read(fd, sys.ptr(b), len)); - if (n < 0) { - return io_tup(file, io_fail(sys.errno())); + const n = Number(offset === null ? sys.read(file, sys.ptr(b), len) + : sys.pread(file, sys.ptr(b), len, BigInt(offset))); + return io_tup(file, n < 0 ? io_fail(sys.errno()) : io_done(pack(b, n))); +} + +function file_read_list(b, n) { + let xs = { $: "Nil" }; + for (let i = n; i > 0; i -= 1) { + xs = { $: "Con", head: b[i - 1], tail: xs }; } - return io_tup(file, io_done(io_text(b, n))); + return xs; +} + +function file_read(file, max) { + return file_read_with(file, max, null, io_text); +} + +function file_read_bytes(file, max) { + return file_read_with(file, max, null, file_read_list); +} + +function file_read_at(file, offset, max) { + return file_read_with(file, max, offset, file_read_list); } diff --git a/bend2/effs/file_read_at.c b/bend2/effs/file_read_at.c deleted file mode 100644 index 94cf2d904..000000000 --- a/bend2/effs/file_read_at.c +++ /dev/null @@ -1,36 +0,0 @@ -// File -// ==== - -// The bytes at an offset, as file_read_bytes gives them; the position of -// the file does not move. -static void file_read_at_call(IoWork* w) { - int fd = (int)w->hand; - w->size = io_sys_end(w, pread(fd, w->data, w->word, (off_t)w->made)); -} - -static Term file_read_at_pack(Env e, IoWork* w) { - Term r; - if (w->code) { - r = io_fail(e, w->code, NULL); - } else { - Term xs = term_pak(CID_NIL, 0); - for (u64 i = w->size; i > 0; i -= 1) { - xs = io_node(e, CID_CON, ((uint8_t*)w->data)[i - 1], xs); - } - r = io_done(e, xs); - } - free(w->data); - return io_tup(e, io_hand(w->hand), r); -} - -Term file_read_at_run(Env e, Term* f, IoWork* w) { - w->hand = (intptr_t)io_hand_v(f[0]); - w->made = (intptr_t)f[1]; - w->word = f[2] < INT32_MAX ? f[2] : INT32_MAX; - w->data = io_mem(malloc(w->word + 1)); - return io_work(w, file_read_at_call, file_read_at_pack); -} - -static void __attribute__((constructor)) file_read_at_use(void) { - io_eff(CID_FILE_READ_AT, file_read_at_run, 0); -} diff --git a/bend2/effs/file_read_at.js b/bend2/effs/file_read_at.js deleted file mode 100644 index 5f388b9ac..000000000 --- a/bend2/effs/file_read_at.js +++ /dev/null @@ -1,18 +0,0 @@ -// File -// ==== - -function file_read_at(file, offset, max) { - const sys = io_sys(); - const fd = file; - const len = Math.min(max, 2147483647); - const b = new Uint8Array(Math.max(len, 1)); - const n = Number(sys.pread(fd, sys.ptr(b), len, BigInt(offset))); - if (n < 0) { - return io_tup(file, io_fail(sys.errno())); - } - let xs = { $: "Nil" }; - for (let i = n; i > 0; i -= 1) { - xs = { $: "Con", head: b[i - 1], tail: xs }; - } - return io_tup(file, io_done(xs)); -} diff --git a/bend2/effs/file_read_bytes.c b/bend2/effs/file_read_bytes.c deleted file mode 100644 index a4d3d2c17..000000000 --- a/bend2/effs/file_read_bytes.c +++ /dev/null @@ -1,35 +0,0 @@ -// File -// ==== - -// The bytes as they are (0..255), one List cell each; a text reader -// would decode them as UTF-8. -static void file_read_bytes_call(IoWork* w) { - int fd = (int)w->hand; - w->size = io_sys_end(w, read(fd, w->data, w->word)); -} - -static Term file_read_bytes_pack(Env e, IoWork* w) { - Term r; - if (w->code) { - r = io_fail(e, w->code, NULL); - } else { - Term xs = term_pak(CID_NIL, 0); - for (u64 i = w->size; i > 0; i -= 1) { - xs = io_node(e, CID_CON, ((uint8_t*)w->data)[i - 1], xs); - } - r = io_done(e, xs); - } - free(w->data); - return io_tup(e, io_hand(w->hand), r); -} - -Term file_read_bytes_run(Env e, Term* f, IoWork* w) { - w->hand = (intptr_t)io_hand_v(f[0]); - w->word = f[1] < INT32_MAX ? f[1] : INT32_MAX; - w->data = io_mem(malloc(w->word + 1)); - return io_work(w, file_read_bytes_call, file_read_bytes_pack); -} - -static void __attribute__((constructor)) file_read_bytes_use(void) { - io_eff(CID_FILE_READ_BYTES, file_read_bytes_run, 0); -} diff --git a/bend2/effs/file_read_bytes.js b/bend2/effs/file_read_bytes.js deleted file mode 100644 index 55e8c8e5b..000000000 --- a/bend2/effs/file_read_bytes.js +++ /dev/null @@ -1,18 +0,0 @@ -// File -// ==== - -function file_read_bytes(file, max) { - const sys = io_sys(); - const fd = file; - const len = Math.min(max, 2147483647); - const b = new Uint8Array(Math.max(len, 1)); - const n = Number(sys.read(fd, sys.ptr(b), len)); - if (n < 0) { - return io_tup(file, io_fail(sys.errno())); - } - let xs = { $: "Nil" }; - for (let i = n; i > 0; i -= 1) { - xs = { $: "Con", head: b[i - 1], tail: xs }; - } - return io_tup(file, io_done(xs)); -} diff --git a/bend2/effs/file_write.c b/bend2/effs/file_write.c index a81ad9d80..e825fac50 100644 --- a/bend2/effs/file_write.c +++ b/bend2/effs/file_write.c @@ -17,6 +17,8 @@ static Term file_write_pack(Env e, IoWork* w) { return io_tup(e, io_hand(w->hand), r); } +#ifdef CID_FILE_WRITE + Term file_write_run(Env e, Term* f, IoWork* w) { w->hand = (intptr_t)io_hand_v(f[0]); w->data = io_cstr(e, f[1], &w->size); @@ -26,3 +28,37 @@ Term file_write_run(Env e, Term* f, IoWork* w) { static void __attribute__((constructor)) file_write_use(void) { io_eff(CID_FILE_WRITE, file_write_run, 0); } + +#endif + +#ifdef CID_FILE_WRITE_BYTES + +// The bytes as they are (0..255), one List cell each; a value past 255 +// fails with EINVAL before any byte is written. +Term file_write_bytes_run(Env e, Term* f, IoWork* w) { + u64 cap = 64; + Term xs = f[1]; + w->hand = (intptr_t)io_hand_v(f[0]); + w->code = 0; + w->size = 0; + w->data = io_mem(malloc(cap)); + while (term_aux(xs) == CID_CON) { + Term fb[2]; + spare_free(e, cls_fit(2), ctr_take(e, xs, 2, fb)); + if (w->size == cap) { + cap *= 2; + w->data = io_mem(realloc(w->data, cap)); + } + w->code = fb[0] > 255 ? EINVAL : w->code; + w->data[w->size++] = (char)fb[0]; + xs = fb[1]; + } + return w->code ? file_write_pack(e, w) + : io_work(w, file_write_call, file_write_pack); +} + +static void __attribute__((constructor)) file_write_bytes_use(void) { + io_eff(CID_FILE_WRITE_BYTES, file_write_bytes_run, 0); +} + +#endif diff --git a/bend2/effs/file_write.js b/bend2/effs/file_write.js index 28f5f32d4..6ff24fd86 100644 --- a/bend2/effs/file_write.js +++ b/bend2/effs/file_write.js @@ -1,10 +1,9 @@ // File // ==== -function file_write(file, data) { +function file_write_buffer(file, b) { const fs = require("fs"); const fd = file; - const b = io_bytes(data); let at = 0; try { while (at < b.length) { @@ -15,3 +14,18 @@ function file_write(file, data) { return io_tup(file, io_fail(Math.abs(e.errno ?? 5))); } } + +function file_write(file, data) { + return file_write_buffer(file, io_bytes(data)); +} + +function file_write_bytes(file, data) { + const bytes = []; + for (let xs = data; xs.$ === "Con"; xs = xs.tail) { + bytes.push(xs.head); + } + if (bytes.some((x) => x > 255)) { + return io_tup(file, io_fail(22)); + } + return file_write_buffer(file, Uint8Array.from(bytes)); +} diff --git a/bend2/effs/file_write_bytes.c b/bend2/effs/file_write_bytes.c deleted file mode 100644 index 21008bdff..000000000 --- a/bend2/effs/file_write_bytes.c +++ /dev/null @@ -1,46 +0,0 @@ -// File -// ==== - -// The bytes as they are (0..255), one List cell each; a value past 255 -// fails with EINVAL before any byte is written. -static void file_write_bytes_call(IoWork* w) { - int fd = (int)w->hand; - ssize_t n = 0; - for (uint64_t at = 0; n >= 0 && at < w->size; at += (uint64_t)n) { - n = write(fd, w->data + at, w->size - at); - } - io_sys_end(w, n); -} - -static Term file_write_bytes_pack(Env e, IoWork* w) { - Term r = w->code != 0 ? io_fail(e, w->code, NULL) - : io_done(e, term_pak(CID_UNIT, 0)); - free(w->data); - return io_tup(e, io_hand(w->hand), r); -} - -Term file_write_bytes_run(Env e, Term* f, IoWork* w) { - u64 cap = 64; - Term xs = f[1]; - w->hand = (intptr_t)io_hand_v(f[0]); - w->code = 0; - w->size = 0; - w->data = io_mem(malloc(cap)); - while (term_aux(xs) == CID_CON) { - Term fb[2]; - spare_free(e, cls_fit(2), ctr_take(e, xs, 2, fb)); - if (w->size == cap) { - cap *= 2; - w->data = io_mem(realloc(w->data, cap)); - } - w->code = fb[0] > 255 ? EINVAL : w->code; - w->data[w->size++] = (char)fb[0]; - xs = fb[1]; - } - return w->code ? file_write_bytes_pack(e, w) - : io_work(w, file_write_bytes_call, file_write_bytes_pack); -} - -static void __attribute__((constructor)) file_write_bytes_use(void) { - io_eff(CID_FILE_WRITE_BYTES, file_write_bytes_run, 0); -} diff --git a/bend2/effs/file_write_bytes.js b/bend2/effs/file_write_bytes.js deleted file mode 100644 index 5edc065e6..000000000 --- a/bend2/effs/file_write_bytes.js +++ /dev/null @@ -1,24 +0,0 @@ -// File -// ==== - -function file_write_bytes(file, data) { - const fs = require("fs"); - const fd = file; - const bytes = []; - for (let xs = data; xs.$ === "Con"; xs = xs.tail) { - bytes.push(xs.head); - } - if (bytes.some((x) => x > 255)) { - return io_tup(file, io_fail(22)); - } - const b = Uint8Array.from(bytes); - let at = 0; - try { - while (at < b.length) { - at += fs.writeSync(fd, b, at, b.length - at, null); - } - return io_tup(file, io_done({ $: "Unit" })); - } catch (e) { - return io_tup(file, io_fail(Math.abs(e.errno ?? 5))); - } -}