From 38e2b953d0c4a3b2a11c57320004a8dd811b32c4 Mon Sep 17 00:00:00 2001 From: Ethan Woodbury <42052950+ewoodbury@users.noreply.github.com> Date: Mon, 21 Sep 2026 09:27:21 -0700 Subject: [PATCH 1/3] The window effects share BendWin through // @src Window.open, frame, set_title and close each carried the X11 BendWin struct so any one of them compiled alone. The generated program then pasted that block four times. eff_src already skipped a path it had seen; a `// @src name.c` line now loads a sibling through that same set, and the four effects share window_host.c. --- bend2/comp.ts | 4 +++- bend2/effs/window_close.c | 19 ++----------------- bend2/effs/window_frame.c | 19 ++----------------- bend2/effs/window_host.c | 19 +++++++++++++++++++ bend2/effs/window_open.c | 23 ++--------------------- bend2/effs/window_set_title.c | 19 ++----------------- 6 files changed, 30 insertions(+), 73 deletions(-) create mode 100644 bend2/effs/window_host.c diff --git a/bend2/comp.ts b/bend2/comp.ts index f2c29dad0..e298a480c 100644 --- a/bend2/comp.ts +++ b/bend2/comp.ts @@ -1235,7 +1235,9 @@ function eff_src(path: string, seen: Set): string { return ""; } seen.add(path); - return fs.readFileSync(path, "utf8"); + const dir = path.slice(0, path.lastIndexOf("/")); + return fs.readFileSync(path, "utf8").replace(/^\/\/ @src ([a-z0-9_]+\.c)$/gm, + (_: string, name: string) => eff_src(dir + "/" + name, seen)); } // Io diff --git a/bend2/effs/window_close.c b/bend2/effs/window_close.c index affde2de3..e74bb31c9 100644 --- a/bend2/effs/window_close.c +++ b/bend2/effs/window_close.c @@ -1,6 +1,8 @@ // Window // ====== +// @src window_host.c + #ifdef __OBJC__ #import @@ -12,23 +14,6 @@ static void window_close(intptr_t at) { #elif defined(__linux__) -#ifndef BendWin -#define BendWin BendWin -#include -#include -#include - -typedef struct { - Display* dpy; - Window win; - Atom del; - XImage* img; - u32 n; - u32 cap; - u32* evs; -} BendWin; -#endif - static void window_close(intptr_t at) { BendWin* win = (BendWin*)at; XDestroyImage(win->img); diff --git a/bend2/effs/window_frame.c b/bend2/effs/window_frame.c index f35c0bf1e..a0d2cdf6b 100644 --- a/bend2/effs/window_frame.c +++ b/bend2/effs/window_frame.c @@ -1,6 +1,8 @@ // Window // ====== +// @src window_host.c + // An event is five words: kind (0 key, 1 mouse, 2 move, 3 close) and // its fields; a frame answers the events pumped since the last one. #if defined(__OBJC__) || defined(__linux__) @@ -183,23 +185,6 @@ static Term window_frame(Env e, intptr_t at, Term image) { #elif defined(__linux__) -#ifndef BendWin -#define BendWin BendWin -#include -#include -#include - -typedef struct { - Display* dpy; - Window win; - Atom del; - XImage* img; - u32 n; - u32 cap; - u32* evs; -} BendWin; -#endif - // The Mac's key codes: a key's character in lower case, the function // keys' private-use characters (the arrows at 63232), a modifier's // 65536 + its key code. diff --git a/bend2/effs/window_host.c b/bend2/effs/window_host.c new file mode 100644 index 000000000..35c1440f4 --- /dev/null +++ b/bend2/effs/window_host.c @@ -0,0 +1,19 @@ +// Pulled by Window.open/frame/set_title/close (`// @src window_host.c`). +// The X11 window: its own connection (so its queue holds only its +// events), the frame's image and the events pumped since the last +// frame, five words each (kind, a, b, c, d) as on the Mac. +#if defined(__linux__) +#include +#include +#include + +typedef struct { + Display* dpy; + Window win; + Atom del; + XImage* img; + u32 n; + u32 cap; + u32* evs; +} BendWin; +#endif diff --git a/bend2/effs/window_open.c b/bend2/effs/window_open.c index 5ac73521e..63f33ddda 100644 --- a/bend2/effs/window_open.c +++ b/bend2/effs/window_open.c @@ -1,6 +1,8 @@ // Window // ====== +// @src window_host.c + #ifdef __OBJC__ #import @@ -178,27 +180,6 @@ static u32 window_make(const char* title, u32 w, u32 h, intptr_t* out, #elif defined(__linux__) -// The X11 window: its own connection (so its queue holds only its -// events), the frame's image and the events pumped since the last -// frame, five words each (kind, a, b, c, d) as on the Mac. The same -// block sits in window_frame.c and window_close.c under this guard. -#ifndef BendWin -#define BendWin BendWin -#include -#include -#include - -typedef struct { - Display* dpy; - Window win; - Atom del; - XImage* img; - u32 n; - u32 cap; - u32* evs; -} BendWin; -#endif - static u32 window_make(const char* title, u32 w, u32 h, intptr_t* out, const char** why) { if (w < 1 || h < 1 || w > 16384 || h > 16384) { diff --git a/bend2/effs/window_set_title.c b/bend2/effs/window_set_title.c index 91f3c7e3b..698872e83 100644 --- a/bend2/effs/window_set_title.c +++ b/bend2/effs/window_set_title.c @@ -1,6 +1,8 @@ // Window // ====== +// @src window_host.c + #ifdef __OBJC__ #import @@ -13,23 +15,6 @@ static void window_set_title(intptr_t at, const char* text, u64 n) { #elif defined(__linux__) -#ifndef BendWin -#define BendWin BendWin -#include -#include -#include - -typedef struct { - Display* dpy; - Window win; - Atom del; - XImage* img; - u32 n; - u32 cap; - u32* evs; -} BendWin; -#endif - static void window_set_title(intptr_t at, const char* text, u64 n) { BendWin* win = (BendWin*)at; XStoreName(win->dpy, win->win, text); From a122ccd10e8fe326281b9db3b40b7ab8d0106242 Mon Sep 17 00:00:00 2001 From: Ethan Woodbury <42052950+ewoodbury@users.noreply.github.com> Date: Tue, 22 Sep 2026 01:33:04 -0700 Subject: [PATCH 2/3] The CLI keeps // @src helpers with the files it reads eff_src inlines a sibling .c while emitting, but the import list the CLI walks does not name it. --publish then shipped a package that could not rebuild, and -o helper.c exited 0 and replaced the helper with generated C. src_deps follows that same line, transitively. pkg_files packs those paths; cli_file refuses to overwrite them. Base stays out of a package, as before. --- bend2/main.ts | 58 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/bend2/main.ts b/bend2/main.ts index b9f7c3e5d..c8aa3de8c 100755 --- a/bend2/main.ts +++ b/bend2/main.ts @@ -241,8 +241,12 @@ async function cli_file(args: string[]): Promise { process.exitCode = book_run(book, n0, argv); return; } - const ins = new Set([...seen.keys(), ...Object.values(book.tlds).flatMap((t) => - t.$ === "Def" && t.i !== undefined ? t.i.map(path_real) : [])]); + // ins is every file this run reads. A // @src helper is read while + // emitting, the same as the .c that names it, so -o must not replace it. + const foreign = Object.values(book.tlds).flatMap((t) => + t.$ === "Def" && t.i !== undefined ? t.i : []); + const ins = new Set([...seen.keys(), ...foreign.map(path_real), + ...src_deps(foreign)]); for (const out of outs) { const at = path_real(out); if (ins.has(at) || (fs.existsSync(at) && fs.statSync(at).isDirectory())) { @@ -291,6 +295,37 @@ function path_real(p: string): string { return fs.existsSync(p) ? fs.realpathSync(p) : path.resolve(p); } +// src_deps is every sibling .c a foreign .c pulls with `// @src name.c`, +// real paths, a helper named by a helper included. eff_src inlines that +// same line when it emits; the CLI otherwise only sees the import itself. +function src_deps(files: string[]): string[] { + const out: string[] = []; + const seen = new Set(); + const walk = (at: string, keep: boolean): void => { + const real = fs.realpathSync(at); + if (seen.has(real)) { + return; + } + seen.add(real); + if (keep) { + out.push(real); + } + const dir = real.slice(0, real.lastIndexOf("/")); + for (const line of fs.readFileSync(real, "utf8").split("\n")) { + const m = /^\/\/ @src ([a-z0-9_]+\.c)$/.exec(line); + if (m !== null) { + walk(dir + "/" + m[1], true); + } + } + }; + for (const file of files) { + if (file.endsWith(".c") && fs.existsSync(file)) { + walk(file, false); + } + } + return out; +} + function cli_emit(book: Bend.Book, out: string): void { if (/\.c?js$/.test(out)) { fs.writeFileSync(out, Comp.js_book(book)); @@ -449,19 +484,24 @@ async function cli_publish(file: string): Promise { // pkg_files is the package the loader read for this file, the entry first: // every .bend file at its namespace (the entry at its name), every foreign -// .c or .js file at its path from the entry's directory; base and the -// store's packages stay out. A path that climbs above the entry's directory -// takes the entry's ancestor directories along, as many as the deepest climb. +// .c or .js file at its path from the entry's directory, and every .c a +// foreign .c pulls with // @src; base and the store's packages stay out. +// A path that climbs above the entry's directory takes the entry's ancestor +// directories along, as many as the deepest climb. function pkg_files(file: string, book: Bend.Book, seen: Map): Record { const dir = file.slice(0, file.lastIndexOf("/") + 1); + const root = fs.realpathSync(dir === "" ? "." : dir.slice(0, -1)); + const owned = Object.entries(book.tlds).flatMap(([k, tld]): string[] => + tld.$ !== "Def" || tld.i === undefined || tld.b === true + || k.startsWith("0x") ? [] : tld.i); const raws = [...[...seen].flatMap(([real, ns]): [string, string][] => real === BASE || ns === null || ns.startsWith("0x") ? [] : [[ns === "" ? path.basename(file) : ns + ".bend", real]]), - ...Object.entries(book.tlds).flatMap(([k, tld]): [string, string][] => - tld.$ !== "Def" || tld.i === undefined || tld.b === true - || k.startsWith("0x") ? [] : tld.i.map((f) => - [f.startsWith(dir) ? f.slice(dir.length) : f, f]))]; + ...owned.map((f): [string, string] => + [f.startsWith(dir) ? f.slice(dir.length) : f, f]), + ...src_deps(owned).map((real): [string, string] => + [path.relative(root, real).split(path.sep).join("/"), real])]; const ups = raws.map(([p]) => path.posix.normalize(p).split("/") .filter((s) => s === "..").length); const anc = fs.realpathSync(path.dirname(file)).split("/") From 60bce7630c907af561a1fc2d927efb9e267d0281 Mon Sep 17 00:00:00 2001 From: Ethan Woodbury <42052950+ewoodbury@users.noreply.github.com> Date: Tue, 22 Sep 2026 01:37:19 -0700 Subject: [PATCH 3/3] Shorten the // @src file walk Same two call sites, one queue, one comment. --- bend2/main.ts | 46 +++++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/bend2/main.ts b/bend2/main.ts index c8aa3de8c..49e5631df 100755 --- a/bend2/main.ts +++ b/bend2/main.ts @@ -241,8 +241,6 @@ async function cli_file(args: string[]): Promise { process.exitCode = book_run(book, n0, argv); return; } - // ins is every file this run reads. A // @src helper is read while - // emitting, the same as the .c that names it, so -o must not replace it. const foreign = Object.values(book.tlds).flatMap((t) => t.$ === "Def" && t.i !== undefined ? t.i : []); const ins = new Set([...seen.keys(), ...foreign.map(path_real), @@ -295,33 +293,23 @@ function path_real(p: string): string { return fs.existsSync(p) ? fs.realpathSync(p) : path.resolve(p); } -// src_deps is every sibling .c a foreign .c pulls with `// @src name.c`, -// real paths, a helper named by a helper included. eff_src inlines that -// same line when it emits; the CLI otherwise only sees the import itself. +// Real paths of // @src siblings, a helper's helper included. function src_deps(files: string[]): string[] { const out: string[] = []; - const seen = new Set(); - const walk = (at: string, keep: boolean): void => { - const real = fs.realpathSync(at); - if (seen.has(real)) { - return; - } - seen.add(real); - if (keep) { - out.push(real); - } + const seen = new Set(files.flatMap((f) => + f.endsWith(".c") && fs.existsSync(f) ? [fs.realpathSync(f)] : [])); + for (const q = [...seen]; q.length > 0;) { + const real = q.pop() as string; const dir = real.slice(0, real.lastIndexOf("/")); - for (const line of fs.readFileSync(real, "utf8").split("\n")) { - const m = /^\/\/ @src ([a-z0-9_]+\.c)$/.exec(line); - if (m !== null) { - walk(dir + "/" + m[1], true); + for (const m of fs.readFileSync(real, "utf8") + .matchAll(/^\/\/ @src ([a-z0-9_]+\.c)$/gm)) { + const dep = fs.realpathSync(dir + "/" + m[1]); + if (!seen.has(dep)) { + seen.add(dep); + out.push(dep); + q.push(dep); } } - }; - for (const file of files) { - if (file.endsWith(".c") && fs.existsSync(file)) { - walk(file, false); - } } return out; } @@ -484,10 +472,10 @@ async function cli_publish(file: string): Promise { // pkg_files is the package the loader read for this file, the entry first: // every .bend file at its namespace (the entry at its name), every foreign -// .c or .js file at its path from the entry's directory, and every .c a -// foreign .c pulls with // @src; base and the store's packages stay out. -// A path that climbs above the entry's directory takes the entry's ancestor -// directories along, as many as the deepest climb. +// .c or .js file at its path from the entry's directory, and each // @src +// sibling; base and the store's packages stay out. A path that climbs above +// the entry's directory takes the entry's ancestor directories along, as +// many as the deepest climb. function pkg_files(file: string, book: Bend.Book, seen: Map): Record { const dir = file.slice(0, file.lastIndexOf("/") + 1); @@ -501,7 +489,7 @@ function pkg_files(file: string, book: Bend.Book, ...owned.map((f): [string, string] => [f.startsWith(dir) ? f.slice(dir.length) : f, f]), ...src_deps(owned).map((real): [string, string] => - [path.relative(root, real).split(path.sep).join("/"), real])]; + [path.posix.relative(root, real), real])]; const ups = raws.map(([p]) => path.posix.normalize(p).split("/") .filter((s) => s === "..").length); const anc = fs.realpathSync(path.dirname(file)).split("/")