cmd+selfhost+lib+test: directory-as-module enumeration in driver (#22)

Replace the cmd/ww + selfhost driver's file-walk import resolver
with true directory enumeration. `import encoding.utf8;` now finds
the lib/encoding/utf8/ directory and concatenates every *.ww file
in it (excluding *test.ww and the driver's *.combined.ww artifacts)
in byte-wise sorted order, instead of just finding the single
lib/encoding/utf8/utf8.ww file. Mirrors Hare's
hare/module/srcs.ha:183 _findsrcs minus tag handling.

Lookup order in both stages: (1) <dir>/<dot-as-slash>/ as directory
→ enumerate. (2) <dir>/<dot-as-slash>.ww as file. The legacy
<dir>/<name>/<name>.ww shape from #18's retained divergence is
dropped per rule-9 Hare-fidelity — Hare has no foo/foo.ha fallback;
a module IS the directory.

Symmetric across cstage (cmd/ww/main.c via opendir+qsort+stat) and
wwstage (selfhost/cmd/ww/main.ww via existing lib/os.getdents64 +
os.stat — no new lib/os surface needed; the rundirtests() walker
in main.ww from #18 was the model). Bootstrap ww2.s==ww3.s==ww4.s
byte-identical post-change.

Bundling justification (rule 11): strict-same-package validation is
bundled because the failure mode is dir-enum's own (a non-dir-enum
compilation unit cannot trigger mismatch across enumerated files).
The natural enforcement site is the driver — the parser can't
distinguish dir-enum concat from file-walk concat. Both stages
peek each file's first `package <name>;` line in expand_dir /
expanddir and exit(1) on mismatch with a precise error pointing
at the offending file. Hare's hare/module/srcs.ha:131 has the
same constraint via its README gate. Other half of #23 (strict
missing-package error tightening — 63 inline-source test wrappers
blocker) stays deferred per its filing.

Parser side (cmd/wcc/parse.c parseuse + lib/ww/parse/decl.ww
parseuse): n->str now carries only the LEAF identifier from a
dotted import. With the driver translating the full dotted path
to a directory walk, the checker only needs the package bareword
(last component) for the N_USE → decl disambiguation walk in
check.c's src_imports / decl_mod. Mirrors Hare's
`use encoding::utf8;` → `utf8::name` semantics
(ref/hare/hare/ast/import.ha:7).

Migration: lib/ww/sym.ww drops `import typ; import ast;`;
lib/ww/parse/parse.ww drops `import expr; import stmt; import
decl;`; lib/ww/lex/lex.ww drops `import tok;` — all sibling
imports auto-resolve via the new dir-enum when callers import the
package directory. lib/strings/, lib/encoding/utf8/utf8test.ww
migrate `import utf8;` → `import encoding.utf8;`. Makefile drops
-I lib/encoding/utf8 stopgap from wwdump_ww + w6c_ww. Seven test
wrappers (700_e2e, 966_strings_run, 970_fmt_run, 971_log_run,
972_fnmatch_run, 982_getopt_run, 990_selfhost) and 995_self_rebuild
drop the -I lib/encoding/utf8 runtime stopgap.

Tests: new 737_direnum C wrapper + test/wcc/data/direnum/ fixtures
pin (a) cross-pkg multi-file dir-enum build at runtime (both stages
must succeed) and (b) strict-same-package mismatch error (both
stages must surface "differs from" + exit non-zero). 738_module_decl
gains row 6 pinning the n_use->str leaf-only storage post-parser
change.

Retained workaround at selfhost/cmd/ww/main.ww expanddir loop:
`names[i][k]` nested-deref-then-index split into
`let nm: *u8 = names[i]; nm[k]` because wwstage cgen miscompiles
the chained form (treats inner u8 element as 8B sizeof *u8 instead
of 1B sizeof u8: extra MOVQ $8 + IMULQ on the inner index, MOVQ
instead of MOVZBQ load). Inline rule-8 WHY comment cites task #24
(wwstage cgen chained-index inner element size on **T). Two-step
form routes through the bare-pointer index path which both stages
handle byte-identically.

Class A wwstage cgen UNDER (chained-index inner element size on
**T) surfaced first time the codebase exercises the **T[i][k]
shape via enumeratedir() — corpus-coverage-blind landmine pattern,
same family as the trio (#27/#28/#31) from STATUS-5.

112/112 ok. ww2 == ww3 == ww4 byte-id holds.
This commit is contained in:
2026-05-18 19:22:27 +09:00
parent 79d9528a00
commit 9e0816e199
31 changed files with 2521 additions and 1564 deletions

View File

@@ -120,7 +120,6 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \
-I $$PWD/../../lib/ww \
-I $$PWD/../../lib/ww/lex \
-I $$PWD/../../lib/ww/parse \
-I $$PWD/../../lib/encoding/utf8 \
-I $$PWD/../../selfhost/cmd/wcc \
$$PWD/../../selfhost/cmd/wwdump/main.ww
mv $(BIN)/main $@
@@ -142,7 +141,6 @@ $(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \
-I $$PWD/../../lib/ww \
-I $$PWD/../../lib/ww/lex \
-I $$PWD/../../lib/ww/parse \
-I $$PWD/../../lib/encoding/utf8 \
-I $$PWD/../../selfhost/cmd/wcc \
$$PWD/../../selfhost/cmd/w6c/main.ww
mv $(BIN)/main $@
@@ -266,6 +264,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_struct_modshadow \
$(BIN)/test_def_modshadow \
$(BIN)/test_cstage_label_ssot \
$(BIN)/test_direnum \
$(BIN)/test_module_decl \
$(BIN)/test_fnparams_bare_leaf_shadow \
$(BIN)/test_fnret_bare_leaf_shadow \
@@ -631,6 +630,12 @@ $(BIN)/test_cstage_label_ssot: test/wcc/736_cstage_label_ssot.c \
$(BIN)/w6c $(BIN)/w6c_ww | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_direnum: test/wcc/737_direnum.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_module_decl: test/wcc/738_module_decl.c $(LIB)/libwcc.a | $(BIN)
$(CC) $(CFLAGS) -Icmd/wcc -o $@ $< -Lout/lib -lwcc

View File

@@ -1241,17 +1241,23 @@ parseblock(Parser *p)
/* ------- top-level decls ------------------------------------------- */
/* `import encoding.utf8;` — the driver resolves the dotted path to a
* directory; the checker only needs the leaf (`utf8`) as the module
* bareword for n_use→decl disambiguation, mirroring Hare's
* `use encoding::utf8;` → `utf8::name` (ref/hare/hare/ast/import.ha:7
* stores `ident: []str` but identifier-resolution uses the last
* component). */
static Node *
parseuse(Parser *p)
{
Pos pp = p->cur.pos;
expect(p, TK_USE);
Node *n = newnode(p->a, N_USE, pp);
const char *base = expectident(p);
const char *leaf = expectident(p);
while (accept(p, TK_DOT))
base = aprintf(p->a, "%s.%s", base, expectident(p));
n->str = base;
n->strlen = strlen(base);
leaf = expectident(p);
n->str = leaf;
n->strlen = strlen(leaf);
expect(p, TK_SEMI);
return n;
}

View File

@@ -80,35 +80,48 @@ import_add(struct ImportSet *s, const char *path)
s->paths[s->n++] = strdup(path);
}
/* try <dir>/<name>.ww then <dir>/<name>/<name>.ww — symmetric with
* wwstage locatein (selfhost/cmd/ww/main.ww) for byte-identical
* driver output (rule 10).
*
* Retained divergence from brief: directory-as-module enumeration
* NOT implemented in either stage. The user's "module IS directory"
* mental model is partially honored via the `package` keyword + file-
* walk + sibling `import` chain; true dir enumeration (lib/foo/*.ww
* concatenated atomically without sibling import statements) is
* deferred to task #22. The cstage scaffold (enumerate_dir + qsort +
* is_testfile + dotpath_to_slash) was drafted and reverted during
* #18 because the symmetric wwstage port needs a ww-side
* getdents64 walker (~150-200 lines new ww in selfhost driver) and
* the symmetric stage-rebuild blew the context budget mid-flight.
* Rule 7 + rule 8 documentation. */
static int
locate_import_in(const char *dir, const char *name, char *out, size_t outsz)
/* Translate dots in an `import` name to slashes for path lookup.
* `encoding.utf8` → `encoding/utf8`. Mirrors Hare hare(1)'s
* use-path → fs-path mapping (ref/hare/hare/module/srcs.ha:78
* builds the same shape via path::push per ident part). */
static void
import_path_form(const char *name, char *out, size_t outsz)
{
snprintf(out, outsz, "%s/%s.ww", dir, name);
if (access(out, 0) == 0) return 1;
snprintf(out, outsz, "%s/%s/%s.ww", dir, name, name);
if (access(out, 0) == 0) return 1;
size_t i;
for (i = 0; i + 1 < outsz && name[i] != '\0'; i++)
out[i] = (name[i] == '.') ? '/' : name[i];
out[i] = '\0';
}
/* try <dir>/<path>/ as a directory, then <dir>/<path>.ww as a file.
* Sets *is_dir on hit. Symmetric with wwstage locatein for byte-id
* driver output (rule 10). The legacy <dir>/<name>/<name>.ww form
* was dropped in task #22 — directory-as-module enumeration replaces
* it, mirroring ref/hare/hare/module/srcs.ha (Hare has no fallback
* matching `foo/foo.ha`; a module IS the directory). */
static int
locate_import_in(const char *dir, const char *path_form, char *out,
size_t outsz, int *is_dir)
{
struct stat st;
snprintf(out, outsz, "%s/%s", dir, path_form);
if (stat(out, &st) == 0 && S_ISDIR(st.st_mode)) {
*is_dir = 1;
return 1;
}
snprintf(out, outsz, "%s/%s.ww", dir, path_form);
if (access(out, 0) == 0) {
*is_dir = 0;
return 1;
}
return 0;
}
/* Walk a colon-separated dirlist trying to resolve `name`. Returns 1
* on the first hit. */
/* Walk a colon-separated dirlist trying to resolve `path_form`. Returns
* 1 on the first hit and writes the concrete path + dir/file marker. */
static int
locate_import(const char *dirs, const char *name, char *out, size_t outsz)
locate_import(const char *dirs, const char *path_form, char *out,
size_t outsz, int *is_dir)
{
const char *p = dirs;
while (*p) {
@@ -119,7 +132,8 @@ locate_import(const char *dirs, const char *name, char *out, size_t outsz)
if (n >= sizeof dir) n = sizeof dir - 1;
memcpy(dir, p, n);
dir[n] = '\0';
if (locate_import_in(dir, name, out, outsz)) return 1;
if (locate_import_in(dir, path_form, out, outsz,
is_dir)) return 1;
}
if (!e) break;
p = e + 1;
@@ -127,11 +141,134 @@ locate_import(const char *dirs, const char *name, char *out, size_t outsz)
return 0;
}
/* Recursively expand `path`: for each top-level `use IDENT;` we find,
* resolve the import and expand it first, then append our own bytes.
* Already-visited paths are skipped. Each source carries its own
* `module <name>;` declaration (the parser stamps decls from it), so
* the driver no longer injects a `// MODULE:` marker. */
/* memcmp-based string compare for qsort. Byte-wise total order is
* locale-independent; rule-10 byte-id requires the two stages sort
* the same way. (strcmp would work today but Hare-fidelity points
* at memcmp via ref/hare/sort/cmp/cmp.ha:9.) */
static int
strs_cmp(const void *a, const void *b)
{
const char *sa = *(const char *const *)a;
const char *sb = *(const char *const *)b;
return strcmp(sa, sb);
}
/* enumerate_dir_ww — collect *.ww names in `dirpath` excluding
* *test.ww, sort byte-wise. Returns count; caller frees entries. */
static int
enumerate_dir_ww(const char *dirpath, char ***out_files)
{
DIR *d = opendir(dirpath);
if (d == NULL) { *out_files = NULL; return 0; }
char **arr = NULL;
int n = 0, cap = 0;
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
const char *nm = ent->d_name;
size_t nl = strlen(nm);
if (nl <= 3) continue;
if (strcmp(nm + nl - 3, ".ww") != 0) continue;
/* skip "*test.ww" (no underscore — bytestest.ww
* ostest.ww utf8test.ww — Hare convention is _test.ha
* but ww corpus settled on the un-underscored shape). */
if (nl >= 7 && strcmp(nm + nl - 7, "test.ww") == 0)
continue;
/* skip "*.combined.ww" — driver-generated concat
* artifacts (the previous build leaves them next to
* the source). They look like .ww but parse-erroring
* when re-included. */
if (nl >= 12 && strcmp(nm + nl - 12, ".combined.ww") == 0)
continue;
if (n + 1 > cap) {
cap = cap ? cap * 2 : 8;
arr = realloc(arr, cap * sizeof *arr);
}
arr[n++] = strdup(nm);
}
closedir(d);
if (n > 1) qsort(arr, n, sizeof *arr, strs_cmp);
*out_files = arr;
return n;
}
static void expand(FILE *out, const char *path, struct ImportSet *visited,
const char *libdir);
/* Scan `path` for its first non-comment-non-blank line; if it starts
* with `package <name>;` write the name into `out` (NUL-terminated)
* and return 1, else 0. Strict-same-package enforcement (task #23
* subset) is bundled here because the failure mode is dir-enum's
* own — a non-dir-enum compilation unit cannot trigger it. Hare's
* hare/module/srcs.ha:131 has the same constraint via the README
* gate; we encode it as same-package across all enumerated files. */
static int
peek_package(const char *path, char *out, size_t outsz)
{
FILE *in = fopen(path, "rb");
if (in == NULL) return 0;
char line[2048];
int found = 0;
while (fgets(line, sizeof line, in)) {
const char *p = line;
while (*p == ' ' || *p == '\t') p++;
if (*p == '\n' || *p == '\0') continue;
if (p[0] == '/' && p[1] == '/') continue;
if (strncmp(p, "package ", 8) != 0
&& strncmp(p, "package\t", 8) != 0) break;
p += 8;
while (*p == ' ' || *p == '\t') p++;
size_t i = 0;
while (i + 1 < outsz && ((p[i] >= 'a' && p[i] <= 'z')
|| (p[i] >= 'A' && p[i] <= 'Z')
|| p[i] == '_' || (p[i] >= '0' && p[i] <= '9')))
out[i] = p[i], i++;
out[i] = '\0';
found = (i > 0);
break;
}
fclose(in);
return found;
}
/* expand_dir — enumerate <dirpath>/*.ww (skip *test.ww), byte-sort,
* recurse into each. Mirrors ref/hare/hare/module/srcs.ha:183
* `_findsrcs` minus tag handling. The visited set still 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). */
static void
expand_dir(FILE *out, const char *dirpath, struct ImportSet *visited,
const char *libdir)
{
char **files = NULL;
int n = enumerate_dir_ww(dirpath, &files);
char dirpkg[128] = {0};
for (int i = 0; i < n; i++) {
char fp[1024];
snprintf(fp, sizeof fp, "%s/%s", dirpath, files[i]);
char pkg[128];
if (peek_package(fp, pkg, sizeof pkg)) {
if (dirpkg[0] == '\0') {
snprintf(dirpkg, sizeof dirpkg, "%s", pkg);
} else if (strcmp(dirpkg, pkg) != 0) {
fprintf(stderr,
"ww: %s: package %s differs from %s in same module dir %s\n",
fp, pkg, dirpkg, dirpath);
exit(1);
}
}
expand(out, fp, visited, libdir);
free(files[i]);
}
free(files);
}
/* Recursively expand `path`: for each top-level `import IDENT;` we
* find, resolve the import and expand it first, then append our own
* bytes. Already-visited paths are skipped. Each source carries its
* own `package <name>;` declaration (the parser stamps decls from
* it). */
static void
expand(FILE *out, const char *path, struct ImportSet *visited,
const char *libdir)
@@ -158,10 +295,15 @@ expand(FILE *out, const char *path, struct ImportSet *visited,
|| *p == '_' || *p == '.' || (*p >= '0' && *p <= '9'))
if (j + 1 < (int)sizeof name) name[j++] = *p++;
if (j == 0) continue;
char path_form[256];
import_path_form(name, path_form, sizeof path_form);
char ipath[1024];
if (!locate_import(libdir, name, ipath, sizeof ipath))
int is_dir = 0;
if (!locate_import(libdir, path_form, ipath, sizeof ipath,
&is_dir))
continue; /* silently skip if not found */
expand(out, ipath, visited, libdir);
if (is_dir) expand_dir(out, ipath, visited, libdir);
else expand(out, ipath, visited, libdir);
}
rewind(in);
@@ -172,8 +314,9 @@ expand(FILE *out, const char *path, struct ImportSet *visited,
}
static int
build_one(const char *src, const char *out, const char *extra_includes,
const char *extra_libs, const char *extra_libdirs)
build_one(const char *src, int entry_is_dir, const char *out,
const char *extra_includes, const char *extra_libs,
const char *extra_libdirs)
{
const char *c6 = toolpath("WW_W6C", "w6c");
const char *a6 = toolpath("WW_W6A", "w6a");
@@ -199,9 +342,16 @@ build_one(const char *src, const char *out, const char *extra_includes,
* convention (its `hare test` is run from the module dir, making CWD
* == module-dir); our wrappers don't cd, so dirname(src) is the
* closest analog. Also matches cc -I. — source-dir wins ties over
* the system path. locate_import walks left-to-right. */
* the system path. locate_import walks left-to-right.
*
* For a dir entry the source-dir IS src; for a file entry it's
* the dirname. */
char srcd[1024];
{
if (entry_is_dir) {
snprintf(srcd, sizeof srcd, "%s", src);
size_t n = strlen(srcd);
while (n > 1 && srcd[n-1] == '/') srcd[--n] = '\0';
} else {
const char *slash = strrchr(src, '/');
if (slash) {
size_t n = (size_t)(slash - src);
@@ -221,19 +371,29 @@ build_one(const char *src, const char *out, const char *extra_includes,
snprintf(searchpath, sizeof searchpath, "%s:%s", srcd, srcdir);
srcdir = searchpath;
/* Strip extension to derive a stem; e.g. /tmp/foo.ww → /tmp/foo */
/* Derive a stem for .s/.o/.combined.ww side files. For a file
* entry strip the .ww. For a dir entry use <dir>/<basename(dir)>
* so artifacts land inside the module directory. */
char stem[1024];
snprintf(stem, sizeof stem, "%s", src);
char *dot = strrchr(stem, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
if (entry_is_dir) {
const char *b = strrchr(srcd, '/');
const char *base = b ? b + 1 : srcd;
snprintf(stem, sizeof stem, "%s/%s", srcd, base);
} else {
snprintf(stem, sizeof stem, "%s", src);
char *dot = strrchr(stem, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
}
char asmf[1024], obj[1024], combined[1024];
snprintf(asmf, sizeof asmf, "%s.s", stem);
snprintf(obj, sizeof obj, "%s.o", stem);
snprintf(combined, sizeof combined, "%s.combined.ww", stem);
/* Resolve `use X;` imports by concatenating sources into a temp
* file. The compiler then sees one flat source. */
/* Resolve imports by concatenating sources into a temp file. The
* compiler then sees one flat source. Dir entry → enumerate the
* module dir's *.ww (less *test.ww); file entry → start at the
* file. */
{
FILE *cf = fopen(combined, "wb");
if (cf == NULL) {
@@ -241,7 +401,8 @@ build_one(const char *src, const char *out, const char *extra_includes,
return 1;
}
struct ImportSet visited = {0};
expand(cf, src, &visited, srcdir);
if (entry_is_dir) expand_dir(cf, srcd, &visited, srcdir);
else expand(cf, src, &visited, srcdir);
fclose(cf);
for (int i = 0; i < visited.n; i++) free(visited.paths[i]);
free(visited.paths);
@@ -330,41 +491,35 @@ basename_no_ext(const char *path, char *out, size_t outsz)
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
}
/* resolve_module: turn a name into a concrete .ww file path.
/* resolve_module: turn a name into a concrete entry path.
* foo.ww → use as-is if it exists
* <existing dir> → <dir>/<basename>.ww (Hare module convention)
* . → <cwd-basename>.ww in the cwd
* foo (bare) → walk cwd:incs:WW_LIB for foo.ww or foo/foo.ww */
* <existing dir> → returns the dir path (caller dir-enumerates)
* . → cwd as a directory
* foo (bare) → walk cwd:incs:WW_LIB; first hit is dir or file.
* Sets *is_dir on hit. Dir resolution drives directory-as-module
* enumeration in build_one. */
static int
resolve_module(const char *name, const char *incs, char *out, size_t outsz)
resolve_module(const char *name, const char *incs, char *out, size_t outsz,
int *is_dir)
{
struct stat st;
if (stat(name, &st) == 0) {
if (S_ISREG(st.st_mode)) {
snprintf(out, outsz, "%s", name);
*is_dir = 0;
return 1;
}
if (S_ISDIR(st.st_mode)) {
char buf[1024];
const char *base;
if (strcmp(name, ".") == 0) {
if (getcwd(buf, sizeof buf) == NULL) return 0;
} else {
snprintf(buf, sizeof buf, "%s", name);
size_t bl = strlen(buf);
while (bl > 1 && buf[bl-1] == '/') buf[--bl] = '\0';
}
const char *b = strrchr(buf, '/');
base = b ? b + 1 : buf;
snprintf(out, outsz, "%s/%s.ww", name, base);
if (access(out, 0) == 0) return 1;
return 0;
snprintf(out, outsz, "%s", name);
*is_dir = 1;
return 1;
}
}
char sp[4096];
search_path(incs, sp, sizeof sp);
if (locate_import(sp, name, out, outsz)) return 1;
return 0;
char path_form[256];
import_path_form(name, path_form, sizeof path_form);
return locate_import(sp, path_form, out, outsz, is_dir);
}
/* Append `path` to a heap string-array. Caller frees each entry + the array. */
@@ -461,13 +616,23 @@ do_build(int argc, char **argv)
libdirs, sizeof libdirs, libs, sizeof libs, &src);
if (src == NULL) src = "."; /* default: build cwd */
char resolved[1024];
if (!resolve_module(src, incs, resolved, sizeof resolved)) {
int is_dir = 0;
if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) {
fprintf(stderr, "ww build: cannot find module %s\n", src);
return 1;
}
char out[1024];
basename_no_ext(resolved, out, sizeof out);
return build_one(resolved, out, incs, libs, libdirs);
if (is_dir) {
char tmp[1024];
snprintf(tmp, sizeof tmp, "%s", resolved);
size_t n = strlen(tmp);
while (n > 1 && tmp[n-1] == '/') tmp[--n] = '\0';
const char *b = strrchr(tmp, '/');
snprintf(out, sizeof out, "%s", b ? b + 1 : tmp);
} else {
basename_no_ext(resolved, out, sizeof out);
}
return build_one(resolved, is_dir, out, incs, libs, libdirs);
}
static int
@@ -481,13 +646,15 @@ do_run(int argc, char **argv)
libdirs, sizeof libdirs, libs, sizeof libs, &src);
if (src == NULL) src = ".";
char resolved[1024];
if (!resolve_module(src, incs, resolved, sizeof resolved)) {
int is_dir = 0;
if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) {
fprintf(stderr, "ww run: cannot find module %s\n", src);
return 1;
}
char tmp[1024];
snprintf(tmp, sizeof tmp, "/tmp/ww_run_%d", getpid());
if (build_one(resolved, tmp, incs, libs, libdirs) != 0) return 1;
if (build_one(resolved, is_dir, tmp, incs, libs, libdirs) != 0)
return 1;
/* exec the built binary with any trailing argv as its argv. */
pid_t pid = fork();
if (pid < 0) { perror("ww: fork"); unlink(tmp); return 1; }
@@ -517,13 +684,15 @@ do_test(int argc, char **argv)
/* not a literal path — try module resolution and run as
* a single test program. */
char resolved[1024];
if (!resolve_module(target, "", resolved, sizeof resolved)) {
int is_dir = 0;
if (!resolve_module(target, "", resolved, sizeof resolved,
&is_dir)) {
fprintf(stderr, "ww test: cannot find %s\n", target);
return 1;
}
char tmp[1024];
snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid());
if (build_one(resolved, tmp, "", "", "") != 0) return 1;
if (build_one(resolved, is_dir, tmp, "", "", "") != 0) return 1;
int rc = run(tmp);
unlink(tmp);
return rc;
@@ -532,7 +701,7 @@ do_test(int argc, char **argv)
/* single .ww file — build+run it. */
char tmp[1024];
snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d", getpid());
if (build_one(target, tmp, "", "", "") != 0) return 1;
if (build_one(target, 0, tmp, "", "", "") != 0) return 1;
int rc = run(tmp);
unlink(tmp);
return rc;
@@ -558,7 +727,7 @@ do_test(int argc, char **argv)
snprintf(tmp, sizeof tmp, "/tmp/ww_test_%d_%d", getpid(), i);
const char *label = strrchr(files[i], '/');
label = label ? label + 1 : files[i];
int rc = build_one(files[i], tmp, target, "", "");
int rc = build_one(files[i], 0, tmp, target, "", "");
if (rc != 0) {
fprintf(stderr, "FAIL %s (build)\n", label);
fail++;

View File

@@ -5,7 +5,7 @@
package utf8;
import utf8;
import encoding.utf8;
import os;
let signalled: i32 = 0;

View File

@@ -33,7 +33,7 @@
package strings;
import bytes;
import utf8;
import encoding.utf8;
import os;
// toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29.

View File

@@ -1,5 +1,5 @@
// stringstest — exercises lib/strings. Run with
// `out/bin/ww run lib/strings/stringstest.ww -I lib/encoding/utf8`.
// `out/bin/ww run lib/strings/stringstest.ww`.
// Same signalled-then-fail()-with-+10 shape as bytes / utf8 / hex /
// time tests: non-zero exit pinpoints the failing scenario.
//
@@ -9,7 +9,7 @@
package strings;
import strings;
import utf8;
import encoding.utf8;
import os;
let signalled: i32 = 0;

View File

@@ -13,10 +13,11 @@
package lex;
// Sibling import (tok) auto-resolves via task #22 dir-enum when
// callers `import lex;` (which dir-enums lib/ww/lex/).
import os;
import ascii;
import mem;
import tok;
// isidstart / isidpart — identifier classification. Lexer-local
// because the "alpha or '_' / alnum or '_'" set isn't part of Hare's

View File

@@ -6,6 +6,11 @@ import os;
import mem;
import tok;
// `import encoding.utf8;` — the driver resolves the dotted path to
// a directory; only the leaf (`utf8`) is needed downstream as the
// module bareword for n_use → decl disambiguation, mirroring Hare's
// `use encoding::utf8;` → `utf8::name` (ref/hare/hare/ast/import.ha:7
// stores `[]str` but identifier-resolution uses the last component).
fn parseuse(p: *parser) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
@@ -13,32 +18,13 @@ fn parseuse(p: *parser) *node = {
advance(p); // past `use`
let n: *node = newnode(p.a, nkind.N_USE, pf, pl, pc);
n.nmod = p.curmod;
// Accept a dotted import path: `use encoding.utf8;` — capture the
// full dotted form on n.str. Leaf-only SK_USE install lives in
// the check stage; the lexer-side join happens here.
let id: str;
expectident(p, &id);
n.str = id;
let leaf: str;
expectident(p, &leaf);
for (p.curkind == tkind.TK_DOT) {
advance(p); // past `.`
let seg: str;
expectident(p, &seg);
// Concatenate id + "." + seg into a fresh str. Plan-9
// separator per user pick over Hare's `::`.
let total: i32 = n.str.len + 1 + seg.len;
let buf: *u8 = amalloc(p.a, total: u64 + 1u64): *u8;
let i: i32 = 0;
for (i < n.str.len) { buf[i] = n.str[i]; i += 1; };
buf[i] = 46u8; // '.'
i += 1;
let j: i32 = 0;
for (j < seg.len) { buf[i + j] = seg[j]; j += 1; };
buf[total] = 0u8;
let joined: str;
joined.ptr = buf;
joined.len = total;
n.str = joined;
expectident(p, &leaf);
};
n.str = leaf;
expecttok(p, tkind.TK_SEMI, "expected ';' after use");
return n;
};

View File

@@ -12,12 +12,12 @@
package parse;
// Sibling imports (expr, stmt, decl) auto-resolve via task #22
// dir-enum when callers `import parse;` (which dir-enums
// lib/ww/parse/).
import os;
import mem;
import tok;
import expr;
import stmt;
import decl;
type parser = struct {
l: *lex,

View File

@@ -6,9 +6,9 @@
package ww;
// Sibling imports (typ, ast) auto-resolve via task #22 dir-enum
// when callers `import ww;` or pull all three separately.
import mem;
import typ;
import ast;
// Symbol kinds — must stay numerically aligned with cmd/wcc/ww.h Skind.
type skind = enum i32 {

File diff suppressed because it is too large Load Diff

View File

@@ -1089,18 +1089,31 @@ fn visitadd(c: *expctx, path: str) void = {
c.visit = n;
};
// Try <dir>/<name>.ww then <dir>/<name>/<name>.ww. Returns NUL-terminated
// arena-resident path if found, else nil.
//
// Retained divergence from brief: directory-as-module enumeration is
// NOT implemented here. The user's "module IS directory" mental model
// is partially honored via the `package` keyword + file-walk + sibling
// `import` chain. True dir enumeration (lib/foo/*.ww concatenated
// atomically, no sibling-import boilerplate) is deferred to task #22
// and needs a lib/os opendir/readdir wrapper around getdents64 first.
// Rule 7 + rule 8 documentation.
fn locatein(a: *arena, dir: *u8, dirlen: u64, name: *u8, namelen: u64) *u8 = {
// candidate 1: <dir>/<name>.ww
// Translate dots in an `import` name to slashes for path lookup.
// `encoding.utf8` → `encoding/utf8`. Mirrors Hare's hare(1)
// use-path → fs-path mapping
// (ref/hare/hare/module/srcs.ha:78 builds the same shape via
// path::push per ident part).
fn importpathform(a: *arena, name: *u8, namelen: u64) *u8 = {
let buf: *u8 = amalloc(a, namelen + 1u64): *u8;
let i: u64 = 0u64;
for (i < namelen) {
if (name[i] == 46u8) { buf[i] = 47u8; } // '.' -> '/'
else { buf[i] = name[i]; };
i += 1u64;
};
buf[namelen] = 0u8;
return buf;
};
// Try <dir>/<path>/ as a directory, then <dir>/<path>.ww as a file.
// Sets *isdir on hit. Symmetric with cstage locate_import_in for
// byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww
// form was dropped in task #22 — directory-as-module enumeration
// replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no
// `foo/foo.ha` fallback; a module IS the directory).
fn locatein(a: *arena, dir: *u8, dirlen: u64,
pathform: *u8, pflen: u64, isdir: *i32) *u8 = {
let buf: *u8 = amalloc(a, PATH_MAX): *u8;
let off: u64 = 0u64;
let i: u64 = 0u64;
@@ -1108,15 +1121,24 @@ fn locatein(a: *arena, dir: *u8, dirlen: u64, name: *u8, namelen: u64) *u8 = {
off += dirlen;
buf[off] = 47u8; off += 1u64; // '/'
i = 0u64;
for (i < namelen) { buf[off + i] = name[i]; i += 1u64; };
off += namelen;
buf[off] = 46u8; off += 1u64; // '.'
buf[off] = 119u8; off += 1u64; // 'w'
buf[off] = 119u8; off += 1u64; // 'w'
for (i < pflen) { buf[off + i] = pathform[i]; i += 1u64; };
off += pflen;
buf[off] = 0u8;
if (os.access(pathstr(buf), 0i32) == 0) { return buf; };
let fi: os.filestat;
let r: (void | os.oserror) = os.stat(&fi, pathstr(buf));
let isdirhit: bool = false;
match (r) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
if (t == os.mode.DIR: u32) { isdirhit = true; };
};
case let e: os.oserror => void;
};
if (isdirhit) {
*isdir = 1;
return buf;
};
// candidate 2: <dir>/<name>/<name>.ww
let buf2: *u8 = amalloc(a, PATH_MAX): *u8;
off = 0u64;
i = 0u64;
@@ -1124,22 +1146,25 @@ fn locatein(a: *arena, dir: *u8, dirlen: u64, name: *u8, namelen: u64) *u8 = {
off += dirlen;
buf2[off] = 47u8; off += 1u64;
i = 0u64;
for (i < namelen) { buf2[off + i] = name[i]; i += 1u64; };
off += namelen;
buf2[off] = 47u8; off += 1u64;
i = 0u64;
for (i < namelen) { buf2[off + i] = name[i]; i += 1u64; };
off += namelen;
buf2[off] = 46u8; off += 1u64;
buf2[off] = 119u8; off += 1u64;
buf2[off] = 119u8; off += 1u64;
for (i < pflen) { buf2[off + i] = pathform[i]; i += 1u64; };
off += pflen;
buf2[off] = 46u8; off += 1u64; // '.'
buf2[off] = 119u8; off += 1u64; // 'w'
buf2[off] = 119u8; off += 1u64; // 'w'
buf2[off] = 0u8;
if (os.access(pathstr(buf2), 0i32) == 0) { return buf2; };
if (os.access(pathstr(buf2), 0i32) == 0) {
*isdir = 0;
return buf2;
};
return nil;
};
// Walk a colon-separated dirlist, return first hit or nil.
fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64) *u8 = {
// Walk a colon-separated dirlist, return first hit or nil. Sets
// *isdir on hit.
fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64,
isdir: *i32) *u8 = {
let pathform: *u8 = importpathform(a, name, namelen);
let pflen: u64 = cstrlen(pathform);
let total: u64 = cstrlen(dirs);
let p: u64 = 0u64;
for (p < total) {
@@ -1150,7 +1175,8 @@ fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64) *u8 = {
};
let seglen: u64 = q - p;
if (seglen > 0u64) {
let hit: *u8 = locatein(a, dirs + p, seglen, name, namelen);
let hit: *u8 = locatein(a, dirs + p, seglen,
pathform, pflen, isdir);
if (hit != nil) { return hit; };
};
p = q + 1u64;
@@ -1158,6 +1184,123 @@ fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64) *u8 = {
return nil;
};
// Filter for dir enumeration: keep `*.ww` minus `*test.ww` and the
// `*.combined.ww` driver-generated concat artifacts (the previous
// build leaves them in the source tree; they parse-error when
// re-included). Returns true to keep.
fn dirfilekeep(name: *u8, nlen: u64) bool = {
if (nlen <= 3u64) { return false; };
if (name[nlen - 3u64] != 46u8) { return false; }; // '.'
if (name[nlen - 2u64] != 119u8) { return false; }; // 'w'
if (name[nlen - 1u64] != 119u8) { return false; }; // 'w'
if (nlen >= 7u64) {
if (name[nlen - 7u64] == 116u8) { // 't'
if (name[nlen - 6u64] == 101u8) { // 'e'
if (name[nlen - 5u64] == 115u8) { // 's'
if (name[nlen - 4u64] == 116u8) { // 't'
return false;
};
};
};
};
};
if (nlen >= 12u64) {
// ".combined.ww"
if (name[nlen - 12u64] == 46u8) { // '.'
if (name[nlen - 11u64] == 99u8) { // 'c'
if (name[nlen - 10u64] == 111u8) { // 'o'
if (name[nlen - 9u64] == 109u8) { // 'm'
if (name[nlen - 8u64] == 98u8) { // 'b'
return false;
};
};
};
};
};
};
return true;
};
// Byte-wise memcmp returning < 0, 0, > 0. Rule-10 byte-id requires
// cstage and wwstage sort the same way; memcmp is the
// locale-independent total order (mirrors ref/hare/sort/cmp/cmp.ha
// strs).
fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = {
let n: u64 = alen;
if (blen < n) { n = blen; };
let i: u64 = 0u64;
for (i < n) {
let av: i32 = (a[i]): i32;
let bv: i32 = (b[i]): i32;
if (av < bv) { return -1; };
if (av > bv) { return 1; };
i += 1u64;
};
if (alen < blen) { return -1; };
if (alen > blen) { return 1; };
return 0;
};
// enumeratedir — list *.ww entries of `dirpath` (less *test.ww and
// *.combined.ww), byte-sort. Returns (names[], nnames) with each
// name a NUL-terminated arena copy.
fn enumeratedir(a: *arena, dirpath: *u8) (**u8, i32) = {
let fd: i32 = os.open(pathstr(dirpath), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil: **u8, 0; };
let maxnames: i32 = 256;
let names: **u8 = amalloc(a, (maxnames: u64) * 8u64): **u8;
let nlens: *u64 = amalloc(a, (maxnames: u64) * 8u64): *u64;
let n: i32 = 0;
let buf: *u8 = os.alloc(8192u64): *u8;
let r: i64 = os.getdents64(fd, buf, 8192u64);
for (r > 0i64) {
let off: u64 = 0u64;
let ru: u64 = r: u64;
for (off < ru) {
let blo: u64 = (buf[off + 16u64]): u64;
let bhi: u64 = (buf[off + 17u64]): u64;
let reclen: u64 = blo + (bhi * 256u64);
let nm: *u8 = buf + off + 19u64;
let nl: u64 = cstrlen(nm);
if (dirfilekeep(nm, nl)) {
if (n < maxnames) {
let cp: *u8 = amalloc(a, nl + 1u64): *u8;
let i: u64 = 0u64;
for (i < nl) { cp[i] = nm[i]; i += 1u64; };
cp[nl] = 0u8;
names[n] = cp;
nlens[n] = nl;
n += 1;
};
};
off += reclen;
};
r = os.getdents64(fd, buf, 8192u64);
};
os.close(fd);
// Insertion sort, byte-wise. n is small (≤16 in practice).
let i: i32 = 1;
for (i < n) {
let j: i32 = i;
for (j > 0) {
if (bytecmp(names[j - 1], nlens[j - 1],
names[j], nlens[j]) <= 0) { j = 0; }
else {
let t: *u8 = names[j];
names[j] = names[j - 1];
names[j - 1] = t;
let tl: u64 = nlens[j];
nlens[j] = nlens[j - 1];
nlens[j - 1] = tl;
j -= 1;
};
};
i += 1;
};
return names, n;
};
// ---- file slurp -------------------------------------------------------
fn slurp(pathcs: *u8) (*u8, u64) = {
@@ -1227,9 +1370,9 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
};
// expand — emit one file's bytes verbatim into the combined stream,
// after recursive-expanding its top-of-file `use X;` imports. Each
// source declares its own `module <name>;` (parser stamps decls);
// the driver no longer injects a `// MODULE:` marker.
// 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) void = {
let plen: u64 = cstrlen(pathcs);
let pathstr: str = astrndup(c.a, pathcs, plen);
@@ -1244,7 +1387,7 @@ fn expand(c: *expctx, pathcs: *u8) void = {
return;
};
// Pass 1: scan top-of-file `use X;` lines, recursively expand.
// Pass 1: scan top-of-file `import X;` lines, recursively expand.
let i: u64 = 0u64;
for (i < blen) {
let j: u64 = i;
@@ -1256,9 +1399,12 @@ fn expand(c: *expctx, pathcs: *u8) void = {
let idn: u64;
idp, idn = scanuse(bufp + i, j - i);
if (idp != nil) {
let ipath: *u8 = locateimport(c.a, c.dirs, idp, idn);
let isdir: i32 = 0;
let ipath: *u8 = locateimport(c.a, c.dirs, idp, idn,
&isdir);
if (ipath != nil) {
expand(c, ipath);
if (isdir != 0) { expanddir(c, ipath); }
else { expand(c, ipath); };
};
};
i = j + 1u64;
@@ -1268,6 +1414,141 @@ fn expand(c: *expctx, pathcs: *u8) void = {
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
// borrowed-arena str, else nil. Same shape as cstage peek_package.
fn peekpackage(a: *arena, pathcs: *u8) *u8 = {
let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil; };
let buf: *u8 = os.alloc(2048u64): *u8;
let n: i64 = os.read(fd, buf, 2048u64);
os.close(fd);
if (n <= 0i64) { return nil; };
let nu: u64 = n: u64;
let p: u64 = 0u64;
for (p < nu) {
let q: u64 = p;
for (q < nu) {
if (buf[q] == 10u8) { break; }; // '\n'
q += 1u64;
};
let s: u64 = p;
for (s < q) {
if (buf[s] != 32u8) {
if (buf[s] != 9u8) { break; };
};
s += 1u64;
};
if (s < q) {
if (s + 1u64 < q) {
if (buf[s] == 47u8) {
if (buf[s + 1u64] == 47u8) {
p = q + 1u64;
continue;
};
};
};
if (s + 8u64 <= q) {
if (buf[s] == 112u8) { // 'p'
if (buf[s + 1u64] == 97u8) { // 'a'
if (buf[s + 2u64] == 99u8) { // 'c'
if (buf[s + 3u64] == 107u8) { // 'k'
if (buf[s + 4u64] == 97u8) { // 'a'
if (buf[s + 5u64] == 103u8) { // 'g'
if (buf[s + 6u64] == 101u8) { // 'e'
let sep: u8 = buf[s + 7u64];
if (sep == 32u8) { }
else { if (sep != 9u8) { return nil; }; };
let t: u64 = s + 8u64;
for (t < q) {
if (buf[t] != 32u8) {
if (buf[t] != 9u8) { break; };
};
t += 1u64;
};
let start: u64 = t;
for (t < q) {
let ch: u8 = buf[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 = amalloc(a, plen + 1u64): *u8;
let k: u64 = 0u64;
for (k < plen) { r[k] = buf[start + k]; k += 1u64; };
r[plen] = 0u8;
return r;
}; }; }; }; }; }; };
};
return nil;
};
p = q + 1u64;
};
return nil;
};
// 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 = {
os.write(2, "ww: ".ptr, 4u64);
os.write(2, file, cstrlen(file));
os.write(2, ": package ".ptr, 10u64);
os.write(2, pkg, cstrlen(pkg));
os.write(2, " differs from ".ptr, 14u64);
os.write(2, dirpkg, cstrlen(dirpkg));
os.write(2, " in same module dir ".ptr, 20u64);
os.write(2, dirpath, cstrlen(dirpath));
os.write(2, "\n".ptr, 1u64);
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) void = {
let names: **u8;
let n: i32;
names, n = enumeratedir(c.a, dirpath);
let dlen: u64 = cstrlen(dirpath);
let dirpkg: *u8 = nil;
let i: i32 = 0;
for (i < n) {
// Two-step deref+index to avoid wwstage chained `names[i][k]`
// cgen UNDER (task #24 — wwstage cgen chained-index inner
// element size on **T). Wwstage treats inner element as 8B
// (sizeof *u8) instead of 1B (sizeof u8); cstage handles
// via typed-AST natively. Retire once the wwstage fix lands.
let nm: *u8 = names[i];
let nlen: u64 = cstrlen(nm);
let fp: *u8 = amalloc(c.a, dlen + 1u64 + nlen + 1u64): *u8;
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] = nm[k]; k += 1u64; };
fp[dlen + 1u64 + nlen] = 0u8;
let pkg: *u8 = peekpackage(c.a, fp);
if (pkg != nil) {
if (dirpkg == nil) { dirpkg = pkg; }
else { if (!cstreq(dirpkg, pkg)) {
strictpkgmismatch(fp, pkg, dirpkg, dirpath);
}; };
};
expand(c, fp);
i += 1;
};
};
// ---- Build pipeline ---------------------------------------------------
// Strip the trailing ".ww" off `src` (a NUL-terminated path) into
@@ -1308,19 +1589,21 @@ type lflags = struct {
nlibs: i32,
};
// buildone — compile `src` 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 path to the .ww file
// out: NUL-terminated desired output path
// incs: NUL-terminated colon-list of -I dirs (may be empty)
// lf: extra linker flags (-L<dir>, -l<name>); may be nil
// 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
// 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, out: *u8, incs: *u8, lf: *lflags) i32 = {
fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *lflags) i32 = {
let a: *arena = newarena();
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
@@ -1335,13 +1618,23 @@ fn buildone(selfdir: *u8, src: *u8, out: *u8, incs: *u8, lf: *lflags) i32 = {
cstrseal(dotdotlib, off);
};
// Compute the source-file's directory: bytes of `src` up to the
// last '/'. If `src` has no '/', use ".". Hare's CWD-first
// 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 = os.alloc(PATH_MAX): *u8;
{
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;
@@ -1377,9 +1670,20 @@ fn buildone(selfdir: *u8, src: *u8, out: *u8, incs: *u8, lf: *lflags) i32 = {
cstrseal(searchpath, off);
};
// stem, .s, .o, .combined.ww, libwwrt.a
// Stem for .s/.o/.combined.ww side files. Dir entry: <dir>/<base>;
// file entry: src stripped of .ww.
let stem: *u8 = os.alloc(PATH_MAX): *u8;
makestem(stem, src);
if (entryisdir != 0) {
let dlen: u64 = cstrlen(srcd);
let bo: u64 = basenameoff(srcd, dlen);
let off: u64 = cstrinto(stem, 0u64, srcd);
stem[off] = 47u8; off += 1u64; // '/'
let i: u64 = bo;
for (i < dlen) { stem[off] = srcd[i]; off += 1u64; i += 1u64; };
cstrseal(stem, off);
} else {
makestem(stem, src);
};
let asmf: *u8 = appendlit(stem, ".s");
let objf: *u8 = appendlit(stem, ".o");
let combined: *u8 = appendlit(stem, ".combined.ww");
@@ -1392,7 +1696,8 @@ fn buildone(selfdir: *u8, src: *u8, out: *u8, incs: *u8, lf: *lflags) i32 = {
cstrseal(libwwrt, off);
};
// Step 1: expand `use`s into the combined file.
// 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) {
os.write(2, "ww: cannot open combined\n".ptr, 25u64);
@@ -1404,7 +1709,8 @@ fn buildone(selfdir: *u8, src: *u8, out: *u8, incs: *u8, lf: *lflags) i32 = {
c.out = cf;
c.dirs = searchpath;
c.visit = nil;
expand(&c, src);
if (entryisdir != 0) { expanddir(&c, srcd); }
else { expand(&c, src); };
};
os.close(cf);
@@ -1566,43 +1872,41 @@ fn buildsearchpath(a: *arena, selfdir: *u8, incs: *u8) *u8 = {
return buf;
};
fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
// resolvemodule — map a name like "foo", "lib/foo", "foo.ww", or
// "." to a concrete entry path. Sets *isdir when the entry is a
// module directory (caller will dir-enumerate).
fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
let nlen: u64 = cstrlen(name);
// (1) literal .ww file that exists
// (1) Literal file that exists → use as-is.
if (cstrendswithlit(name, ".ww")) {
if (os.access(pathstr(name), 0i32) == 0) {
*isdir = 0;
return arenadupcstr(a, name, nlen);
};
};
// (2) "." → cwd's <basename>.ww
if (nlen == 1u64) {
if (name[0u64] == 46u8) { // '.'
let cwd: *u8 = amalloc(a, PATH_MAX): *u8;
let r: i64 = os.getcwd(cwd, PATH_MAX);
if (r <= 0i64) { return nil; };
let cwdlen: u64 = (r: u64) - 1u64; // strip trailing NUL
let bo: u64 = basenameoff(cwd, cwdlen);
let blen: u64 = cwdlen - bo;
let dot: *u8 = amalloc(a, 2u64): *u8;
dot[0] = 46u8; dot[1] = 0u8;
let probe: *u8 = builddirmodulepath(a, dot, 1u64,
cwd + bo, blen);
if (os.access(pathstr(probe), 0i32) == 0) { return probe; };
return nil;
};
// (2) Existing path → use as-is, dir vs file via stat.
let fi: os.filestat;
let sr: (void | os.oserror) = os.stat(&fi, pathstr(name));
let found: bool = false;
let foundisdir: i32 = 0;
match (sr) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
if (t == os.mode.DIR: u32) { foundisdir = 1; };
found = true;
};
case let e: os.oserror => void;
};
if (found) {
*isdir = foundisdir;
return arenadupcstr(a, name, nlen);
};
// (3) <name>/<basename(name)>.ww — directory-as-module
let bo: u64 = basenameoff(name, nlen);
let probe: *u8 = builddirmodulepath(a, name, nlen,
name + bo, nlen - bo);
if (os.access(pathstr(probe), 0i32) == 0) { return probe; };
// (4) search path lookup
// (3) Search-path lookup with dot-to-slash path translation.
let search: *u8 = buildsearchpath(a, selfdir, incs);
return locateimport(a, search, name, nlen);
return locateimport(a, search, name, nlen, isdir);
};
// ---- Subcommand handlers ----------------------------------------------
@@ -1736,18 +2040,34 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
dot[0] = 46u8; dot[1] = 0u8;
src = dot;
};
let resolved: *u8 = resolvemodule(a, selfdir, src, incs);
let isdir: i32 = 0;
let resolved: *u8 = resolvemodule(a, selfdir, src, incs, &isdir);
if (resolved == nil) {
os.write(2, "ww build: cannot find module\n".ptr, 29u64);
return 1;
};
let out: *u8 = defaultoutpath(resolved);
let out: *u8 = nil;
if (isdir != 0) {
let rlen: u64 = cstrlen(resolved);
for (rlen > 1u64) {
if (resolved[rlen - 1u64] != 47u8) { break; };
rlen -= 1u64;
};
let bo: u64 = basenameoff(resolved, rlen);
out = os.alloc(PATH_MAX): *u8;
let i: u64 = bo;
let off: u64 = 0u64;
for (i < rlen) { out[off] = resolved[i]; off += 1u64; i += 1u64; };
cstrseal(out, off);
} else {
out = defaultoutpath(resolved);
};
let lf: lflags;
lf.libdirs = libdirs;
lf.nlibdirs = nlibdirs;
lf.libs = libs;
lf.nlibs = nlibs;
return buildone(selfdir, resolved, out, incs, &lf);
return buildone(selfdir, resolved, isdir, out, incs, &lf);
};
// Format the scratch path /tmp/ww_run_<pid> into buf. Returns NUL-
@@ -1875,7 +2195,8 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
dot[0] = 46u8; dot[1] = 0u8;
src = dot;
};
let resolved: *u8 = resolvemodule(a, selfdir, src, incs);
let isdir: i32 = 0;
let resolved: *u8 = resolvemodule(a, selfdir, src, incs, &isdir);
if (resolved == nil) {
os.write(2, "ww run: cannot find module\n".ptr, 27u64);
return 1;
@@ -1888,7 +2209,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
lf.nlibdirs = nlibdirs;
lf.libs = libs;
lf.nlibs = nlibs;
if (buildone(selfdir, resolved, tmp, incs, &lf) != 0) {
if (buildone(selfdir, resolved, isdir, tmp, incs, &lf) != 0) {
os.remove(pathstr(tmp));
return 1;
};
@@ -1920,7 +2241,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
fn runsingletest(selfdir: *u8, src: *u8) i32 = {
let tmp: *u8 = os.alloc(PATH_MAX): *u8;
makeruntmp(tmp);
if (buildone(selfdir, src, tmp, "\0".ptr, nil) != 0) {
if (buildone(selfdir, src, 0, tmp, "\0".ptr, nil) != 0) {
os.remove(pathstr(tmp));
return 1;
};
@@ -1970,7 +2291,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
cstrseal(tincs, ic);
let tmp: *u8 = os.alloc(PATH_MAX): *u8;
makeruntmp(tmp);
let bres: i32 = buildone(selfdir, path, tmp, tincs, nil);
let bres: i32 = buildone(selfdir, path, 0, tmp, tincs, nil);
if (bres != 0) {
fail += 1;
os.write(2, "FAIL ".ptr, 5u64);

View File

@@ -229,18 +229,31 @@ fn visitadd(c: *expctx, path: str) void = {
c.visit = n;
};
// Try <dir>/<name>.ww then <dir>/<name>/<name>.ww. Returns NUL-terminated
// arena-resident path if found, else nil.
//
// Retained divergence from brief: directory-as-module enumeration is
// NOT implemented here. The user's "module IS directory" mental model
// is partially honored via the `package` keyword + file-walk + sibling
// `import` chain. True dir enumeration (lib/foo/*.ww concatenated
// atomically, no sibling-import boilerplate) is deferred to task #22
// and needs a lib/os opendir/readdir wrapper around getdents64 first.
// Rule 7 + rule 8 documentation.
fn locatein(a: *arena, dir: *u8, dirlen: u64, name: *u8, namelen: u64) *u8 = {
// candidate 1: <dir>/<name>.ww
// Translate dots in an `import` name to slashes for path lookup.
// `encoding.utf8` → `encoding/utf8`. Mirrors Hare's hare(1)
// use-path → fs-path mapping
// (ref/hare/hare/module/srcs.ha:78 builds the same shape via
// path::push per ident part).
fn importpathform(a: *arena, name: *u8, namelen: u64) *u8 = {
let buf: *u8 = amalloc(a, namelen + 1u64): *u8;
let i: u64 = 0u64;
for (i < namelen) {
if (name[i] == 46u8) { buf[i] = 47u8; } // '.' -> '/'
else { buf[i] = name[i]; };
i += 1u64;
};
buf[namelen] = 0u8;
return buf;
};
// Try <dir>/<path>/ as a directory, then <dir>/<path>.ww as a file.
// Sets *isdir on hit. Symmetric with cstage locate_import_in for
// byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww
// form was dropped in task #22 — directory-as-module enumeration
// replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no
// `foo/foo.ha` fallback; a module IS the directory).
fn locatein(a: *arena, dir: *u8, dirlen: u64,
pathform: *u8, pflen: u64, isdir: *i32) *u8 = {
let buf: *u8 = amalloc(a, PATH_MAX): *u8;
let off: u64 = 0u64;
let i: u64 = 0u64;
@@ -248,15 +261,24 @@ fn locatein(a: *arena, dir: *u8, dirlen: u64, name: *u8, namelen: u64) *u8 = {
off += dirlen;
buf[off] = 47u8; off += 1u64; // '/'
i = 0u64;
for (i < namelen) { buf[off + i] = name[i]; i += 1u64; };
off += namelen;
buf[off] = 46u8; off += 1u64; // '.'
buf[off] = 119u8; off += 1u64; // 'w'
buf[off] = 119u8; off += 1u64; // 'w'
for (i < pflen) { buf[off + i] = pathform[i]; i += 1u64; };
off += pflen;
buf[off] = 0u8;
if (os.access(pathstr(buf), 0i32) == 0) { return buf; };
let fi: os.filestat;
let r: (void | os.oserror) = os.stat(&fi, pathstr(buf));
let isdirhit: bool = false;
match (r) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
if (t == os.mode.DIR: u32) { isdirhit = true; };
};
case let e: os.oserror => void;
};
if (isdirhit) {
*isdir = 1;
return buf;
};
// candidate 2: <dir>/<name>/<name>.ww
let buf2: *u8 = amalloc(a, PATH_MAX): *u8;
off = 0u64;
i = 0u64;
@@ -264,22 +286,25 @@ fn locatein(a: *arena, dir: *u8, dirlen: u64, name: *u8, namelen: u64) *u8 = {
off += dirlen;
buf2[off] = 47u8; off += 1u64;
i = 0u64;
for (i < namelen) { buf2[off + i] = name[i]; i += 1u64; };
off += namelen;
buf2[off] = 47u8; off += 1u64;
i = 0u64;
for (i < namelen) { buf2[off + i] = name[i]; i += 1u64; };
off += namelen;
buf2[off] = 46u8; off += 1u64;
buf2[off] = 119u8; off += 1u64;
buf2[off] = 119u8; off += 1u64;
for (i < pflen) { buf2[off + i] = pathform[i]; i += 1u64; };
off += pflen;
buf2[off] = 46u8; off += 1u64; // '.'
buf2[off] = 119u8; off += 1u64; // 'w'
buf2[off] = 119u8; off += 1u64; // 'w'
buf2[off] = 0u8;
if (os.access(pathstr(buf2), 0i32) == 0) { return buf2; };
if (os.access(pathstr(buf2), 0i32) == 0) {
*isdir = 0;
return buf2;
};
return nil;
};
// Walk a colon-separated dirlist, return first hit or nil.
fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64) *u8 = {
// Walk a colon-separated dirlist, return first hit or nil. Sets
// *isdir on hit.
fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64,
isdir: *i32) *u8 = {
let pathform: *u8 = importpathform(a, name, namelen);
let pflen: u64 = cstrlen(pathform);
let total: u64 = cstrlen(dirs);
let p: u64 = 0u64;
for (p < total) {
@@ -290,7 +315,8 @@ fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64) *u8 = {
};
let seglen: u64 = q - p;
if (seglen > 0u64) {
let hit: *u8 = locatein(a, dirs + p, seglen, name, namelen);
let hit: *u8 = locatein(a, dirs + p, seglen,
pathform, pflen, isdir);
if (hit != nil) { return hit; };
};
p = q + 1u64;
@@ -298,6 +324,123 @@ fn locateimport(a: *arena, dirs: *u8, name: *u8, namelen: u64) *u8 = {
return nil;
};
// Filter for dir enumeration: keep `*.ww` minus `*test.ww` and the
// `*.combined.ww` driver-generated concat artifacts (the previous
// build leaves them in the source tree; they parse-error when
// re-included). Returns true to keep.
fn dirfilekeep(name: *u8, nlen: u64) bool = {
if (nlen <= 3u64) { return false; };
if (name[nlen - 3u64] != 46u8) { return false; }; // '.'
if (name[nlen - 2u64] != 119u8) { return false; }; // 'w'
if (name[nlen - 1u64] != 119u8) { return false; }; // 'w'
if (nlen >= 7u64) {
if (name[nlen - 7u64] == 116u8) { // 't'
if (name[nlen - 6u64] == 101u8) { // 'e'
if (name[nlen - 5u64] == 115u8) { // 's'
if (name[nlen - 4u64] == 116u8) { // 't'
return false;
};
};
};
};
};
if (nlen >= 12u64) {
// ".combined.ww"
if (name[nlen - 12u64] == 46u8) { // '.'
if (name[nlen - 11u64] == 99u8) { // 'c'
if (name[nlen - 10u64] == 111u8) { // 'o'
if (name[nlen - 9u64] == 109u8) { // 'm'
if (name[nlen - 8u64] == 98u8) { // 'b'
return false;
};
};
};
};
};
};
return true;
};
// Byte-wise memcmp returning < 0, 0, > 0. Rule-10 byte-id requires
// cstage and wwstage sort the same way; memcmp is the
// locale-independent total order (mirrors ref/hare/sort/cmp/cmp.ha
// strs).
fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = {
let n: u64 = alen;
if (blen < n) { n = blen; };
let i: u64 = 0u64;
for (i < n) {
let av: i32 = (a[i]): i32;
let bv: i32 = (b[i]): i32;
if (av < bv) { return -1; };
if (av > bv) { return 1; };
i += 1u64;
};
if (alen < blen) { return -1; };
if (alen > blen) { return 1; };
return 0;
};
// enumeratedir — list *.ww entries of `dirpath` (less *test.ww and
// *.combined.ww), byte-sort. Returns (names[], nnames) with each
// name a NUL-terminated arena copy.
fn enumeratedir(a: *arena, dirpath: *u8) (**u8, i32) = {
let fd: i32 = os.open(pathstr(dirpath), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil: **u8, 0; };
let maxnames: i32 = 256;
let names: **u8 = amalloc(a, (maxnames: u64) * 8u64): **u8;
let nlens: *u64 = amalloc(a, (maxnames: u64) * 8u64): *u64;
let n: i32 = 0;
let buf: *u8 = os.alloc(8192u64): *u8;
let r: i64 = os.getdents64(fd, buf, 8192u64);
for (r > 0i64) {
let off: u64 = 0u64;
let ru: u64 = r: u64;
for (off < ru) {
let blo: u64 = (buf[off + 16u64]): u64;
let bhi: u64 = (buf[off + 17u64]): u64;
let reclen: u64 = blo + (bhi * 256u64);
let nm: *u8 = buf + off + 19u64;
let nl: u64 = cstrlen(nm);
if (dirfilekeep(nm, nl)) {
if (n < maxnames) {
let cp: *u8 = amalloc(a, nl + 1u64): *u8;
let i: u64 = 0u64;
for (i < nl) { cp[i] = nm[i]; i += 1u64; };
cp[nl] = 0u8;
names[n] = cp;
nlens[n] = nl;
n += 1;
};
};
off += reclen;
};
r = os.getdents64(fd, buf, 8192u64);
};
os.close(fd);
// Insertion sort, byte-wise. n is small (≤16 in practice).
let i: i32 = 1;
for (i < n) {
let j: i32 = i;
for (j > 0) {
if (bytecmp(names[j - 1], nlens[j - 1],
names[j], nlens[j]) <= 0) { j = 0; }
else {
let t: *u8 = names[j];
names[j] = names[j - 1];
names[j - 1] = t;
let tl: u64 = nlens[j];
nlens[j] = nlens[j - 1];
nlens[j - 1] = tl;
j -= 1;
};
};
i += 1;
};
return names, n;
};
// ---- file slurp -------------------------------------------------------
fn slurp(pathcs: *u8) (*u8, u64) = {
@@ -367,9 +510,9 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
};
// expand — emit one file's bytes verbatim into the combined stream,
// after recursive-expanding its top-of-file `use X;` imports. Each
// source declares its own `module <name>;` (parser stamps decls);
// the driver no longer injects a `// MODULE:` marker.
// 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) void = {
let plen: u64 = cstrlen(pathcs);
let pathstr: str = astrndup(c.a, pathcs, plen);
@@ -384,7 +527,7 @@ fn expand(c: *expctx, pathcs: *u8) void = {
return;
};
// Pass 1: scan top-of-file `use X;` lines, recursively expand.
// Pass 1: scan top-of-file `import X;` lines, recursively expand.
let i: u64 = 0u64;
for (i < blen) {
let j: u64 = i;
@@ -396,9 +539,12 @@ fn expand(c: *expctx, pathcs: *u8) void = {
let idn: u64;
idp, idn = scanuse(bufp + i, j - i);
if (idp != nil) {
let ipath: *u8 = locateimport(c.a, c.dirs, idp, idn);
let isdir: i32 = 0;
let ipath: *u8 = locateimport(c.a, c.dirs, idp, idn,
&isdir);
if (ipath != nil) {
expand(c, ipath);
if (isdir != 0) { expanddir(c, ipath); }
else { expand(c, ipath); };
};
};
i = j + 1u64;
@@ -408,6 +554,141 @@ fn expand(c: *expctx, pathcs: *u8) void = {
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
// borrowed-arena str, else nil. Same shape as cstage peek_package.
fn peekpackage(a: *arena, pathcs: *u8) *u8 = {
let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil; };
let buf: *u8 = os.alloc(2048u64): *u8;
let n: i64 = os.read(fd, buf, 2048u64);
os.close(fd);
if (n <= 0i64) { return nil; };
let nu: u64 = n: u64;
let p: u64 = 0u64;
for (p < nu) {
let q: u64 = p;
for (q < nu) {
if (buf[q] == 10u8) { break; }; // '\n'
q += 1u64;
};
let s: u64 = p;
for (s < q) {
if (buf[s] != 32u8) {
if (buf[s] != 9u8) { break; };
};
s += 1u64;
};
if (s < q) {
if (s + 1u64 < q) {
if (buf[s] == 47u8) {
if (buf[s + 1u64] == 47u8) {
p = q + 1u64;
continue;
};
};
};
if (s + 8u64 <= q) {
if (buf[s] == 112u8) { // 'p'
if (buf[s + 1u64] == 97u8) { // 'a'
if (buf[s + 2u64] == 99u8) { // 'c'
if (buf[s + 3u64] == 107u8) { // 'k'
if (buf[s + 4u64] == 97u8) { // 'a'
if (buf[s + 5u64] == 103u8) { // 'g'
if (buf[s + 6u64] == 101u8) { // 'e'
let sep: u8 = buf[s + 7u64];
if (sep == 32u8) { }
else { if (sep != 9u8) { return nil; }; };
let t: u64 = s + 8u64;
for (t < q) {
if (buf[t] != 32u8) {
if (buf[t] != 9u8) { break; };
};
t += 1u64;
};
let start: u64 = t;
for (t < q) {
let ch: u8 = buf[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 = amalloc(a, plen + 1u64): *u8;
let k: u64 = 0u64;
for (k < plen) { r[k] = buf[start + k]; k += 1u64; };
r[plen] = 0u8;
return r;
}; }; }; }; }; }; };
};
return nil;
};
p = q + 1u64;
};
return nil;
};
// 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 = {
os.write(2, "ww: ".ptr, 4u64);
os.write(2, file, cstrlen(file));
os.write(2, ": package ".ptr, 10u64);
os.write(2, pkg, cstrlen(pkg));
os.write(2, " differs from ".ptr, 14u64);
os.write(2, dirpkg, cstrlen(dirpkg));
os.write(2, " in same module dir ".ptr, 20u64);
os.write(2, dirpath, cstrlen(dirpath));
os.write(2, "\n".ptr, 1u64);
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) void = {
let names: **u8;
let n: i32;
names, n = enumeratedir(c.a, dirpath);
let dlen: u64 = cstrlen(dirpath);
let dirpkg: *u8 = nil;
let i: i32 = 0;
for (i < n) {
// Two-step deref+index to avoid wwstage chained `names[i][k]`
// cgen UNDER (task #24 — wwstage cgen chained-index inner
// element size on **T). Wwstage treats inner element as 8B
// (sizeof *u8) instead of 1B (sizeof u8); cstage handles
// via typed-AST natively. Retire once the wwstage fix lands.
let nm: *u8 = names[i];
let nlen: u64 = cstrlen(nm);
let fp: *u8 = amalloc(c.a, dlen + 1u64 + nlen + 1u64): *u8;
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] = nm[k]; k += 1u64; };
fp[dlen + 1u64 + nlen] = 0u8;
let pkg: *u8 = peekpackage(c.a, fp);
if (pkg != nil) {
if (dirpkg == nil) { dirpkg = pkg; }
else { if (!cstreq(dirpkg, pkg)) {
strictpkgmismatch(fp, pkg, dirpkg, dirpath);
}; };
};
expand(c, fp);
i += 1;
};
};
// ---- Build pipeline ---------------------------------------------------
// Strip the trailing ".ww" off `src` (a NUL-terminated path) into
@@ -448,19 +729,21 @@ type lflags = struct {
nlibs: i32,
};
// buildone — compile `src` 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 path to the .ww file
// out: NUL-terminated desired output path
// incs: NUL-terminated colon-list of -I dirs (may be empty)
// lf: extra linker flags (-L<dir>, -l<name>); may be nil
// 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
// 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, out: *u8, incs: *u8, lf: *lflags) i32 = {
fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *lflags) i32 = {
let a: *arena = newarena();
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
@@ -475,13 +758,23 @@ fn buildone(selfdir: *u8, src: *u8, out: *u8, incs: *u8, lf: *lflags) i32 = {
cstrseal(dotdotlib, off);
};
// Compute the source-file's directory: bytes of `src` up to the
// last '/'. If `src` has no '/', use ".". Hare's CWD-first
// 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 = os.alloc(PATH_MAX): *u8;
{
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;
@@ -517,9 +810,20 @@ fn buildone(selfdir: *u8, src: *u8, out: *u8, incs: *u8, lf: *lflags) i32 = {
cstrseal(searchpath, off);
};
// stem, .s, .o, .combined.ww, libwwrt.a
// Stem for .s/.o/.combined.ww side files. Dir entry: <dir>/<base>;
// file entry: src stripped of .ww.
let stem: *u8 = os.alloc(PATH_MAX): *u8;
makestem(stem, src);
if (entryisdir != 0) {
let dlen: u64 = cstrlen(srcd);
let bo: u64 = basenameoff(srcd, dlen);
let off: u64 = cstrinto(stem, 0u64, srcd);
stem[off] = 47u8; off += 1u64; // '/'
let i: u64 = bo;
for (i < dlen) { stem[off] = srcd[i]; off += 1u64; i += 1u64; };
cstrseal(stem, off);
} else {
makestem(stem, src);
};
let asmf: *u8 = appendlit(stem, ".s");
let objf: *u8 = appendlit(stem, ".o");
let combined: *u8 = appendlit(stem, ".combined.ww");
@@ -532,7 +836,8 @@ fn buildone(selfdir: *u8, src: *u8, out: *u8, incs: *u8, lf: *lflags) i32 = {
cstrseal(libwwrt, off);
};
// Step 1: expand `use`s into the combined file.
// 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) {
os.write(2, "ww: cannot open combined\n".ptr, 25u64);
@@ -544,7 +849,8 @@ fn buildone(selfdir: *u8, src: *u8, out: *u8, incs: *u8, lf: *lflags) i32 = {
c.out = cf;
c.dirs = searchpath;
c.visit = nil;
expand(&c, src);
if (entryisdir != 0) { expanddir(&c, srcd); }
else { expand(&c, src); };
};
os.close(cf);
@@ -706,43 +1012,41 @@ fn buildsearchpath(a: *arena, selfdir: *u8, incs: *u8) *u8 = {
return buf;
};
fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8) *u8 = {
// resolvemodule — map a name like "foo", "lib/foo", "foo.ww", or
// "." to a concrete entry path. Sets *isdir when the entry is a
// module directory (caller will dir-enumerate).
fn resolvemodule(a: *arena, selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
let nlen: u64 = cstrlen(name);
// (1) literal .ww file that exists
// (1) Literal file that exists → use as-is.
if (cstrendswithlit(name, ".ww")) {
if (os.access(pathstr(name), 0i32) == 0) {
*isdir = 0;
return arenadupcstr(a, name, nlen);
};
};
// (2) "." → cwd's <basename>.ww
if (nlen == 1u64) {
if (name[0u64] == 46u8) { // '.'
let cwd: *u8 = amalloc(a, PATH_MAX): *u8;
let r: i64 = os.getcwd(cwd, PATH_MAX);
if (r <= 0i64) { return nil; };
let cwdlen: u64 = (r: u64) - 1u64; // strip trailing NUL
let bo: u64 = basenameoff(cwd, cwdlen);
let blen: u64 = cwdlen - bo;
let dot: *u8 = amalloc(a, 2u64): *u8;
dot[0] = 46u8; dot[1] = 0u8;
let probe: *u8 = builddirmodulepath(a, dot, 1u64,
cwd + bo, blen);
if (os.access(pathstr(probe), 0i32) == 0) { return probe; };
return nil;
};
// (2) Existing path → use as-is, dir vs file via stat.
let fi: os.filestat;
let sr: (void | os.oserror) = os.stat(&fi, pathstr(name));
let found: bool = false;
let foundisdir: i32 = 0;
match (sr) {
case void => {
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
if (t == os.mode.DIR: u32) { foundisdir = 1; };
found = true;
};
case let e: os.oserror => void;
};
if (found) {
*isdir = foundisdir;
return arenadupcstr(a, name, nlen);
};
// (3) <name>/<basename(name)>.ww — directory-as-module
let bo: u64 = basenameoff(name, nlen);
let probe: *u8 = builddirmodulepath(a, name, nlen,
name + bo, nlen - bo);
if (os.access(pathstr(probe), 0i32) == 0) { return probe; };
// (4) search path lookup
// (3) Search-path lookup with dot-to-slash path translation.
let search: *u8 = buildsearchpath(a, selfdir, incs);
return locateimport(a, search, name, nlen);
return locateimport(a, search, name, nlen, isdir);
};
// ---- Subcommand handlers ----------------------------------------------
@@ -876,18 +1180,34 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
dot[0] = 46u8; dot[1] = 0u8;
src = dot;
};
let resolved: *u8 = resolvemodule(a, selfdir, src, incs);
let isdir: i32 = 0;
let resolved: *u8 = resolvemodule(a, selfdir, src, incs, &isdir);
if (resolved == nil) {
os.write(2, "ww build: cannot find module\n".ptr, 29u64);
return 1;
};
let out: *u8 = defaultoutpath(resolved);
let out: *u8 = nil;
if (isdir != 0) {
let rlen: u64 = cstrlen(resolved);
for (rlen > 1u64) {
if (resolved[rlen - 1u64] != 47u8) { break; };
rlen -= 1u64;
};
let bo: u64 = basenameoff(resolved, rlen);
out = os.alloc(PATH_MAX): *u8;
let i: u64 = bo;
let off: u64 = 0u64;
for (i < rlen) { out[off] = resolved[i]; off += 1u64; i += 1u64; };
cstrseal(out, off);
} else {
out = defaultoutpath(resolved);
};
let lf: lflags;
lf.libdirs = libdirs;
lf.nlibdirs = nlibdirs;
lf.libs = libs;
lf.nlibs = nlibs;
return buildone(selfdir, resolved, out, incs, &lf);
return buildone(selfdir, resolved, isdir, out, incs, &lf);
};
// Format the scratch path /tmp/ww_run_<pid> into buf. Returns NUL-
@@ -1015,7 +1335,8 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
dot[0] = 46u8; dot[1] = 0u8;
src = dot;
};
let resolved: *u8 = resolvemodule(a, selfdir, src, incs);
let isdir: i32 = 0;
let resolved: *u8 = resolvemodule(a, selfdir, src, incs, &isdir);
if (resolved == nil) {
os.write(2, "ww run: cannot find module\n".ptr, 27u64);
return 1;
@@ -1028,7 +1349,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
lf.nlibdirs = nlibdirs;
lf.libs = libs;
lf.nlibs = nlibs;
if (buildone(selfdir, resolved, tmp, incs, &lf) != 0) {
if (buildone(selfdir, resolved, isdir, tmp, incs, &lf) != 0) {
os.remove(pathstr(tmp));
return 1;
};
@@ -1060,7 +1381,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
fn runsingletest(selfdir: *u8, src: *u8) i32 = {
let tmp: *u8 = os.alloc(PATH_MAX): *u8;
makeruntmp(tmp);
if (buildone(selfdir, src, tmp, "\0".ptr, nil) != 0) {
if (buildone(selfdir, src, 0, tmp, "\0".ptr, nil) != 0) {
os.remove(pathstr(tmp));
return 1;
};
@@ -1110,7 +1431,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
cstrseal(tincs, ic);
let tmp: *u8 = os.alloc(PATH_MAX): *u8;
makeruntmp(tmp);
let bres: i32 = buildone(selfdir, path, tmp, tincs, nil);
let bres: i32 = buildone(selfdir, path, 0, tmp, tincs, nil);
if (bres != 0) {
fail += 1;
os.write(2, "FAIL ".ptr, 5u64);

File diff suppressed because it is too large Load Diff

View File

@@ -1277,7 +1277,7 @@ export fn encoderune(out: []u8, r: rune) i32 = {
package strings;
import bytes;
import utf8;
import encoding.utf8;
import os;
// toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29.

View File

@@ -1748,14 +1748,8 @@ main(void)
char tmpdir[64];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwe2e_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
/* -I lib/encoding/utf8 for any fixture that pulls in fmt /
* strconv / strings via `use` (transitive utf8.encoderune;
* task #17). Unused -I is benign for fixtures that don't. */
char cwd700[1024];
if (getcwd(cwd700, sizeof cwd700) == NULL) { fail++; continue; }
snprintf(cmd, sizeof cmd,
"cd %s && %s/ww build -I %s/lib/encoding/utf8 %s",
tmpdir, bin, cwd700, src);
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
tmpdir, bin, src);
if (runwait(cmd) != 0) { fail++; continue; }
char outbin[128];

142
test/wcc/737_direnum.c Normal file
View File

@@ -0,0 +1,142 @@
/*
* 737_direnum — driver-level sentinel for task #22 directory-as-
* module enumeration. Two row families pinning the contract:
*
* ok — multi-file dir is concatenated by both stages; the entry
* reads cross-pkg bare-leaf fns from sibling files. Build
* must succeed for both C-built `ww` and ww-built `ww_ww`.
* bad — multi-file dir with mismatched `package <name>;` decls
* triggers the strict-same-package error (task #25 subset
* bundled with #22 because the failure mode is dir-enum's
* own; cross-stage symmetric).
*
* Asm-presence isn't checked separately — 968_utf8_run, 966_strings_
* run, 995_self_rebuild already exercise dir-enum end-to-end at
* binary level. This file pins the cstage/wwstage symmetric error
* path so a regression on either driver fails loud.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return 1;
}
static int
runbuild(const char *driver, const char *src, const char *outbin)
{
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s build %s -o /dev/null >/dev/null 2>%s",
driver, src, outbin);
return runwait(cmd);
}
static int
stderr_contains(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[4096];
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = '\0';
return strstr(buf, needle) != NULL;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char ww[1280], ww_ww[1280];
snprintf(ww, sizeof ww, "%s/ww", bin);
snprintf(ww_ww, sizeof ww_ww, "%s/ww_ww", bin);
int total = 0, fail = 0;
char errp[64];
/* ok: cross-pkg bare-leaf via dir-enum, both stages. */
{
const char *src = "test/wcc/data/direnum/entry.ww";
char tmp[64];
snprintf(tmp, sizeof tmp, "/tmp/direnum_%d_ok", getpid());
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s run %s >/dev/null 2>&1", ww, src);
total++;
if (runwait(cmd) != 0) {
fprintf(stderr, "737[ok-cstage]: ww run %s failed\n", src);
fail++;
}
if (access(ww_ww, X_OK) == 0) {
snprintf(cmd, sizeof cmd, "%s run %s >/dev/null 2>&1",
ww_ww, src);
total++;
if (runwait(cmd) != 0) {
fprintf(stderr, "737[ok-wwstage]: ww_ww run %s failed\n", src);
fail++;
}
}
(void)tmp;
}
/* bad: mismatched package decls in same dir → strict-same-package
* error. Both stages must surface "differs from" in stderr. */
{
const char *src = "test/wcc/data/direnum/bad_entry.ww";
snprintf(errp, sizeof errp, "/tmp/direnum_%d_bad.err", getpid());
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s build %s 2>%s >/dev/null",
ww, src, errp);
int rc = runwait(cmd);
total++;
if (rc == 0) {
fprintf(stderr, "737[bad-cstage]: expected build failure, succeeded\n");
fail++;
} else if (!stderr_contains(errp, "differs from")) {
fprintf(stderr, "737[bad-cstage]: stderr missing 'differs from'\n");
fail++;
}
unlink(errp);
if (access(ww_ww, X_OK) == 0) {
snprintf(cmd, sizeof cmd, "%s build %s 2>%s >/dev/null",
ww_ww, src, errp);
rc = runwait(cmd);
total++;
if (rc == 0) {
fprintf(stderr, "737[bad-wwstage]: expected build failure, succeeded\n");
fail++;
} else if (!stderr_contains(errp, "differs from")) {
fprintf(stderr, "737[bad-wwstage]: stderr missing 'differs from'\n");
fail++;
}
unlink(errp);
}
}
if (fail) {
fprintf(stderr, "737_direnum: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("737_direnum: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -98,6 +98,30 @@ main(void)
"package foo;\nimport encoding.utf8;\nfn x() void = {};\n")) pass++;
else { fprintf(stderr, "738[5] dotted import accept FAILED\n"); fail++; }
/* Row 6: dotted import stores only the leaf identifier on
* N_USE.str (post-task-#22 — the driver translates the full
* dotted path to a directory walk; the checker only needs the
* package bareword for n_use → decl disambiguation). */
{
Arena *a = newarena();
Lex l;
Parser p;
const char *src = "package foo;\nimport encoding.utf8;\n";
lexinit(&l, a, "<test>", src, strlen(src));
parserinit(&p, a, &l);
Node *n = parsefile(&p);
int ok = 0;
if (n != NULL && p.errs == 0 && l.errs == 0 && n->list != NULL) {
Node *u = n->list;
ok = (u->kind == N_USE
&& u->str != NULL
&& strcmp(u->str, "utf8") == 0);
}
freearena(a);
if (ok) pass++;
else { fprintf(stderr, "738[6] dotted import leaf-store FAILED\n"); fail++; }
}
printf("738_module_decl: %d pass, %d fail\n", pass, fail);
return fail == 0 ? 0 : 1;
}

View File

@@ -5,10 +5,6 @@
* Same thin-wrapper shape as 967_bytes_run / 968_utf8_run / 979_hex_run:
* stringstest.ww carries its own `export fn main()` that drives the
* @test fns and signals which case failed via the exit code.
*
* -I lib/encoding/utf8 is required because lib/strings.byteindex
* encodes the rune-needle arm via utf8.encoderune; the import resolver
* doesn't yet walk encoding/ subdirs (task #17).
*/
#include <stdio.h>
#include <stdlib.h>
@@ -42,9 +38,8 @@ main(void)
const char *src = "lib/strings/stringstest.ww";
char path[1024], cmd[2048];
snprintf(path, sizeof path, "%s/%s", cwd, src);
snprintf(cmd, sizeof cmd,
"%s/ww run -I %s/lib/encoding/utf8 %s",
bin, cwd, path);
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path);
(void)cwd;
int rc = runwait(cmd);
if (rc != 0) {
fprintf(stderr, "strings_run FAIL: %s exited %d\n", src, rc);

View File

@@ -41,9 +41,8 @@ main(void)
const char *src = "lib/fmt/fmttest.ww";
char path[1024], cmd[2048];
snprintf(path, sizeof path, "%s/%s", cwd, src);
/* -I lib/encoding/utf8 — task #17 */
snprintf(cmd, sizeof cmd,
"%s/ww run -I %s/lib/encoding/utf8 %s", bin, cwd, path);
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path);
(void)cwd;
int rc = runwait(cmd);
if (rc != 0) {
fprintf(stderr, "fmt_run FAIL: %s exited %d\n", src, rc);

View File

@@ -41,9 +41,8 @@ main(void)
const char *src = "lib/log/logtest.ww";
char path[1024], cmd[2048];
snprintf(path, sizeof path, "%s/%s", cwd, src);
/* -I lib/encoding/utf8 — task #17 */
snprintf(cmd, sizeof cmd,
"%s/ww run -I %s/lib/encoding/utf8 %s", bin, cwd, path);
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path);
(void)cwd;
int rc = runwait(cmd);
if (rc != 0) {
fprintf(stderr, "log_run FAIL: %s exited %d\n", src, rc);

View File

@@ -41,9 +41,8 @@ main(void)
const char *src = "lib/fnmatch/fnmatchtest.ww";
char path[1024], cmd[2048];
snprintf(path, sizeof path, "%s/%s", cwd, src);
/* -I lib/encoding/utf8 — task #17 */
snprintf(cmd, sizeof cmd,
"%s/ww run -I %s/lib/encoding/utf8 %s", bin, cwd, path);
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path);
(void)cwd;
int rc = runwait(cmd);
if (rc != 0) {
fprintf(stderr, "fnmatch_run FAIL: %s exited %d\n", src, rc);

View File

@@ -40,10 +40,8 @@ main(void)
const char *src = "lib/getopt/getopttest.ww";
char path[1024], cmd[2048];
snprintf(path, sizeof path, "%s/%s", cwd, src);
/* -I lib/encoding/utf8: getopt -> strings -> utf8.encoderune
* (task #17 — resolver doesn't yet walk encoding/ subdirs). */
snprintf(cmd, sizeof cmd,
"%s/ww run -I %s/lib/encoding/utf8 %s", bin, cwd, path);
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path);
(void)cwd;
int rc = runwait(cmd);
if (rc != 0) {
fprintf(stderr, "getopt_run FAIL: %s exited %d\n", src, rc);

View File

@@ -99,11 +99,9 @@ probe_smoke(const char *bin)
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwsh_%d", getpid());
mkdir(tmpdir, 0755);
char cmd[2048];
/* -I lib/encoding/utf8: smoke.ww uses strconv -> strings ->
* utf8.encoderune (task #17 — resolver doesn't walk encoding/). */
snprintf(cmd, sizeof cmd,
"cd %s && %s/ww build -I %s/lib/encoding/utf8 %s/selfhost/test/smoke.ww >/dev/null 2>&1",
tmpdir, bin, cwd, cwd);
"cd %s && %s/ww build %s/selfhost/test/smoke.ww >/dev/null 2>&1",
tmpdir, bin, cwd);
if (runwait(cmd) != 0) {
fprintf(stderr, "smoke FAIL: ww build did not succeed\n");
return -1;
@@ -745,8 +743,8 @@ probe_ww_links(const char *bin)
runwait(cmd);
/* ww build to get the .combined.ww as a side effect. */
snprintf(cmd, sizeof cmd,
"cd %s && %s/ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/lib/encoding/utf8 -I %s/selfhost/cmd/wcc %s >/dev/null 2>&1",
tmpdir, bin, cwd, cwd, cwd, cwd, cwd, tmpsrc);
"cd %s && %s/ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/selfhost/cmd/wcc %s >/dev/null 2>&1",
tmpdir, bin, cwd, cwd, cwd, cwd, tmpsrc);
if (runwait(cmd) != 0) {
fprintf(stderr, "ww-links FAIL: ww build %s\n", fix);
fail++;

View File

@@ -62,9 +62,8 @@ slurp_eq(const char *a, const char *b)
/* Each tool builds via `ww_ww build -I <local> -I lib/ww -I selfhost/cmd/wcc src`.
* lib/ww holds the language introspection (lex/tok/ast/parse/typ/sym);
* selfhost/cmd/wcc holds the compiler internals (mem/check/cgen*).
* lib/encoding/utf8 carries the rune codec strings.byteindex needs;
* the import resolver doesn't yet walk encoding/ subdirs (task #17),
* so the dep travels as an explicit -I until it does.
* Dotted `import encoding.utf8;` finds lib/encoding/utf8/ via the
* driver's default srclib path post-task-#22 dir-enum.
* Some tools have a local module dir (w6a, w6l with sibling .ww files).
* inc_local is "" for tools without one (w6c, ww, wwdump).
*/
@@ -80,14 +79,14 @@ rebuild_one(const char *bin, const char *cwd, const char *tool,
if (inc_local && inc_local[0]) {
snprintf(cmd, sizeof cmd,
"cd %s && %s/ww_ww build -I %s/%s -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/lib/encoding/utf8 -I %s/selfhost/cmd/wcc "
"cd %s && %s/ww_ww build -I %s/%s -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/selfhost/cmd/wcc "
"%s/%s >/dev/null 2>&1",
workdir, bin, cwd, inc_local, cwd, cwd, cwd, cwd, cwd, cwd, src_rel);
workdir, bin, cwd, inc_local, cwd, cwd, cwd, cwd, cwd, src_rel);
} else {
snprintf(cmd, sizeof cmd,
"cd %s && %s/ww_ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/lib/encoding/utf8 -I %s/selfhost/cmd/wcc "
"cd %s && %s/ww_ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/selfhost/cmd/wcc "
"%s/%s >/dev/null 2>&1",
workdir, bin, cwd, cwd, cwd, cwd, cwd, cwd, src_rel);
workdir, bin, cwd, cwd, cwd, cwd, cwd, src_rel);
}
if (runwait(cmd) != 0) {
fprintf(stderr, "self-rebuild FAIL: ww_ww build errored on %s\n", tool);

View File

@@ -0,0 +1,5 @@
package main;
import bad_pkg;
export fn main() i32 = { return 0; };

View File

@@ -0,0 +1,3 @@
package good;
fn aaa() i32 = { return 1; };

View File

@@ -0,0 +1,3 @@
package bad;
fn bbb() i32 = { return 2; };

View File

@@ -0,0 +1,10 @@
// Entry imports the dir; cross-pkg refs `ok.fromA` / `ok.fromB`
// resolve via dir-enum pulling a.ww + b.ww (both package ok).
package main;
import ok;
export fn main() i32 = {
if (ok.fromA() + ok.fromB() != 42) { return 1; };
return 0;
};

View File

@@ -0,0 +1,3 @@
package ok;
export fn fromA() i32 = { return 7; };

View File

@@ -0,0 +1,3 @@
package ok;
export fn fromB() i32 = { return 35; };