compiler: load imports and enforce package exports
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
// (the package, not the file, is the unit of testing).
|
||||
package strings_test;
|
||||
|
||||
import bytes;
|
||||
import strings;
|
||||
import os;
|
||||
import test;
|
||||
|
||||
@@ -77,9 +77,26 @@ fn accepttok(p: *parser, k: tkind) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
fn putdec(v: i32) void = {
|
||||
let digits: [16]u8;
|
||||
let n: i32 = 0;
|
||||
let x: i32 = v;
|
||||
if (x <= 0) { digits[n] = '0'; n += 1; }
|
||||
else {
|
||||
for (x > 0) {
|
||||
digits[n] = ((x % 10) + ('0': i32)): u8;
|
||||
n += 1;
|
||||
x = x / 10;
|
||||
};
|
||||
};
|
||||
for (n > 0) { n -= 1; os.write(2, &digits[n], 1u64); };
|
||||
};
|
||||
|
||||
fn errmsg(p: *parser, msg: str) void = {
|
||||
let pre = "parse: ";
|
||||
os.write(2, pre.ptr, pre.len: u64);
|
||||
os.write(2, p.curfile.ptr, p.curfile.len: u64);
|
||||
os.write(2, ":".ptr, 1u64); putdec(p.curline);
|
||||
os.write(2, ":".ptr, 1u64); putdec(p.curcol);
|
||||
os.write(2, ": error: ".ptr, 9u64);
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.write(2, "\n".ptr, 1u64);
|
||||
p.errs += 1;
|
||||
@@ -91,11 +108,12 @@ fn expecttok(p: *parser, k: tkind, what: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Returns the empty str on error (and advances to make progress).
|
||||
// Returns false without consuming the unexpected token; the enclosing
|
||||
// production owns recovery, matching the C frontend.
|
||||
fn expectident(p: *parser, into: *str) bool = {
|
||||
if (p.curkind != tkind.TK_IDENT) {
|
||||
errmsg(p, "expected identifier");
|
||||
advance(p);
|
||||
errmsg(p, strings.concat("expected identifier, got ",
|
||||
tokname(p.curkind)));
|
||||
return false;
|
||||
};
|
||||
*into = p.curtext;
|
||||
@@ -413,6 +431,153 @@ fn isassignop(k: tkind) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
fn skipimportdecl(p: *parser) void = {
|
||||
let paren: i32 = 0;
|
||||
let bracket: i32 = 0;
|
||||
let brace: i32 = 0;
|
||||
for (p.curkind != tkind.TK_EOF) {
|
||||
if (p.curkind == tkind.TK_LPAREN) { paren += 1; }
|
||||
else { if (p.curkind == tkind.TK_RPAREN) {
|
||||
if (paren > 0) { paren -= 1; };
|
||||
} else { if (p.curkind == tkind.TK_LBRACK) { bracket += 1; }
|
||||
else { if (p.curkind == tkind.TK_RBRACK) {
|
||||
if (bracket > 0) { bracket -= 1; };
|
||||
} else { if (p.curkind == tkind.TK_LBRACE) { brace += 1; }
|
||||
else { if (p.curkind == tkind.TK_RBRACE) {
|
||||
if (brace > 0) { brace -= 1; };
|
||||
} else { if (p.curkind == tkind.TK_SEMI) {
|
||||
advance(p);
|
||||
if (paren == 0 && bracket == 0 && brace == 0) { return; };
|
||||
continue;
|
||||
};};};};};};};
|
||||
advance(p);
|
||||
};
|
||||
};
|
||||
|
||||
// Consume attributes without parsing their argument expressions. The
|
||||
// imports-only pass only needs to recognize and reject an attributed import.
|
||||
fn skipimportattrs(p: *parser) void = {
|
||||
for (p.curkind == tkind.TK_AT) {
|
||||
advance(p);
|
||||
if (p.curkind == tkind.TK_IDENT) { advance(p); }
|
||||
else {
|
||||
errmsg(p, strings.concat("expected identifier, got ",
|
||||
tokname(p.curkind)));
|
||||
};
|
||||
if (p.curkind == tkind.TK_LPAREN) {
|
||||
advance(p);
|
||||
let depth: i32 = 1;
|
||||
for (p.curkind != tkind.TK_EOF && depth > 0) {
|
||||
if (p.curkind == tkind.TK_LPAREN) { depth += 1; }
|
||||
else { if (p.curkind == tkind.TK_RPAREN) { depth -= 1; }; };
|
||||
advance(p);
|
||||
};
|
||||
if (depth > 0) { errmsg(p, "expected ')' after attribute"); };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export fn parseimports(p: *parser) *node = {
|
||||
let f = newnode(nkind.N_FILE, p.curfile, 1, 1);
|
||||
let head: *node = nil;
|
||||
let tail: *node = nil;
|
||||
let packages: *node = nil;
|
||||
let packagetail: *node = nil;
|
||||
let sawpackage: bool = false;
|
||||
for (p.curkind != tkind.TK_EOF) {
|
||||
// Compiler/driver bundle markers carry package identity out of
|
||||
// band. They are not source declarations, so keep scanning for
|
||||
// the following package clause and imports.
|
||||
if (p.curkind == tkind.TK_MODPATH) {
|
||||
sawpackage = false;
|
||||
p.pathmod = p.curtext;
|
||||
p.curmod = p.curtext;
|
||||
p.resetmod = "";
|
||||
advance(p);
|
||||
continue;
|
||||
};
|
||||
if (p.curkind == tkind.TK_MODRESET) {
|
||||
let rp: str = p.curtext;
|
||||
sawpackage = false;
|
||||
advance(p);
|
||||
p.pathmod = "";
|
||||
p.curmod = rp;
|
||||
p.resetmod = rp;
|
||||
continue;
|
||||
};
|
||||
if (p.curkind == tkind.TK_MODULE) {
|
||||
let pf: str = p.curfile;
|
||||
let pl: i32 = p.curline;
|
||||
let pc: i32 = p.curcol;
|
||||
advance(p);
|
||||
if (p.curkind != tkind.TK_IDENT) {
|
||||
errmsg(p, "invalid or missing package clause");
|
||||
sawpackage = true;
|
||||
skipimportdecl(p);
|
||||
continue;
|
||||
};
|
||||
let name: str;
|
||||
expectident(p, &name);
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
|
||||
p.curmod = name;
|
||||
let pm = newnode(nkind.N_FILE, pf, pl, pc);
|
||||
pm.nmod = name;
|
||||
if (packages == nil) { packages = pm; }
|
||||
else { packagetail.next = pm; };
|
||||
packagetail = pm;
|
||||
if (!sawpackage) {
|
||||
f.nmod = name;
|
||||
f.file = pf;
|
||||
f.line = pl;
|
||||
f.col = pc;
|
||||
sawpackage = true;
|
||||
};
|
||||
continue;
|
||||
};
|
||||
if (!sawpackage && p.pathmod.len == 0 && p.resetmod.len == 0) {
|
||||
errmsg(p, "invalid or missing package clause");
|
||||
sawpackage = true;
|
||||
};
|
||||
if (p.curkind == tkind.TK_USE) {
|
||||
let d: *node = parseuse(p);
|
||||
d.nmod = p.curmod;
|
||||
if (head == nil) { head = d; } else { tail.next = d; };
|
||||
tail = d;
|
||||
continue;
|
||||
};
|
||||
if (p.curkind == tkind.TK_AT) {
|
||||
skipimportattrs(p);
|
||||
if (p.curkind == tkind.TK_EXPORT) { advance(p); };
|
||||
if (p.curkind == tkind.TK_USE) {
|
||||
errmsg(p, "import cannot be exported or attributed");
|
||||
let d: *node = parseuse(p);
|
||||
d.nmod = p.curmod;
|
||||
if (head == nil) { head = d; } else { tail.next = d; };
|
||||
tail = d;
|
||||
continue;
|
||||
};
|
||||
skipimportdecl(p);
|
||||
continue;
|
||||
};
|
||||
if (p.curkind == tkind.TK_EXPORT) {
|
||||
advance(p);
|
||||
if (p.curkind == tkind.TK_USE) {
|
||||
errmsg(p, "import cannot be exported or attributed");
|
||||
let d: *node = parseuse(p);
|
||||
d.nmod = p.curmod;
|
||||
if (head == nil) { head = d; } else { tail.next = d; };
|
||||
tail = d;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
skipimportdecl(p);
|
||||
};
|
||||
f.list = head;
|
||||
// body carries package-clause markers; list remains imports only.
|
||||
f.body = packages;
|
||||
return f;
|
||||
};
|
||||
|
||||
// Forward references between parseunary/parseexpr/parsebin/parsepostfix
|
||||
// are resolved by the two-pass checker — no body-less prototypes needed.
|
||||
|
||||
@@ -523,6 +688,9 @@ export fn parsefile(p: *parser) *node = {
|
||||
|
||||
let d: *node = nil;
|
||||
if (p.curkind == tkind.TK_USE) {
|
||||
if (attrs != nil || exported != 0) {
|
||||
errmsg(p, "import cannot be exported or attributed");
|
||||
};
|
||||
d = parseuse(p);
|
||||
} else { if (p.curkind == tkind.TK_DEF) {
|
||||
d = parsedef(p, exported);
|
||||
|
||||
@@ -110,11 +110,9 @@ export fn scopelookup(s: *scope, name: str) *sym = {
|
||||
// scopelookupinmodule(c, leaf, leaf) won't find it.
|
||||
//
|
||||
// #58/#50: within each scope, Pass-1 prefers an SK_TYPE whose `sym.mod`
|
||||
// matches `mod`; Pass-2 falls back to the first SK_TYPE regardless of
|
||||
// mod (chain-first, the prior behavior). scopedefineinmodule PREPENDS,
|
||||
// so chain-first = last-registered — when two modules export the same
|
||||
// type leaf the bare walk silently picked the newest-installed one,
|
||||
// install-order-dependent, while cstage is deterministic on cur_mod.
|
||||
// matches `mod`; Pass-2 accepts only a primary-package or builtin type. The
|
||||
// synthesized global `nomem` has an empty source location and is builtin too.
|
||||
// Flattened transitive interface types are never an unqualified fallback.
|
||||
// Mirrors cstage cmd/wcc/sym.c scope_lookup_type(s, mod, name) (the
|
||||
// kind-filtered + mod-preferring single walk); sole caller passes
|
||||
// c.curmod. i32-correct: streq throughout, only `.len > 0` guards (the
|
||||
@@ -136,7 +134,12 @@ export fn scopelookuptype(s: *scope, mod: str, name: str) *sym = {
|
||||
};
|
||||
};
|
||||
};
|
||||
if (fallback == nil) { fallback = b; };
|
||||
let builtin: bool = b.decl == nil;
|
||||
if (!builtin && b.decl.file.len == 0 && b.decl.line == 0) {
|
||||
builtin = true;
|
||||
};
|
||||
if (b.mod.len == 0 && (mod.len == 0 || builtin)
|
||||
&& fallback == nil) { fallback = b; };
|
||||
};
|
||||
};
|
||||
b = b.hashnext;
|
||||
@@ -220,8 +223,10 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
|
||||
};
|
||||
|
||||
// Within each scope's bucket: Pass 1 prefers entries whose
|
||||
// `sym.mod` matches `mod`; Pass 2 falls back to the first match
|
||||
// regardless of mod (same semantics as scopelookup). We only descend
|
||||
// `sym.mod` matches `mod`; Pass 2 accepts only a source-visible bare entry.
|
||||
// For an imported package that means a lexical local (child scope) or a
|
||||
// decl-less builtin, never another package's flattened interface symbol.
|
||||
// We only descend
|
||||
// to the parent scope when the current scope has no matching entry at
|
||||
// all — so a local binding in a closer scope still shadows a same-name
|
||||
// fn from a parent scope, even when the parent entry mod-matches.
|
||||
@@ -234,7 +239,6 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
|
||||
// io.read that happens to hash earlier into the flat scope. Mirrors
|
||||
// cmd/wcc/sym.c scope_lookup_prefer.
|
||||
export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
|
||||
if (mod.len == 0) { return scopelookup(s, name); };
|
||||
let p: *scope = s;
|
||||
for (p != nil) {
|
||||
let h: u64 = hashstr(name);
|
||||
@@ -243,12 +247,13 @@ export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
|
||||
let fallback: *sym = nil;
|
||||
for (b != nil) {
|
||||
if (streq(b.name, name)) {
|
||||
if (b.mod.len > 0) {
|
||||
if (mod.len > 0 && b.mod.len > 0) {
|
||||
if (streq(b.mod, mod)) {
|
||||
return b;
|
||||
};
|
||||
};
|
||||
if (fallback == nil) { fallback = b; };
|
||||
if (b.mod.len == 0 && (mod.len == 0 || p.parent != nil
|
||||
|| b.decl == nil) && fallback == nil) { fallback = b; };
|
||||
};
|
||||
b = b.hashnext;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user