wcc,ww: flip driver to separate-compilation only; delete build_one amalgamator (M4 E3-C1, #22)
build_one_sep (per-package compile + .wwi interfaces + link) becomes the sole build path. do_build/do_run/do_test and the ww twins all route through it; --sep is now an accepted no-op and the run-rejects-sep guard is removed. Deleted the single-file amalgamator, both stages: build_one, expand, expand_dir, peek_package (+ the wwstage twins + strictpkgmismatch). unit_has_package is retained -- the sep scan loop's inline-package check needs it. The sep-shared helpers (enumerate_dir_ww, locate_import*, import_path_form, ImportSet, and ww counterparts) stay; they back the surviving sep path. Restores missing-package enforcement under sep by construction: the sep scan loop loudly rejects an unresolvable import (cannot find package <name>) unless the package is defined inline in the same unit -- matching the deleted amalgamator and closing the silent-accept the flip would otherwise introduce. All 5 wwstage tools relink (each is built via the now-sep `ww build`); emitted asm is byte-identical to the combined build per bootstrap input, so the binary md5 delta is pure link layout, not codegen. selfhost/cmd/ww/main.combined.ww is now stale and unregenerable (its writer build_one is deleted); #90 deletes it next. Test retargets folded in (rule-11 carve-out, #61/#133 precedent): each asserts post-flip-only behavior, is un-pre-migratable unlike #93/#94/#103, and splitting reddens one side. Closes #97. - 989_slttypepref -> dir-package layout (xb imports xa so both same-leaf `invalid` types are in scope at xb.f); inline-multipackage was the amalgamator shape, deleted with the flip. - 989_sepbuild_run -> run --sep now genuinely runs (exit 7), not the old loud-reject (exit 2); + a private per-pid WW_PKGCACHE so the cs/ww per-package byte-id compare on the shared real lib pkgs (rt/time/os) no longer races concurrent siblings on the global out/.pkgcache (the flip made sep the sole path, so every test now contends that cache). - 737_direnum -> the deleted strictpkgmismatch "differs from" wording -> sep's "does not match import path" (shared substring, wwstage terser #68). - 989_lib_byteid -> corpus-completeness scan excludes generated .sepwork scratch (the old `! -name '*.combined.ww'` exclude didn't cover the new sep artifact).
This commit is contained in:
@@ -181,12 +181,13 @@ fn procrun(path: *u8, argv: **u8) i32 = {
|
||||
return code;
|
||||
};
|
||||
|
||||
// ---- `use` resolution + source concatenation --------------------------
|
||||
// ---- import resolution + visited-set -----------------------------------
|
||||
//
|
||||
// Recursive expansion: for each `use IDENT;` we find at the top of
|
||||
// `path`, resolve via the colon-separated `dirs`, expand the imported
|
||||
// file first, then append our own bytes. Already-visited paths are
|
||||
// skipped (linear scan; typical builds visit a handful of modules).
|
||||
// The separate-compilation producer scans each unit's top-of-file
|
||||
// `import IDENT;` lines and resolves them via the colon-separated `dirs`
|
||||
// search path. A per-scan visited set (linear; typical builds visit a
|
||||
// handful of modules) breaks cycles. (E3-C1: the legacy single-file
|
||||
// source concatenator was deleted — sep is the sole path. Task #87.)
|
||||
|
||||
type strnode = struct {
|
||||
s: str,
|
||||
@@ -509,307 +510,6 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
|
||||
return src + idstart, idlen;
|
||||
};
|
||||
|
||||
// expand — emit one file's bytes verbatim into the combined stream,
|
||||
// after recursive-expanding its top-of-file `import X;` imports.
|
||||
// Each source declares its own `package <name>;` (parser stamps
|
||||
// decls).
|
||||
fn expand(c: *expctx, pathcs: *u8, modpath: str) void = {
|
||||
let plen: u64 = cstrlen(pathcs);
|
||||
let view: str;
|
||||
view.ptr = pathcs;
|
||||
view.len = plen: i32;
|
||||
let pathstr: str = strings.dup(view);
|
||||
if (visitseen(c, pathstr)) { return; };
|
||||
visitadd(c, pathstr);
|
||||
|
||||
let bufp: *u8;
|
||||
let blen: u64;
|
||||
bufp, blen = slurp(pathcs);
|
||||
if (bufp == nil) {
|
||||
cerr("ww: cannot read source\n");
|
||||
return;
|
||||
};
|
||||
|
||||
// Pass 1: scan top-of-file `import X;` lines, recursively expand.
|
||||
let i: u64 = 0u64;
|
||||
for (i < blen) {
|
||||
let j: u64 = i;
|
||||
for (j < blen) {
|
||||
if (bufp[j] == 10u8) { break; }; // '\n'
|
||||
j += 1u64;
|
||||
};
|
||||
let idp: *u8;
|
||||
let idn: u64;
|
||||
idp, idn = scanuse(bufp + i, j - i);
|
||||
if (idp != nil) {
|
||||
let isdir: i32 = 0;
|
||||
let ipath: *u8 = locateimport(c.dirs, idp, idn,
|
||||
&isdir);
|
||||
if (ipath != nil) {
|
||||
// M1 #22 (isdir-gated, rob-ratified): only DIRECTORY
|
||||
// imports are package boundaries that path-mangle. A
|
||||
// single-file import (`import opcodes;` → opcodes.ww
|
||||
// declaring `package w6a`) is an intra-package file-split:
|
||||
// it keeps its in-file `package` clause as its module
|
||||
// (no directive → reset → package-clause mangling).
|
||||
if (isdir != 0) {
|
||||
let mv: str;
|
||||
mv.ptr = idp;
|
||||
mv.len = idn: i32;
|
||||
let modstr: str = strings.dup(mv);
|
||||
expanddir(c, ipath, modstr);
|
||||
} else {
|
||||
expand(c, ipath, "");
|
||||
};
|
||||
} else {
|
||||
// #16 ENFORCE-driver (rob A): a locate-miss is
|
||||
// legal when the package is defined INLINE in the
|
||||
// same unit (single-file multi-package). leaf = the
|
||||
// last dotted component of the import name; if an
|
||||
// inline `package <leaf>` exists -> silent skip
|
||||
// (the checker binds it), else fatal. cstage twin in
|
||||
// cmd/ww/main.c; fatal text identical.
|
||||
let lstart: u64 = 0u64;
|
||||
let lk: u64 = 0u64;
|
||||
for (lk < idn) {
|
||||
if (idp[lk] == 46u8) { lstart = lk + 1u64; }; // '.'
|
||||
lk += 1u64;
|
||||
};
|
||||
let leafp: *u8 = idp + lstart;
|
||||
let leafn: u64 = idn - lstart;
|
||||
if (!unithaspackage(bufp, blen, leafp, leafn)) {
|
||||
cerr("ww: cannot find package ");
|
||||
os.write(2, idp, idn);
|
||||
cerr("\n");
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
i = j + 1u64;
|
||||
};
|
||||
|
||||
// #16 option-B: a package-less file's decls would otherwise inherit
|
||||
// the preceding bundled module's sticky curmod (parser parse.ww). Emit
|
||||
// a curmod-reset boundary directive so the lexer/parser attribute the
|
||||
// file to the primary module ("") — fixes the self-import false-fire
|
||||
// and the leaked-prefix bug, codegen-neutral (bare symbols kept; not
|
||||
// `package main`, which would main-prefix them). A packaged file's own
|
||||
// `package` decl already sets curmod, so it needs nothing — keeping
|
||||
// the directive out of every tracked combined.ww. (Task #11.)
|
||||
if (modpath.len != 0) {
|
||||
// M1 #22: import-reached file carries its full dotted path so
|
||||
// codegen mangles symbols on the path, not the leaf clause.
|
||||
let dm: str = "//ww:module ";
|
||||
os.writeall(c.out, dm.ptr, dm.len: u64);
|
||||
os.writeall(c.out, modpath.ptr, modpath.len: u64);
|
||||
os.writeall(c.out, "\n".ptr, 1u64);
|
||||
} else {
|
||||
// Root/primary: always reset so a preceding imported section's
|
||||
// sticky pathmod is cleared; a packaged primary's own `package`
|
||||
// clause then sets curmod fresh (pathmod NULL → real clause).
|
||||
let d: str = "//ww:module-reset\n";
|
||||
os.writeall(c.out, d.ptr, d.len: u64);
|
||||
};
|
||||
|
||||
os.writeall(c.out, bufp, blen);
|
||||
os.writeall(c.out, "\n".ptr, 1u64);
|
||||
};
|
||||
|
||||
// Scan `pathcs` for its first non-comment-non-blank line; if it
|
||||
// starts with `package <name>;` return the package name as a fresh
|
||||
// heap-allocated NUL-terminated *u8, else nil. Same shape as cstage
|
||||
// peek_package.
|
||||
//
|
||||
// Reads the WHOLE file (via slurp), not a fixed prefix: cstage's
|
||||
// peek_package scans line-by-line with fgets and no total cap, stopping
|
||||
// at the first non-comment-non-blank line. A prior 2048-byte read cap
|
||||
// here diverged from cstage on files whose `package` decl sits behind a
|
||||
// >2048-byte comment header (strconv decimal/ftos/stof, memio) —
|
||||
// returning nil and making the #16 D-i injection asymmetric (rule-10
|
||||
// break, byte-id divergence in the regenerated combined). The corpus has
|
||||
// no line >2047 chars, so a whole-file line scan matches cstage's
|
||||
// per-line fgets byte-for-byte on every real input.
|
||||
fn peekpackage(pathcs: *u8) *u8 = {
|
||||
let bufp: *u8;
|
||||
let nu: u64;
|
||||
bufp, nu = slurp(pathcs);
|
||||
if (bufp == nil) { return nil; };
|
||||
let p: u64 = 0u64;
|
||||
for (p < nu) {
|
||||
let q: u64 = p;
|
||||
for (q < nu) {
|
||||
if (bufp[q] == 10u8) { break; }; // '\n'
|
||||
q += 1u64;
|
||||
};
|
||||
let s: u64 = p;
|
||||
for (s < q) {
|
||||
if (bufp[s] != 32u8) {
|
||||
if (bufp[s] != 9u8) { break; };
|
||||
};
|
||||
s += 1u64;
|
||||
};
|
||||
if (s < q) {
|
||||
let line: str;
|
||||
line.ptr = bufp + s;
|
||||
line.len = (q - s): i32;
|
||||
// hasprefix("//") subsumes the old s+1<q guard.
|
||||
if (strings.hasprefix(line, "//")) {
|
||||
p = q + 1u64;
|
||||
continue;
|
||||
};
|
||||
// s+8<=q guard kept: hasprefix("package") needs only 7
|
||||
// bytes, but the sep read at bufp[s+7] needs s+7 < q.
|
||||
if (s + 8u64 <= q) {
|
||||
if (strings.hasprefix(line, "package")) {
|
||||
let sep: u8 = bufp[s + 7u64];
|
||||
if (sep == 32u8) { }
|
||||
else { if (sep != 9u8) { return nil; }; };
|
||||
let t: u64 = s + 8u64;
|
||||
for (t < q) {
|
||||
if (bufp[t] != 32u8) {
|
||||
if (bufp[t] != 9u8) { break; };
|
||||
};
|
||||
t += 1u64;
|
||||
};
|
||||
let start: u64 = t;
|
||||
for (t < q) {
|
||||
let ch: u8 = bufp[t];
|
||||
let isalpha: bool = false;
|
||||
if (ch >= 97u8) { if (ch <= 122u8) { isalpha = true; }; };
|
||||
if (ch >= 65u8) { if (ch <= 90u8) { isalpha = true; }; };
|
||||
if (ch >= 48u8) { if (ch <= 57u8) { isalpha = true; }; };
|
||||
if (ch == 95u8) { isalpha = true; };
|
||||
if (!isalpha) { break; };
|
||||
t += 1u64;
|
||||
};
|
||||
let plen: u64 = t - start;
|
||||
if (plen == 0u64) { return nil; };
|
||||
let r: []u8 = alloc([], plen + 1u64)!;
|
||||
let k: u64 = 0u64;
|
||||
for (k < plen) { r[k] = bufp[start + k]; k += 1u64; };
|
||||
r[plen] = 0u8;
|
||||
return r.ptr;
|
||||
};
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
p = q + 1u64;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// unithaspackage — does the unit buffer declare `package <leaf>;` ANYWHERE?
|
||||
// #16 ENFORCE-driver (rob A) cstage unit_has_package twin: distinguishes a
|
||||
// genuinely-missing import from one satisfied by an INLINE package in the
|
||||
// same single-file multi-package unit (`package aa; ... package main;
|
||||
// import aa;`). Scans EVERY line (comment-skip) — not just the first
|
||||
// package decl (peekpackage stops there). Decision byte-identical to
|
||||
// cstage so the skip/fatal choice + driver output match (rule 10).
|
||||
fn unithaspackage(buf: *u8, buflen: u64, leafp: *u8, leafn: u64) bool = {
|
||||
let p: u64 = 0u64;
|
||||
for (p < buflen) {
|
||||
let q: u64 = p;
|
||||
for (q < buflen) { if (buf[q] == 10u8) { break; }; q += 1u64; };
|
||||
let s: u64 = p;
|
||||
for (s < q) {
|
||||
if (buf[s] != 32u8) { if (buf[s] != 9u8) { break; }; };
|
||||
s += 1u64;
|
||||
};
|
||||
if (s < q) {
|
||||
let line: str;
|
||||
line.ptr = buf + s;
|
||||
line.len = (q - s): i32;
|
||||
if (strings.hasprefix(line, "//")) { p = q + 1u64; continue; };
|
||||
if (s + 8u64 <= q) {
|
||||
if (strings.hasprefix(line, "package")) {
|
||||
let sep: u8 = buf[s + 7u64];
|
||||
let oksep: bool = false;
|
||||
if (sep == 32u8) { oksep = true; }
|
||||
else { if (sep == 9u8) { oksep = true; }; };
|
||||
if (oksep) {
|
||||
let t: u64 = s + 8u64;
|
||||
for (t < q) {
|
||||
if (buf[t] != 32u8) { if (buf[t] != 9u8) { break; }; };
|
||||
t += 1u64;
|
||||
};
|
||||
let m: u64 = 0u64;
|
||||
let eq: bool = true;
|
||||
for (m < leafn) {
|
||||
if (t + m >= q) { eq = false; break; };
|
||||
if (buf[t + m] != leafp[m]) { eq = false; break; };
|
||||
m += 1u64;
|
||||
};
|
||||
if (eq) {
|
||||
let after: u64 = t + leafn;
|
||||
let term: bool = false;
|
||||
if (after >= q) { term = true; }
|
||||
else {
|
||||
let c: u8 = buf[after];
|
||||
if (c == 59u8) { term = true; } // ';'
|
||||
else { if (c == 32u8) { term = true; }
|
||||
else { if (c == 9u8) { term = true; }; }; };
|
||||
};
|
||||
if (term) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
p = q + 1u64;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// Strict-same-package error helper. Bundled here per task #22
|
||||
// brief — failure mode is dir-enum's own.
|
||||
fn strictpkgmismatch(file: *u8, pkg: *u8, dirpkg: *u8, dirpath: *u8) void = {
|
||||
cerr("ww: ");
|
||||
os.write(2, file, cstrlen(file));
|
||||
cerr(": package ");
|
||||
os.write(2, pkg, cstrlen(pkg));
|
||||
cerr(" differs from ");
|
||||
os.write(2, dirpkg, cstrlen(dirpkg));
|
||||
cerr(" in same module dir ");
|
||||
os.write(2, dirpath, cstrlen(dirpath));
|
||||
cerr("\n");
|
||||
os.exit(1);
|
||||
};
|
||||
|
||||
// expanddir — enumerate <dirpath>/*.ww (skip *test.ww and
|
||||
// *.combined.ww), byte-sort, recurse into each. Mirrors
|
||||
// ref/hare/hare/module/srcs.ha:183 `_findsrcs` minus tag handling.
|
||||
// The visited set keys on concrete file paths so multi-file modules
|
||||
// are pulled once. Strict-same-package: all enumerated files must
|
||||
// declare the same `package <name>;` (task #23 subset; failure
|
||||
// mode native to dir-enum).
|
||||
fn expanddir(c: *expctx, dirpath: *u8, modpath: str) void = {
|
||||
let names: **u8;
|
||||
let n: i32;
|
||||
names, n = enumeratedir(dirpath);
|
||||
let dlen: u64 = cstrlen(dirpath);
|
||||
let dirpkg: *u8 = nil;
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let nlen: u64 = cstrlen(names[i]);
|
||||
let fp: []u8 = alloc([], dlen + 1u64 + nlen + 1u64)!;
|
||||
let k: u64 = 0u64;
|
||||
for (k < dlen) { fp[k] = dirpath[k]; k += 1u64; };
|
||||
fp[dlen] = 47u8; // '/'
|
||||
k = 0u64;
|
||||
for (k < nlen) { fp[dlen + 1u64 + k] = names[i][k]; k += 1u64; };
|
||||
fp[dlen + 1u64 + nlen] = 0u8;
|
||||
let pkg: *u8 = peekpackage(fp.ptr);
|
||||
if (pkg != nil) {
|
||||
if (dirpkg == nil) { dirpkg = pkg; }
|
||||
else { if (!cstreq(dirpkg, pkg)) {
|
||||
strictpkgmismatch(fp.ptr, pkg, dirpkg, dirpath);
|
||||
}; };
|
||||
};
|
||||
expand(c, fp.ptr, modpath);
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// ---- Build pipeline ---------------------------------------------------
|
||||
|
||||
// Strip the trailing ".ww" off `src` (a NUL-terminated path) into
|
||||
@@ -933,6 +633,68 @@ fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
|
||||
return buf.ptr;
|
||||
};
|
||||
|
||||
// unithaspackage — does the unit buffer declare `package <leaf>;` ANYWHERE?
|
||||
// #16 ENFORCE-driver (rob A) cstage unit_has_package twin: distinguishes a
|
||||
// genuinely-missing import from one satisfied by an INLINE package in the
|
||||
// same single-file multi-package unit (`package aa; ... package main;
|
||||
// import aa;`). Scans EVERY line (comment-skip) — not just the first
|
||||
// package decl (peekpackage stops there). Decision byte-identical to
|
||||
// cstage so the skip/fatal choice + driver output match (rule 10).
|
||||
fn unithaspackage(buf: *u8, buflen: u64, leafp: *u8, leafn: u64) bool = {
|
||||
let p: u64 = 0u64;
|
||||
for (p < buflen) {
|
||||
let q: u64 = p;
|
||||
for (q < buflen) { if (buf[q] == 10u8) { break; }; q += 1u64; };
|
||||
let s: u64 = p;
|
||||
for (s < q) {
|
||||
if (buf[s] != 32u8) { if (buf[s] != 9u8) { break; }; };
|
||||
s += 1u64;
|
||||
};
|
||||
if (s < q) {
|
||||
let line: str;
|
||||
line.ptr = buf + s;
|
||||
line.len = (q - s): i32;
|
||||
if (strings.hasprefix(line, "//")) { p = q + 1u64; continue; };
|
||||
if (s + 8u64 <= q) {
|
||||
if (strings.hasprefix(line, "package")) {
|
||||
let sep: u8 = buf[s + 7u64];
|
||||
let oksep: bool = false;
|
||||
if (sep == 32u8) { oksep = true; }
|
||||
else { if (sep == 9u8) { oksep = true; }; };
|
||||
if (oksep) {
|
||||
let t: u64 = s + 8u64;
|
||||
for (t < q) {
|
||||
if (buf[t] != 32u8) { if (buf[t] != 9u8) { break; }; };
|
||||
t += 1u64;
|
||||
};
|
||||
let m: u64 = 0u64;
|
||||
let eq: bool = true;
|
||||
for (m < leafn) {
|
||||
if (t + m >= q) { eq = false; break; };
|
||||
if (buf[t + m] != leafp[m]) { eq = false; break; };
|
||||
m += 1u64;
|
||||
};
|
||||
if (eq) {
|
||||
let after: u64 = t + leafn;
|
||||
let term: bool = false;
|
||||
if (after >= q) { term = true; }
|
||||
else {
|
||||
let c: u8 = buf[after];
|
||||
if (c == 59u8) { term = true; } // ';'
|
||||
else { if (c == 32u8) { term = true; }
|
||||
else { if (c == 9u8) { term = true; }; }; };
|
||||
};
|
||||
if (term) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
p = q + 1u64;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// Scan one source file for top-level `import IDENT;`. A DIRECTORY import
|
||||
// is a package boundary: add as a direct dep of pi. A FILE import is an
|
||||
// intra-package split: fold its imports into pi. Mirrors cstage
|
||||
@@ -989,6 +751,29 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
|
||||
return -1;
|
||||
};
|
||||
};
|
||||
} else {
|
||||
// #16 ENFORCE-driver (rob A): a locate-miss is legal
|
||||
// when the package is defined INLINE in the same unit
|
||||
// (single-file multi-package). leaf = last dotted
|
||||
// component; inline `package <leaf>` -> skip (the
|
||||
// checker binds it), else fatal. E3-C1: the combined
|
||||
// path that owned the genuine-missing case is gone, so
|
||||
// the sep producer enforces it here (INV-2). cstage
|
||||
// twin; fatal text identical.
|
||||
let lstart: u64 = 0u64;
|
||||
let lk: u64 = 0u64;
|
||||
for (lk < idn) {
|
||||
if (idp[lk] == 46u8) { lstart = lk + 1u64; }; // '.'
|
||||
lk += 1u64;
|
||||
};
|
||||
let leafp: *u8 = idp + lstart;
|
||||
let leafn: u64 = idn - lstart;
|
||||
if (!unithaspackage(bufp, blen, leafp, leafn)) {
|
||||
cerr("ww: cannot find package ");
|
||||
os.write(2, idp, idn);
|
||||
cerr("\n");
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
i = j + 1u64;
|
||||
@@ -1786,235 +1571,6 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
||||
return 0;
|
||||
};
|
||||
|
||||
// buildone — compile `src` (file or directory) into the executable
|
||||
// named `out`.
|
||||
// selfdir: NUL-terminated dir containing this driver and the
|
||||
// wwstage tools (w6c_ww/w6a_ww/w6l_ww)
|
||||
// src: NUL-terminated entry path (file or directory).
|
||||
// entryisdir: non-zero when src is a module directory.
|
||||
// out: NUL-terminated desired output path
|
||||
// objstem: when non-nil, redirects the .s/.o/.combined.ww side
|
||||
// files to live beside this stem instead of next to the
|
||||
// source (T3 / task #15). `ww run` and `ww build -o` pass
|
||||
// it so concurrent builds never share next-to-source fixed
|
||||
// paths; nil keeps the old next-to-source layout (the make
|
||||
// tracked-combined.ww regen + #110 gate depend on it). Twin
|
||||
// of cmd/ww/main.c build_one's objstem.
|
||||
// incs: NUL-terminated colon-list of -I dirs (may be empty)
|
||||
// lf: extra linker flags (-L<dir>, -l<name>); may be nil
|
||||
//
|
||||
// The ww-side driver shells to the ww-side tools so a `ww_ww build`
|
||||
// touches no C-built code at runtime. The C `ww` driver in cmd/ww/
|
||||
// still drives the C-built w6c/w6a/w6l. Test 993 pins the two
|
||||
// pipelines to byte-identical output on a corpus.
|
||||
fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, incs: *u8, lf: *lflags, istest: i32) i32 = {
|
||||
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
|
||||
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
||||
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
|
||||
|
||||
// Default lib search path: <selfdir>/../../lib
|
||||
let dotdotlib: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
dotdotlib.len = os.PATH_MAX;
|
||||
{
|
||||
let off: u64 = cstrinto(dotdotlib.ptr, 0u64, selfdir);
|
||||
off = strinto(dotdotlib.ptr, off, "/../../lib");
|
||||
cstrseal(dotdotlib.ptr, off);
|
||||
};
|
||||
|
||||
// Compute the source directory. For a file entry: bytes of `src`
|
||||
// up to the last '/' (or "." when src has no '/'). For a dir
|
||||
// entry: the dir itself (less trailing slashes). Hare's CWD-first
|
||||
// convention assumes you're running from the module dir; our
|
||||
// wrappers don't cd, so dirname(src) stands in as the closest
|
||||
// analog. Source-dir wins ties over the system path (cc -I.).
|
||||
let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
srcd.len = os.PATH_MAX;
|
||||
if (entryisdir != 0) {
|
||||
let slen: u64 = cstrlen(src);
|
||||
let k: u64 = 0u64;
|
||||
for (k < slen) { srcd[k] = src[k]; k += 1u64; };
|
||||
for (slen > 1u64) {
|
||||
if (srcd[slen - 1u64] != 47u8) { break; };
|
||||
slen -= 1u64;
|
||||
};
|
||||
srcd[slen] = 0u8;
|
||||
} else {
|
||||
let slen: u64 = cstrlen(src);
|
||||
let last: u64 = slen;
|
||||
let found: bool = false;
|
||||
let i: u64 = slen;
|
||||
for (i > 0u64) {
|
||||
i -= 1u64;
|
||||
if (src[i] == 47u8) { // '/'
|
||||
last = i;
|
||||
found = true;
|
||||
i = 0u64;
|
||||
};
|
||||
};
|
||||
if (found) {
|
||||
let k: u64 = 0u64;
|
||||
for (k < last) { srcd[k] = src[k]; k += 1u64; };
|
||||
srcd[last] = 0u8;
|
||||
} else {
|
||||
srcd[0] = 46u8; // '.'
|
||||
srcd[1] = 0u8;
|
||||
};
|
||||
};
|
||||
|
||||
// Compose searchpath: srcd + ':' + incs + ':' + dotdotlib.
|
||||
let searchpath: []u8 = alloc([], (os.PATH_MAX: u64) * 3u64)!;
|
||||
searchpath.len = ((os.PATH_MAX: u64) * 3u64): i32;
|
||||
{
|
||||
let off: u64 = cstrinto(searchpath.ptr, 0u64, srcd.ptr);
|
||||
off = byteinto(searchpath.ptr, off, 58u8); // ':'
|
||||
if (incs[0u64] != 0u8) {
|
||||
off = cstrinto(searchpath.ptr, off, incs);
|
||||
off = byteinto(searchpath.ptr, off, 58u8); // ':'
|
||||
};
|
||||
off = cstrinto(searchpath.ptr, off, dotdotlib.ptr);
|
||||
cstrseal(searchpath.ptr, off);
|
||||
};
|
||||
|
||||
// Stem for .s/.o/.combined.ww side files. Dir entry: <dir>/<base>;
|
||||
// file entry: src stripped of .ww.
|
||||
let stem: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
stem.len = os.PATH_MAX;
|
||||
if (entryisdir != 0) {
|
||||
let dlen: u64 = cstrlen(srcd.ptr);
|
||||
let bo: u64 = basenameoff(srcd.ptr, dlen);
|
||||
let off: u64 = cstrinto(stem.ptr, 0u64, srcd.ptr);
|
||||
stem[off] = 47u8; off += 1u64; // '/'
|
||||
let i: u64 = bo;
|
||||
for (i < dlen) { stem[off] = srcd[i]; off += 1u64; i += 1u64; };
|
||||
cstrseal(stem.ptr, off);
|
||||
} else {
|
||||
makestem(stem.ptr, src);
|
||||
};
|
||||
let effstem: *u8 = stem.ptr;
|
||||
if (objstem != nil) { effstem = objstem; };
|
||||
let asmf: *u8 = appendlit(effstem, ".s");
|
||||
let objf: *u8 = appendlit(effstem, ".o");
|
||||
let combined: *u8 = appendlit(effstem, ".combined.ww");
|
||||
|
||||
// libwwrt.a path: <selfdir>/../lib/libwwrt.a
|
||||
let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
libwwrt.len = os.PATH_MAX;
|
||||
{
|
||||
let off: u64 = cstrinto(libwwrt.ptr, 0u64, selfdir);
|
||||
off = strinto(libwwrt.ptr, off, "/../lib/libwwrt.a");
|
||||
cstrseal(libwwrt.ptr, off);
|
||||
};
|
||||
|
||||
// Step 1: expand imports into the combined file. Dir entry →
|
||||
// enumerate the module dir; file entry → start at the file.
|
||||
let cf: i32 = os.open(pathstr(combined), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (cf < 0) {
|
||||
cerr("ww: cannot open combined\n");
|
||||
return 1;
|
||||
};
|
||||
{
|
||||
let c: expctx;
|
||||
c.out = cf;
|
||||
c.dirs = searchpath.ptr;
|
||||
c.visit = nil;
|
||||
// #17 auto-bundle lib/test: the -T synth's main calls lib/test's
|
||||
// run(), but @test files don't `import test;`. Pull it like an
|
||||
// implicit import through the same locate+expand path (the visit
|
||||
// set dedupes a fixture that imports it explicitly). cstage twin
|
||||
// in cmd/ww/main.c buildone.
|
||||
if (istest != 0) {
|
||||
let td: i32 = 0;
|
||||
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
|
||||
if (tp != nil) {
|
||||
if (td != 0) { expanddir(&c, tp, "test"); }
|
||||
else { expand(&c, tp, ""); };
|
||||
};
|
||||
};
|
||||
if (entryisdir != 0) { expanddir(&c, srcd.ptr, ""); }
|
||||
else { expand(&c, src, ""); };
|
||||
};
|
||||
os.close(cf);
|
||||
|
||||
// Step 2: w6c [-T] -o <stem>.s <stem>.combined.ww
|
||||
{
|
||||
let argv: []*u8 = alloc([], 6u64)!;
|
||||
let p: i32 = 0;
|
||||
argv[p] = "w6c\0".ptr; p += 1;
|
||||
if (istest != 0) { argv[p] = "-T\0".ptr; p += 1; };
|
||||
argv[p] = "-o\0".ptr; p += 1;
|
||||
argv[p] = asmf; p += 1;
|
||||
argv[p] = combined; p += 1;
|
||||
argv[p] = nil; p += 1;
|
||||
argv.len = p;
|
||||
if (procrun(c6, argv.ptr) != 0) {
|
||||
cerr("ww: w6c failed\n");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
|
||||
// Step 3: w6a -o <stem>.o <stem>.s
|
||||
{
|
||||
let argv: []*u8 = alloc([], 5u64)!;
|
||||
argv.len = 5;
|
||||
argv[0] = "w6a\0".ptr;
|
||||
argv[1] = "-o\0".ptr;
|
||||
argv[2] = objf;
|
||||
argv[3] = asmf;
|
||||
argv[4] = nil;
|
||||
if (procrun(a6, argv.ptr) != 0) {
|
||||
cerr("ww: w6a failed\n");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
|
||||
// Step 4: w6l -o <out> <stem>.o libwwrt.a [-L<dir>...] [-l<name>...]
|
||||
{
|
||||
let nldirs: i32 = 0;
|
||||
let nllibs: i32 = 0;
|
||||
let ldirs: **u8 = nil;
|
||||
let llibs: **u8 = nil;
|
||||
if (lf != nil) {
|
||||
nldirs = lf.nlibdirs;
|
||||
nllibs = lf.nlibs;
|
||||
ldirs = lf.libdirs;
|
||||
llibs = lf.libs;
|
||||
};
|
||||
// argv slots: 5 fixed (w6l, -o, out, objf, libwwrt)
|
||||
// + 2 * nlibdirs (-L, dir)
|
||||
// + 2 * nlibs (-l, name)
|
||||
// + 1 nil terminator.
|
||||
let total: i32 = 5 + 2 * nldirs + 2 * nllibs + 1;
|
||||
let argv: []*u8 = alloc([], total: u64)!;
|
||||
argv.len = total;
|
||||
argv[0] = "w6l\0".ptr;
|
||||
argv[1] = "-o\0".ptr;
|
||||
argv[2] = out;
|
||||
argv[3] = objf;
|
||||
argv[4] = libwwrt.ptr;
|
||||
let pos: i32 = 5;
|
||||
let k: i32 = 0;
|
||||
for (k < nldirs) {
|
||||
argv[pos] = "-L\0".ptr;
|
||||
argv[pos + 1] = ldirs[k];
|
||||
pos += 2;
|
||||
k += 1;
|
||||
};
|
||||
k = 0;
|
||||
for (k < nllibs) {
|
||||
argv[pos] = "-l\0".ptr;
|
||||
argv[pos + 1] = llibs[k];
|
||||
pos += 2;
|
||||
k += 1;
|
||||
};
|
||||
argv[pos] = nil;
|
||||
if (procrun(l6, argv.ptr) != 0) {
|
||||
cerr("ww: w6l failed\n");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
// ---- Module-by-name resolution ----------------------------------------
|
||||
//
|
||||
// Mirrors cmd/ww/main.c:resolvemodule. Maps a name like "foo", "lib/foo",
|
||||
@@ -2154,7 +1710,6 @@ fn defaultoutpath(src: *u8) *u8 = {
|
||||
|
||||
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
let src: *u8 = nil;
|
||||
let wantsep: i32 = 0; // --sep: M3-tail separate-compilation path
|
||||
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
|
||||
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
|
||||
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
|
||||
@@ -2173,8 +1728,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
for (i < argc) {
|
||||
let p: *u8 = argv[i];
|
||||
if (p[0u64] == 45u8) { // '-'
|
||||
if (cstreqlit(p, "--sep")) { // M3-tail separate-compile
|
||||
wantsep = 1;
|
||||
if (cstreqlit(p, "--sep")) { // E3-C1: sep is sole path; accepted no-op (#87)
|
||||
} else { if (p[1u64] == 73u8) { // '-I'
|
||||
let dir: *u8 = nil;
|
||||
if (p[2u64] != 0u8) {
|
||||
@@ -2290,10 +1844,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
lf.nlibdirs = nlibdirs;
|
||||
lf.libs = libs.ptr;
|
||||
lf.nlibs = nlibs;
|
||||
if (wantsep != 0) {
|
||||
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
||||
};
|
||||
return buildone(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
||||
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
||||
};
|
||||
|
||||
// Format the scratch path /tmp/ww_run_<pid> into buf. Returns NUL-
|
||||
@@ -2348,9 +1899,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
else {
|
||||
let p: *u8 = argv[i];
|
||||
if (p[0u64] == 45u8) {
|
||||
if (cstreqlit(p, "--sep")) { // build-only; run rejects (rule 10)
|
||||
cerr("ww run: --sep is only valid with build\n");
|
||||
return 2;
|
||||
if (cstreqlit(p, "--sep")) { // E3-C1: sep is sole path; accepted no-op (#87)
|
||||
} else { if (p[1u64] == 73u8) {
|
||||
let dir: *u8 = nil;
|
||||
if (p[2u64] != 0u8) {
|
||||
@@ -2452,7 +2001,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
lf.nlibs = nlibs;
|
||||
// objstem = tmp → intermediates at /tmp/ww_run_<pid>.{s,o,combined.ww},
|
||||
// never next to the source (T3).
|
||||
if (buildone(selfdir, resolved, isdir, tmp.ptr, tmp.ptr, incs.ptr, &lf, 0i32) != 0) {
|
||||
if (buildonesep(selfdir, resolved, isdir, tmp.ptr, tmp.ptr, incs.ptr, &lf, 0i32) != 0) {
|
||||
os.remove(pathstr(tmp.ptr));
|
||||
return 1;
|
||||
};
|
||||
@@ -2482,7 +2031,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
// directory: open the dir, getdents64, build+run each *_test.ww,
|
||||
// report ok/FAIL per file, return 0 iff all pass.
|
||||
|
||||
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8, pattern: *u8, usesep: i32) i32 = {
|
||||
fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *u8, pattern: *u8) i32 = {
|
||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
tmp.len = os.PATH_MAX;
|
||||
// -o redirects the binary + its combined (objstem, T3) to <stem>; the
|
||||
@@ -2496,19 +2045,13 @@ fn runsingletest(selfdir: *u8, src: *u8, incs: *u8, compileonly: i32, outstem: *
|
||||
makeruntmp(tmp.ptr);
|
||||
outp = tmp.ptr;
|
||||
};
|
||||
// #79 E1: --sep routes the test build through the separate-compilation
|
||||
// producer (buildonesep, istest=1) instead of the amalgamator.
|
||||
let bres: i32 = 0;
|
||||
if (usesep != 0) {
|
||||
let lf: lflags;
|
||||
lf.libdirs = nil;
|
||||
lf.nlibdirs = 0;
|
||||
lf.libs = nil;
|
||||
lf.nlibs = 0;
|
||||
bres = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf, 1i32);
|
||||
} else {
|
||||
bres = buildone(selfdir, src, 0, outp, objstem, incs, nil, 1i32);
|
||||
};
|
||||
// E3-C1: separate compilation is the sole build path (task #87).
|
||||
let lf: lflags;
|
||||
lf.libdirs = nil;
|
||||
lf.nlibdirs = 0;
|
||||
lf.libs = nil;
|
||||
lf.nlibs = 0;
|
||||
let bres: i32 = buildonesep(selfdir, src, 0, outp, objstem, incs, &lf, 1i32);
|
||||
if (bres != 0) {
|
||||
if (outstem == nil) { os.remove(pathstr(outp)); };
|
||||
return 1;
|
||||
@@ -2576,7 +2119,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
||||
let tmp: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
tmp.len = os.PATH_MAX;
|
||||
makeruntmp(tmp.ptr);
|
||||
let bres: i32 = buildone(selfdir, path.ptr, 0, tmp.ptr, nil, tincs.ptr, nil, 1i32);
|
||||
let bres: i32 = buildonesep(selfdir, path.ptr, 0, tmp.ptr, nil, tincs.ptr, nil, 1i32);
|
||||
if (bres != 0) {
|
||||
fail += 1;
|
||||
cerr("FAIL ");
|
||||
@@ -2639,16 +2182,11 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
// twin: cmd/ww/main.c do_test (error wording identical).
|
||||
let compileonly: i32 = 0;
|
||||
let outstem: *u8 = nil;
|
||||
// #79 E1: --sep routes a single-file test through the separate-
|
||||
// compilation producer (buildonesep, istest=1). Dir-mode --sep is
|
||||
// deferred to E2. cstage twin: do_test use_sep.
|
||||
let usesep: i32 = 0;
|
||||
let i: i32 = start;
|
||||
for (i < argc) {
|
||||
let p: *u8 = argv[i];
|
||||
if (p[0u64] == 45u8) { // '-'
|
||||
if (cstreqlit(p, "--sep")) { // #79: separate-compile test
|
||||
usesep = 1;
|
||||
if (cstreqlit(p, "--sep")) { // E3-C1: sep is sole path; accepted no-op (#87)
|
||||
} else { if (p[1u64] == 73u8) { // '-I'
|
||||
let dir: *u8 = nil;
|
||||
if (p[2u64] != 0u8) {
|
||||
@@ -2698,7 +2236,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
// single-file mode: literal *.ww that exists
|
||||
if (cstrendswithlit(target, ".ww")) {
|
||||
if (os.access(pathstr(target), 0i32) == 0) {
|
||||
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem, patarg, usesep);
|
||||
return runsingletest(selfdir, target, incs.ptr, compileonly, outstem, patarg);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2706,11 +2244,6 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
cerr("ww test: -c/-o need a single test file\n");
|
||||
return 2;
|
||||
};
|
||||
// #79 E1: dir-mode --sep deferred to E2; single-file proves the path.
|
||||
if (usesep != 0) {
|
||||
cerr("ww test: --sep needs a single test file\n");
|
||||
return 2;
|
||||
};
|
||||
// #17: a name-filter pattern is per-binary; dir mode builds one binary
|
||||
// per *_test.ww, so a single pattern can't route. cstage twin parity.
|
||||
if (patarg != nil) {
|
||||
|
||||
Reference in New Issue
Block a user