Switch symbol mangling from the import leaf clause to the full dotted import path for directory packages; single-file imports keep package-clause mangling (isdir-gate: imported<=>directory-import). The root build unit's fn main stays bare, every other top-level decl mangles, closing #31's duplicate-main hazard by construction (#32). Both stages, byte-identical. Single commit, not split: the bare rename (f244af3) is red on its own because it unmasks cross-module resolution gaps that do not reproduce pre-M1, so the fixes are intrinsic to making the rename correct. Included: wwstage fnret/fnparamslookupmod map import alias->path (#199b cross-module union-variant scrutinee resolved the wrong fn's union); cstage use_path prefers the referencing module's import for an ambiguous leaf alias (sha256 crypto.math vs strconv math). Tests table-driven: 989_m1mangle_run/_sym, 989_m1union_run (gate-visible per-arm exit codes + cs==ww byte-id).
This commit is contained in:
32
Makefile
32
Makefile
@@ -265,6 +265,9 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_libprecond_abort \
|
||||
$(BIN)/test_idxarg_run \
|
||||
$(BIN)/test_chainidx_run \
|
||||
$(BIN)/test_m1mangle_run \
|
||||
$(BIN)/test_m1mangle_sym \
|
||||
$(BIN)/test_m1union_run \
|
||||
$(BIN)/test_tupfieldsize_run \
|
||||
$(BIN)/test_tagtupfieldsize_run \
|
||||
$(BIN)/test_nestfield_run \
|
||||
@@ -817,6 +820,35 @@ $(BIN)/test_chainidx_run: test/wcc/989_chainidx_run.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 989_m1mangle_run / _sym (M1, #22 + #32): path-qualified symbol mangling
|
||||
# and root-unit entry detection. _run builds+runs a tiny package tree on
|
||||
# BOTH stages (rule-10); _sym asserts the emitted symbol names + cs.s==ww.s.
|
||||
$(BIN)/test_m1mangle_run: test/wcc/989_m1mangle_run.c \
|
||||
$(BIN)/ww $(BIN)/ww_ww \
|
||||
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_m1mangle_sym: test/wcc/989_m1mangle_sym.c \
|
||||
$(BIN)/ww $(BIN)/ww_ww \
|
||||
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 989_m1union_run (M1, #199b): a `match` on a cross-module fn's tagged-union
|
||||
# return must keep each nominal void-alias variant on a distinct tag. Builds+
|
||||
# runs the utf8.next 4-arm match per-arm on BOTH stages (rule-10) and pins
|
||||
# cstage.s==wwstage.s on the all-arms program — the gate-VISIBLE regression
|
||||
# teeth for the variant-collapse the doc flagged as byte-id-only.
|
||||
$(BIN)/test_m1union_run: test/wcc/989_m1union_run.c \
|
||||
$(BIN)/ww $(BIN)/ww_ww \
|
||||
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 989_tupfieldsize_run (F7-c4, #43): a for-range destructure over an array
|
||||
# of tuples must stride by the tuple's true size (a slice/str/tuple field
|
||||
# carries its full width). Builds+runs on BOTH driver twins (rule-10), the
|
||||
|
||||
@@ -1167,6 +1167,43 @@ struct Mod {
|
||||
};
|
||||
static Mod *mod_map;
|
||||
|
||||
/* M1 #22: alias→import-path map built from the N_USE nodes, so a
|
||||
* qualified-ref codegen hint (`utf8.decoderune`) keys mod_map on the
|
||||
* dotted path the symbols are registered under, not the bare alias.
|
||||
* For single-level packages alias == path, so this is a no-op there. */
|
||||
typedef struct Use Use;
|
||||
struct Use {
|
||||
const char *alias;
|
||||
const char *path;
|
||||
Use *next;
|
||||
};
|
||||
static Use *use_map;
|
||||
|
||||
/*
|
||||
* Retained divergence (M1 #22): this map is file-global — when two
|
||||
* modules in the same unit bind the same leaf alias to different paths
|
||||
* (sha256's `import crypto.math` vs strconv's `import math`), the first
|
||||
* match wins. The checker's twin (use_path, check.c) is module-scoped
|
||||
* to fix exactly this for qualified-NAME resolution; the cgen mangle
|
||||
* hint is NOT, because it is unreachable with an ambiguous alias: a
|
||||
* cross-module qualified ref resolves only to an EXPORTED symbol, and
|
||||
* exported non-fn decls stay bare (mod_collect skips them, so the hint
|
||||
* is moot) while an exported-fn collision would require two same-leaf
|
||||
* packages to export the same fn name AND a third caller — not present
|
||||
* (the tree links cleanly). Left file-global to stay byte-identical
|
||||
* with wwstage's symmetric usehint (cgen.ww), which is also file-global
|
||||
* (rule 10). Module-scope both stages together if a real collision
|
||||
* surfaces — filed as the M1 cgen-hint twin follow-up.
|
||||
*/
|
||||
static const char *
|
||||
use_hint(const char *alias)
|
||||
{
|
||||
if (alias == NULL) return alias;
|
||||
for (Use *u = use_map; u; u = u->next)
|
||||
if (strcmp(u->alias, alias) == 0) return u->path;
|
||||
return alias;
|
||||
}
|
||||
|
||||
/* Top-level `let` map. Populated alongside mod_map; consulted by the
|
||||
* N_IDENT store path and the &-of path to route reads/writes through
|
||||
* a RIP-relative reference rather than dropping them as the (pre-
|
||||
@@ -1383,8 +1420,18 @@ static void
|
||||
mod_collect(Cg *c, Node *file)
|
||||
{
|
||||
mod_map = NULL;
|
||||
use_map = NULL;
|
||||
if (file == NULL) return;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
/* M1 #22: record alias→path for the qualified-ref hint. */
|
||||
if (d->kind == N_USE && d->str && d->usepath) {
|
||||
Use *u = amalloc(c->a, sizeof *u);
|
||||
u->alias = d->str;
|
||||
u->path = d->usepath;
|
||||
u->next = use_map;
|
||||
use_map = u;
|
||||
continue;
|
||||
}
|
||||
int isfn = (d->kind == N_FNDECL);
|
||||
int track = isfn || (d->kind == N_TYPEDECL)
|
||||
|| (d->kind == N_DEF) || (d->kind == N_LET);
|
||||
@@ -1399,8 +1446,11 @@ mod_collect(Cg *c, Node *file)
|
||||
if (decl_has_ffisym(d)) continue;
|
||||
/* `main` is the linker entry-point convention. Even when not
|
||||
* marked `export`, it must keep its bare name so w6l can
|
||||
* resolve `_start`'s `CALL main(SB)`. */
|
||||
if (d->str && strcmp(d->str, "main") == 0) continue;
|
||||
* resolve `_start`'s `CALL main(SB)`. M1 #32: only the ROOT
|
||||
* unit's main stays bare; an IMPORTED `fn main` mangles on its
|
||||
* path (closes #31's dup-main by construction). */
|
||||
if (d->str && strcmp(d->str, "main") == 0 && !d->imported)
|
||||
continue;
|
||||
Mod *m = amalloc(c->a, sizeof *m);
|
||||
m->name = d->str;
|
||||
m->module = d->module;
|
||||
@@ -4321,7 +4371,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (lu && lu->kind == TY_FN)
|
||||
ins2(c, A_LEAQ,
|
||||
mafn(c, opnd->str,
|
||||
opnd->lhs->str),
|
||||
use_hint(opnd->lhs->str)),
|
||||
areg(D_AX));
|
||||
else
|
||||
/* #229: dotted-module value
|
||||
@@ -4330,7 +4380,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* same-leaf collision. */
|
||||
ins2(c, A_LEAQ,
|
||||
mahint(c, opnd->str,
|
||||
opnd->lhs->str),
|
||||
use_hint(opnd->lhs->str)),
|
||||
areg(D_AX));
|
||||
break;
|
||||
}
|
||||
@@ -10181,7 +10231,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* the bareword as the hint so cross-module
|
||||
* same-leaf exports resolve correctly. */
|
||||
ins1(c, A_CALL,
|
||||
mafn(c, n->lhs->str, n->lhs->lhs->str));
|
||||
mafn(c, n->lhs->str,
|
||||
use_hint(n->lhs->lhs->str)));
|
||||
} else {
|
||||
cgexpr(c, n->lhs, locals); /* AX = fn ptr */
|
||||
ins1(c, A_CALL, areg(D_AX));
|
||||
@@ -11057,7 +11108,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
/* `mod.fn` address-of via N_DOT — pass the
|
||||
* module bareword as the disambiguation hint. */
|
||||
ins2(c, A_LEAQ,
|
||||
mafn(c, n->str, n->lhs->str), areg(D_AX));
|
||||
mafn(c, n->str, use_hint(n->lhs->str)),
|
||||
areg(D_AX));
|
||||
break;
|
||||
}
|
||||
{
|
||||
@@ -11073,7 +11125,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
for (s = sdefs; s; s = s->next) {
|
||||
if (strcmp(s->name, n->str) != 0)
|
||||
continue;
|
||||
if (sdef_mod_match_hint(s, n->lhs->str))
|
||||
if (sdef_mod_match_hint(s, use_hint(n->lhs->str)))
|
||||
break;
|
||||
}
|
||||
if (s == NULL) {
|
||||
@@ -11105,10 +11157,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* branch above already uses n->lhs->str via mafn. */
|
||||
if (mqop == A_MOVQ) {
|
||||
ins2(c, A_MOVQ,
|
||||
mahint(c, n->str, n->lhs->str), areg(D_AX));
|
||||
mahint(c, n->str, use_hint(n->lhs->str)),
|
||||
areg(D_AX));
|
||||
} else {
|
||||
ins2(c, A_LEAQ,
|
||||
mahint(c, n->str, n->lhs->str), areg(D_CX));
|
||||
mahint(c, n->str, use_hint(n->lhs->str)),
|
||||
areg(D_CX));
|
||||
ins2(c, mqop, amem(D_CX, 0), areg(D_AX));
|
||||
}
|
||||
goto dot_done;
|
||||
@@ -14920,8 +14974,15 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
/* TEXT directive comes first; framesize is filled at the end. */
|
||||
Prog *text = newprog(c, A_TEXT);
|
||||
/* Mangle the label using the fn's own module as the hint — picks
|
||||
* the right entry when multiple modules export the same leaf. */
|
||||
text->to = mafn(c, fn->str, c->cur_mod);
|
||||
* the right entry when multiple modules export the same leaf. M1 #32:
|
||||
* the ROOT-unit main (imported==0) is the bare `_start` entry — emit
|
||||
* it bare directly, mirroring mod_collect's skip; without this its
|
||||
* NULL cur_mod would fall through mafn's first-leaf match onto an
|
||||
* IMPORTED package's now-registered `pkg.main`. */
|
||||
if (fn->str && strcmp(fn->str, "main") == 0 && !fn->imported)
|
||||
text->to = asym("main");
|
||||
else
|
||||
text->to = mafn(c, fn->str, c->cur_mod);
|
||||
text->from.offset = 0; /* framesize patched below */
|
||||
emit(c, text);
|
||||
|
||||
@@ -15988,7 +16049,7 @@ node_fnptr_sym(Cg *c, Node *ev)
|
||||
return NULL;
|
||||
Type *du = type_chase_named(opnd->type);
|
||||
if (du == NULL || du->kind != TY_FN) return NULL;
|
||||
return mod_mangle_fn(c, opnd->str, opnd->lhs->str);
|
||||
return mod_mangle_fn(c, opnd->str, use_hint(opnd->lhs->str));
|
||||
}
|
||||
if (opnd == NULL || opnd->kind != N_IDENT) return NULL;
|
||||
Type *ou = type_chase_named(opnd->type);
|
||||
|
||||
@@ -60,6 +60,7 @@ lookup_builtin(const char *name)
|
||||
}
|
||||
|
||||
static const char *decl_mod(Node *file, Node *d);
|
||||
static const char *use_path(Node *file, const char *curmod, const char *alias);
|
||||
static void resolve_typedecl(Checker *c, Node *d);
|
||||
|
||||
static Type *
|
||||
@@ -84,9 +85,15 @@ resolve_typename(Checker *c, Node *n)
|
||||
size_t hl = (size_t)(dot - nm);
|
||||
if (hl < sizeof head) memcpy(head, nm, hl);
|
||||
Sym *m = scope_lookup(c->cur, head);
|
||||
if (m && (m->kind == SK_USE || m->use_alias))
|
||||
s = scope_lookup_in_module(c->cur, head,
|
||||
if (m && (m->kind == SK_USE || m->use_alias)) {
|
||||
/* M1 #22: map the qualifier alias to its dotted
|
||||
* import path (symbols are path-keyed). */
|
||||
const char *mk = use_path(c->file, c->cur_mod,
|
||||
head);
|
||||
if (mk == NULL) mk = head;
|
||||
s = scope_lookup_in_module(c->cur, mk,
|
||||
dot + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s == NULL || s->kind != SK_TYPE)
|
||||
@@ -599,7 +606,10 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
|
||||
}
|
||||
case N_DOT: {
|
||||
if (n->lhs == NULL || n->lhs->kind != N_IDENT) return 0;
|
||||
Sym *s = scope_lookup_in_module(c->cur, n->lhs->str, n->str);
|
||||
/* M1 #22: map the qualifier alias to its dotted import path. */
|
||||
const char *mk = use_path(c->file, c->cur_mod, n->lhs->str);
|
||||
if (mk == NULL) mk = n->lhs->str;
|
||||
Sym *s = scope_lookup_in_module(c->cur, mk, n->str);
|
||||
if (s == NULL || s->kind != SK_DEF ||
|
||||
s->decl == NULL || s->decl->rhs == NULL)
|
||||
return 0;
|
||||
@@ -1343,8 +1353,14 @@ cexpr(Checker *c, Node *n)
|
||||
* same-leaf-name types from different
|
||||
* imports (`bufio.stream`/`io.stream`)
|
||||
* disambiguate to the right one. */
|
||||
/* M1 #22: symbols are keyed on the dotted
|
||||
* import path; map the alias the user wrote to
|
||||
* that path before looking up the leaf. */
|
||||
const char *mk = use_path(c->file, c->cur_mod,
|
||||
n->lhs->str);
|
||||
if (mk == NULL) mk = n->lhs->str;
|
||||
Sym *fs = scope_lookup_in_module(c->cur,
|
||||
n->lhs->str, n->str);
|
||||
mk, n->str);
|
||||
if (fs)
|
||||
return n->type = fs->type;
|
||||
if (ms->kind == SK_USE) {
|
||||
@@ -2671,13 +2687,52 @@ decl_mod(Node *file, Node *d)
|
||||
{
|
||||
if (d == NULL || d->module == NULL || file == NULL) return NULL;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind == N_USE && u->str
|
||||
&& strcmp(u->str, d->module) == 0)
|
||||
/* M1 #22: a decl is imported iff some `use` directive's full
|
||||
* dotted import path equals the decl's module (now the path,
|
||||
* not the leaf). For single-level packages usepath == leaf so
|
||||
* this is unchanged; nested packages (`encoding.utf8`) match
|
||||
* here instead of on the bare leaf. */
|
||||
if (u->kind == N_USE && u->usepath
|
||||
&& strcmp(u->usepath, d->module) == 0)
|
||||
return d->module;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* use_path — map a `use` alias (leaf bareword the user writes, `utf8`)
|
||||
* to the full dotted import path it binds (`encoding.utf8`), for the
|
||||
* module-qualified resolution and the codegen hint (M1 #22, §2.4). For
|
||||
* single-level packages usepath == alias so the result is unchanged.
|
||||
*
|
||||
* The alias→path map is NOT file-global: two modules in the same
|
||||
* concatenated unit may bind the same leaf alias to different paths
|
||||
* (sha256's `import crypto.math` and strconv's `import math` both bind
|
||||
* alias `math`). The import declared in the SAME module as the
|
||||
* reference (`curmod`) is the authoritative one; preferring it closes
|
||||
* the cross-module mis-resolution a first-match scan caused. Falls back
|
||||
* to any matching alias when the referencing module has no own import
|
||||
* (single-occurrence case, unchanged). Returns NULL if no such `use`.
|
||||
*/
|
||||
static const char *
|
||||
use_path(Node *file, const char *curmod, const char *alias)
|
||||
{
|
||||
if (file == NULL || alias == NULL) return NULL;
|
||||
const char *any = NULL;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->str == NULL
|
||||
|| strcmp(u->str, alias) != 0)
|
||||
continue;
|
||||
const char *p = u->usepath ? u->usepath : u->str;
|
||||
int same = (u->module == NULL) ? (curmod == NULL)
|
||||
: (curmod != NULL && strcmp(u->module, curmod) == 0);
|
||||
if (same)
|
||||
return p;
|
||||
if (any == NULL) any = p;
|
||||
}
|
||||
return any;
|
||||
}
|
||||
|
||||
/*
|
||||
* resolve_typedecl — resolve d's body into its installed TY_NAMED
|
||||
* placeholder. Reached from check_file's typedecl pass AND on demand
|
||||
@@ -2739,7 +2794,7 @@ src_imports(Node *file, const char *modtag, const char *name)
|
||||
* even though its module tag is also "fmt"; that directive
|
||||
* doesn't introduce a foreign module bareword and lib/fmt's
|
||||
* own `fn bsprintf(fmt: str, ...)` is not a shadow of it. */
|
||||
if (u->module && u->str && strcmp(u->module, u->str) == 0)
|
||||
if (u->module && u->usepath && strcmp(u->module, u->usepath) == 0)
|
||||
continue;
|
||||
/* decl_mod normalises the raw `// MODULE:` tag back to NULL
|
||||
* for primary-source N_USEs (the primary's own tag won't
|
||||
@@ -2817,7 +2872,11 @@ check_file(Checker *c, Node *file)
|
||||
* (b)/(d) membership DEFERRED to task #8 (filename-
|
||||
* keyed pulls lack import->file->symbol provenance). */
|
||||
const char *owner = decl_mod(file, d);
|
||||
if (owner && owner[0] && strcmp(d->str, owner) == 0)
|
||||
/* M1 #22: self-import ⟺ the imported path equals the
|
||||
* use's own (owning) module path. Compares paths, not
|
||||
* leaves, so nested packages are caught too. */
|
||||
if (owner && owner[0] && d->usepath
|
||||
&& strcmp(d->usepath, owner) == 0)
|
||||
err(c, d->pos, "self-import: package "
|
||||
"'%s' cannot import itself", owner);
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
@@ -2962,16 +3021,14 @@ check_file(Checker *c, Node *file)
|
||||
}
|
||||
c->cur_mod = NULL;
|
||||
|
||||
/* Program-global, name-only, cross-module uniqueness on `main`.
|
||||
* `main` lowers to ONE bare entry symbol, so a second top-level
|
||||
* decl named `main` (any kind, any package) collides with the
|
||||
* entry at link time — today a silent segfault / link-fail in
|
||||
* both stages. The (name, module) duplicate rejects above read a
|
||||
* cross-package `foo.main` and the bare entry as distinct, so they
|
||||
* miss this. Correct multi-main mangling (entry stays bare, the
|
||||
* rest qualify) is deferred (task #32); reject loudly meanwhile
|
||||
* (rule 7). Walks USER decls only — runs before the -T synth main
|
||||
* is appended below — so a hosted-test build never false-counts. */
|
||||
/* Program-global uniqueness on the ENTRY `main`. M1 #32: the entry
|
||||
* is the ROOT-unit main (imported==0) — it alone lowers to the bare
|
||||
* `main` symbol w6l's _start calls. An IMPORTED package's `main`
|
||||
* (imported==1) mangles on its path (`foo.bar.main`) and may coexist
|
||||
* — closing the old dup-main collision by construction (#31). Two
|
||||
* ROOT entries still collide on the bare symbol → reject loud (rule
|
||||
* 7). Walks USER decls only — runs before the -T synth main is
|
||||
* appended below — so a hosted-test build never false-counts. */
|
||||
{
|
||||
Node *firstmain = NULL;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
@@ -2980,12 +3037,14 @@ check_file(Checker *c, Node *file)
|
||||
if (d->kind != N_FNDECL && d->kind != N_LET
|
||||
&& d->kind != N_DEF && d->kind != N_TYPEDECL)
|
||||
continue;
|
||||
if (d->imported)
|
||||
continue;
|
||||
if (firstmain == NULL) {
|
||||
firstmain = d;
|
||||
continue;
|
||||
}
|
||||
err(c, d->pos, "duplicate top-level main: only the "
|
||||
"entry main may exist (task #32)");
|
||||
err(c, d->pos, "duplicate entry main: only one root "
|
||||
"main may exist (#32)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,14 +102,38 @@ skipws(Lex *l)
|
||||
* then skipped like any comment. Mirrors the removed
|
||||
* `// MODULE:` lexer directive. */
|
||||
{
|
||||
static const char dir[] = "ww:module-reset";
|
||||
static const char pre[] = "ww:module";
|
||||
size_t i = 0;
|
||||
while (dir[i] && lpeek(l, i) == dir[i])
|
||||
while (pre[i] && lpeek(l, i) == pre[i])
|
||||
i++;
|
||||
if (dir[i] == '\0') {
|
||||
if (pre[i] == '\0') {
|
||||
int nx = lpeek(l, i);
|
||||
if (nx == '\n' || nx < 0)
|
||||
l->modreset = 1;
|
||||
if (nx == '-') {
|
||||
static const char rest[] = "-reset";
|
||||
size_t j = 0;
|
||||
while (rest[j] && lpeek(l, i + j) == rest[j])
|
||||
j++;
|
||||
if (rest[j] == '\0') {
|
||||
int af = lpeek(l, i + j);
|
||||
if (af == '\n' || af < 0)
|
||||
l->modreset = 1;
|
||||
}
|
||||
} else if (nx == ' ' || nx == '\t') {
|
||||
/* `//ww:module <path>` — M1 import boundary. */
|
||||
size_t k = i;
|
||||
while (lpeek(l, k) == ' '
|
||||
|| lpeek(l, k) == '\t')
|
||||
k++;
|
||||
size_t s = k;
|
||||
int ch;
|
||||
while ((ch = lpeek(l, k)) >= 0
|
||||
&& ch != '\n' && ch != '\r'
|
||||
&& ch != ' ' && ch != '\t')
|
||||
k++;
|
||||
if (k > s)
|
||||
l->modpath = astrndup(l->a,
|
||||
l->src + l->pos + s, k - s);
|
||||
}
|
||||
}
|
||||
}
|
||||
while ((c = lpeek(l, 0)) >= 0 && c != '\n')
|
||||
@@ -403,6 +427,13 @@ lexnext(Lex *l)
|
||||
/* A `//ww:module-reset` seen in the skipped run surfaces as its own
|
||||
* token before the next real one (#16 option-B boundary reset). */
|
||||
if (l->modreset) { l->modreset = 0; EMIT(TK_MODRESET); }
|
||||
if (l->modpath) {
|
||||
const char *mp = l->modpath;
|
||||
l->modpath = NULL;
|
||||
Tok _t = (Tok){ TK_MODPATH, start, NULL, 0, {0}, TK_NONE };
|
||||
_t.text = mp; _t.tlen = strlen(mp);
|
||||
return _t;
|
||||
}
|
||||
if (!more) {
|
||||
Tok t = (Tok){ TK_EOF, start, "", 0, {0}, TK_NONE };
|
||||
return t;
|
||||
|
||||
@@ -1253,11 +1253,24 @@ parseuse(Parser *p)
|
||||
Pos pp = p->cur.pos;
|
||||
expect(p, TK_USE);
|
||||
Node *n = newnode(p->a, N_USE, pp);
|
||||
/* M1 #22: accumulate the full dotted import path (n->module) so the
|
||||
* checker can match decl identity on the path, while n->str stays the
|
||||
* leaf alias the user writes (`utf8.x`). */
|
||||
char pathbuf[256];
|
||||
size_t pl = 0;
|
||||
const char *leaf = expectident(p);
|
||||
while (accept(p, TK_DOT))
|
||||
for (size_t i = 0; leaf[i] && pl + 1 < sizeof pathbuf; i++)
|
||||
pathbuf[pl++] = leaf[i];
|
||||
while (accept(p, TK_DOT)) {
|
||||
leaf = expectident(p);
|
||||
if (pl + 1 < sizeof pathbuf) pathbuf[pl++] = '.';
|
||||
for (size_t i = 0; leaf[i] && pl + 1 < sizeof pathbuf; i++)
|
||||
pathbuf[pl++] = leaf[i];
|
||||
}
|
||||
pathbuf[pl] = '\0';
|
||||
n->str = leaf;
|
||||
n->strlen = strlen(leaf);
|
||||
n->usepath = astrndup(p->a, pathbuf, pl);
|
||||
expect(p, TK_SEMI);
|
||||
return n;
|
||||
}
|
||||
@@ -1360,7 +1373,32 @@ parsefile(Parser *p)
|
||||
advance(p);
|
||||
const char *name = expectident(p);
|
||||
expect(p, TK_SEMI);
|
||||
p->curmod = name;
|
||||
if (p->pathmod != NULL) {
|
||||
/* M1 #22: while an import path is active the
|
||||
* in-file `package` clause is an ASSERTION — its
|
||||
* leaf must equal the path's last component; it
|
||||
* does NOT overwrite the path-derived module. */
|
||||
const char *dot = strrchr(p->pathmod, '.');
|
||||
const char *last = dot ? dot + 1 : p->pathmod;
|
||||
if (strcmp(name, last) != 0) {
|
||||
errorf(p->cur.pos,
|
||||
"package %s does not match import path %s",
|
||||
name, p->pathmod);
|
||||
p->errs++;
|
||||
}
|
||||
} else {
|
||||
p->curmod = name;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* `//ww:module <path>` — M1 #22 import boundary. The following
|
||||
* file's decls mangle on the full dotted import path, not the
|
||||
* leaf `package` clause, and are flagged imported (gates the
|
||||
* root-only bare-`main` rule, #32). */
|
||||
if (p->cur.kind == TK_MODPATH) {
|
||||
p->pathmod = p->cur.text;
|
||||
p->curmod = p->cur.text;
|
||||
advance(p);
|
||||
continue;
|
||||
}
|
||||
/* `//ww:module-reset` — bundle boundary before a package-less
|
||||
@@ -1374,6 +1412,7 @@ parsefile(Parser *p)
|
||||
if (p->cur.kind == TK_MODRESET) {
|
||||
advance(p);
|
||||
p->curmod = NULL;
|
||||
p->pathmod = NULL;
|
||||
continue;
|
||||
}
|
||||
Node *attrs = parseattrs(p);
|
||||
@@ -1399,7 +1438,10 @@ parsefile(Parser *p)
|
||||
advance(p);
|
||||
continue;
|
||||
}
|
||||
if (d != NULL) d->module = p->curmod;
|
||||
if (d != NULL) {
|
||||
d->module = p->curmod;
|
||||
d->imported = (p->pathmod != NULL);
|
||||
}
|
||||
if (head == NULL) head = d;
|
||||
else tail->next = d;
|
||||
tail = d;
|
||||
|
||||
@@ -104,6 +104,7 @@ tokname(Tkind k)
|
||||
case TK_ENUM: return "enum";
|
||||
case TK_MODULE: return "package";
|
||||
case TK_MODRESET: return "//ww:module-reset";
|
||||
case TK_MODPATH: return "//ww:module";
|
||||
|
||||
case TK_LPAREN: return "(";
|
||||
case TK_RPAREN: return ")";
|
||||
|
||||
23
cmd/wcc/ww.h
23
cmd/wcc/ww.h
@@ -184,6 +184,10 @@ typedef enum {
|
||||
* curmod to NULL before a package-less file's bytes
|
||||
* (#16 option-B; the package-less-entry attribution fix
|
||||
* that replaces the withdrawn `package main` inject). */
|
||||
TK_MODPATH, /* `//ww:module <dotted-path>` — driver import boundary:
|
||||
* the following file's decls mangle on the full import
|
||||
* path, not the leaf `package` clause (M1 #22). Token
|
||||
* text carries the dotted path. */
|
||||
|
||||
TK_LAST /* sentinel for tables */
|
||||
} Tkind;
|
||||
@@ -214,6 +218,9 @@ struct Lex {
|
||||
int modreset; /* a `//ww:module-reset` directive was seen in
|
||||
* the last skipped run; lexnext emits TK_MODRESET
|
||||
* before the next real token. */
|
||||
const char *modpath; /* a `//ww:module <path>` directive was seen in
|
||||
* the last skipped run; lexnext emits TK_MODPATH
|
||||
* carrying this dotted path (M1 #22). */
|
||||
};
|
||||
|
||||
void lexinit(Lex*, Arena*, const char *file, const char *src, u64 len);
|
||||
@@ -340,7 +347,17 @@ struct Node {
|
||||
* decl's section in combined.ww.
|
||||
* NULL for nested nodes; only top-
|
||||
* level decls (fn/def/type/let)
|
||||
* carry it. */
|
||||
* carry it. On an N_USE node this is
|
||||
* the importing (owning) module. */
|
||||
const char *usepath; /* M1 #22: on an N_USE node, the full
|
||||
* dotted IMPORT path (`encoding.utf8`)
|
||||
* vs the leaf alias in `str`. Drives
|
||||
* the path-keyed decl_mod match and the
|
||||
* qualified-ref codegen hint. */
|
||||
int imported; /* M1 #22: decl reached through an
|
||||
* `//ww:module <path>` import boundary
|
||||
* (vs root/primary). Gates the root-only
|
||||
* bare-`main` rule (#32). */
|
||||
};
|
||||
|
||||
Node *newnode(Arena*, Nkind, Pos);
|
||||
@@ -358,6 +375,10 @@ struct Parser {
|
||||
const char *curmod; /* most-recent `module foo;` declaration —
|
||||
* stamped onto each top-level decl that
|
||||
* follows. */
|
||||
const char *pathmod; /* M1 #22: active `//ww:module <path>` dotted
|
||||
* import path; while set, decls stamp
|
||||
* module=pathmod and imported=1, and the
|
||||
* in-file `package` clause is an assertion. */
|
||||
};
|
||||
|
||||
void parserinit(Parser*, Arena*, Lex*);
|
||||
|
||||
@@ -218,7 +218,7 @@ enumerate_dir_ww(const char *dirpath, char ***out_files)
|
||||
}
|
||||
|
||||
static void expand(FILE *out, const char *path, struct ImportSet *visited,
|
||||
const char *libdir);
|
||||
const char *libdir, const char *modpath);
|
||||
|
||||
/* Scan `path` for its first non-comment-non-blank line; if it starts
|
||||
* with `package <name>;` write the name into `out` (NUL-terminated)
|
||||
@@ -299,7 +299,7 @@ unit_has_package(const char *path, const char *leaf)
|
||||
* dir-enum). */
|
||||
static void
|
||||
expand_dir(FILE *out, const char *dirpath, struct ImportSet *visited,
|
||||
const char *libdir)
|
||||
const char *libdir, const char *modpath)
|
||||
{
|
||||
char **files = NULL;
|
||||
int n = enumerate_dir_ww(dirpath, &files);
|
||||
@@ -318,7 +318,7 @@ expand_dir(FILE *out, const char *dirpath, struct ImportSet *visited,
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
expand(out, fp, visited, libdir);
|
||||
expand(out, fp, visited, libdir, modpath);
|
||||
free(files[i]);
|
||||
}
|
||||
free(files);
|
||||
@@ -331,7 +331,7 @@ expand_dir(FILE *out, const char *dirpath, struct ImportSet *visited,
|
||||
* it). */
|
||||
static void
|
||||
expand(FILE *out, const char *path, struct ImportSet *visited,
|
||||
const char *libdir)
|
||||
const char *libdir, const char *modpath)
|
||||
{
|
||||
if (import_seen(visited, path)) return;
|
||||
import_add(visited, path);
|
||||
@@ -377,8 +377,13 @@ expand(FILE *out, const char *path, struct ImportSet *visited,
|
||||
fprintf(stderr, "ww: cannot find package %s\n", name);
|
||||
exit(1);
|
||||
}
|
||||
if (is_dir) expand_dir(out, ipath, visited, libdir);
|
||||
else expand(out, ipath, visited, libdir);
|
||||
/* M1 #22 (isdir-gated, rob-ratified): a package IS a directory, so
|
||||
* only DIRECTORY imports are package boundaries that path-mangle.
|
||||
* A single-file import (`import opcodes;` → opcodes.ww declaring
|
||||
* `package w6a`) is an intra-package file-split — it keeps its
|
||||
* in-file `package` clause as its module (no directive). */
|
||||
if (is_dir) expand_dir(out, ipath, visited, libdir, name);
|
||||
else expand(out, ipath, visited, libdir, NULL);
|
||||
}
|
||||
|
||||
/* #16 option-B: a package-less file's decls would otherwise inherit
|
||||
@@ -389,10 +394,19 @@ expand(FILE *out, const char *path, struct ImportSet *visited,
|
||||
* `package main`, which would main-prefix them). A packaged file's own
|
||||
* `package` decl already sets curmod, so it needs nothing — keeping
|
||||
* the directive out of every tracked combined.ww. (Task #11.) */
|
||||
{
|
||||
char pkg[128];
|
||||
if (!peek_package(path, pkg, sizeof pkg))
|
||||
fputs("//ww:module-reset\n", out);
|
||||
if (modpath != NULL && modpath[0] != '\0') {
|
||||
/* M1 (#22): an import-reached file carries its full dotted
|
||||
* import path so codegen mangles symbols on the path, not the
|
||||
* leaf `package` clause. The directive's absence is the root
|
||||
* marker (#32): root/primary files take the branch below. */
|
||||
fprintf(out, "//ww:module %s\n", modpath);
|
||||
} else {
|
||||
/* Root/primary file: reset the bundle boundary so a preceding
|
||||
* imported section's sticky pathmod (M1 #22) is cleared. A
|
||||
* package-less file then stays primary ("") as before; a
|
||||
* packaged primary's own `package` clause sets curmod fresh
|
||||
* (pathmod now NULL → real clause, not an assertion). */
|
||||
fputs("//ww:module-reset\n", out);
|
||||
}
|
||||
rewind(in);
|
||||
int ch;
|
||||
@@ -505,12 +519,12 @@ build_one(const char *src, int entry_is_dir, const char *out,
|
||||
char tpath[1024];
|
||||
int tdir = 0;
|
||||
if (locate_import(srcdir, "test", tpath, sizeof tpath, &tdir)) {
|
||||
if (tdir) expand_dir(cf, tpath, &visited, srcdir);
|
||||
else expand(cf, tpath, &visited, srcdir);
|
||||
if (tdir) expand_dir(cf, tpath, &visited, srcdir, "test");
|
||||
else expand(cf, tpath, &visited, srcdir, NULL);
|
||||
}
|
||||
}
|
||||
if (entry_is_dir) expand_dir(cf, srcd, &visited, srcdir);
|
||||
else expand(cf, src, &visited, srcdir);
|
||||
if (entry_is_dir) expand_dir(cf, srcd, &visited, srcdir, NULL);
|
||||
else expand(cf, src, &visited, srcdir, NULL);
|
||||
fclose(cf);
|
||||
for (int i = 0; i < visited.n; i++) free(visited.paths[i]);
|
||||
free(visited.paths);
|
||||
|
||||
@@ -137,13 +137,17 @@ type node = struct {
|
||||
type_: *void, // filled in by checker; type.ww treats it as *tinfo
|
||||
tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...)
|
||||
nmod: str, // originating module from `// MODULE: foo`; "" if none
|
||||
usepath: str, // on an N_USE: full dotted IMPORT path vs leaf alias
|
||||
// in `str` (M1 #22); "" otherwise
|
||||
imported: i32, // M1 #22: decl reached via `//ww:module <path>` import
|
||||
// boundary (vs root/primary); gates root-only bare main
|
||||
};
|
||||
|
||||
export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
|
||||
// fval cast-init: 990's wwdump TK_FLOAT diff requires this file
|
||||
// to tokenise identically through C and ww (lex.ww:382 has the
|
||||
// same workaround for the cstage %g-formats vs ww-skips divergence).
|
||||
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod=""})!;
|
||||
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod="", usepath="", imported=0})!;
|
||||
return n;
|
||||
};
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@ type lex = struct {
|
||||
// a `//ww:module-reset` directive was seen in the last skipped run;
|
||||
// lexnext emits TK_MODRESET before the next real token (#16 opt-B).
|
||||
modreset: i32,
|
||||
// a `//ww:module <path>` directive was seen in the last skipped run;
|
||||
// lexnext emits TK_MODPATH carrying this dotted path (M1 #22).
|
||||
modpathset: i32,
|
||||
modpath: str,
|
||||
};
|
||||
|
||||
export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
|
||||
@@ -70,6 +74,7 @@ export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
|
||||
l.col = 1;
|
||||
l.errs = 0;
|
||||
l.modreset = 0;
|
||||
l.modpathset = 0;
|
||||
};
|
||||
|
||||
// srcb — byte at offset; helper that lifts the cast out of indexing.
|
||||
@@ -137,19 +142,62 @@ fn skipws(l: *lex) bool = {
|
||||
// The body is then skipped like any comment.
|
||||
// Mirrors cstage lex.c skipws. Compare via lpeek
|
||||
// (no consume) so the skip loop below is unchanged.
|
||||
let dir: str = "ww:module-reset";
|
||||
let pre: str = "ww:module";
|
||||
let di: i32 = 0;
|
||||
let matched: bool = true;
|
||||
for (di < dir.len) {
|
||||
if (lpeek(l, di: u64) != dir[di]: i32) {
|
||||
for (di < pre.len) {
|
||||
if (lpeek(l, di: u64) != pre[di]: i32) {
|
||||
matched = false; break;
|
||||
};
|
||||
di += 1;
|
||||
};
|
||||
if (matched) {
|
||||
let nx: i32 = lpeek(l, dir.len: u64);
|
||||
if (nx == '\n') { l.modreset = 1; }
|
||||
else { if (nx < 0) { l.modreset = 1; }; };
|
||||
let nx: i32 = lpeek(l, pre.len: u64);
|
||||
if (nx == '-') {
|
||||
let rest: str = "-reset";
|
||||
let rj: i32 = 0;
|
||||
let rm: bool = true;
|
||||
for (rj < rest.len) {
|
||||
if (lpeek(l, (pre.len + rj): u64)
|
||||
!= rest[rj]: i32) {
|
||||
rm = false; break;
|
||||
};
|
||||
rj += 1;
|
||||
};
|
||||
if (rm) {
|
||||
let af: i32 = lpeek(l,
|
||||
(pre.len + rest.len): u64);
|
||||
if (af == '\n') { l.modreset = 1; }
|
||||
else { if (af < 0) { l.modreset = 1; }; };
|
||||
};
|
||||
} else { if (nx == ' ' || nx == '\t') {
|
||||
// `//ww:module <path>` — M1 import boundary.
|
||||
let k: u64 = pre.len: u64;
|
||||
for (true) {
|
||||
let sc: i32 = lpeek(l, k);
|
||||
if (sc == ' ' || sc == '\t') {
|
||||
k += 1u64; continue;
|
||||
};
|
||||
break;
|
||||
};
|
||||
let s0: u64 = k;
|
||||
for (true) {
|
||||
let pc: i32 = lpeek(l, k);
|
||||
if (pc < 0) { break; };
|
||||
if (pc == '\n' || pc == '\r'
|
||||
|| pc == ' ' || pc == '\t') {
|
||||
break;
|
||||
};
|
||||
k += 1u64;
|
||||
};
|
||||
if (k > s0) {
|
||||
let view: str;
|
||||
view.ptr = l.src + l.lpos + s0;
|
||||
view.len = (k - s0): i32;
|
||||
l.modpath = strings.dup(view);
|
||||
l.modpathset = 1;
|
||||
};
|
||||
}; };
|
||||
};
|
||||
for (true) {
|
||||
let cx: i32 = lpeek(l, 0u64);
|
||||
@@ -663,6 +711,12 @@ export fn lexnext(l: *lex, out: *tok) void = {
|
||||
emitsimple(&start, tkind.TK_MODRESET, out);
|
||||
return;
|
||||
};
|
||||
if (l.modpathset != 0) {
|
||||
l.modpathset = 0;
|
||||
emitsimple(&start, tkind.TK_MODPATH, out);
|
||||
out.text = l.modpath;
|
||||
return;
|
||||
};
|
||||
if (!more) {
|
||||
emitsimple(&start, tkind.TK_EOF, out);
|
||||
return;
|
||||
|
||||
@@ -121,7 +121,10 @@ type tkind = enum i32 {
|
||||
TK_MODRESET = 87, // `//ww:module-reset` driver bundle boundary:
|
||||
// reset curmod to "" before a package-less file
|
||||
// (#16 option-B; cstage TK_MODRESET twin)
|
||||
TK_LAST = 88,
|
||||
TK_MODPATH = 88, // `//ww:module <dotted-path>` driver import
|
||||
// boundary; decls mangle on the path, not the
|
||||
// leaf `package` clause (M1 #22; cstage twin)
|
||||
TK_LAST = 89,
|
||||
};
|
||||
|
||||
// ---- Pos / Tok --------------------------------------------------------
|
||||
@@ -244,6 +247,7 @@ export fn tokname(k: tkind) str = {
|
||||
case tkind.TK_ENUM: return "enum";
|
||||
case tkind.TK_MODULE: return "package";
|
||||
case tkind.TK_MODRESET: return "//ww:module-reset";
|
||||
case tkind.TK_MODPATH: return "//ww:module";
|
||||
|
||||
case tkind.TK_LPAREN: return "(";
|
||||
case tkind.TK_RPAREN: return ")";
|
||||
|
||||
@@ -129,11 +129,13 @@ fn checkname(k: tkind, want: str) void = {
|
||||
checkname(tkind.TK_FATARROW, "=>");
|
||||
|
||||
checkname(tkind.TK_MODRESET, "//ww:module-reset");
|
||||
checkname(tkind.TK_MODPATH, "//ww:module");
|
||||
checkname(tkind.TK_LAST, "<last>");
|
||||
|
||||
// Unknown kind → the post-switch fallback. TK_LAST is the highest
|
||||
// named value (88); 89 is out of band, exercising the "<?>" tail.
|
||||
checkname(89: tkind, "<?>");
|
||||
// named value (89, after TK_MODPATH=88 landed); 90 is out of band,
|
||||
// exercising the "<?>" tail.
|
||||
checkname(90: tkind, "<?>");
|
||||
};
|
||||
|
||||
fn checkkw(s: str, want: tkind) void = {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package parse;
|
||||
|
||||
import os;
|
||||
import strings;
|
||||
import tok;
|
||||
|
||||
// `import encoding.utf8;` — the driver resolves the dotted path to
|
||||
@@ -17,13 +18,19 @@ fn parseuse(p: *parser) *node = {
|
||||
advance(p); // past `use`
|
||||
let n: *node = newnode(nkind.N_USE, pf, pl, pc);
|
||||
n.nmod = p.curmod;
|
||||
// M1 #22: accumulate the full dotted import path (n.usepath) for the
|
||||
// checker's path-keyed module match; n.str stays the leaf alias the
|
||||
// user writes (`utf8.x`).
|
||||
let leaf: str;
|
||||
expectident(p, &leaf);
|
||||
let path: str = leaf;
|
||||
for (p.curkind == tkind.TK_DOT) {
|
||||
advance(p); // past `.`
|
||||
expectident(p, &leaf);
|
||||
path = strings.concat(path, ".", leaf);
|
||||
};
|
||||
n.str = leaf;
|
||||
n.usepath = path;
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after use");
|
||||
return n;
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ package parse;
|
||||
// dir-enum when callers `import parse;` (which dir-enums
|
||||
// lib/ww/parse/).
|
||||
import os;
|
||||
import strings;
|
||||
import tok;
|
||||
|
||||
type parser = struct {
|
||||
@@ -43,6 +44,10 @@ type parser = struct {
|
||||
// multi-file streams successive `module` decls mark per-file
|
||||
// section boundaries. Mirrors cstage Parser.curmod.
|
||||
curmod: str,
|
||||
// M1 #22: active `//ww:module <path>` dotted import path. While set,
|
||||
// decls stamp nmod=pathmod and imported=1, and the in-file `package`
|
||||
// clause is an assertion. "" means inactive (root/primary).
|
||||
pathmod: str,
|
||||
};
|
||||
|
||||
fn refill(p: *parser) void = {
|
||||
@@ -62,6 +67,7 @@ export fn parserinit(p: *parser, l: *lex) void = {
|
||||
p.l = l;
|
||||
p.errs = 0;
|
||||
p.nocast = 0;
|
||||
p.pathmod = "";
|
||||
refill(p);
|
||||
};
|
||||
|
||||
@@ -408,7 +414,30 @@ export fn parsefile(p: *parser) *node = {
|
||||
let name: str;
|
||||
expectident(p, &name);
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
|
||||
p.curmod = name;
|
||||
if (p.pathmod.len != 0) {
|
||||
// M1 #22: while an import path is active the in-file
|
||||
// `package` clause is an ASSERTION — its leaf must
|
||||
// equal the path's last component; it does NOT
|
||||
// overwrite the path-derived module.
|
||||
let (pre, post) = strings.rcut(p.pathmod, ".");
|
||||
let last: str = post;
|
||||
if (post.len == 0) { last = p.pathmod; };
|
||||
if (strings.compare(name, last) != 0) {
|
||||
errmsg(p, "package does not match import path");
|
||||
};
|
||||
} else {
|
||||
p.curmod = name;
|
||||
};
|
||||
continue;
|
||||
};
|
||||
// `//ww:module <path>` — M1 #22 import boundary. The following
|
||||
// file's decls mangle on the full dotted import path, not the
|
||||
// leaf `package` clause, and are flagged imported (gates the
|
||||
// root-only bare-`main` rule, #32).
|
||||
if (p.curkind == tkind.TK_MODPATH) {
|
||||
p.pathmod = p.curtext;
|
||||
p.curmod = p.curtext;
|
||||
advance(p);
|
||||
continue;
|
||||
};
|
||||
// `//ww:module-reset` — bundle boundary before a package-less
|
||||
@@ -425,6 +454,7 @@ export fn parsefile(p: *parser) *node = {
|
||||
empty.ptr = nil;
|
||||
empty.len = 0;
|
||||
p.curmod = empty;
|
||||
p.pathmod = "";
|
||||
continue;
|
||||
};
|
||||
let attrs = parseattrs(p);
|
||||
@@ -476,6 +506,9 @@ export fn parsefile(p: *parser) *node = {
|
||||
};};};};};};
|
||||
|
||||
if (d != nil) {
|
||||
// M1 #22: flag decls reached via an import-path boundary
|
||||
// (gates the root-only bare-`main` rule, #32).
|
||||
if (p.pathmod.len != 0) { d.imported = 1; };
|
||||
if (head == nil) {
|
||||
head = d;
|
||||
tail = d;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//ww:module time
|
||||
// time — clocks, instants, durations. Mirrors Hare's lib/time
|
||||
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
|
||||
// +linux/functions.ha). Calendar / date / strftime / timezone /
|
||||
@@ -95,6 +96,7 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
//ww:module rt
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
@@ -118,6 +120,7 @@ package rt;
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
//ww:module os
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -856,6 +859,7 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
//ww:module types
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
@@ -900,6 +904,7 @@ def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
|
||||
|
||||
def RUNE_MIN: rune = '\0';
|
||||
|
||||
//ww:module bytes
|
||||
// bytes — slice operations over []u8. Mirrors Hare's bytes module
|
||||
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
|
||||
// helpers used by lib/encoding, lib/bufio, lib/memio.
|
||||
@@ -1451,6 +1456,7 @@ export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
|
||||
};
|
||||
};
|
||||
|
||||
//ww:module encoding.utf8
|
||||
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
|
||||
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
|
||||
//
|
||||
@@ -1908,6 +1914,7 @@ export fn position(d: *decoder) i32 = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module strings
|
||||
// strings — operations over str ({ptr,len}). Hare port; see
|
||||
// ref/hare/strings/.
|
||||
//
|
||||
@@ -2860,6 +2867,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||
return frombytes(buf);
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6a/opcodes.ww — types + constants shared across the
|
||||
// w6a port. Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h.
|
||||
|
||||
@@ -3086,6 +3094,7 @@ type asm_ = struct {
|
||||
errs: i32,
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6a/lex.ww — port of cmd/w6a/lex.c.
|
||||
//
|
||||
// Character-level helpers for w6a's line-oriented parser. The parser
|
||||
@@ -3164,6 +3173,7 @@ export fn parsenum(p: *u8, n: u64) (i64, u64) = {
|
||||
return v, i;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6a/parse.ww — port of cmd/w6a/parse.c.
|
||||
//
|
||||
// Line-oriented parser for the asm subset emitted by w6c.
|
||||
@@ -3764,6 +3774,7 @@ export fn parse(a: *asm_) i32 = {
|
||||
return a.errs;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6a/asm.ww — port of cmd/w6a/asm.c.
|
||||
//
|
||||
// Encode the parsed aprog list into amd64 machine bytes, appending to
|
||||
@@ -4620,6 +4631,7 @@ export fn encode(a: *asm_) i32 = {
|
||||
return a.errs;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6a/obj.ww — port of cmd/w6a/obj.c.
|
||||
//
|
||||
// Emit a tiny ELF64 relocatable object. Layout (in file order):
|
||||
@@ -5033,6 +5045,7 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
|
||||
return 0;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6a/main.ww — port of cmd/w6a/main.c.
|
||||
//
|
||||
// w6a = amd64 assembler. Read .s, parse, encode, emit ELF .o.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//ww:module time
|
||||
// time — clocks, instants, durations. Mirrors Hare's lib/time
|
||||
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
|
||||
// +linux/functions.ha). Calendar / date / strftime / timezone /
|
||||
@@ -95,6 +96,7 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
//ww:module rt
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
@@ -118,6 +120,7 @@ package rt;
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
//ww:module os
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -856,6 +859,7 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
//ww:module types
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
@@ -900,6 +904,7 @@ def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
|
||||
|
||||
def RUNE_MIN: rune = '\0';
|
||||
|
||||
//ww:module bytes
|
||||
// bytes — slice operations over []u8. Mirrors Hare's bytes module
|
||||
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
|
||||
// helpers used by lib/encoding, lib/bufio, lib/memio.
|
||||
@@ -1451,6 +1456,7 @@ export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
|
||||
};
|
||||
};
|
||||
|
||||
//ww:module encoding.utf8
|
||||
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
|
||||
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
|
||||
//
|
||||
@@ -1908,6 +1914,7 @@ export fn position(d: *decoder) i32 = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module strings
|
||||
// strings — operations over str ({ptr,len}). Hare port; see
|
||||
// ref/hare/strings/.
|
||||
//
|
||||
@@ -2860,6 +2867,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||
return frombytes(buf);
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — arbitrary-precision decimal engine for float↔string
|
||||
// conversion. Mirrors ref/hare/strconv/decimal.ha (Hare in turn ports
|
||||
// Go's lib/strconv/decimal.go). Pure integer arithmetic; no f32/f64
|
||||
@@ -3182,6 +3190,7 @@ fn decimal_round(d: *decimal) u64 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
//ww:module math
|
||||
// floats — f64 classification, sign, bit-reinterpret core, and the f64
|
||||
// decompose half (subnormal-normalize + frexp). Ported from
|
||||
// ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
|
||||
@@ -3456,6 +3465,7 @@ export fn frexpf64(n: f64) (f64, i64) = {
|
||||
return (mantissa, exp);
|
||||
};
|
||||
|
||||
//ww:module math
|
||||
// math — numeric helpers. Subset of Hare's math::; only the absolute-
|
||||
// value pair for the signed integer types we currently care about. The
|
||||
// return type is unsigned so that abs(I32_MIN) doesn't overflow.
|
||||
@@ -3472,6 +3482,7 @@ export fn absi64(n: i64) u64 = {
|
||||
return n: u64;
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — float→string via Ryū (shortest round-trippable decimal).
|
||||
// Mirrors ref/hare/strconv/ftos_ryu.ha (the algorithm core) +
|
||||
// ref/hare/strconv/ftos.ha:432 (the f64tos driver). Ryū: Ulf Adams,
|
||||
@@ -4266,6 +4277,7 @@ export fn f32tos(n: f32) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — Ryū float→string lookup tables + bit-count constants.
|
||||
// Mirrors ref/hare/strconv/ftos_ryu.ha:159-222 byte-exact. Pure data
|
||||
// fold (strconv #106 fold-5): no logic, consumed by ftos.ww's
|
||||
@@ -4377,6 +4389,7 @@ let POW5_TABLE: [26]u64 = [
|
||||
59604644775390625u64, 298023223876953125u64,
|
||||
];
|
||||
|
||||
//ww:module ascii
|
||||
// ascii — rune-class predicates and case folding for the ASCII range.
|
||||
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
|
||||
// outside 0..127 always answer `false`. The lexer hot path uses these
|
||||
@@ -4581,6 +4594,7 @@ export fn strupper_buf(s: str, buf: []u8) (str | nomem) = {
|
||||
return strings.frombytes(buf);
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
|
||||
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
|
||||
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
|
||||
@@ -5280,6 +5294,7 @@ export fn stof32(s: str, b: base) (f32 | invalid | overflow) = {
|
||||
return 0: invalid; // unreachable (path-cov)
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — stof/ftos lookup tables. Mirrors ref/hare/strconv/stof_data.ha
|
||||
// byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
|
||||
// re-sequenced 2026-05-26): no logic, exercised transitively when fold-3's
|
||||
@@ -5979,6 +5994,7 @@ let powers_of_ten: [596][2]u64 = [
|
||||
[0x73832EEC6FFF3111u64, 0xD226FC195C6A2F8Cu64],
|
||||
];
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — number↔string conversions.
|
||||
//
|
||||
// Mirrors Hare's strconv:: surface. The *tos functions return a
|
||||
@@ -6397,6 +6413,7 @@ export fn strerror(e: error) str = {
|
||||
return strings.dup("");
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// lib/ww/lex/tok.ww — port of cmd/wcc/tok.c plus the Tkind /
|
||||
// Tok / Pos shapes from cmd/wcc/ww.h.
|
||||
//
|
||||
@@ -6520,7 +6537,10 @@ type tkind = enum i32 {
|
||||
TK_MODRESET = 87, // `//ww:module-reset` driver bundle boundary:
|
||||
// reset curmod to "" before a package-less file
|
||||
// (#16 option-B; cstage TK_MODRESET twin)
|
||||
TK_LAST = 88,
|
||||
TK_MODPATH = 88, // `//ww:module <dotted-path>` driver import
|
||||
// boundary; decls mangle on the path, not the
|
||||
// leaf `package` clause (M1 #22; cstage twin)
|
||||
TK_LAST = 89,
|
||||
};
|
||||
|
||||
// ---- Pos / Tok --------------------------------------------------------
|
||||
@@ -6643,6 +6663,7 @@ export fn tokname(k: tkind) str = {
|
||||
case tkind.TK_ENUM: return "enum";
|
||||
case tkind.TK_MODULE: return "package";
|
||||
case tkind.TK_MODRESET: return "//ww:module-reset";
|
||||
case tkind.TK_MODPATH: return "//ww:module";
|
||||
|
||||
case tkind.TK_LPAREN: return "(";
|
||||
case tkind.TK_RPAREN: return ")";
|
||||
@@ -6797,6 +6818,7 @@ export fn tokprint(fd: i32, t: *tok) void = {
|
||||
fputcbyte(fd, '\n');
|
||||
};
|
||||
|
||||
//ww:module lex
|
||||
// lib/ww/lex/lex.ww — port of cmd/wcc/lex.c.
|
||||
//
|
||||
// The DFA, the helpers, and the order of decisions all mirror the C
|
||||
@@ -6858,6 +6880,10 @@ type lex = struct {
|
||||
// a `//ww:module-reset` directive was seen in the last skipped run;
|
||||
// lexnext emits TK_MODRESET before the next real token (#16 opt-B).
|
||||
modreset: i32,
|
||||
// a `//ww:module <path>` directive was seen in the last skipped run;
|
||||
// lexnext emits TK_MODPATH carrying this dotted path (M1 #22).
|
||||
modpathset: i32,
|
||||
modpath: str,
|
||||
};
|
||||
|
||||
export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
|
||||
@@ -6869,6 +6895,7 @@ export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
|
||||
l.col = 1;
|
||||
l.errs = 0;
|
||||
l.modreset = 0;
|
||||
l.modpathset = 0;
|
||||
};
|
||||
|
||||
// srcb — byte at offset; helper that lifts the cast out of indexing.
|
||||
@@ -6936,19 +6963,62 @@ fn skipws(l: *lex) bool = {
|
||||
// The body is then skipped like any comment.
|
||||
// Mirrors cstage lex.c skipws. Compare via lpeek
|
||||
// (no consume) so the skip loop below is unchanged.
|
||||
let dir: str = "ww:module-reset";
|
||||
let pre: str = "ww:module";
|
||||
let di: i32 = 0;
|
||||
let matched: bool = true;
|
||||
for (di < dir.len) {
|
||||
if (lpeek(l, di: u64) != dir[di]: i32) {
|
||||
for (di < pre.len) {
|
||||
if (lpeek(l, di: u64) != pre[di]: i32) {
|
||||
matched = false; break;
|
||||
};
|
||||
di += 1;
|
||||
};
|
||||
if (matched) {
|
||||
let nx: i32 = lpeek(l, dir.len: u64);
|
||||
if (nx == '\n') { l.modreset = 1; }
|
||||
else { if (nx < 0) { l.modreset = 1; }; };
|
||||
let nx: i32 = lpeek(l, pre.len: u64);
|
||||
if (nx == '-') {
|
||||
let rest: str = "-reset";
|
||||
let rj: i32 = 0;
|
||||
let rm: bool = true;
|
||||
for (rj < rest.len) {
|
||||
if (lpeek(l, (pre.len + rj): u64)
|
||||
!= rest[rj]: i32) {
|
||||
rm = false; break;
|
||||
};
|
||||
rj += 1;
|
||||
};
|
||||
if (rm) {
|
||||
let af: i32 = lpeek(l,
|
||||
(pre.len + rest.len): u64);
|
||||
if (af == '\n') { l.modreset = 1; }
|
||||
else { if (af < 0) { l.modreset = 1; }; };
|
||||
};
|
||||
} else { if (nx == ' ' || nx == '\t') {
|
||||
// `//ww:module <path>` — M1 import boundary.
|
||||
let k: u64 = pre.len: u64;
|
||||
for (true) {
|
||||
let sc: i32 = lpeek(l, k);
|
||||
if (sc == ' ' || sc == '\t') {
|
||||
k += 1u64; continue;
|
||||
};
|
||||
break;
|
||||
};
|
||||
let s0: u64 = k;
|
||||
for (true) {
|
||||
let pc: i32 = lpeek(l, k);
|
||||
if (pc < 0) { break; };
|
||||
if (pc == '\n' || pc == '\r'
|
||||
|| pc == ' ' || pc == '\t') {
|
||||
break;
|
||||
};
|
||||
k += 1u64;
|
||||
};
|
||||
if (k > s0) {
|
||||
let view: str;
|
||||
view.ptr = l.src + l.lpos + s0;
|
||||
view.len = (k - s0): i32;
|
||||
l.modpath = strings.dup(view);
|
||||
l.modpathset = 1;
|
||||
};
|
||||
}; };
|
||||
};
|
||||
for (true) {
|
||||
let cx: i32 = lpeek(l, 0u64);
|
||||
@@ -7462,6 +7532,12 @@ export fn lexnext(l: *lex, out: *tok) void = {
|
||||
emitsimple(&start, tkind.TK_MODRESET, out);
|
||||
return;
|
||||
};
|
||||
if (l.modpathset != 0) {
|
||||
l.modpathset = 0;
|
||||
emitsimple(&start, tkind.TK_MODPATH, out);
|
||||
out.text = l.modpath;
|
||||
return;
|
||||
};
|
||||
if (!more) {
|
||||
emitsimple(&start, tkind.TK_EOF, out);
|
||||
return;
|
||||
@@ -7578,6 +7654,7 @@ export fn lexnext(l: *lex, out: *tok) void = {
|
||||
out.text = strings.dup(view);
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// lib/ww/ast.ww — port of cmd/wcc/ast.c (Node defs + printer).
|
||||
//
|
||||
// Status: AST printer is fully ported. Constructor `newnode` is here.
|
||||
@@ -7717,13 +7794,17 @@ type node = struct {
|
||||
type_: *void, // filled in by checker; type.ww treats it as *tinfo
|
||||
tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...)
|
||||
nmod: str, // originating module from `// MODULE: foo`; "" if none
|
||||
usepath: str, // on an N_USE: full dotted IMPORT path vs leaf alias
|
||||
// in `str` (M1 #22); "" otherwise
|
||||
imported: i32, // M1 #22: decl reached via `//ww:module <path>` import
|
||||
// boundary (vs root/primary); gates root-only bare main
|
||||
};
|
||||
|
||||
export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
|
||||
// fval cast-init: 990's wwdump TK_FLOAT diff requires this file
|
||||
// to tokenise identically through C and ww (lex.ww:382 has the
|
||||
// same workaround for the cstage %g-formats vs ww-skips divergence).
|
||||
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod=""})!;
|
||||
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod="", usepath="", imported=0})!;
|
||||
return n;
|
||||
};
|
||||
|
||||
@@ -7965,11 +8046,13 @@ export fn astprint(fd: i32, n: *node) void = {
|
||||
pr(fd, n, 0);
|
||||
};
|
||||
|
||||
//ww:module parse
|
||||
// lib/ww/parse/decl.ww — declaration parsing, split out of parse.ww.
|
||||
|
||||
package parse;
|
||||
|
||||
import os;
|
||||
import strings;
|
||||
import tok;
|
||||
|
||||
// `import encoding.utf8;` — the driver resolves the dotted path to
|
||||
@@ -7984,13 +8067,19 @@ fn parseuse(p: *parser) *node = {
|
||||
advance(p); // past `use`
|
||||
let n: *node = newnode(nkind.N_USE, pf, pl, pc);
|
||||
n.nmod = p.curmod;
|
||||
// M1 #22: accumulate the full dotted import path (n.usepath) for the
|
||||
// checker's path-keyed module match; n.str stays the leaf alias the
|
||||
// user writes (`utf8.x`).
|
||||
let leaf: str;
|
||||
expectident(p, &leaf);
|
||||
let path: str = leaf;
|
||||
for (p.curkind == tkind.TK_DOT) {
|
||||
advance(p); // past `.`
|
||||
expectident(p, &leaf);
|
||||
path = strings.concat(path, ".", leaf);
|
||||
};
|
||||
n.str = leaf;
|
||||
n.usepath = path;
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after use");
|
||||
return n;
|
||||
};
|
||||
@@ -8147,6 +8236,7 @@ fn parsetypedecl(p: *parser, exported: i32) *node = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module parse
|
||||
// lib/ww/parse/expr.ww — expression parsing, split out of parse.ww.
|
||||
|
||||
package parse;
|
||||
@@ -8616,6 +8706,7 @@ fn parseexpr(p: *parser) *node = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module parse
|
||||
// lib/ww/parse/parse.ww — port of cmd/wcc/parse.c (entry + plumbing).
|
||||
//
|
||||
// Split into Hare-style submodule: parse.ww (here) holds the parser
|
||||
@@ -8634,6 +8725,7 @@ package parse;
|
||||
// dir-enum when callers `import parse;` (which dir-enums
|
||||
// lib/ww/parse/).
|
||||
import os;
|
||||
import strings;
|
||||
import tok;
|
||||
|
||||
type parser = struct {
|
||||
@@ -8661,6 +8753,10 @@ type parser = struct {
|
||||
// multi-file streams successive `module` decls mark per-file
|
||||
// section boundaries. Mirrors cstage Parser.curmod.
|
||||
curmod: str,
|
||||
// M1 #22: active `//ww:module <path>` dotted import path. While set,
|
||||
// decls stamp nmod=pathmod and imported=1, and the in-file `package`
|
||||
// clause is an assertion. "" means inactive (root/primary).
|
||||
pathmod: str,
|
||||
};
|
||||
|
||||
fn refill(p: *parser) void = {
|
||||
@@ -8680,6 +8776,7 @@ export fn parserinit(p: *parser, l: *lex) void = {
|
||||
p.l = l;
|
||||
p.errs = 0;
|
||||
p.nocast = 0;
|
||||
p.pathmod = "";
|
||||
refill(p);
|
||||
};
|
||||
|
||||
@@ -9026,7 +9123,30 @@ export fn parsefile(p: *parser) *node = {
|
||||
let name: str;
|
||||
expectident(p, &name);
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
|
||||
p.curmod = name;
|
||||
if (p.pathmod.len != 0) {
|
||||
// M1 #22: while an import path is active the in-file
|
||||
// `package` clause is an ASSERTION — its leaf must
|
||||
// equal the path's last component; it does NOT
|
||||
// overwrite the path-derived module.
|
||||
let (pre, post) = strings.rcut(p.pathmod, ".");
|
||||
let last: str = post;
|
||||
if (post.len == 0) { last = p.pathmod; };
|
||||
if (strings.compare(name, last) != 0) {
|
||||
errmsg(p, "package does not match import path");
|
||||
};
|
||||
} else {
|
||||
p.curmod = name;
|
||||
};
|
||||
continue;
|
||||
};
|
||||
// `//ww:module <path>` — M1 #22 import boundary. The following
|
||||
// file's decls mangle on the full dotted import path, not the
|
||||
// leaf `package` clause, and are flagged imported (gates the
|
||||
// root-only bare-`main` rule, #32).
|
||||
if (p.curkind == tkind.TK_MODPATH) {
|
||||
p.pathmod = p.curtext;
|
||||
p.curmod = p.curtext;
|
||||
advance(p);
|
||||
continue;
|
||||
};
|
||||
// `//ww:module-reset` — bundle boundary before a package-less
|
||||
@@ -9043,6 +9163,7 @@ export fn parsefile(p: *parser) *node = {
|
||||
empty.ptr = nil;
|
||||
empty.len = 0;
|
||||
p.curmod = empty;
|
||||
p.pathmod = "";
|
||||
continue;
|
||||
};
|
||||
let attrs = parseattrs(p);
|
||||
@@ -9094,6 +9215,9 @@ export fn parsefile(p: *parser) *node = {
|
||||
};};};};};};
|
||||
|
||||
if (d != nil) {
|
||||
// M1 #22: flag decls reached via an import-path boundary
|
||||
// (gates the root-only bare-`main` rule, #32).
|
||||
if (p.pathmod.len != 0) { d.imported = 1; };
|
||||
if (head == nil) {
|
||||
head = d;
|
||||
tail = d;
|
||||
@@ -9107,6 +9231,7 @@ export fn parsefile(p: *parser) *node = {
|
||||
return f;
|
||||
};
|
||||
|
||||
//ww:module parse
|
||||
// lib/ww/parse/stmt.ww — statement parsing, split out of parse.ww.
|
||||
|
||||
package parse;
|
||||
@@ -9521,6 +9646,7 @@ fn parsestmt(p: *parser) *node = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module-reset
|
||||
// lib/ww/typ.ww — port of cmd/wcc/type.c.
|
||||
//
|
||||
// Status: full structural port. The C version uses module-globals for
|
||||
@@ -10137,6 +10263,7 @@ export fn typeeq(a: *tinfo, b: *tinfo) bool = {
|
||||
return true; // primitives match by kind alone
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// lib/ww/sym.ww — port of cmd/wcc/sym.c.
|
||||
//
|
||||
// Per-scope hashtable, chained to the parent. Lookup walks up.
|
||||
@@ -10472,6 +10599,7 @@ export fn scopesamekeysym(s: *scope, name: str, mod: str) *sym = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c.
|
||||
//
|
||||
// Status: name-resolution + primitive-type seeding only. Full type
|
||||
@@ -10638,14 +10766,48 @@ fn declmod(file: *node, d: *node) str = {
|
||||
if (file == nil) { return empty; };
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
// M1 #22: a decl is imported iff some `use` directive's full
|
||||
// dotted import path equals the decl's module (now the path).
|
||||
// Single-level packages have usepath == leaf so this is
|
||||
// unchanged; nested (`encoding.utf8`) match here, not on leaf.
|
||||
if (u.kind == nkind.N_USE) {
|
||||
if (streq(u.str, d.nmod)) { return d.nmod; };
|
||||
if (streq(u.usepath, d.nmod)) { return d.nmod; };
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
|
||||
// usepath — map a `use` alias (leaf bareword the user writes, `utf8`)
|
||||
// to the full dotted import path it binds (`encoding.utf8`), for the
|
||||
// module-qualified resolution and codegen hint (M1 #22). Single-level
|
||||
// packages have usepath == alias so the result is unchanged. The
|
||||
// current tree has one occurrence per leaf, so the map is unambiguous.
|
||||
fn usepathfor(file: *node, alias: str) str = {
|
||||
let empty: str;
|
||||
if (file == nil) { return empty; };
|
||||
if (alias.len == 0) { return empty; };
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == nkind.N_USE) {
|
||||
if (streq(u.str, alias)) {
|
||||
if (u.usepath.len != 0) { return u.usepath; };
|
||||
return u.str;
|
||||
};
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
|
||||
// modkeyfor — usepathfor with leaf-alias fallback: the module key for a
|
||||
// path-keyed scopelookupinmodule given the alias the user wrote (M1 #22).
|
||||
fn modkeyfor(c: *checker, alias: str) str = {
|
||||
let mk: str = usepathfor(c.file, alias);
|
||||
if (mk.len == 0) { return alias; };
|
||||
return mk;
|
||||
};
|
||||
|
||||
// srcimports — does the source file that contributed decl-module
|
||||
// `modtag` carry `use <name>;`? Mirrors cstage's src_imports —
|
||||
// `modtag.len == 0` means primary, matching declmod's empty-str
|
||||
@@ -10662,7 +10824,7 @@ fn srcimports(file: *node, modtag: str, name: str) bool = {
|
||||
// module bareword and lib/fmt's own
|
||||
// `fn bsprintf(fmt: str, ...)` is not a shadow.
|
||||
if (u.nmod.len > 0) {
|
||||
if (streq(u.nmod, u.str)) {
|
||||
if (streq(u.nmod, u.usepath)) {
|
||||
u = u.next;
|
||||
continue;
|
||||
};
|
||||
@@ -10751,7 +10913,9 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
// pulls lack import->file->symbol provenance). Message byte-identical
|
||||
// to cstage check.c.
|
||||
if (k == nkind.N_USE) {
|
||||
if (mod.len != 0 && streq(nm, mod)) {
|
||||
// M1 #22: self-import ⟺ the imported path equals the use's own
|
||||
// (owning) module path. Compares paths, not leaves.
|
||||
if (mod.len != 0 && streq(d.usepath, mod)) {
|
||||
cerr("self-import: package '"); cerr(mod);
|
||||
cerr("' cannot import itself\n"); c.errs += 1i32;
|
||||
};
|
||||
@@ -10960,7 +11124,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + (dot + 1): u64;
|
||||
leaf.len = nm.len - (dot + 1);
|
||||
s = scopelookupinmodule(c.cur, head, leaf);
|
||||
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -11358,7 +11522,7 @@ fn aliassym(c: *checker, n: *node) *sym = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
|
||||
leaf.len = nm.len - dotidx - 1;
|
||||
s = scopelookupinmodule(c.cur, head, leaf);
|
||||
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
|
||||
} else {
|
||||
// #53: same-module preference. Mirrors cstage
|
||||
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
|
||||
@@ -11609,7 +11773,7 @@ fn scruttype(c: *checker, e: *node) *node = {
|
||||
if (e.kind == nkind.N_DOT) {
|
||||
if (e.lhs == nil) { return nil; };
|
||||
if (e.lhs.kind != nkind.N_IDENT) { return nil; };
|
||||
let s: *sym = scopelookupinmodule(c.cur, e.lhs.str, e.str);
|
||||
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.str), e.str);
|
||||
if (s == nil) { return nil; };
|
||||
if (s.decl == nil) { return nil; };
|
||||
return s.decl.lhs;
|
||||
@@ -12148,7 +12312,7 @@ fn evaldefconst(c: *checker, n: *node, out: *u64, depth: i32) bool = {
|
||||
if (k == nkind.N_DOT) {
|
||||
if (n.lhs == nil) { return false; };
|
||||
if (n.lhs.kind != nkind.N_IDENT) { return false; };
|
||||
let s: *sym = scopelookupinmodule(c.cur, n.lhs.str, n.str);
|
||||
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, n.lhs.str), n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_DEF) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
@@ -13265,7 +13429,7 @@ fn unoptype(c: *checker, e: *node) *node = {
|
||||
// cstage's actual acceptance reason.
|
||||
if (e.lhs.kind == nkind.N_DOT) {
|
||||
if (e.lhs.lhs != nil && e.lhs.lhs.kind == nkind.N_IDENT) {
|
||||
let fs: *sym = scopelookupinmodule(c.cur, e.lhs.lhs.str, e.lhs.str);
|
||||
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.lhs.str), e.lhs.str);
|
||||
if (fs != nil) {
|
||||
if (fs.skind == skind.SK_FN) {
|
||||
if (fs.decl != nil) {
|
||||
@@ -13923,7 +14087,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// global-leaf path) and harec check_autodereference
|
||||
// (ref/harec/src/check.c:1566-1581).
|
||||
if (ms != nil && (ms.skind == skind.SK_USE || ms.use_alias != 0i32)) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
s = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
|
||||
};
|
||||
};
|
||||
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
|
||||
@@ -13983,7 +14147,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// qualified resolution and the lenient checker policy
|
||||
// keeps the silent miss documented at scruttype L656.
|
||||
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
|
||||
let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str);
|
||||
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, lhsn.str), e.str);
|
||||
if (fs != nil) { if (fs.decl != nil) {
|
||||
// #34: a module-qualified bare fn rvalue `mod.fn` types as
|
||||
// its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is
|
||||
@@ -15840,7 +16004,7 @@ fn calleefndecl(c: *checker, callee: *node) *node = {
|
||||
};
|
||||
if (ms != nil) {
|
||||
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
|
||||
let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str);
|
||||
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), callee.str);
|
||||
if (fs != nil) {
|
||||
if (fs.skind == skind.SK_FN) { return fs.decl; };
|
||||
};
|
||||
@@ -16794,29 +16958,25 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
d = d.next;
|
||||
};
|
||||
|
||||
// Program-global, name-only, cross-module uniqueness on `main`.
|
||||
// `main` lowers to ONE bare entry symbol, so a second top-level
|
||||
// decl named `main` (any kind, any package) collides with the
|
||||
// entry at link time — today a silent segfault / link-fail in
|
||||
// both stages. The (name, module) duplicate rejects in installtop
|
||||
// read a cross-package `foo.main` and the bare entry as distinct,
|
||||
// so they miss this. Correct multi-main mangling (entry stays bare,
|
||||
// the rest qualify) is deferred (task #32); reject loudly meanwhile
|
||||
// (rule 7). Walks USER decls only — runs before the -T synth main
|
||||
// is appended below — so a hosted-test build never false-counts.
|
||||
// Twin of cmd/wcc/check.c.
|
||||
// Program-global uniqueness on the ENTRY `main`. M1 #32: the entry is
|
||||
// the ROOT-unit main (imported==0) — it alone lowers to the bare
|
||||
// `main` symbol. An IMPORTED package's `main` (imported==1) mangles on
|
||||
// its path (`foo.bar.main`) and may coexist, closing the old dup-main
|
||||
// collision by construction (#31). Two ROOT entries still collide on
|
||||
// the bare symbol → reject loud (rule 7). Walks USER decls only — runs
|
||||
// before the -T synth main is appended below. Twin of cmd/wcc/check.c.
|
||||
let firstmain: *node = nil;
|
||||
let mm: *node = file.list;
|
||||
for (mm != nil) {
|
||||
let ismain: bool = (mm.kind == nkind.N_FNDECL
|
||||
|| mm.kind == nkind.N_LET || mm.kind == nkind.N_DEF
|
||||
|| mm.kind == nkind.N_TYPEDECL) && streq(mm.str, "main");
|
||||
if (ismain) {
|
||||
if (ismain && mm.imported == 0) {
|
||||
if (firstmain == nil) {
|
||||
firstmain = mm;
|
||||
} else {
|
||||
cerr(mm.file);
|
||||
cerr(": error: duplicate top-level main: only the entry main may exist (task #32)\n");
|
||||
cerr(": error: duplicate entry main: only one root main may exist (#32)\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
@@ -17146,6 +17306,7 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
|
||||
};
|
||||
|
||||
//ww:module io
|
||||
// io — stream interface (Plan 9 Bio / Hare io::stream shape).
|
||||
//
|
||||
// No closures, no methods. A `stream` is a pointer to a `vtable` of
|
||||
@@ -17173,6 +17334,7 @@ export type eof = void;
|
||||
// underlying buffer-length type is i32 today.
|
||||
export type underread = !i32;
|
||||
|
||||
//ww:module io
|
||||
// stream — Hare-shaped vtable surface. Project #94 fold-eFinal.
|
||||
//
|
||||
// The single io stream surface (the fold-eFinal collapse retired the
|
||||
@@ -17396,6 +17558,7 @@ export fn empty() stream = {
|
||||
return &_empty_vt;
|
||||
};
|
||||
|
||||
//ww:module errors
|
||||
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
|
||||
//
|
||||
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
|
||||
@@ -17532,6 +17695,7 @@ fn rt_strerror(op: *opaque_data) str = {
|
||||
return os.strerror(*e);
|
||||
};
|
||||
|
||||
//ww:module io
|
||||
// types — error union, mode/whence enums, reader/writer/closer
|
||||
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
|
||||
// `stream` (= `*vtable`, the single io surface).
|
||||
@@ -17617,6 +17781,7 @@ export type closer = fn(s: stream) (void | error);
|
||||
// `stream` directly, mirroring the reader/writer/closer aliases above.
|
||||
export type seeker = fn(s: stream, off: off, w: whence) (off | error);
|
||||
|
||||
//ww:module memio
|
||||
// memio — in-memory io stream. Project #94 fold-eFinal.
|
||||
//
|
||||
// Hare's memio:: surface, drop underscores. Two flavours behind a
|
||||
@@ -17922,6 +18087,7 @@ export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
|
||||
return r;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/cgenutil.ww — split out of cgen.ww.
|
||||
//
|
||||
// General helpers used across cgenexpr / cgenstmt / cgendecl:
|
||||
@@ -20091,10 +20257,14 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
let leaf: str;
|
||||
leaf.ptr = name.ptr + ((i + 1): u64);
|
||||
leaf.len = name.len - (i + 1);
|
||||
// M1 #22: the embedded qualifier (`utf8`) is the use ALIAS;
|
||||
// the struct's smod is now the dotted import PATH
|
||||
// (`encoding.utf8`). Map alias→path before comparing.
|
||||
let pkgmod: str = usehint(c, pkg);
|
||||
let b: *structinfo = c.structs;
|
||||
for (b != nil) {
|
||||
if (streq(b.sname, leaf)) {
|
||||
if (streq(b.smod, pkg)) {
|
||||
if (streq(b.smod, pkgmod)) {
|
||||
return b;
|
||||
};
|
||||
};
|
||||
@@ -23474,6 +23644,7 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
|
||||
cgstructlitfill(c, si, lit, 0, 0, "", bpoff);
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
|
||||
//
|
||||
// cgexpr is a thin dispatcher over n.kind; each non-trivial branch
|
||||
@@ -27782,10 +27953,10 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// lhs.str is the explicit module hint so a same-leaf
|
||||
// def in another module (head of c.fnrets) can't shadow
|
||||
// the explicit qualifier (#17 N_DOT-arm omission audit).
|
||||
let frt: *node = fnretlookupmod(c, fld, lhs.str);
|
||||
let frt: *node = fnretlookupmod(c, fld, usehint(c, lhs.str));
|
||||
if (frt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitfnname(c, fld, lhs.str);
|
||||
emitfnname(c, fld, usehint(c, lhs.str));
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -27797,7 +27968,7 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// the explicit module hint — a 3rd-module qualifier
|
||||
// `alpha.MSG` from gamma needs alpha (not c.curmod)
|
||||
// to beat a head-of-c.defs beta.MSG collision (#11).
|
||||
let drhs: *node = deflookuprhsmod(c, fld, lhs.str);
|
||||
let drhs: *node = deflookuprhsmod(c, fld, usehint(c, lhs.str));
|
||||
if (drhs != nil) {
|
||||
if (drhs.kind == nkind.N_STRLIT) {
|
||||
let bytes: str = drhs.str;
|
||||
@@ -27818,11 +27989,11 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// threads lhs.str via emitfnname.
|
||||
if (streq(mqop, "MOVQ")) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymnamehint(c, fld, lhs.str);
|
||||
emitsymnamehint(c, fld, usehint(c, lhs.str));
|
||||
emitline("(SB), AX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymnamehint(c, fld, lhs.str);
|
||||
emitsymnamehint(c, fld, usehint(c, lhs.str));
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mqop);
|
||||
@@ -28702,10 +28873,10 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// pre-#149 gap (file as #150-family).
|
||||
if (!isletvar(c, basenm) && !deflookup(c, basenm)) {
|
||||
let fld: str = opnd.str;
|
||||
let frt: *node = fnretlookupmod(c, fld, basenm);
|
||||
let frt: *node = fnretlookupmod(c, fld, usehint(c, basenm));
|
||||
if (frt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitfnname(c, fld, basenm);
|
||||
emitfnname(c, fld, usehint(c, basenm));
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -28714,7 +28885,7 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// &aa.v takes aa's global, not a
|
||||
// same-leaf collision.
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymnamehint(c, fld, basenm);
|
||||
emitsymnamehint(c, fld, usehint(c, basenm));
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -31703,7 +31874,7 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
hint.len = 0;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
hint = callee.lhs.str;
|
||||
hint = usehint(c, callee.lhs.str);
|
||||
};
|
||||
};
|
||||
emitfnname(c, calleename, hint);
|
||||
@@ -36013,6 +36184,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
|
||||
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/cgenstmt.ww — split out of cgen.ww.
|
||||
//
|
||||
// cgstmt is a thin dispatcher over n.kind; each branch defers to a
|
||||
@@ -40362,6 +40534,7 @@ fn cgcontinue(c: *cgen, n: *node) void = {
|
||||
|
||||
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/cgendecl.ww — split out of cgen.ww.
|
||||
//
|
||||
// Houses the top-level emission glue:
|
||||
@@ -40966,9 +41139,17 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
|
||||
// Emit the TEXT label via emitfnname so the def site picks up the
|
||||
// same skip rule (FFI / `main` / empty-module) and the same module
|
||||
// hint (this fn's own module) that the call sites use.
|
||||
// hint (this fn's own module) that the call sites use. M1 #32: the
|
||||
// ROOT-unit main (imported==0) is the bare `_start` entry — emit it
|
||||
// bare directly, mirroring collectmods' skip; without this its
|
||||
// fn_.nmod hint would fall through modlookupforfn's first-leaf match
|
||||
// onto an IMPORTED package's now-registered `pkg.main`.
|
||||
emitline("TEXT ");
|
||||
emitfnname(c, fn_.str, fn_.nmod);
|
||||
if (streq(fn_.str, "main") && fn_.imported == 0) {
|
||||
emitbytes(fn_.str.ptr, fn_.str.len: u64);
|
||||
} else {
|
||||
emitfnname(c, fn_.str, fn_.nmod);
|
||||
};
|
||||
emitline(",$");
|
||||
emitint(frame: i64);
|
||||
emitline("\n");
|
||||
@@ -41015,6 +41196,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
||||
emitletdataw(c, file);
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c.
|
||||
//
|
||||
// Status: GROWING. Each subsystem we add is verified by `wwdump_ww -c`
|
||||
@@ -41140,10 +41322,13 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
||||
let leaf: str;
|
||||
leaf.ptr = name.ptr + ((i + 1): u64);
|
||||
leaf.len = name.len - (i + 1);
|
||||
// M1 #22: map the embedded use ALIAS (`utf8`) to the
|
||||
// dotted import PATH the decl's module now carries.
|
||||
let pkgmod: str = usehint(c, pkg);
|
||||
let b: *aliasent = c.aliases;
|
||||
for (b != nil) {
|
||||
if (streq(b.aname, leaf)) {
|
||||
if (streq(b.amod, pkg)) {
|
||||
if (streq(b.amod, pkgmod)) {
|
||||
return b.target;
|
||||
};
|
||||
};
|
||||
@@ -41328,10 +41513,13 @@ fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
let leaf: str;
|
||||
leaf.ptr = name.ptr + ((i + 1): u64);
|
||||
leaf.len = name.len - (i + 1);
|
||||
// M1 #22: map the embedded use ALIAS (`utf8`) to the
|
||||
// dotted import PATH the decl's module now carries.
|
||||
let pkgmod: str = usehint(c, pkg);
|
||||
let b: *enumtype = c.enums;
|
||||
for (b != nil) {
|
||||
if (streq(b.ename, leaf)) {
|
||||
if (streq(b.emod, pkg)) {
|
||||
if (streq(b.emod, pkgmod)) {
|
||||
return b;
|
||||
};
|
||||
};
|
||||
@@ -41502,6 +41690,9 @@ type cgen = struct {
|
||||
enums: *enumtype,
|
||||
mods: *modent, // fn (any export status) + non-exported
|
||||
// let/def/type decls → originating module
|
||||
uses: *modent, // M1 #22: N_USE alias → dotted import path,
|
||||
// for the qualified-ref codegen hint
|
||||
// (mname=alias, nmod=path)
|
||||
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||
fnname: str,
|
||||
curmod: str, // current fn's `// MODULE: foo` directive (len=0
|
||||
@@ -43877,7 +44068,7 @@ fn emittuplerowrelocs(c: *cgen, name: str, module: str, backing: bool, rowoff: i
|
||||
// the leaf with the MODULE ident; same-module `&fn`
|
||||
// stays on curmod.
|
||||
if (ev.lhs.kind == nkind.N_DOT) {
|
||||
emitfnname(c, ev.lhs.str, ev.lhs.lhs.str);
|
||||
emitfnname(c, ev.lhs.str, usehint(c, ev.lhs.lhs.str));
|
||||
} else {
|
||||
emitfnname(c, ev.lhs.str, c.curmod);
|
||||
};
|
||||
@@ -44064,7 +44255,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
// leaf with the MODULE ident; same-module `&fn`
|
||||
// stays on curmod.
|
||||
if (r.lhs.kind == nkind.N_DOT) {
|
||||
emitfnname(c, r.lhs.str, r.lhs.lhs.str);
|
||||
emitfnname(c, r.lhs.str, usehint(c, r.lhs.lhs.str));
|
||||
} else {
|
||||
emitfnname(c, r.lhs.str, c.curmod);
|
||||
};
|
||||
@@ -44535,11 +44726,24 @@ fn fnretlookup(c: *cgen, name: str) *node = {
|
||||
// the scrutinee tagged type to `(rune | done)` — flatvariantidx then
|
||||
// can't see arms 2/3 and collapses them onto tag 0 (task #31).
|
||||
fn fnretlookupmod(c: *cgen, name: str, mod: str) *node = {
|
||||
if (mod.len > 0) {
|
||||
// M1 #22 (#199b): the qualifier may be the import ALIAS the user
|
||||
// wrote (`utf8`); fn decls register f.fmod under the dotted import
|
||||
// PATH (`encoding.utf8`). Map alias->path so a nested-package callee
|
||||
// matches its own module instead of falling back to the name-only
|
||||
// pass — which a same-leaf caller-module fn (e.g. strings.next vs
|
||||
// utf8.next) otherwise wins, resolving a match scrutinee to the
|
||||
// caller's union and collapsing arms 2+. usehint is idempotent on a
|
||||
// path / c.curmod (returns the input when no `use` matches), so the
|
||||
// already-mapped callers (cgenexpr.ww:4309/5229) and the bare-ident
|
||||
// c.curmod callers are unaffected. The choke-point twin of the
|
||||
// struct/alias/enum usehint splitters (cgen.ww:128/319,
|
||||
// cgenutil.ww:2173) — closes the whole fnret class by construction.
|
||||
let mk: str = usehint(c, mod);
|
||||
if (mk.len > 0) {
|
||||
let f: *fnret = c.fnrets;
|
||||
for (f != nil) {
|
||||
if (streq(f.fname, name)) {
|
||||
if (streq(f.fmod, mod)) { return f.rtype; };
|
||||
if (streq(f.fmod, mk)) { return f.rtype; };
|
||||
};
|
||||
f = f.frnext;
|
||||
};
|
||||
@@ -44599,11 +44803,15 @@ fn samemodfn(c: *cgen, name: str) bool = {
|
||||
// matching module is registered — mirrors aliaslookup's two-pass shape
|
||||
// (cgen.ww:75, fixed in #27).
|
||||
fn fnparamslookupmod(c: *cgen, name: str, mod: str) *node = {
|
||||
if (mod.len > 0) {
|
||||
// M1 #22 (#199b): map import alias -> dotted path, identical to
|
||||
// fnretlookupmod (the param-side twin). usehint is idempotent on a
|
||||
// path / c.curmod so existing callers are unaffected.
|
||||
let mk: str = usehint(c, mod);
|
||||
if (mk.len > 0) {
|
||||
let f: *fnret = c.fnrets;
|
||||
for (f != nil) {
|
||||
if (streq(f.fname, name)) {
|
||||
if (streq(f.fmod, mod)) { return f.params; };
|
||||
if (streq(f.fmod, mk)) { return f.params; };
|
||||
};
|
||||
f = f.frnext;
|
||||
};
|
||||
@@ -44783,9 +44991,17 @@ type modent = struct {
|
||||
|
||||
fn collectmods(c: *cgen, file: *node) void = {
|
||||
c.mods = nil;
|
||||
c.uses = nil;
|
||||
if (file == nil) { return; };
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
// M1 #22: record alias→path for the qualified-ref hint.
|
||||
if (d.kind == nkind.N_USE) {
|
||||
if (d.usepath.len > 0) {
|
||||
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, mnext=c.uses})!;
|
||||
c.uses = um;
|
||||
};
|
||||
};
|
||||
// Mirror collectfnrets' shape exactly (plain prepend in one
|
||||
// branch). Earlier nested-if/early-return variants tickled a
|
||||
// wwstage cgen bug that dropped most prepends.
|
||||
@@ -44803,7 +45019,9 @@ fn collectmods(c: *cgen, file: *node) void = {
|
||||
a = a.next;
|
||||
};
|
||||
if (!isffi) {
|
||||
if (!streq(d.str, "main")) {
|
||||
// M1 #32: the ROOT main (imported==0) stays bare;
|
||||
// an IMPORTED `fn main` mangles on its path.
|
||||
if (!streq(d.str, "main") || d.imported != 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
|
||||
c.mods = m;
|
||||
};
|
||||
@@ -44850,6 +45068,19 @@ fn modlookup(c: *cgen, name: str) str = {
|
||||
return empty;
|
||||
};
|
||||
|
||||
// usehint — M1 #22: map a qualified-ref alias (`utf8`) to its dotted
|
||||
// import path (`encoding.utf8`) so the codegen hint keys the path-keyed
|
||||
// mods map. For single-level packages alias == path (no-op). Returns the
|
||||
// alias unchanged when no matching `use` exists.
|
||||
fn usehint(c: *cgen, alias: str) str = {
|
||||
let m: *modent = c.uses;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, alias)) { return m.nmod; };
|
||||
m = m.mnext;
|
||||
};
|
||||
return alias;
|
||||
};
|
||||
|
||||
// modlookupforfn — hint-aware lookup for fn names. Walks c.mods
|
||||
// preferring entries where module matches `hint`; falls back to the
|
||||
// first leaf-name match when nothing matches the hint (legacy single-
|
||||
@@ -45041,6 +45272,7 @@ export fn fargregname(i: i32) str = {
|
||||
return "?";
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6c/main.ww — port of cmd/w6c/main.c.
|
||||
//
|
||||
// w6c = amd64 compiler. Read .ww, parse, codegen, emit Plan 9 amd64
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//ww:module time
|
||||
// time — clocks, instants, durations. Mirrors Hare's lib/time
|
||||
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
|
||||
// +linux/functions.ha). Calendar / date / strftime / timezone /
|
||||
@@ -95,6 +96,7 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
//ww:module rt
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
@@ -118,6 +120,7 @@ package rt;
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
//ww:module os
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -856,6 +859,7 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
//ww:module types
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
@@ -900,6 +904,7 @@ def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
|
||||
|
||||
def RUNE_MIN: rune = '\0';
|
||||
|
||||
//ww:module bytes
|
||||
// bytes — slice operations over []u8. Mirrors Hare's bytes module
|
||||
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
|
||||
// helpers used by lib/encoding, lib/bufio, lib/memio.
|
||||
@@ -1451,6 +1456,7 @@ export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
|
||||
};
|
||||
};
|
||||
|
||||
//ww:module encoding.utf8
|
||||
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
|
||||
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
|
||||
//
|
||||
@@ -1908,6 +1914,7 @@ export fn position(d: *decoder) i32 = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module strings
|
||||
// strings — operations over str ({ptr,len}). Hare port; see
|
||||
// ref/hare/strings/.
|
||||
//
|
||||
@@ -2860,6 +2867,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||
return frombytes(buf);
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6l/sym.ww — port of cmd/w6l/sym.c.
|
||||
//
|
||||
// Linker symbol table. Singly-linked list, usually a few hundred
|
||||
@@ -2961,6 +2969,7 @@ export fn lookup(l: *lnk, name: str) *lsym = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6l/obj.ww — port of cmd/w6l/obj.c.
|
||||
//
|
||||
// Loads relocatable ELF64 .o files emitted by w6a, appends .text to
|
||||
@@ -3580,6 +3589,7 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
|
||||
return 0;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6l/dyn.ww — port of cmd/w6l/dyn.c.
|
||||
//
|
||||
// Load a shared object (ET_DYN) so the linker knows which symbols it
|
||||
@@ -3983,6 +3993,7 @@ export fn soversion(so: *lso, name: str) str = {
|
||||
return result;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6l/pass.ww — port of cmd/w6l/pass.c.
|
||||
//
|
||||
// Resolution + relocation. l_resolve flags every undefined symbol
|
||||
@@ -4115,6 +4126,7 @@ export fn relocate(l: *lnk, textva: u64, datava: u64) i32 = {
|
||||
return l.errs;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6l/dynout.ww — port of cmd/w6l/dynout.c.
|
||||
//
|
||||
// Emit a dynamic-linked ELF executable. The shape is the simplest
|
||||
@@ -4867,6 +4879,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
||||
return 0;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6l/out.ww — port of cmd/w6l/out.c.
|
||||
//
|
||||
// Emit a static ELF64 executable. File layout (per the C original):
|
||||
@@ -5048,6 +5061,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
||||
return 0;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/w6l/main.ww — port of cmd/w6l/main.c.
|
||||
//
|
||||
// w6l = amd64 linker. Reads relocatable ELF .o files, SysV `ar`
|
||||
|
||||
@@ -123,10 +123,13 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
||||
let leaf: str;
|
||||
leaf.ptr = name.ptr + ((i + 1): u64);
|
||||
leaf.len = name.len - (i + 1);
|
||||
// M1 #22: map the embedded use ALIAS (`utf8`) to the
|
||||
// dotted import PATH the decl's module now carries.
|
||||
let pkgmod: str = usehint(c, pkg);
|
||||
let b: *aliasent = c.aliases;
|
||||
for (b != nil) {
|
||||
if (streq(b.aname, leaf)) {
|
||||
if (streq(b.amod, pkg)) {
|
||||
if (streq(b.amod, pkgmod)) {
|
||||
return b.target;
|
||||
};
|
||||
};
|
||||
@@ -311,10 +314,13 @@ fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
let leaf: str;
|
||||
leaf.ptr = name.ptr + ((i + 1): u64);
|
||||
leaf.len = name.len - (i + 1);
|
||||
// M1 #22: map the embedded use ALIAS (`utf8`) to the
|
||||
// dotted import PATH the decl's module now carries.
|
||||
let pkgmod: str = usehint(c, pkg);
|
||||
let b: *enumtype = c.enums;
|
||||
for (b != nil) {
|
||||
if (streq(b.ename, leaf)) {
|
||||
if (streq(b.emod, pkg)) {
|
||||
if (streq(b.emod, pkgmod)) {
|
||||
return b;
|
||||
};
|
||||
};
|
||||
@@ -485,6 +491,9 @@ type cgen = struct {
|
||||
enums: *enumtype,
|
||||
mods: *modent, // fn (any export status) + non-exported
|
||||
// let/def/type decls → originating module
|
||||
uses: *modent, // M1 #22: N_USE alias → dotted import path,
|
||||
// for the qualified-ref codegen hint
|
||||
// (mname=alias, nmod=path)
|
||||
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||
fnname: str,
|
||||
curmod: str, // current fn's `// MODULE: foo` directive (len=0
|
||||
@@ -2860,7 +2869,7 @@ fn emittuplerowrelocs(c: *cgen, name: str, module: str, backing: bool, rowoff: i
|
||||
// the leaf with the MODULE ident; same-module `&fn`
|
||||
// stays on curmod.
|
||||
if (ev.lhs.kind == nkind.N_DOT) {
|
||||
emitfnname(c, ev.lhs.str, ev.lhs.lhs.str);
|
||||
emitfnname(c, ev.lhs.str, usehint(c, ev.lhs.lhs.str));
|
||||
} else {
|
||||
emitfnname(c, ev.lhs.str, c.curmod);
|
||||
};
|
||||
@@ -3047,7 +3056,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
// leaf with the MODULE ident; same-module `&fn`
|
||||
// stays on curmod.
|
||||
if (r.lhs.kind == nkind.N_DOT) {
|
||||
emitfnname(c, r.lhs.str, r.lhs.lhs.str);
|
||||
emitfnname(c, r.lhs.str, usehint(c, r.lhs.lhs.str));
|
||||
} else {
|
||||
emitfnname(c, r.lhs.str, c.curmod);
|
||||
};
|
||||
@@ -3518,11 +3527,24 @@ fn fnretlookup(c: *cgen, name: str) *node = {
|
||||
// the scrutinee tagged type to `(rune | done)` — flatvariantidx then
|
||||
// can't see arms 2/3 and collapses them onto tag 0 (task #31).
|
||||
fn fnretlookupmod(c: *cgen, name: str, mod: str) *node = {
|
||||
if (mod.len > 0) {
|
||||
// M1 #22 (#199b): the qualifier may be the import ALIAS the user
|
||||
// wrote (`utf8`); fn decls register f.fmod under the dotted import
|
||||
// PATH (`encoding.utf8`). Map alias->path so a nested-package callee
|
||||
// matches its own module instead of falling back to the name-only
|
||||
// pass — which a same-leaf caller-module fn (e.g. strings.next vs
|
||||
// utf8.next) otherwise wins, resolving a match scrutinee to the
|
||||
// caller's union and collapsing arms 2+. usehint is idempotent on a
|
||||
// path / c.curmod (returns the input when no `use` matches), so the
|
||||
// already-mapped callers (cgenexpr.ww:4309/5229) and the bare-ident
|
||||
// c.curmod callers are unaffected. The choke-point twin of the
|
||||
// struct/alias/enum usehint splitters (cgen.ww:128/319,
|
||||
// cgenutil.ww:2173) — closes the whole fnret class by construction.
|
||||
let mk: str = usehint(c, mod);
|
||||
if (mk.len > 0) {
|
||||
let f: *fnret = c.fnrets;
|
||||
for (f != nil) {
|
||||
if (streq(f.fname, name)) {
|
||||
if (streq(f.fmod, mod)) { return f.rtype; };
|
||||
if (streq(f.fmod, mk)) { return f.rtype; };
|
||||
};
|
||||
f = f.frnext;
|
||||
};
|
||||
@@ -3582,11 +3604,15 @@ fn samemodfn(c: *cgen, name: str) bool = {
|
||||
// matching module is registered — mirrors aliaslookup's two-pass shape
|
||||
// (cgen.ww:75, fixed in #27).
|
||||
fn fnparamslookupmod(c: *cgen, name: str, mod: str) *node = {
|
||||
if (mod.len > 0) {
|
||||
// M1 #22 (#199b): map import alias -> dotted path, identical to
|
||||
// fnretlookupmod (the param-side twin). usehint is idempotent on a
|
||||
// path / c.curmod so existing callers are unaffected.
|
||||
let mk: str = usehint(c, mod);
|
||||
if (mk.len > 0) {
|
||||
let f: *fnret = c.fnrets;
|
||||
for (f != nil) {
|
||||
if (streq(f.fname, name)) {
|
||||
if (streq(f.fmod, mod)) { return f.params; };
|
||||
if (streq(f.fmod, mk)) { return f.params; };
|
||||
};
|
||||
f = f.frnext;
|
||||
};
|
||||
@@ -3766,9 +3792,17 @@ type modent = struct {
|
||||
|
||||
fn collectmods(c: *cgen, file: *node) void = {
|
||||
c.mods = nil;
|
||||
c.uses = nil;
|
||||
if (file == nil) { return; };
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
// M1 #22: record alias→path for the qualified-ref hint.
|
||||
if (d.kind == nkind.N_USE) {
|
||||
if (d.usepath.len > 0) {
|
||||
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, mnext=c.uses})!;
|
||||
c.uses = um;
|
||||
};
|
||||
};
|
||||
// Mirror collectfnrets' shape exactly (plain prepend in one
|
||||
// branch). Earlier nested-if/early-return variants tickled a
|
||||
// wwstage cgen bug that dropped most prepends.
|
||||
@@ -3786,7 +3820,9 @@ fn collectmods(c: *cgen, file: *node) void = {
|
||||
a = a.next;
|
||||
};
|
||||
if (!isffi) {
|
||||
if (!streq(d.str, "main")) {
|
||||
// M1 #32: the ROOT main (imported==0) stays bare;
|
||||
// an IMPORTED `fn main` mangles on its path.
|
||||
if (!streq(d.str, "main") || d.imported != 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
|
||||
c.mods = m;
|
||||
};
|
||||
@@ -3833,6 +3869,19 @@ fn modlookup(c: *cgen, name: str) str = {
|
||||
return empty;
|
||||
};
|
||||
|
||||
// usehint — M1 #22: map a qualified-ref alias (`utf8`) to its dotted
|
||||
// import path (`encoding.utf8`) so the codegen hint keys the path-keyed
|
||||
// mods map. For single-level packages alias == path (no-op). Returns the
|
||||
// alias unchanged when no matching `use` exists.
|
||||
fn usehint(c: *cgen, alias: str) str = {
|
||||
let m: *modent = c.uses;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, alias)) { return m.nmod; };
|
||||
m = m.mnext;
|
||||
};
|
||||
return alias;
|
||||
};
|
||||
|
||||
// modlookupforfn — hint-aware lookup for fn names. Walks c.mods
|
||||
// preferring entries where module matches `hint`; falls back to the
|
||||
// first leaf-name match when nothing matches the hint (legacy single-
|
||||
|
||||
@@ -602,9 +602,17 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
|
||||
// Emit the TEXT label via emitfnname so the def site picks up the
|
||||
// same skip rule (FFI / `main` / empty-module) and the same module
|
||||
// hint (this fn's own module) that the call sites use.
|
||||
// hint (this fn's own module) that the call sites use. M1 #32: the
|
||||
// ROOT-unit main (imported==0) is the bare `_start` entry — emit it
|
||||
// bare directly, mirroring collectmods' skip; without this its
|
||||
// fn_.nmod hint would fall through modlookupforfn's first-leaf match
|
||||
// onto an IMPORTED package's now-registered `pkg.main`.
|
||||
emitline("TEXT ");
|
||||
emitfnname(c, fn_.str, fn_.nmod);
|
||||
if (streq(fn_.str, "main") && fn_.imported == 0) {
|
||||
emitbytes(fn_.str.ptr, fn_.str.len: u64);
|
||||
} else {
|
||||
emitfnname(c, fn_.str, fn_.nmod);
|
||||
};
|
||||
emitline(",$");
|
||||
emitint(frame: i64);
|
||||
emitline("\n");
|
||||
|
||||
@@ -4306,10 +4306,10 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// lhs.str is the explicit module hint so a same-leaf
|
||||
// def in another module (head of c.fnrets) can't shadow
|
||||
// the explicit qualifier (#17 N_DOT-arm omission audit).
|
||||
let frt: *node = fnretlookupmod(c, fld, lhs.str);
|
||||
let frt: *node = fnretlookupmod(c, fld, usehint(c, lhs.str));
|
||||
if (frt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitfnname(c, fld, lhs.str);
|
||||
emitfnname(c, fld, usehint(c, lhs.str));
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -4321,7 +4321,7 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// the explicit module hint — a 3rd-module qualifier
|
||||
// `alpha.MSG` from gamma needs alpha (not c.curmod)
|
||||
// to beat a head-of-c.defs beta.MSG collision (#11).
|
||||
let drhs: *node = deflookuprhsmod(c, fld, lhs.str);
|
||||
let drhs: *node = deflookuprhsmod(c, fld, usehint(c, lhs.str));
|
||||
if (drhs != nil) {
|
||||
if (drhs.kind == nkind.N_STRLIT) {
|
||||
let bytes: str = drhs.str;
|
||||
@@ -4342,11 +4342,11 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// threads lhs.str via emitfnname.
|
||||
if (streq(mqop, "MOVQ")) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymnamehint(c, fld, lhs.str);
|
||||
emitsymnamehint(c, fld, usehint(c, lhs.str));
|
||||
emitline("(SB), AX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymnamehint(c, fld, lhs.str);
|
||||
emitsymnamehint(c, fld, usehint(c, lhs.str));
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mqop);
|
||||
@@ -5226,10 +5226,10 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// pre-#149 gap (file as #150-family).
|
||||
if (!isletvar(c, basenm) && !deflookup(c, basenm)) {
|
||||
let fld: str = opnd.str;
|
||||
let frt: *node = fnretlookupmod(c, fld, basenm);
|
||||
let frt: *node = fnretlookupmod(c, fld, usehint(c, basenm));
|
||||
if (frt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitfnname(c, fld, basenm);
|
||||
emitfnname(c, fld, usehint(c, basenm));
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -5238,7 +5238,7 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// &aa.v takes aa's global, not a
|
||||
// same-leaf collision.
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymnamehint(c, fld, basenm);
|
||||
emitsymnamehint(c, fld, usehint(c, basenm));
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -8227,7 +8227,7 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
hint.len = 0;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
hint = callee.lhs.str;
|
||||
hint = usehint(c, callee.lhs.str);
|
||||
};
|
||||
};
|
||||
emitfnname(c, calleename, hint);
|
||||
|
||||
@@ -2167,10 +2167,14 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
let leaf: str;
|
||||
leaf.ptr = name.ptr + ((i + 1): u64);
|
||||
leaf.len = name.len - (i + 1);
|
||||
// M1 #22: the embedded qualifier (`utf8`) is the use ALIAS;
|
||||
// the struct's smod is now the dotted import PATH
|
||||
// (`encoding.utf8`). Map alias→path before comparing.
|
||||
let pkgmod: str = usehint(c, pkg);
|
||||
let b: *structinfo = c.structs;
|
||||
for (b != nil) {
|
||||
if (streq(b.sname, leaf)) {
|
||||
if (streq(b.smod, pkg)) {
|
||||
if (streq(b.smod, pkgmod)) {
|
||||
return b;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -164,14 +164,48 @@ fn declmod(file: *node, d: *node) str = {
|
||||
if (file == nil) { return empty; };
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
// M1 #22: a decl is imported iff some `use` directive's full
|
||||
// dotted import path equals the decl's module (now the path).
|
||||
// Single-level packages have usepath == leaf so this is
|
||||
// unchanged; nested (`encoding.utf8`) match here, not on leaf.
|
||||
if (u.kind == nkind.N_USE) {
|
||||
if (streq(u.str, d.nmod)) { return d.nmod; };
|
||||
if (streq(u.usepath, d.nmod)) { return d.nmod; };
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
|
||||
// usepath — map a `use` alias (leaf bareword the user writes, `utf8`)
|
||||
// to the full dotted import path it binds (`encoding.utf8`), for the
|
||||
// module-qualified resolution and codegen hint (M1 #22). Single-level
|
||||
// packages have usepath == alias so the result is unchanged. The
|
||||
// current tree has one occurrence per leaf, so the map is unambiguous.
|
||||
fn usepathfor(file: *node, alias: str) str = {
|
||||
let empty: str;
|
||||
if (file == nil) { return empty; };
|
||||
if (alias.len == 0) { return empty; };
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == nkind.N_USE) {
|
||||
if (streq(u.str, alias)) {
|
||||
if (u.usepath.len != 0) { return u.usepath; };
|
||||
return u.str;
|
||||
};
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
|
||||
// modkeyfor — usepathfor with leaf-alias fallback: the module key for a
|
||||
// path-keyed scopelookupinmodule given the alias the user wrote (M1 #22).
|
||||
fn modkeyfor(c: *checker, alias: str) str = {
|
||||
let mk: str = usepathfor(c.file, alias);
|
||||
if (mk.len == 0) { return alias; };
|
||||
return mk;
|
||||
};
|
||||
|
||||
// srcimports — does the source file that contributed decl-module
|
||||
// `modtag` carry `use <name>;`? Mirrors cstage's src_imports —
|
||||
// `modtag.len == 0` means primary, matching declmod's empty-str
|
||||
@@ -188,7 +222,7 @@ fn srcimports(file: *node, modtag: str, name: str) bool = {
|
||||
// module bareword and lib/fmt's own
|
||||
// `fn bsprintf(fmt: str, ...)` is not a shadow.
|
||||
if (u.nmod.len > 0) {
|
||||
if (streq(u.nmod, u.str)) {
|
||||
if (streq(u.nmod, u.usepath)) {
|
||||
u = u.next;
|
||||
continue;
|
||||
};
|
||||
@@ -277,7 +311,9 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
// pulls lack import->file->symbol provenance). Message byte-identical
|
||||
// to cstage check.c.
|
||||
if (k == nkind.N_USE) {
|
||||
if (mod.len != 0 && streq(nm, mod)) {
|
||||
// M1 #22: self-import ⟺ the imported path equals the use's own
|
||||
// (owning) module path. Compares paths, not leaves.
|
||||
if (mod.len != 0 && streq(d.usepath, mod)) {
|
||||
cerr("self-import: package '"); cerr(mod);
|
||||
cerr("' cannot import itself\n"); c.errs += 1i32;
|
||||
};
|
||||
@@ -486,7 +522,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + (dot + 1): u64;
|
||||
leaf.len = nm.len - (dot + 1);
|
||||
s = scopelookupinmodule(c.cur, head, leaf);
|
||||
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -884,7 +920,7 @@ fn aliassym(c: *checker, n: *node) *sym = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
|
||||
leaf.len = nm.len - dotidx - 1;
|
||||
s = scopelookupinmodule(c.cur, head, leaf);
|
||||
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
|
||||
} else {
|
||||
// #53: same-module preference. Mirrors cstage
|
||||
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
|
||||
@@ -1135,7 +1171,7 @@ fn scruttype(c: *checker, e: *node) *node = {
|
||||
if (e.kind == nkind.N_DOT) {
|
||||
if (e.lhs == nil) { return nil; };
|
||||
if (e.lhs.kind != nkind.N_IDENT) { return nil; };
|
||||
let s: *sym = scopelookupinmodule(c.cur, e.lhs.str, e.str);
|
||||
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.str), e.str);
|
||||
if (s == nil) { return nil; };
|
||||
if (s.decl == nil) { return nil; };
|
||||
return s.decl.lhs;
|
||||
@@ -1674,7 +1710,7 @@ fn evaldefconst(c: *checker, n: *node, out: *u64, depth: i32) bool = {
|
||||
if (k == nkind.N_DOT) {
|
||||
if (n.lhs == nil) { return false; };
|
||||
if (n.lhs.kind != nkind.N_IDENT) { return false; };
|
||||
let s: *sym = scopelookupinmodule(c.cur, n.lhs.str, n.str);
|
||||
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, n.lhs.str), n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_DEF) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
@@ -2791,7 +2827,7 @@ fn unoptype(c: *checker, e: *node) *node = {
|
||||
// cstage's actual acceptance reason.
|
||||
if (e.lhs.kind == nkind.N_DOT) {
|
||||
if (e.lhs.lhs != nil && e.lhs.lhs.kind == nkind.N_IDENT) {
|
||||
let fs: *sym = scopelookupinmodule(c.cur, e.lhs.lhs.str, e.lhs.str);
|
||||
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.lhs.str), e.lhs.str);
|
||||
if (fs != nil) {
|
||||
if (fs.skind == skind.SK_FN) {
|
||||
if (fs.decl != nil) {
|
||||
@@ -3449,7 +3485,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// global-leaf path) and harec check_autodereference
|
||||
// (ref/harec/src/check.c:1566-1581).
|
||||
if (ms != nil && (ms.skind == skind.SK_USE || ms.use_alias != 0i32)) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
s = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
|
||||
};
|
||||
};
|
||||
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
|
||||
@@ -3509,7 +3545,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// qualified resolution and the lenient checker policy
|
||||
// keeps the silent miss documented at scruttype L656.
|
||||
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
|
||||
let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str);
|
||||
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, lhsn.str), e.str);
|
||||
if (fs != nil) { if (fs.decl != nil) {
|
||||
// #34: a module-qualified bare fn rvalue `mod.fn` types as
|
||||
// its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is
|
||||
@@ -5366,7 +5402,7 @@ fn calleefndecl(c: *checker, callee: *node) *node = {
|
||||
};
|
||||
if (ms != nil) {
|
||||
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
|
||||
let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str);
|
||||
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), callee.str);
|
||||
if (fs != nil) {
|
||||
if (fs.skind == skind.SK_FN) { return fs.decl; };
|
||||
};
|
||||
@@ -6320,29 +6356,25 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
d = d.next;
|
||||
};
|
||||
|
||||
// Program-global, name-only, cross-module uniqueness on `main`.
|
||||
// `main` lowers to ONE bare entry symbol, so a second top-level
|
||||
// decl named `main` (any kind, any package) collides with the
|
||||
// entry at link time — today a silent segfault / link-fail in
|
||||
// both stages. The (name, module) duplicate rejects in installtop
|
||||
// read a cross-package `foo.main` and the bare entry as distinct,
|
||||
// so they miss this. Correct multi-main mangling (entry stays bare,
|
||||
// the rest qualify) is deferred (task #32); reject loudly meanwhile
|
||||
// (rule 7). Walks USER decls only — runs before the -T synth main
|
||||
// is appended below — so a hosted-test build never false-counts.
|
||||
// Twin of cmd/wcc/check.c.
|
||||
// Program-global uniqueness on the ENTRY `main`. M1 #32: the entry is
|
||||
// the ROOT-unit main (imported==0) — it alone lowers to the bare
|
||||
// `main` symbol. An IMPORTED package's `main` (imported==1) mangles on
|
||||
// its path (`foo.bar.main`) and may coexist, closing the old dup-main
|
||||
// collision by construction (#31). Two ROOT entries still collide on
|
||||
// the bare symbol → reject loud (rule 7). Walks USER decls only — runs
|
||||
// before the -T synth main is appended below. Twin of cmd/wcc/check.c.
|
||||
let firstmain: *node = nil;
|
||||
let mm: *node = file.list;
|
||||
for (mm != nil) {
|
||||
let ismain: bool = (mm.kind == nkind.N_FNDECL
|
||||
|| mm.kind == nkind.N_LET || mm.kind == nkind.N_DEF
|
||||
|| mm.kind == nkind.N_TYPEDECL) && streq(mm.str, "main");
|
||||
if (ismain) {
|
||||
if (ismain && mm.imported == 0) {
|
||||
if (firstmain == nil) {
|
||||
firstmain = mm;
|
||||
} else {
|
||||
cerr(mm.file);
|
||||
cerr(": error: duplicate top-level main: only the entry main may exist (task #32)\n");
|
||||
cerr(": error: duplicate entry main: only one root main may exist (#32)\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//ww:module time
|
||||
// time — clocks, instants, durations. Mirrors Hare's lib/time
|
||||
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
|
||||
// +linux/functions.ha). Calendar / date / strftime / timezone /
|
||||
@@ -95,6 +96,7 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
//ww:module rt
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
@@ -118,6 +120,7 @@ package rt;
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
//ww:module os
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -856,6 +859,7 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
//ww:module types
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
@@ -900,6 +904,7 @@ def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
|
||||
|
||||
def RUNE_MIN: rune = '\0';
|
||||
|
||||
//ww:module bytes
|
||||
// bytes — slice operations over []u8. Mirrors Hare's bytes module
|
||||
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
|
||||
// helpers used by lib/encoding, lib/bufio, lib/memio.
|
||||
@@ -1451,6 +1456,7 @@ export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
|
||||
};
|
||||
};
|
||||
|
||||
//ww:module encoding.utf8
|
||||
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
|
||||
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
|
||||
//
|
||||
@@ -1908,6 +1914,7 @@ export fn position(d: *decoder) i32 = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module strings
|
||||
// strings — operations over str ({ptr,len}). Hare port; see
|
||||
// ref/hare/strings/.
|
||||
//
|
||||
@@ -2860,6 +2867,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||
return frombytes(buf);
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/ww/main.ww — port of cmd/ww/main.c.
|
||||
//
|
||||
// The user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue:
|
||||
@@ -3367,7 +3375,7 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
|
||||
// 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 = {
|
||||
fn expand(c: *expctx, pathcs: *u8, modpath: str) void = {
|
||||
let plen: u64 = cstrlen(pathcs);
|
||||
let view: str;
|
||||
view.ptr = pathcs;
|
||||
@@ -3400,8 +3408,21 @@ fn expand(c: *expctx, pathcs: *u8) void = {
|
||||
let ipath: *u8 = locateimport(c.dirs, idp, idn,
|
||||
&isdir);
|
||||
if (ipath != nil) {
|
||||
if (isdir != 0) { expanddir(c, ipath); }
|
||||
else { expand(c, ipath); };
|
||||
// M1 #22 (isdir-gated, rob-ratified): only DIRECTORY
|
||||
// imports are package boundaries that path-mangle. A
|
||||
// single-file import (`import opcodes;` → opcodes.ww
|
||||
// declaring `package w6a`) is an intra-package file-split:
|
||||
// it keeps its in-file `package` clause as its module
|
||||
// (no directive → reset → package-clause mangling).
|
||||
if (isdir != 0) {
|
||||
let mv: str;
|
||||
mv.ptr = idp;
|
||||
mv.len = idn: i32;
|
||||
let modstr: str = strings.dup(mv);
|
||||
expanddir(c, ipath, modstr);
|
||||
} else {
|
||||
expand(c, ipath, "");
|
||||
};
|
||||
} else {
|
||||
// #16 ENFORCE-driver (rob A): a locate-miss is
|
||||
// legal when the package is defined INLINE in the
|
||||
@@ -3437,7 +3458,17 @@ fn expand(c: *expctx, pathcs: *u8) void = {
|
||||
// `package main`, which would main-prefix them). A packaged file's own
|
||||
// `package` decl already sets curmod, so it needs nothing — keeping
|
||||
// the directive out of every tracked combined.ww. (Task #11.)
|
||||
if (peekpackage(pathcs) == nil) {
|
||||
if (modpath.len != 0) {
|
||||
// M1 #22: import-reached file carries its full dotted path so
|
||||
// codegen mangles symbols on the path, not the leaf clause.
|
||||
let dm: str = "//ww:module ";
|
||||
os.writeall(c.out, dm.ptr, dm.len: u64);
|
||||
os.writeall(c.out, modpath.ptr, modpath.len: u64);
|
||||
os.writeall(c.out, "\n".ptr, 1u64);
|
||||
} else {
|
||||
// Root/primary: always reset so a preceding imported section's
|
||||
// sticky pathmod is cleared; a packaged primary's own `package`
|
||||
// clause then sets curmod fresh (pathmod NULL → real clause).
|
||||
let d: str = "//ww:module-reset\n";
|
||||
os.writeall(c.out, d.ptr, d.len: u64);
|
||||
};
|
||||
@@ -3613,7 +3644,7 @@ fn strictpkgmismatch(file: *u8, pkg: *u8, dirpkg: *u8, dirpath: *u8) void = {
|
||||
// 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 = {
|
||||
fn expanddir(c: *expctx, dirpath: *u8, modpath: str) void = {
|
||||
let names: **u8;
|
||||
let n: i32;
|
||||
names, n = enumeratedir(dirpath);
|
||||
@@ -3636,7 +3667,7 @@ fn expanddir(c: *expctx, dirpath: *u8) void = {
|
||||
strictpkgmismatch(fp.ptr, pkg, dirpkg, dirpath);
|
||||
}; };
|
||||
};
|
||||
expand(c, fp.ptr);
|
||||
expand(c, fp.ptr, modpath);
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
@@ -3825,12 +3856,12 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, inc
|
||||
let td: i32 = 0;
|
||||
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
|
||||
if (tp != nil) {
|
||||
if (td != 0) { expanddir(&c, tp); }
|
||||
else { expand(&c, tp); };
|
||||
if (td != 0) { expanddir(&c, tp, "test"); }
|
||||
else { expand(&c, tp, ""); };
|
||||
};
|
||||
};
|
||||
if (entryisdir != 0) { expanddir(&c, srcd.ptr); }
|
||||
else { expand(&c, src); };
|
||||
if (entryisdir != 0) { expanddir(&c, srcd.ptr, ""); }
|
||||
else { expand(&c, src, ""); };
|
||||
};
|
||||
os.close(cf);
|
||||
|
||||
|
||||
@@ -505,7 +505,7 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
|
||||
// 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 = {
|
||||
fn expand(c: *expctx, pathcs: *u8, modpath: str) void = {
|
||||
let plen: u64 = cstrlen(pathcs);
|
||||
let view: str;
|
||||
view.ptr = pathcs;
|
||||
@@ -538,8 +538,21 @@ fn expand(c: *expctx, pathcs: *u8) void = {
|
||||
let ipath: *u8 = locateimport(c.dirs, idp, idn,
|
||||
&isdir);
|
||||
if (ipath != nil) {
|
||||
if (isdir != 0) { expanddir(c, ipath); }
|
||||
else { expand(c, ipath); };
|
||||
// M1 #22 (isdir-gated, rob-ratified): only DIRECTORY
|
||||
// imports are package boundaries that path-mangle. A
|
||||
// single-file import (`import opcodes;` → opcodes.ww
|
||||
// declaring `package w6a`) is an intra-package file-split:
|
||||
// it keeps its in-file `package` clause as its module
|
||||
// (no directive → reset → package-clause mangling).
|
||||
if (isdir != 0) {
|
||||
let mv: str;
|
||||
mv.ptr = idp;
|
||||
mv.len = idn: i32;
|
||||
let modstr: str = strings.dup(mv);
|
||||
expanddir(c, ipath, modstr);
|
||||
} else {
|
||||
expand(c, ipath, "");
|
||||
};
|
||||
} else {
|
||||
// #16 ENFORCE-driver (rob A): a locate-miss is
|
||||
// legal when the package is defined INLINE in the
|
||||
@@ -575,7 +588,17 @@ fn expand(c: *expctx, pathcs: *u8) void = {
|
||||
// `package main`, which would main-prefix them). A packaged file's own
|
||||
// `package` decl already sets curmod, so it needs nothing — keeping
|
||||
// the directive out of every tracked combined.ww. (Task #11.)
|
||||
if (peekpackage(pathcs) == nil) {
|
||||
if (modpath.len != 0) {
|
||||
// M1 #22: import-reached file carries its full dotted path so
|
||||
// codegen mangles symbols on the path, not the leaf clause.
|
||||
let dm: str = "//ww:module ";
|
||||
os.writeall(c.out, dm.ptr, dm.len: u64);
|
||||
os.writeall(c.out, modpath.ptr, modpath.len: u64);
|
||||
os.writeall(c.out, "\n".ptr, 1u64);
|
||||
} else {
|
||||
// Root/primary: always reset so a preceding imported section's
|
||||
// sticky pathmod is cleared; a packaged primary's own `package`
|
||||
// clause then sets curmod fresh (pathmod NULL → real clause).
|
||||
let d: str = "//ww:module-reset\n";
|
||||
os.writeall(c.out, d.ptr, d.len: u64);
|
||||
};
|
||||
@@ -751,7 +774,7 @@ fn strictpkgmismatch(file: *u8, pkg: *u8, dirpkg: *u8, dirpath: *u8) void = {
|
||||
// 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 = {
|
||||
fn expanddir(c: *expctx, dirpath: *u8, modpath: str) void = {
|
||||
let names: **u8;
|
||||
let n: i32;
|
||||
names, n = enumeratedir(dirpath);
|
||||
@@ -774,7 +797,7 @@ fn expanddir(c: *expctx, dirpath: *u8) void = {
|
||||
strictpkgmismatch(fp.ptr, pkg, dirpkg, dirpath);
|
||||
}; };
|
||||
};
|
||||
expand(c, fp.ptr);
|
||||
expand(c, fp.ptr, modpath);
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
@@ -963,12 +986,12 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, objstem: *u8, inc
|
||||
let td: i32 = 0;
|
||||
let tp: *u8 = locateimport(searchpath.ptr, "test".ptr, "test".len: u64, &td);
|
||||
if (tp != nil) {
|
||||
if (td != 0) { expanddir(&c, tp); }
|
||||
else { expand(&c, tp); };
|
||||
if (td != 0) { expanddir(&c, tp, "test"); }
|
||||
else { expand(&c, tp, ""); };
|
||||
};
|
||||
};
|
||||
if (entryisdir != 0) { expanddir(&c, srcd.ptr); }
|
||||
else { expand(&c, src); };
|
||||
if (entryisdir != 0) { expanddir(&c, srcd.ptr, ""); }
|
||||
else { expand(&c, src, ""); };
|
||||
};
|
||||
os.close(cf);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//ww:module time
|
||||
// time — clocks, instants, durations. Mirrors Hare's lib/time
|
||||
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
|
||||
// +linux/functions.ha). Calendar / date / strftime / timezone /
|
||||
@@ -95,6 +96,7 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
//ww:module rt
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
@@ -118,6 +120,7 @@ package rt;
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
//ww:module os
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -856,6 +859,7 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — arbitrary-precision decimal engine for float↔string
|
||||
// conversion. Mirrors ref/hare/strconv/decimal.ha (Hare in turn ports
|
||||
// Go's lib/strconv/decimal.go). Pure integer arithmetic; no f32/f64
|
||||
@@ -1178,6 +1182,7 @@ fn decimal_round(d: *decimal) u64 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
//ww:module math
|
||||
// floats — f64 classification, sign, bit-reinterpret core, and the f64
|
||||
// decompose half (subnormal-normalize + frexp). Ported from
|
||||
// ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
|
||||
@@ -1452,6 +1457,7 @@ export fn frexpf64(n: f64) (f64, i64) = {
|
||||
return (mantissa, exp);
|
||||
};
|
||||
|
||||
//ww:module math
|
||||
// math — numeric helpers. Subset of Hare's math::; only the absolute-
|
||||
// value pair for the signed integer types we currently care about. The
|
||||
// return type is unsigned so that abs(I32_MIN) doesn't overflow.
|
||||
@@ -1468,6 +1474,7 @@ export fn absi64(n: i64) u64 = {
|
||||
return n: u64;
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — float→string via Ryū (shortest round-trippable decimal).
|
||||
// Mirrors ref/hare/strconv/ftos_ryu.ha (the algorithm core) +
|
||||
// ref/hare/strconv/ftos.ha:432 (the f64tos driver). Ryū: Ulf Adams,
|
||||
@@ -2262,6 +2269,7 @@ export fn f32tos(n: f32) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — Ryū float→string lookup tables + bit-count constants.
|
||||
// Mirrors ref/hare/strconv/ftos_ryu.ha:159-222 byte-exact. Pure data
|
||||
// fold (strconv #106 fold-5): no logic, consumed by ftos.ww's
|
||||
@@ -2373,6 +2381,7 @@ let POW5_TABLE: [26]u64 = [
|
||||
59604644775390625u64, 298023223876953125u64,
|
||||
];
|
||||
|
||||
//ww:module types
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
@@ -2417,6 +2426,7 @@ def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
|
||||
|
||||
def RUNE_MIN: rune = '\0';
|
||||
|
||||
//ww:module bytes
|
||||
// bytes — slice operations over []u8. Mirrors Hare's bytes module
|
||||
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
|
||||
// helpers used by lib/encoding, lib/bufio, lib/memio.
|
||||
@@ -2968,6 +2978,7 @@ export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
|
||||
};
|
||||
};
|
||||
|
||||
//ww:module encoding.utf8
|
||||
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
|
||||
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
|
||||
//
|
||||
@@ -3425,6 +3436,7 @@ export fn position(d: *decoder) i32 = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module strings
|
||||
// strings — operations over str ({ptr,len}). Hare port; see
|
||||
// ref/hare/strings/.
|
||||
//
|
||||
@@ -4377,6 +4389,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||
return frombytes(buf);
|
||||
};
|
||||
|
||||
//ww:module ascii
|
||||
// ascii — rune-class predicates and case folding for the ASCII range.
|
||||
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
|
||||
// outside 0..127 always answer `false`. The lexer hot path uses these
|
||||
@@ -4581,6 +4594,7 @@ export fn strupper_buf(s: str, buf: []u8) (str | nomem) = {
|
||||
return strings.frombytes(buf);
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
|
||||
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
|
||||
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
|
||||
@@ -5280,6 +5294,7 @@ export fn stof32(s: str, b: base) (f32 | invalid | overflow) = {
|
||||
return 0: invalid; // unreachable (path-cov)
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — stof/ftos lookup tables. Mirrors ref/hare/strconv/stof_data.ha
|
||||
// byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
|
||||
// re-sequenced 2026-05-26): no logic, exercised transitively when fold-3's
|
||||
@@ -5979,6 +5994,7 @@ let powers_of_ten: [596][2]u64 = [
|
||||
[0x73832EEC6FFF3111u64, 0xD226FC195C6A2F8Cu64],
|
||||
];
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — number↔string conversions.
|
||||
//
|
||||
// Mirrors Hare's strconv:: surface. The *tos functions return a
|
||||
@@ -6397,6 +6413,7 @@ export fn strerror(e: error) str = {
|
||||
return strings.dup("");
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// lib/ww/lex/tok.ww — port of cmd/wcc/tok.c plus the Tkind /
|
||||
// Tok / Pos shapes from cmd/wcc/ww.h.
|
||||
//
|
||||
@@ -6520,7 +6537,10 @@ type tkind = enum i32 {
|
||||
TK_MODRESET = 87, // `//ww:module-reset` driver bundle boundary:
|
||||
// reset curmod to "" before a package-less file
|
||||
// (#16 option-B; cstage TK_MODRESET twin)
|
||||
TK_LAST = 88,
|
||||
TK_MODPATH = 88, // `//ww:module <dotted-path>` driver import
|
||||
// boundary; decls mangle on the path, not the
|
||||
// leaf `package` clause (M1 #22; cstage twin)
|
||||
TK_LAST = 89,
|
||||
};
|
||||
|
||||
// ---- Pos / Tok --------------------------------------------------------
|
||||
@@ -6643,6 +6663,7 @@ export fn tokname(k: tkind) str = {
|
||||
case tkind.TK_ENUM: return "enum";
|
||||
case tkind.TK_MODULE: return "package";
|
||||
case tkind.TK_MODRESET: return "//ww:module-reset";
|
||||
case tkind.TK_MODPATH: return "//ww:module";
|
||||
|
||||
case tkind.TK_LPAREN: return "(";
|
||||
case tkind.TK_RPAREN: return ")";
|
||||
@@ -6797,6 +6818,7 @@ export fn tokprint(fd: i32, t: *tok) void = {
|
||||
fputcbyte(fd, '\n');
|
||||
};
|
||||
|
||||
//ww:module lex
|
||||
// lib/ww/lex/lex.ww — port of cmd/wcc/lex.c.
|
||||
//
|
||||
// The DFA, the helpers, and the order of decisions all mirror the C
|
||||
@@ -6858,6 +6880,10 @@ type lex = struct {
|
||||
// a `//ww:module-reset` directive was seen in the last skipped run;
|
||||
// lexnext emits TK_MODRESET before the next real token (#16 opt-B).
|
||||
modreset: i32,
|
||||
// a `//ww:module <path>` directive was seen in the last skipped run;
|
||||
// lexnext emits TK_MODPATH carrying this dotted path (M1 #22).
|
||||
modpathset: i32,
|
||||
modpath: str,
|
||||
};
|
||||
|
||||
export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
|
||||
@@ -6869,6 +6895,7 @@ export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
|
||||
l.col = 1;
|
||||
l.errs = 0;
|
||||
l.modreset = 0;
|
||||
l.modpathset = 0;
|
||||
};
|
||||
|
||||
// srcb — byte at offset; helper that lifts the cast out of indexing.
|
||||
@@ -6936,19 +6963,62 @@ fn skipws(l: *lex) bool = {
|
||||
// The body is then skipped like any comment.
|
||||
// Mirrors cstage lex.c skipws. Compare via lpeek
|
||||
// (no consume) so the skip loop below is unchanged.
|
||||
let dir: str = "ww:module-reset";
|
||||
let pre: str = "ww:module";
|
||||
let di: i32 = 0;
|
||||
let matched: bool = true;
|
||||
for (di < dir.len) {
|
||||
if (lpeek(l, di: u64) != dir[di]: i32) {
|
||||
for (di < pre.len) {
|
||||
if (lpeek(l, di: u64) != pre[di]: i32) {
|
||||
matched = false; break;
|
||||
};
|
||||
di += 1;
|
||||
};
|
||||
if (matched) {
|
||||
let nx: i32 = lpeek(l, dir.len: u64);
|
||||
if (nx == '\n') { l.modreset = 1; }
|
||||
else { if (nx < 0) { l.modreset = 1; }; };
|
||||
let nx: i32 = lpeek(l, pre.len: u64);
|
||||
if (nx == '-') {
|
||||
let rest: str = "-reset";
|
||||
let rj: i32 = 0;
|
||||
let rm: bool = true;
|
||||
for (rj < rest.len) {
|
||||
if (lpeek(l, (pre.len + rj): u64)
|
||||
!= rest[rj]: i32) {
|
||||
rm = false; break;
|
||||
};
|
||||
rj += 1;
|
||||
};
|
||||
if (rm) {
|
||||
let af: i32 = lpeek(l,
|
||||
(pre.len + rest.len): u64);
|
||||
if (af == '\n') { l.modreset = 1; }
|
||||
else { if (af < 0) { l.modreset = 1; }; };
|
||||
};
|
||||
} else { if (nx == ' ' || nx == '\t') {
|
||||
// `//ww:module <path>` — M1 import boundary.
|
||||
let k: u64 = pre.len: u64;
|
||||
for (true) {
|
||||
let sc: i32 = lpeek(l, k);
|
||||
if (sc == ' ' || sc == '\t') {
|
||||
k += 1u64; continue;
|
||||
};
|
||||
break;
|
||||
};
|
||||
let s0: u64 = k;
|
||||
for (true) {
|
||||
let pc: i32 = lpeek(l, k);
|
||||
if (pc < 0) { break; };
|
||||
if (pc == '\n' || pc == '\r'
|
||||
|| pc == ' ' || pc == '\t') {
|
||||
break;
|
||||
};
|
||||
k += 1u64;
|
||||
};
|
||||
if (k > s0) {
|
||||
let view: str;
|
||||
view.ptr = l.src + l.lpos + s0;
|
||||
view.len = (k - s0): i32;
|
||||
l.modpath = strings.dup(view);
|
||||
l.modpathset = 1;
|
||||
};
|
||||
}; };
|
||||
};
|
||||
for (true) {
|
||||
let cx: i32 = lpeek(l, 0u64);
|
||||
@@ -7462,6 +7532,12 @@ export fn lexnext(l: *lex, out: *tok) void = {
|
||||
emitsimple(&start, tkind.TK_MODRESET, out);
|
||||
return;
|
||||
};
|
||||
if (l.modpathset != 0) {
|
||||
l.modpathset = 0;
|
||||
emitsimple(&start, tkind.TK_MODPATH, out);
|
||||
out.text = l.modpath;
|
||||
return;
|
||||
};
|
||||
if (!more) {
|
||||
emitsimple(&start, tkind.TK_EOF, out);
|
||||
return;
|
||||
@@ -7578,6 +7654,7 @@ export fn lexnext(l: *lex, out: *tok) void = {
|
||||
out.text = strings.dup(view);
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// lib/ww/ast.ww — port of cmd/wcc/ast.c (Node defs + printer).
|
||||
//
|
||||
// Status: AST printer is fully ported. Constructor `newnode` is here.
|
||||
@@ -7717,13 +7794,17 @@ type node = struct {
|
||||
type_: *void, // filled in by checker; type.ww treats it as *tinfo
|
||||
tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...)
|
||||
nmod: str, // originating module from `// MODULE: foo`; "" if none
|
||||
usepath: str, // on an N_USE: full dotted IMPORT path vs leaf alias
|
||||
// in `str` (M1 #22); "" otherwise
|
||||
imported: i32, // M1 #22: decl reached via `//ww:module <path>` import
|
||||
// boundary (vs root/primary); gates root-only bare main
|
||||
};
|
||||
|
||||
export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
|
||||
// fval cast-init: 990's wwdump TK_FLOAT diff requires this file
|
||||
// to tokenise identically through C and ww (lex.ww:382 has the
|
||||
// same workaround for the cstage %g-formats vs ww-skips divergence).
|
||||
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod=""})!;
|
||||
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod="", usepath="", imported=0})!;
|
||||
return n;
|
||||
};
|
||||
|
||||
@@ -7965,11 +8046,13 @@ export fn astprint(fd: i32, n: *node) void = {
|
||||
pr(fd, n, 0);
|
||||
};
|
||||
|
||||
//ww:module parse
|
||||
// lib/ww/parse/decl.ww — declaration parsing, split out of parse.ww.
|
||||
|
||||
package parse;
|
||||
|
||||
import os;
|
||||
import strings;
|
||||
import tok;
|
||||
|
||||
// `import encoding.utf8;` — the driver resolves the dotted path to
|
||||
@@ -7984,13 +8067,19 @@ fn parseuse(p: *parser) *node = {
|
||||
advance(p); // past `use`
|
||||
let n: *node = newnode(nkind.N_USE, pf, pl, pc);
|
||||
n.nmod = p.curmod;
|
||||
// M1 #22: accumulate the full dotted import path (n.usepath) for the
|
||||
// checker's path-keyed module match; n.str stays the leaf alias the
|
||||
// user writes (`utf8.x`).
|
||||
let leaf: str;
|
||||
expectident(p, &leaf);
|
||||
let path: str = leaf;
|
||||
for (p.curkind == tkind.TK_DOT) {
|
||||
advance(p); // past `.`
|
||||
expectident(p, &leaf);
|
||||
path = strings.concat(path, ".", leaf);
|
||||
};
|
||||
n.str = leaf;
|
||||
n.usepath = path;
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after use");
|
||||
return n;
|
||||
};
|
||||
@@ -8147,6 +8236,7 @@ fn parsetypedecl(p: *parser, exported: i32) *node = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module parse
|
||||
// lib/ww/parse/expr.ww — expression parsing, split out of parse.ww.
|
||||
|
||||
package parse;
|
||||
@@ -8616,6 +8706,7 @@ fn parseexpr(p: *parser) *node = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module parse
|
||||
// lib/ww/parse/parse.ww — port of cmd/wcc/parse.c (entry + plumbing).
|
||||
//
|
||||
// Split into Hare-style submodule: parse.ww (here) holds the parser
|
||||
@@ -8634,6 +8725,7 @@ package parse;
|
||||
// dir-enum when callers `import parse;` (which dir-enums
|
||||
// lib/ww/parse/).
|
||||
import os;
|
||||
import strings;
|
||||
import tok;
|
||||
|
||||
type parser = struct {
|
||||
@@ -8661,6 +8753,10 @@ type parser = struct {
|
||||
// multi-file streams successive `module` decls mark per-file
|
||||
// section boundaries. Mirrors cstage Parser.curmod.
|
||||
curmod: str,
|
||||
// M1 #22: active `//ww:module <path>` dotted import path. While set,
|
||||
// decls stamp nmod=pathmod and imported=1, and the in-file `package`
|
||||
// clause is an assertion. "" means inactive (root/primary).
|
||||
pathmod: str,
|
||||
};
|
||||
|
||||
fn refill(p: *parser) void = {
|
||||
@@ -8680,6 +8776,7 @@ export fn parserinit(p: *parser, l: *lex) void = {
|
||||
p.l = l;
|
||||
p.errs = 0;
|
||||
p.nocast = 0;
|
||||
p.pathmod = "";
|
||||
refill(p);
|
||||
};
|
||||
|
||||
@@ -9026,7 +9123,30 @@ export fn parsefile(p: *parser) *node = {
|
||||
let name: str;
|
||||
expectident(p, &name);
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
|
||||
p.curmod = name;
|
||||
if (p.pathmod.len != 0) {
|
||||
// M1 #22: while an import path is active the in-file
|
||||
// `package` clause is an ASSERTION — its leaf must
|
||||
// equal the path's last component; it does NOT
|
||||
// overwrite the path-derived module.
|
||||
let (pre, post) = strings.rcut(p.pathmod, ".");
|
||||
let last: str = post;
|
||||
if (post.len == 0) { last = p.pathmod; };
|
||||
if (strings.compare(name, last) != 0) {
|
||||
errmsg(p, "package does not match import path");
|
||||
};
|
||||
} else {
|
||||
p.curmod = name;
|
||||
};
|
||||
continue;
|
||||
};
|
||||
// `//ww:module <path>` — M1 #22 import boundary. The following
|
||||
// file's decls mangle on the full dotted import path, not the
|
||||
// leaf `package` clause, and are flagged imported (gates the
|
||||
// root-only bare-`main` rule, #32).
|
||||
if (p.curkind == tkind.TK_MODPATH) {
|
||||
p.pathmod = p.curtext;
|
||||
p.curmod = p.curtext;
|
||||
advance(p);
|
||||
continue;
|
||||
};
|
||||
// `//ww:module-reset` — bundle boundary before a package-less
|
||||
@@ -9043,6 +9163,7 @@ export fn parsefile(p: *parser) *node = {
|
||||
empty.ptr = nil;
|
||||
empty.len = 0;
|
||||
p.curmod = empty;
|
||||
p.pathmod = "";
|
||||
continue;
|
||||
};
|
||||
let attrs = parseattrs(p);
|
||||
@@ -9094,6 +9215,9 @@ export fn parsefile(p: *parser) *node = {
|
||||
};};};};};};
|
||||
|
||||
if (d != nil) {
|
||||
// M1 #22: flag decls reached via an import-path boundary
|
||||
// (gates the root-only bare-`main` rule, #32).
|
||||
if (p.pathmod.len != 0) { d.imported = 1; };
|
||||
if (head == nil) {
|
||||
head = d;
|
||||
tail = d;
|
||||
@@ -9107,6 +9231,7 @@ export fn parsefile(p: *parser) *node = {
|
||||
return f;
|
||||
};
|
||||
|
||||
//ww:module parse
|
||||
// lib/ww/parse/stmt.ww — statement parsing, split out of parse.ww.
|
||||
|
||||
package parse;
|
||||
@@ -9521,6 +9646,7 @@ fn parsestmt(p: *parser) *node = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module-reset
|
||||
// lib/ww/typ.ww — port of cmd/wcc/type.c.
|
||||
//
|
||||
// Status: full structural port. The C version uses module-globals for
|
||||
@@ -10137,6 +10263,7 @@ export fn typeeq(a: *tinfo, b: *tinfo) bool = {
|
||||
return true; // primitives match by kind alone
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// lib/ww/sym.ww — port of cmd/wcc/sym.c.
|
||||
//
|
||||
// Per-scope hashtable, chained to the parent. Lookup walks up.
|
||||
@@ -10472,6 +10599,7 @@ export fn scopesamekeysym(s: *scope, name: str, mod: str) *sym = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c.
|
||||
//
|
||||
// Status: name-resolution + primitive-type seeding only. Full type
|
||||
@@ -10638,14 +10766,48 @@ fn declmod(file: *node, d: *node) str = {
|
||||
if (file == nil) { return empty; };
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
// M1 #22: a decl is imported iff some `use` directive's full
|
||||
// dotted import path equals the decl's module (now the path).
|
||||
// Single-level packages have usepath == leaf so this is
|
||||
// unchanged; nested (`encoding.utf8`) match here, not on leaf.
|
||||
if (u.kind == nkind.N_USE) {
|
||||
if (streq(u.str, d.nmod)) { return d.nmod; };
|
||||
if (streq(u.usepath, d.nmod)) { return d.nmod; };
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
|
||||
// usepath — map a `use` alias (leaf bareword the user writes, `utf8`)
|
||||
// to the full dotted import path it binds (`encoding.utf8`), for the
|
||||
// module-qualified resolution and codegen hint (M1 #22). Single-level
|
||||
// packages have usepath == alias so the result is unchanged. The
|
||||
// current tree has one occurrence per leaf, so the map is unambiguous.
|
||||
fn usepathfor(file: *node, alias: str) str = {
|
||||
let empty: str;
|
||||
if (file == nil) { return empty; };
|
||||
if (alias.len == 0) { return empty; };
|
||||
let u: *node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == nkind.N_USE) {
|
||||
if (streq(u.str, alias)) {
|
||||
if (u.usepath.len != 0) { return u.usepath; };
|
||||
return u.str;
|
||||
};
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
return empty;
|
||||
};
|
||||
|
||||
// modkeyfor — usepathfor with leaf-alias fallback: the module key for a
|
||||
// path-keyed scopelookupinmodule given the alias the user wrote (M1 #22).
|
||||
fn modkeyfor(c: *checker, alias: str) str = {
|
||||
let mk: str = usepathfor(c.file, alias);
|
||||
if (mk.len == 0) { return alias; };
|
||||
return mk;
|
||||
};
|
||||
|
||||
// srcimports — does the source file that contributed decl-module
|
||||
// `modtag` carry `use <name>;`? Mirrors cstage's src_imports —
|
||||
// `modtag.len == 0` means primary, matching declmod's empty-str
|
||||
@@ -10662,7 +10824,7 @@ fn srcimports(file: *node, modtag: str, name: str) bool = {
|
||||
// module bareword and lib/fmt's own
|
||||
// `fn bsprintf(fmt: str, ...)` is not a shadow.
|
||||
if (u.nmod.len > 0) {
|
||||
if (streq(u.nmod, u.str)) {
|
||||
if (streq(u.nmod, u.usepath)) {
|
||||
u = u.next;
|
||||
continue;
|
||||
};
|
||||
@@ -10751,7 +10913,9 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
// pulls lack import->file->symbol provenance). Message byte-identical
|
||||
// to cstage check.c.
|
||||
if (k == nkind.N_USE) {
|
||||
if (mod.len != 0 && streq(nm, mod)) {
|
||||
// M1 #22: self-import ⟺ the imported path equals the use's own
|
||||
// (owning) module path. Compares paths, not leaves.
|
||||
if (mod.len != 0 && streq(d.usepath, mod)) {
|
||||
cerr("self-import: package '"); cerr(mod);
|
||||
cerr("' cannot import itself\n"); c.errs += 1i32;
|
||||
};
|
||||
@@ -10960,7 +11124,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + (dot + 1): u64;
|
||||
leaf.len = nm.len - (dot + 1);
|
||||
s = scopelookupinmodule(c.cur, head, leaf);
|
||||
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -11358,7 +11522,7 @@ fn aliassym(c: *checker, n: *node) *sym = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
|
||||
leaf.len = nm.len - dotidx - 1;
|
||||
s = scopelookupinmodule(c.cur, head, leaf);
|
||||
s = scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
|
||||
} else {
|
||||
// #53: same-module preference. Mirrors cstage
|
||||
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
|
||||
@@ -11609,7 +11773,7 @@ fn scruttype(c: *checker, e: *node) *node = {
|
||||
if (e.kind == nkind.N_DOT) {
|
||||
if (e.lhs == nil) { return nil; };
|
||||
if (e.lhs.kind != nkind.N_IDENT) { return nil; };
|
||||
let s: *sym = scopelookupinmodule(c.cur, e.lhs.str, e.str);
|
||||
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.str), e.str);
|
||||
if (s == nil) { return nil; };
|
||||
if (s.decl == nil) { return nil; };
|
||||
return s.decl.lhs;
|
||||
@@ -12148,7 +12312,7 @@ fn evaldefconst(c: *checker, n: *node, out: *u64, depth: i32) bool = {
|
||||
if (k == nkind.N_DOT) {
|
||||
if (n.lhs == nil) { return false; };
|
||||
if (n.lhs.kind != nkind.N_IDENT) { return false; };
|
||||
let s: *sym = scopelookupinmodule(c.cur, n.lhs.str, n.str);
|
||||
let s: *sym = scopelookupinmodule(c.cur, modkeyfor(c, n.lhs.str), n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_DEF) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
@@ -13265,7 +13429,7 @@ fn unoptype(c: *checker, e: *node) *node = {
|
||||
// cstage's actual acceptance reason.
|
||||
if (e.lhs.kind == nkind.N_DOT) {
|
||||
if (e.lhs.lhs != nil && e.lhs.lhs.kind == nkind.N_IDENT) {
|
||||
let fs: *sym = scopelookupinmodule(c.cur, e.lhs.lhs.str, e.lhs.str);
|
||||
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.lhs.str), e.lhs.str);
|
||||
if (fs != nil) {
|
||||
if (fs.skind == skind.SK_FN) {
|
||||
if (fs.decl != nil) {
|
||||
@@ -13923,7 +14087,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// global-leaf path) and harec check_autodereference
|
||||
// (ref/harec/src/check.c:1566-1581).
|
||||
if (ms != nil && (ms.skind == skind.SK_USE || ms.use_alias != 0i32)) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
s = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
|
||||
};
|
||||
};
|
||||
if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) {
|
||||
@@ -13983,7 +14147,7 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// qualified resolution and the lenient checker policy
|
||||
// keeps the silent miss documented at scruttype L656.
|
||||
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
|
||||
let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str);
|
||||
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, lhsn.str), e.str);
|
||||
if (fs != nil) { if (fs.decl != nil) {
|
||||
// #34: a module-qualified bare fn rvalue `mod.fn` types as
|
||||
// its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is
|
||||
@@ -15840,7 +16004,7 @@ fn calleefndecl(c: *checker, callee: *node) *node = {
|
||||
};
|
||||
if (ms != nil) {
|
||||
if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) {
|
||||
let fs: *sym = scopelookupinmodule(c.cur, callee.lhs.str, callee.str);
|
||||
let fs: *sym = scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), callee.str);
|
||||
if (fs != nil) {
|
||||
if (fs.skind == skind.SK_FN) { return fs.decl; };
|
||||
};
|
||||
@@ -16794,29 +16958,25 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
d = d.next;
|
||||
};
|
||||
|
||||
// Program-global, name-only, cross-module uniqueness on `main`.
|
||||
// `main` lowers to ONE bare entry symbol, so a second top-level
|
||||
// decl named `main` (any kind, any package) collides with the
|
||||
// entry at link time — today a silent segfault / link-fail in
|
||||
// both stages. The (name, module) duplicate rejects in installtop
|
||||
// read a cross-package `foo.main` and the bare entry as distinct,
|
||||
// so they miss this. Correct multi-main mangling (entry stays bare,
|
||||
// the rest qualify) is deferred (task #32); reject loudly meanwhile
|
||||
// (rule 7). Walks USER decls only — runs before the -T synth main
|
||||
// is appended below — so a hosted-test build never false-counts.
|
||||
// Twin of cmd/wcc/check.c.
|
||||
// Program-global uniqueness on the ENTRY `main`. M1 #32: the entry is
|
||||
// the ROOT-unit main (imported==0) — it alone lowers to the bare
|
||||
// `main` symbol. An IMPORTED package's `main` (imported==1) mangles on
|
||||
// its path (`foo.bar.main`) and may coexist, closing the old dup-main
|
||||
// collision by construction (#31). Two ROOT entries still collide on
|
||||
// the bare symbol → reject loud (rule 7). Walks USER decls only — runs
|
||||
// before the -T synth main is appended below. Twin of cmd/wcc/check.c.
|
||||
let firstmain: *node = nil;
|
||||
let mm: *node = file.list;
|
||||
for (mm != nil) {
|
||||
let ismain: bool = (mm.kind == nkind.N_FNDECL
|
||||
|| mm.kind == nkind.N_LET || mm.kind == nkind.N_DEF
|
||||
|| mm.kind == nkind.N_TYPEDECL) && streq(mm.str, "main");
|
||||
if (ismain) {
|
||||
if (ismain && mm.imported == 0) {
|
||||
if (firstmain == nil) {
|
||||
firstmain = mm;
|
||||
} else {
|
||||
cerr(mm.file);
|
||||
cerr(": error: duplicate top-level main: only the entry main may exist (task #32)\n");
|
||||
cerr(": error: duplicate entry main: only one root main may exist (#32)\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
@@ -17146,6 +17306,7 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
|
||||
};
|
||||
|
||||
//ww:module io
|
||||
// io — stream interface (Plan 9 Bio / Hare io::stream shape).
|
||||
//
|
||||
// No closures, no methods. A `stream` is a pointer to a `vtable` of
|
||||
@@ -17173,6 +17334,7 @@ export type eof = void;
|
||||
// underlying buffer-length type is i32 today.
|
||||
export type underread = !i32;
|
||||
|
||||
//ww:module io
|
||||
// stream — Hare-shaped vtable surface. Project #94 fold-eFinal.
|
||||
//
|
||||
// The single io stream surface (the fold-eFinal collapse retired the
|
||||
@@ -17396,6 +17558,7 @@ export fn empty() stream = {
|
||||
return &_empty_vt;
|
||||
};
|
||||
|
||||
//ww:module errors
|
||||
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
|
||||
//
|
||||
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
|
||||
@@ -17532,6 +17695,7 @@ fn rt_strerror(op: *opaque_data) str = {
|
||||
return os.strerror(*e);
|
||||
};
|
||||
|
||||
//ww:module io
|
||||
// types — error union, mode/whence enums, reader/writer/closer
|
||||
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
|
||||
// `stream` (= `*vtable`, the single io surface).
|
||||
@@ -17617,6 +17781,7 @@ export type closer = fn(s: stream) (void | error);
|
||||
// `stream` directly, mirroring the reader/writer/closer aliases above.
|
||||
export type seeker = fn(s: stream, off: off, w: whence) (off | error);
|
||||
|
||||
//ww:module memio
|
||||
// memio — in-memory io stream. Project #94 fold-eFinal.
|
||||
//
|
||||
// Hare's memio:: surface, drop underscores. Two flavours behind a
|
||||
@@ -17922,6 +18087,7 @@ export fn borrowedread(s: *stream, amt: i32) ([]u8 | io.eof) = {
|
||||
return r;
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/cgenutil.ww — split out of cgen.ww.
|
||||
//
|
||||
// General helpers used across cgenexpr / cgenstmt / cgendecl:
|
||||
@@ -20091,10 +20257,14 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
let leaf: str;
|
||||
leaf.ptr = name.ptr + ((i + 1): u64);
|
||||
leaf.len = name.len - (i + 1);
|
||||
// M1 #22: the embedded qualifier (`utf8`) is the use ALIAS;
|
||||
// the struct's smod is now the dotted import PATH
|
||||
// (`encoding.utf8`). Map alias→path before comparing.
|
||||
let pkgmod: str = usehint(c, pkg);
|
||||
let b: *structinfo = c.structs;
|
||||
for (b != nil) {
|
||||
if (streq(b.sname, leaf)) {
|
||||
if (streq(b.smod, pkg)) {
|
||||
if (streq(b.smod, pkgmod)) {
|
||||
return b;
|
||||
};
|
||||
};
|
||||
@@ -23474,6 +23644,7 @@ fn cgstructlitfillbp(c: *cgen, si: *structinfo, lit: *node, bpoff: i32) void = {
|
||||
cgstructlitfill(c, si, lit, 0, 0, "", bpoff);
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww.
|
||||
//
|
||||
// cgexpr is a thin dispatcher over n.kind; each non-trivial branch
|
||||
@@ -27782,10 +27953,10 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// lhs.str is the explicit module hint so a same-leaf
|
||||
// def in another module (head of c.fnrets) can't shadow
|
||||
// the explicit qualifier (#17 N_DOT-arm omission audit).
|
||||
let frt: *node = fnretlookupmod(c, fld, lhs.str);
|
||||
let frt: *node = fnretlookupmod(c, fld, usehint(c, lhs.str));
|
||||
if (frt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitfnname(c, fld, lhs.str);
|
||||
emitfnname(c, fld, usehint(c, lhs.str));
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -27797,7 +27968,7 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// the explicit module hint — a 3rd-module qualifier
|
||||
// `alpha.MSG` from gamma needs alpha (not c.curmod)
|
||||
// to beat a head-of-c.defs beta.MSG collision (#11).
|
||||
let drhs: *node = deflookuprhsmod(c, fld, lhs.str);
|
||||
let drhs: *node = deflookuprhsmod(c, fld, usehint(c, lhs.str));
|
||||
if (drhs != nil) {
|
||||
if (drhs.kind == nkind.N_STRLIT) {
|
||||
let bytes: str = drhs.str;
|
||||
@@ -27818,11 +27989,11 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// threads lhs.str via emitfnname.
|
||||
if (streq(mqop, "MOVQ")) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymnamehint(c, fld, lhs.str);
|
||||
emitsymnamehint(c, fld, usehint(c, lhs.str));
|
||||
emitline("(SB), AX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymnamehint(c, fld, lhs.str);
|
||||
emitsymnamehint(c, fld, usehint(c, lhs.str));
|
||||
emitline("(SB), CX\n");
|
||||
emitline("\t");
|
||||
emitline(mqop);
|
||||
@@ -28702,10 +28873,10 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// pre-#149 gap (file as #150-family).
|
||||
if (!isletvar(c, basenm) && !deflookup(c, basenm)) {
|
||||
let fld: str = opnd.str;
|
||||
let frt: *node = fnretlookupmod(c, fld, basenm);
|
||||
let frt: *node = fnretlookupmod(c, fld, usehint(c, basenm));
|
||||
if (frt != nil) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitfnname(c, fld, basenm);
|
||||
emitfnname(c, fld, usehint(c, basenm));
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -28714,7 +28885,7 @@ fn cgun(c: *cgen, n: *node) void = {
|
||||
// &aa.v takes aa's global, not a
|
||||
// same-leaf collision.
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymnamehint(c, fld, basenm);
|
||||
emitsymnamehint(c, fld, usehint(c, basenm));
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -31703,7 +31874,7 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
hint.len = 0;
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == nkind.N_IDENT) {
|
||||
hint = callee.lhs.str;
|
||||
hint = usehint(c, callee.lhs.str);
|
||||
};
|
||||
};
|
||||
emitfnname(c, calleename, hint);
|
||||
@@ -36013,6 +36184,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
|
||||
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/cgenstmt.ww — split out of cgen.ww.
|
||||
//
|
||||
// cgstmt is a thin dispatcher over n.kind; each branch defers to a
|
||||
@@ -40362,6 +40534,7 @@ fn cgcontinue(c: *cgen, n: *node) void = {
|
||||
|
||||
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/cgendecl.ww — split out of cgen.ww.
|
||||
//
|
||||
// Houses the top-level emission glue:
|
||||
@@ -40966,9 +41139,17 @@ fn cgfn(c: *cgen, fn_: *node) void = {
|
||||
|
||||
// Emit the TEXT label via emitfnname so the def site picks up the
|
||||
// same skip rule (FFI / `main` / empty-module) and the same module
|
||||
// hint (this fn's own module) that the call sites use.
|
||||
// hint (this fn's own module) that the call sites use. M1 #32: the
|
||||
// ROOT-unit main (imported==0) is the bare `_start` entry — emit it
|
||||
// bare directly, mirroring collectmods' skip; without this its
|
||||
// fn_.nmod hint would fall through modlookupforfn's first-leaf match
|
||||
// onto an IMPORTED package's now-registered `pkg.main`.
|
||||
emitline("TEXT ");
|
||||
emitfnname(c, fn_.str, fn_.nmod);
|
||||
if (streq(fn_.str, "main") && fn_.imported == 0) {
|
||||
emitbytes(fn_.str.ptr, fn_.str.len: u64);
|
||||
} else {
|
||||
emitfnname(c, fn_.str, fn_.nmod);
|
||||
};
|
||||
emitline(",$");
|
||||
emitint(frame: i64);
|
||||
emitline("\n");
|
||||
@@ -41015,6 +41196,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
|
||||
emitletdataw(c, file);
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c.
|
||||
//
|
||||
// Status: GROWING. Each subsystem we add is verified by `wwdump_ww -c`
|
||||
@@ -41140,10 +41322,13 @@ fn aliaslookup(c: *cgen, name: str) *node = {
|
||||
let leaf: str;
|
||||
leaf.ptr = name.ptr + ((i + 1): u64);
|
||||
leaf.len = name.len - (i + 1);
|
||||
// M1 #22: map the embedded use ALIAS (`utf8`) to the
|
||||
// dotted import PATH the decl's module now carries.
|
||||
let pkgmod: str = usehint(c, pkg);
|
||||
let b: *aliasent = c.aliases;
|
||||
for (b != nil) {
|
||||
if (streq(b.aname, leaf)) {
|
||||
if (streq(b.amod, pkg)) {
|
||||
if (streq(b.amod, pkgmod)) {
|
||||
return b.target;
|
||||
};
|
||||
};
|
||||
@@ -41328,10 +41513,13 @@ fn enumlookup(c: *cgen, name: str) *enumtype = {
|
||||
let leaf: str;
|
||||
leaf.ptr = name.ptr + ((i + 1): u64);
|
||||
leaf.len = name.len - (i + 1);
|
||||
// M1 #22: map the embedded use ALIAS (`utf8`) to the
|
||||
// dotted import PATH the decl's module now carries.
|
||||
let pkgmod: str = usehint(c, pkg);
|
||||
let b: *enumtype = c.enums;
|
||||
for (b != nil) {
|
||||
if (streq(b.ename, leaf)) {
|
||||
if (streq(b.emod, pkg)) {
|
||||
if (streq(b.emod, pkgmod)) {
|
||||
return b;
|
||||
};
|
||||
};
|
||||
@@ -41502,6 +41690,9 @@ type cgen = struct {
|
||||
enums: *enumtype,
|
||||
mods: *modent, // fn (any export status) + non-exported
|
||||
// let/def/type decls → originating module
|
||||
uses: *modent, // M1 #22: N_USE alias → dotted import path,
|
||||
// for the qualified-ref codegen hint
|
||||
// (mname=alias, nmod=path)
|
||||
lets: *letvar, // top-level mutable scalar `let` bindings
|
||||
fnname: str,
|
||||
curmod: str, // current fn's `// MODULE: foo` directive (len=0
|
||||
@@ -43877,7 +44068,7 @@ fn emittuplerowrelocs(c: *cgen, name: str, module: str, backing: bool, rowoff: i
|
||||
// the leaf with the MODULE ident; same-module `&fn`
|
||||
// stays on curmod.
|
||||
if (ev.lhs.kind == nkind.N_DOT) {
|
||||
emitfnname(c, ev.lhs.str, ev.lhs.lhs.str);
|
||||
emitfnname(c, ev.lhs.str, usehint(c, ev.lhs.lhs.str));
|
||||
} else {
|
||||
emitfnname(c, ev.lhs.str, c.curmod);
|
||||
};
|
||||
@@ -44064,7 +44255,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
// leaf with the MODULE ident; same-module `&fn`
|
||||
// stays on curmod.
|
||||
if (r.lhs.kind == nkind.N_DOT) {
|
||||
emitfnname(c, r.lhs.str, r.lhs.lhs.str);
|
||||
emitfnname(c, r.lhs.str, usehint(c, r.lhs.lhs.str));
|
||||
} else {
|
||||
emitfnname(c, r.lhs.str, c.curmod);
|
||||
};
|
||||
@@ -44535,11 +44726,24 @@ fn fnretlookup(c: *cgen, name: str) *node = {
|
||||
// the scrutinee tagged type to `(rune | done)` — flatvariantidx then
|
||||
// can't see arms 2/3 and collapses them onto tag 0 (task #31).
|
||||
fn fnretlookupmod(c: *cgen, name: str, mod: str) *node = {
|
||||
if (mod.len > 0) {
|
||||
// M1 #22 (#199b): the qualifier may be the import ALIAS the user
|
||||
// wrote (`utf8`); fn decls register f.fmod under the dotted import
|
||||
// PATH (`encoding.utf8`). Map alias->path so a nested-package callee
|
||||
// matches its own module instead of falling back to the name-only
|
||||
// pass — which a same-leaf caller-module fn (e.g. strings.next vs
|
||||
// utf8.next) otherwise wins, resolving a match scrutinee to the
|
||||
// caller's union and collapsing arms 2+. usehint is idempotent on a
|
||||
// path / c.curmod (returns the input when no `use` matches), so the
|
||||
// already-mapped callers (cgenexpr.ww:4309/5229) and the bare-ident
|
||||
// c.curmod callers are unaffected. The choke-point twin of the
|
||||
// struct/alias/enum usehint splitters (cgen.ww:128/319,
|
||||
// cgenutil.ww:2173) — closes the whole fnret class by construction.
|
||||
let mk: str = usehint(c, mod);
|
||||
if (mk.len > 0) {
|
||||
let f: *fnret = c.fnrets;
|
||||
for (f != nil) {
|
||||
if (streq(f.fname, name)) {
|
||||
if (streq(f.fmod, mod)) { return f.rtype; };
|
||||
if (streq(f.fmod, mk)) { return f.rtype; };
|
||||
};
|
||||
f = f.frnext;
|
||||
};
|
||||
@@ -44599,11 +44803,15 @@ fn samemodfn(c: *cgen, name: str) bool = {
|
||||
// matching module is registered — mirrors aliaslookup's two-pass shape
|
||||
// (cgen.ww:75, fixed in #27).
|
||||
fn fnparamslookupmod(c: *cgen, name: str, mod: str) *node = {
|
||||
if (mod.len > 0) {
|
||||
// M1 #22 (#199b): map import alias -> dotted path, identical to
|
||||
// fnretlookupmod (the param-side twin). usehint is idempotent on a
|
||||
// path / c.curmod so existing callers are unaffected.
|
||||
let mk: str = usehint(c, mod);
|
||||
if (mk.len > 0) {
|
||||
let f: *fnret = c.fnrets;
|
||||
for (f != nil) {
|
||||
if (streq(f.fname, name)) {
|
||||
if (streq(f.fmod, mod)) { return f.params; };
|
||||
if (streq(f.fmod, mk)) { return f.params; };
|
||||
};
|
||||
f = f.frnext;
|
||||
};
|
||||
@@ -44783,9 +44991,17 @@ type modent = struct {
|
||||
|
||||
fn collectmods(c: *cgen, file: *node) void = {
|
||||
c.mods = nil;
|
||||
c.uses = nil;
|
||||
if (file == nil) { return; };
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
// M1 #22: record alias→path for the qualified-ref hint.
|
||||
if (d.kind == nkind.N_USE) {
|
||||
if (d.usepath.len > 0) {
|
||||
let um: *modent = alloc(modent{mname=d.str, nmod=d.usepath, mnext=c.uses})!;
|
||||
c.uses = um;
|
||||
};
|
||||
};
|
||||
// Mirror collectfnrets' shape exactly (plain prepend in one
|
||||
// branch). Earlier nested-if/early-return variants tickled a
|
||||
// wwstage cgen bug that dropped most prepends.
|
||||
@@ -44803,7 +45019,9 @@ fn collectmods(c: *cgen, file: *node) void = {
|
||||
a = a.next;
|
||||
};
|
||||
if (!isffi) {
|
||||
if (!streq(d.str, "main")) {
|
||||
// M1 #32: the ROOT main (imported==0) stays bare;
|
||||
// an IMPORTED `fn main` mangles on its path.
|
||||
if (!streq(d.str, "main") || d.imported != 0) {
|
||||
let m: *modent = alloc(modent{mname=d.str, nmod=d.nmod, mnext=c.mods})!;
|
||||
c.mods = m;
|
||||
};
|
||||
@@ -44850,6 +45068,19 @@ fn modlookup(c: *cgen, name: str) str = {
|
||||
return empty;
|
||||
};
|
||||
|
||||
// usehint — M1 #22: map a qualified-ref alias (`utf8`) to its dotted
|
||||
// import path (`encoding.utf8`) so the codegen hint keys the path-keyed
|
||||
// mods map. For single-level packages alias == path (no-op). Returns the
|
||||
// alias unchanged when no matching `use` exists.
|
||||
fn usehint(c: *cgen, alias: str) str = {
|
||||
let m: *modent = c.uses;
|
||||
for (m != nil) {
|
||||
if (streq(m.mname, alias)) { return m.nmod; };
|
||||
m = m.mnext;
|
||||
};
|
||||
return alias;
|
||||
};
|
||||
|
||||
// modlookupforfn — hint-aware lookup for fn names. Walks c.mods
|
||||
// preferring entries where module matches `hint`; falls back to the
|
||||
// first leaf-name match when nothing matches the hint (legacy single-
|
||||
@@ -45041,6 +45272,7 @@ export fn fargregname(i: i32) str = {
|
||||
return "?";
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/cmd/wwdump/main.ww — ww-side port of cmd/wwdump/main.c.
|
||||
//
|
||||
// Reads a .ww file, runs the ww-side lexer, prints tokens through
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//ww:module time
|
||||
// time — clocks, instants, durations. Mirrors Hare's lib/time
|
||||
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
|
||||
// +linux/functions.ha). Calendar / date / strftime / timezone /
|
||||
@@ -95,6 +96,7 @@ export fn compare(a: instant, b: instant) i8 = {
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
//ww:module rt
|
||||
// rt — runtime primitives exposed to ww programs.
|
||||
// Mirrors Hare's rt:: module placement (ref/hare/rt/).
|
||||
|
||||
@@ -118,6 +120,7 @@ package rt;
|
||||
// a future task (task #39). ref/hare/rt/malloc.ha:27.
|
||||
@symbol("rt_malloc") export fn malloc(n: u64) *void;
|
||||
|
||||
//ww:module os
|
||||
// os — process and filesystem facade. The body of each call lands
|
||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
||||
// depending on how the program was linked.
|
||||
@@ -856,6 +859,7 @@ export fn exists(path: str) bool = {
|
||||
return r >= 0i64;
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — arbitrary-precision decimal engine for float↔string
|
||||
// conversion. Mirrors ref/hare/strconv/decimal.ha (Hare in turn ports
|
||||
// Go's lib/strconv/decimal.go). Pure integer arithmetic; no f32/f64
|
||||
@@ -1178,6 +1182,7 @@ fn decimal_round(d: *decimal) u64 = {
|
||||
return n;
|
||||
};
|
||||
|
||||
//ww:module math
|
||||
// floats — f64 classification, sign, bit-reinterpret core, and the f64
|
||||
// decompose half (subnormal-normalize + frexp). Ported from
|
||||
// ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
|
||||
@@ -1452,6 +1457,7 @@ export fn frexpf64(n: f64) (f64, i64) = {
|
||||
return (mantissa, exp);
|
||||
};
|
||||
|
||||
//ww:module math
|
||||
// math — numeric helpers. Subset of Hare's math::; only the absolute-
|
||||
// value pair for the signed integer types we currently care about. The
|
||||
// return type is unsigned so that abs(I32_MIN) doesn't overflow.
|
||||
@@ -1468,6 +1474,7 @@ export fn absi64(n: i64) u64 = {
|
||||
return n: u64;
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — float→string via Ryū (shortest round-trippable decimal).
|
||||
// Mirrors ref/hare/strconv/ftos_ryu.ha (the algorithm core) +
|
||||
// ref/hare/strconv/ftos.ha:432 (the f64tos driver). Ryū: Ulf Adams,
|
||||
@@ -2262,6 +2269,7 @@ export fn f32tos(n: f32) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — Ryū float→string lookup tables + bit-count constants.
|
||||
// Mirrors ref/hare/strconv/ftos_ryu.ha:159-222 byte-exact. Pure data
|
||||
// fold (strconv #106 fold-5): no logic, consumed by ftos.ww's
|
||||
@@ -2373,6 +2381,7 @@ let POW5_TABLE: [26]u64 = [
|
||||
59604644775390625u64, 298023223876953125u64,
|
||||
];
|
||||
|
||||
//ww:module types
|
||||
// types — integer limits. Mirrors Hare's types::limits (I8_MAX, …)
|
||||
// platform-fixed for amd64. Numeric helpers live in lib/math, matching
|
||||
// Hare's split between types::limits and math::.
|
||||
@@ -2417,6 +2426,7 @@ def UINTPTR_MAX: uintptr = U64_MAX: uintptr;
|
||||
|
||||
def RUNE_MIN: rune = '\0';
|
||||
|
||||
//ww:module bytes
|
||||
// bytes — slice operations over []u8. Mirrors Hare's bytes module
|
||||
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
|
||||
// helpers used by lib/encoding, lib/bufio, lib/memio.
|
||||
@@ -2968,6 +2978,7 @@ export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
|
||||
};
|
||||
};
|
||||
|
||||
//ww:module encoding.utf8
|
||||
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
|
||||
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
|
||||
//
|
||||
@@ -3425,6 +3436,7 @@ export fn position(d: *decoder) i32 = {
|
||||
};
|
||||
|
||||
|
||||
//ww:module strings
|
||||
// strings — operations over str ({ptr,len}). Hare port; see
|
||||
// ref/hare/strings/.
|
||||
//
|
||||
@@ -4377,6 +4389,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||
return frombytes(buf);
|
||||
};
|
||||
|
||||
//ww:module ascii
|
||||
// ascii — rune-class predicates and case folding for the ASCII range.
|
||||
// Matches Hare's ascii::isdigit family (rune-taking signature). Runes
|
||||
// outside 0..127 always answer `false`. The lexer hot path uses these
|
||||
@@ -4581,6 +4594,7 @@ export fn strupper_buf(s: str, buf: []u8) (str | nomem) = {
|
||||
return strings.frombytes(buf);
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
|
||||
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
|
||||
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
|
||||
@@ -5280,6 +5294,7 @@ export fn stof32(s: str, b: base) (f32 | invalid | overflow) = {
|
||||
return 0: invalid; // unreachable (path-cov)
|
||||
};
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — stof/ftos lookup tables. Mirrors ref/hare/strconv/stof_data.ha
|
||||
// byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
|
||||
// re-sequenced 2026-05-26): no logic, exercised transitively when fold-3's
|
||||
@@ -5979,6 +5994,7 @@ let powers_of_ten: [596][2]u64 = [
|
||||
[0x73832EEC6FFF3111u64, 0xD226FC195C6A2F8Cu64],
|
||||
];
|
||||
|
||||
//ww:module strconv
|
||||
// strconv — number↔string conversions.
|
||||
//
|
||||
// Mirrors Hare's strconv:: surface. The *tos functions return a
|
||||
@@ -6397,6 +6413,7 @@ export fn strerror(e: error) str = {
|
||||
return strings.dup("");
|
||||
};
|
||||
|
||||
//ww:module-reset
|
||||
// selfhost/test/smoke.ww — end-to-end smoke for the selfhost path.
|
||||
//
|
||||
// Exercises the patterns the real ww-side compiler port will use:
|
||||
|
||||
@@ -144,7 +144,11 @@ static const struct row rows[] = {
|
||||
/* Cross-mod: cstage emits the LEAQ via N_DOT TK_AMP (already
|
||||
* working pre-#180). Wwstage bails asserttyped on the same
|
||||
* shape — filed as #184; this row stays cstage-only until that
|
||||
* lifts. */
|
||||
* lifts. M1 #22: `wcamffn764mod` is a directory package, so its
|
||||
* exported `somefn` path-mangles to `wcamffn764mod.somefn` (the
|
||||
* pre-M1 bare `somefn` is gone, mirroring 989_m1mangle_sym); the
|
||||
* &-of hint routes through use_hint and the LEAQ matches the
|
||||
* mangled definition. */
|
||||
{ "cross_module",
|
||||
"import wcamffn764mod;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
@@ -153,7 +157,7 @@ static const struct row rows[] = {
|
||||
"};\n",
|
||||
"wcamffn764mod",
|
||||
"export fn somefn(x: i32) i32 = { return x + 1; };\n",
|
||||
"LEAQ\tsomefn(SB)",
|
||||
"LEAQ\twcamffn764mod.somefn(SB)",
|
||||
STAGE_CS },
|
||||
};
|
||||
|
||||
|
||||
212
test/wcc/989_m1mangle_run.c
Normal file
212
test/wcc/989_m1mangle_run.c
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 989_m1mangle_run — M1 (#22 + #32): path-qualified symbol mangling and
|
||||
* root-unit entry detection. Table-driven, both stages (rule-10: cstage
|
||||
* `ww` and wwstage `ww_ww` must AGREE and hit want_exit).
|
||||
*
|
||||
* Each row lays out a tiny package tree next to a root `main.ww` and
|
||||
* builds+runs the root. Because a directory import path-mangles its
|
||||
* symbols (`aa.bb.val` etc.), a broken hint or skip rule shows up as a
|
||||
* link failure (-1) or wrong runtime value — observable end-to-end.
|
||||
*
|
||||
* row | proves
|
||||
* -----------------+--------------------------------------------------
|
||||
* nested_call | DIRECTORY import path-mangles + the qualified-ref
|
||||
* | hint resolves to the PATH (aa.bb.val), not the
|
||||
* | leaf alias — cross-boundary call returns 42
|
||||
* imported_main | #32/#31: an imported pkg's `fn main` mangles
|
||||
* | (aa.bb.main) instead of colliding with the bare
|
||||
* | root entry; both link, root main runs → 7
|
||||
* priv_global | a module-PRIVATE value global mangles on the path
|
||||
* | (aa.bb.pv) and the owning module resolves it → 9
|
||||
* single_level | control: a single-level package's symbols are
|
||||
* | UNCHANGED (cc.v stays cc.v) — still resolves → 5
|
||||
* deep_nest | 2-level nest aa.bb.cc → aa.bb.cc.val path-mangle → 3
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.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;
|
||||
}
|
||||
|
||||
struct file { const char *path; const char *content; };
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
struct file files[4]; /* {NULL,NULL}-terminated package files */
|
||||
const char *root; /* root main.ww content */
|
||||
int want_exit;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "nested_call",
|
||||
{ { "aa/bb/bb.ww",
|
||||
"package bb;\n"
|
||||
"export fn val() int = { return 42; };\n" },
|
||||
{ NULL, NULL } },
|
||||
"package main;\n"
|
||||
"import aa.bb;\n"
|
||||
"export fn main() int = { return bb.val(); };\n",
|
||||
42 },
|
||||
|
||||
{ "imported_main",
|
||||
{ { "aa/bb/bb.ww",
|
||||
"package bb;\n"
|
||||
"fn main() int = { return 7; };\n"
|
||||
"export fn run() int = { return main(); };\n" },
|
||||
{ NULL, NULL } },
|
||||
"package main;\n"
|
||||
"import aa.bb;\n"
|
||||
"export fn main() int = { return bb.run(); };\n",
|
||||
7 },
|
||||
|
||||
{ "priv_global",
|
||||
{ { "aa/bb/bb.ww",
|
||||
"package bb;\n"
|
||||
"let pv: int = 9;\n"
|
||||
"export fn getpv() int = { return pv; };\n" },
|
||||
{ NULL, NULL } },
|
||||
"package main;\n"
|
||||
"import aa.bb;\n"
|
||||
"export fn main() int = { return bb.getpv(); };\n",
|
||||
9 },
|
||||
|
||||
{ "single_level",
|
||||
{ { "cc/cc.ww",
|
||||
"package cc;\n"
|
||||
"export fn v() int = { return 5; };\n" },
|
||||
{ NULL, NULL } },
|
||||
"package main;\n"
|
||||
"import cc;\n"
|
||||
"export fn main() int = { return cc.v(); };\n",
|
||||
5 },
|
||||
|
||||
{ "deep_nest",
|
||||
{ { "aa/bb/cc/cc.ww",
|
||||
"package cc;\n"
|
||||
"export fn val() int = { return 3; };\n" },
|
||||
{ NULL, NULL } },
|
||||
"package main;\n"
|
||||
"import aa.bb.cc;\n"
|
||||
"export fn main() int = { return cc.val(); };\n",
|
||||
3 },
|
||||
};
|
||||
|
||||
/* write_file — create `dir/rel` (mkdir -p its parents) with `content`. */
|
||||
static int
|
||||
write_file(const char *dir, const char *rel, const char *content)
|
||||
{
|
||||
char path[512], cmd[1024];
|
||||
snprintf(path, sizeof path, "%s/%s", dir, rel);
|
||||
/* mkdir -p the parent of `path` */
|
||||
char parent[512];
|
||||
snprintf(parent, sizeof parent, "%s", path);
|
||||
char *slash = strrchr(parent, '/');
|
||||
if (slash) {
|
||||
*slash = '\0';
|
||||
snprintf(cmd, sizeof cmd, "mkdir -p '%s'", parent);
|
||||
if (runwait(cmd) != 0) return -1;
|
||||
}
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(content, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* run_build — lay out the row's package tree + root under a temp dir,
|
||||
* build+run the root via `driver`; return the binary's exit code, or -1
|
||||
* on a build failure. */
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char dir[64], cmd[2048];
|
||||
snprintf(dir, sizeof dir, "/tmp/m1mangle_%d_%d", getpid(), i);
|
||||
snprintf(cmd, sizeof cmd, "rm -rf '%s' && mkdir -p '%s'", dir, dir);
|
||||
if (runwait(cmd) != 0) return -2;
|
||||
|
||||
for (int k = 0; r->files[k].path; k++)
|
||||
if (write_file(dir, r->files[k].path, r->files[k].content) != 0)
|
||||
return -2;
|
||||
if (write_file(dir, "main.ww", r->root) != 0) return -2;
|
||||
|
||||
/* cd into the build dir so the source-dir-first search path finds the
|
||||
* sibling package tree (mirrors Hare's CWD == module-dir). */
|
||||
snprintf(cmd, sizeof cmd, "cd '%s' && %s build main.ww 2>/dev/null",
|
||||
dir, driver);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) {
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/main", dir);
|
||||
got = runwait(outbin);
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof cmd, "rm -rf '%s'", dir);
|
||||
runwait(cmd);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
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 cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *drv; int gated; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) {
|
||||
fprintf(stderr, "m1mangle_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].drv);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
int got = run_build(drivers[d].drv, &rows[i], i);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "m1mangle_run[%s][%s]: exit=%d "
|
||||
"want=%d\n", drivers[d].name, rows[i].label,
|
||||
got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "m1mangle_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("m1mangle_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
149
test/wcc/989_m1mangle_sym.c
Normal file
149
test/wcc/989_m1mangle_sym.c
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 989_m1mangle_sym — M1 (#22) symbol-name proof. Builds a fixture that
|
||||
* imports the real nested DIRECTORY package `encoding.utf8` to `.s` on
|
||||
* BOTH stages and asserts the emitted symbol table:
|
||||
*
|
||||
* needle | want | proves
|
||||
* --------------------------+------+-----------------------------------
|
||||
* "encoding.utf8.runesz" | yes | nested pkg path-mangles (the M1
|
||||
* | | DELTA: leaf `utf8` → path
|
||||
* | | `encoding.utf8`)
|
||||
* "TEXT main," | yes | root entry stays BARE (#32)
|
||||
* "TEXT utf8.runesz," | no | the pre-M1 leaf-only mangle is gone
|
||||
*
|
||||
* Plus cstage.s == wwstage.s byte-for-byte on the re-baselined names
|
||||
* (rule-10 — the cs==ww gate over the new symbols).
|
||||
*/
|
||||
#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
|
||||
file_has(const char *path, const char *needle)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return -1;
|
||||
char line[4096];
|
||||
int found = 0;
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
if (strstr(line, needle)) { found = 1; break; }
|
||||
}
|
||||
fclose(f);
|
||||
return found;
|
||||
}
|
||||
|
||||
struct check { const char *needle; int want; };
|
||||
|
||||
static const struct check checks[] = {
|
||||
{ "encoding.utf8.runesz", 1 },
|
||||
{ "TEXT main,", 1 },
|
||||
{ "TEXT utf8.runesz,", 0 },
|
||||
};
|
||||
|
||||
/* build_s — build `src` to `<stem>.s` via `driver`; returns 0 on success. */
|
||||
static int
|
||||
build_s(const char *driver, const char *src, const char *stem)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "%s build -o '%s' '%s' 2>/dev/null",
|
||||
driver, stem, src);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
checkall(const char *name, const char *sp)
|
||||
{
|
||||
int fail = 0;
|
||||
for (int i = 0; i < (int)(sizeof checks / sizeof checks[0]); i++) {
|
||||
int got = file_has(sp, checks[i].needle);
|
||||
if (got != checks[i].want) {
|
||||
fprintf(stderr, "m1mangle_sym[%s]: '%s' present=%d want=%d\n",
|
||||
name, checks[i].needle, got, checks[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
return fail;
|
||||
}
|
||||
|
||||
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 cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
char src[64];
|
||||
snprintf(src, sizeof src, "/tmp/m1sym_%d.ww", getpid());
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return 1;
|
||||
fputs("package main;\n"
|
||||
"import encoding.utf8;\n"
|
||||
"export fn main() int = { return utf8.runesz('A'): int; };\n", f);
|
||||
fclose(f);
|
||||
|
||||
int fail = 0;
|
||||
|
||||
char cstem[64], cs[80];
|
||||
snprintf(cstem, sizeof cstem, "/tmp/m1sym_c_%d", getpid());
|
||||
snprintf(cs, sizeof cs, "%s.s", cstem);
|
||||
if (build_s(cdrv, src, cstem) != 0) {
|
||||
fprintf(stderr, "m1mangle_sym: cstage build failed\n");
|
||||
fail++;
|
||||
} else {
|
||||
fail += checkall("cstage", cs);
|
||||
}
|
||||
|
||||
int have_ww = (access(wdrv, X_OK) == 0);
|
||||
char wstem[64], ws[80];
|
||||
snprintf(wstem, sizeof wstem, "/tmp/m1sym_w_%d", getpid());
|
||||
snprintf(ws, sizeof ws, "%s.s", wstem);
|
||||
if (have_ww) {
|
||||
if (build_s(wdrv, src, wstem) != 0) {
|
||||
fprintf(stderr, "m1mangle_sym: wwstage build failed\n");
|
||||
fail++;
|
||||
} else {
|
||||
fail += checkall("wwstage", ws);
|
||||
/* rule-10: cs.s == ww.s on the new mangled names. */
|
||||
char cmp[256];
|
||||
snprintf(cmp, sizeof cmp, "cmp -s '%s' '%s'", cs, ws);
|
||||
if (runwait(cmp) != 0) {
|
||||
fprintf(stderr, "m1mangle_sym: cstage.s != "
|
||||
"wwstage.s (byte-id break)\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "m1mangle_sym: skip wwstage (no ww_ww)\n");
|
||||
}
|
||||
|
||||
char cmd[256];
|
||||
snprintf(cmd, sizeof cmd, "rm -f '%s' '%s'* '%s'*", src, cstem, wstem);
|
||||
runwait(cmd);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "m1mangle_sym: %d checks failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("m1mangle_sym: ok\n");
|
||||
return 0;
|
||||
}
|
||||
225
test/wcc/989_m1union_run.c
Normal file
225
test/wcc/989_m1union_run.c
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* 989_m1union_run — M1 (#199b): a `match` on a tagged union returned by a
|
||||
* CROSS-MODULE fn must keep each nominal variant on a DISTINCT tag. The
|
||||
* canonical case is `utf8.next` (encoding.utf8), whose return
|
||||
* `(rune | done | more | invalid)` has three structurally-identical
|
||||
* void-alias variants (done/more = void, invalid = !void). Pre-#199b the
|
||||
* wwstage collapsed done/more/invalid onto one tag (a byte-id-only
|
||||
* miscompile: the doc's repro hit the rune arm and returned the same value
|
||||
* either way). This test is GATE-VISIBLE: each arm maps to a distinct
|
||||
* return code and the inputs deterministically hit each arm, so a collapse
|
||||
* mis-routes an arm and changes the runtime exit — caught on BOTH stages.
|
||||
*
|
||||
* row | input | arm | want_exit
|
||||
* -------------+------------------+-----------+----------
|
||||
* rune | [65] | rune | 1
|
||||
* done | [65] next twice | done | 2
|
||||
* more | [195] (lead-only)| more | 3
|
||||
* invalid | [255] | invalid | 4
|
||||
* all_arms | all four, mixed | (1,2,4,3) | 219 (1243 % 256)
|
||||
*
|
||||
* Plus a byte-id gate: cstage.s == wwstage.s on the all_arms program
|
||||
* (rule-10 — the cs==ww gate over the cross-module match the doc flagged).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.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;
|
||||
}
|
||||
|
||||
/* classify() is shared boilerplate: a cross-module match whose four arms
|
||||
* each return a distinct code. Each row supplies only the main body. */
|
||||
#define PRELUDE \
|
||||
"package main;\n" \
|
||||
"import encoding.utf8;\n" \
|
||||
"fn classify(d: *utf8.decoder) int = {\n" \
|
||||
" match (utf8.next(d)) {\n" \
|
||||
" case let r: rune => return 1;\n" \
|
||||
" case let dn: utf8.done => return 2;\n" \
|
||||
" case let m: utf8.more => return 3;\n" \
|
||||
" case let e: utf8.invalid => return 4;\n" \
|
||||
" };\n" \
|
||||
" return 0;\n" \
|
||||
"};\n"
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *body; /* main() body, appended to PRELUDE */
|
||||
int want_exit;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "rune",
|
||||
"export fn main() int = {\n"
|
||||
" let s: []u8 = [65u8];\n"
|
||||
" let d = utf8.decode(s);\n"
|
||||
" return classify(&d);\n"
|
||||
"};\n", 1 },
|
||||
{ "done",
|
||||
"export fn main() int = {\n"
|
||||
" let s: []u8 = [65u8];\n"
|
||||
" let d = utf8.decode(s);\n"
|
||||
" classify(&d);\n"
|
||||
" return classify(&d);\n"
|
||||
"};\n", 2 },
|
||||
{ "more",
|
||||
"export fn main() int = {\n"
|
||||
" let s: []u8 = [195u8];\n"
|
||||
" let d = utf8.decode(s);\n"
|
||||
" return classify(&d);\n"
|
||||
"};\n", 3 },
|
||||
{ "invalid",
|
||||
"export fn main() int = {\n"
|
||||
" let s: []u8 = [255u8];\n"
|
||||
" let d = utf8.decode(s);\n"
|
||||
" return classify(&d);\n"
|
||||
"};\n", 4 },
|
||||
{ "all_arms",
|
||||
"export fn main() int = {\n"
|
||||
" let g: []u8 = [65u8];\n"
|
||||
" let dg = utf8.decode(g);\n"
|
||||
" let a = classify(&dg);\n"
|
||||
" let b = classify(&dg);\n"
|
||||
" let bad: []u8 = [255u8];\n"
|
||||
" let db = utf8.decode(bad);\n"
|
||||
" let c = classify(&db);\n"
|
||||
" let tr: []u8 = [195u8];\n"
|
||||
" let dt = utf8.decode(tr);\n"
|
||||
" let e = classify(&dt);\n"
|
||||
" return a * 1000 + b * 100 + c * 10 + e;\n"
|
||||
"};\n", 219 },
|
||||
};
|
||||
|
||||
/* write_src — PRELUDE + row body into <path>. */
|
||||
static int
|
||||
write_src(const char *path, const struct row *r)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(PRELUDE, f);
|
||||
fputs(r->body, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* build_run — build `src` to `<stem>` binary via `driver`, run it; return
|
||||
* the binary's exit code, or -1 on a build failure. */
|
||||
static int
|
||||
build_run(const char *driver, const char *src, const char *stem)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "%s build -o '%s' '%s' 2>/dev/null",
|
||||
driver, stem, src);
|
||||
if (runwait(cmd) != 0) return -1;
|
||||
return runwait(stem);
|
||||
}
|
||||
|
||||
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 cdrv[1024], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *drv; int gated; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
int pid = (int)getpid();
|
||||
|
||||
char src[80], stem[80];
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) {
|
||||
fprintf(stderr, "m1union_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].drv);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
snprintf(src, sizeof src, "/tmp/m1union_%d_%d_%d.ww",
|
||||
pid, d, i);
|
||||
snprintf(stem, sizeof stem, "/tmp/m1union_%d_%d_%d",
|
||||
pid, d, i);
|
||||
if (write_src(src, &rows[i]) != 0) { fail++; continue; }
|
||||
int got = build_run(drivers[d].drv, src, stem);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "m1union_run[%s][%s]: exit=%d "
|
||||
"want=%d\n", drivers[d].name, rows[i].label,
|
||||
got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
char cmd[256];
|
||||
snprintf(cmd, sizeof cmd, "rm -f '%s' '%s' '%s.s' "
|
||||
"'%s.o' '%s.combined.ww'", src, stem, stem, stem,
|
||||
stem);
|
||||
runwait(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/* rule-10 byte-id gate on the all_arms cross-module match. */
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
total++;
|
||||
const struct row *r = &rows[n - 1]; /* all_arms */
|
||||
char csrc[80], cstem[80], cs[96], ws[96], cmd[512];
|
||||
snprintf(csrc, sizeof csrc, "/tmp/m1union_bid_%d.ww", pid);
|
||||
snprintf(cstem, sizeof cstem, "/tmp/m1union_bid_c_%d", pid);
|
||||
snprintf(cs, sizeof cs, "%s.s", cstem);
|
||||
char wstem[80];
|
||||
snprintf(wstem, sizeof wstem, "/tmp/m1union_bid_w_%d", pid);
|
||||
snprintf(ws, sizeof ws, "%s.s", wstem);
|
||||
if (write_src(csrc, r) == 0) {
|
||||
snprintf(cmd, sizeof cmd, "%s build -o '%s' '%s' "
|
||||
"2>/dev/null", cdrv, cstem, csrc);
|
||||
int cb = runwait(cmd);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o '%s' '%s' "
|
||||
"2>/dev/null", wdrv, wstem, csrc);
|
||||
int wb = runwait(cmd);
|
||||
if (cb != 0 || wb != 0) {
|
||||
fprintf(stderr, "m1union_run: byte-id build "
|
||||
"failed (cstage=%d wwstage=%d)\n", cb, wb);
|
||||
fail++;
|
||||
} else {
|
||||
snprintf(cmd, sizeof cmd, "cmp -s '%s' '%s'",
|
||||
cs, ws);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "m1union_run: cstage.s "
|
||||
"!= wwstage.s (byte-id break)\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "rm -f '%s' '%s'* '%s'*", csrc,
|
||||
cstem, wstem);
|
||||
runwait(cmd);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "m1union_run: %d/%d failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("m1union_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user