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:
9
Makefile
9
Makefile
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
315
cmd/ww/main.c
315
cmd/ww/main.c
@@ -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++;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
package utf8;
|
||||
|
||||
import utf8;
|
||||
import encoding.utf8;
|
||||
import os;
|
||||
|
||||
let signalled: i32 = 0;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
142
test/wcc/737_direnum.c
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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);
|
||||
|
||||
5
test/wcc/data/direnum/bad_entry.ww
Normal file
5
test/wcc/data/direnum/bad_entry.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
package main;
|
||||
|
||||
import bad_pkg;
|
||||
|
||||
export fn main() i32 = { return 0; };
|
||||
3
test/wcc/data/direnum/bad_pkg/a.ww
Normal file
3
test/wcc/data/direnum/bad_pkg/a.ww
Normal file
@@ -0,0 +1,3 @@
|
||||
package good;
|
||||
|
||||
fn aaa() i32 = { return 1; };
|
||||
3
test/wcc/data/direnum/bad_pkg/b.ww
Normal file
3
test/wcc/data/direnum/bad_pkg/b.ww
Normal file
@@ -0,0 +1,3 @@
|
||||
package bad;
|
||||
|
||||
fn bbb() i32 = { return 2; };
|
||||
10
test/wcc/data/direnum/entry.ww
Normal file
10
test/wcc/data/direnum/entry.ww
Normal 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;
|
||||
};
|
||||
3
test/wcc/data/direnum/ok/a.ww
Normal file
3
test/wcc/data/direnum/ok/a.ww
Normal file
@@ -0,0 +1,3 @@
|
||||
package ok;
|
||||
|
||||
export fn fromA() i32 = { return 7; };
|
||||
3
test/wcc/data/direnum/ok/b.ww
Normal file
3
test/wcc/data/direnum/ok/b.ww
Normal file
@@ -0,0 +1,3 @@
|
||||
package ok;
|
||||
|
||||
export fn fromB() i32 = { return 35; };
|
||||
Reference in New Issue
Block a user