The last frontend-gap pin: wwstage had no Hare struct embedding
(struct { hash.hash, ... }), rejecting lib/crypto/sha256 at parse.
- parse.ww: the three member forms (named / anonymous struct / bare
dotted-ident embed), consume-then-branch since this parser has no
peek; embeds carry f.str == "" and the type in f.lhs.
- check.ww N_TSTRUCT flatten: promote the inner struct's flattened
fields at base+src.offset (check.c:961-990); the embed is one
nested-struct unit in the slot ladder; the resolved inner AST is
planted on the TFIELD rhs for cgen.
- check.ww walkers: astoffset / exprtype N_DOT / #251 struct-lit
field lookups descend embeds through shared helpers; the collision
and non-struct-embed rejects live in validatestructfields (the
once-per-decl diagnostic site).
- cgenutil.ww registerstruct: regfieldrun walks the AST against the
flattened tfield cursor, descending embeds via the planted inner
AST so promoted fieldinfo entries keep the inner field's own name
and type node.
- wwi printers unchanged (both stages already emit nameless fields).
sha256_test compiles byte-identically end to end and its 6 tests
pass; 989_lib_byteid is now 44 id / 0 divergent / 0 wwreject.
Fixtures r5913_* (promoted rw, offset shift, anonymous embed,
two-level embed + promoted fn-ptr callee, three rejects); corpus pin
1485/2970.
606 lines
20 KiB
Plaintext
606 lines
20 KiB
Plaintext
// lib/ww/syntax/parse.ww — port of cmd/wcc/parse.c (entry + plumbing).
|
|
//
|
|
// Split into Hare-style submodule: parse.ww (here) holds the parser
|
|
// struct, lexer plumbing, parsetype, parsefile (entry). Expression,
|
|
// statement, and declaration parsers live in expr.ww, stmt.ww,
|
|
// decl.ww respectively — all in the same `parse` module.
|
|
//
|
|
// Calling-convention shim: w6c can't yet pass a sub-struct field
|
|
// (e.g. p.cur.line where p.cur is a `tok` of size 76). The parser
|
|
// stores the current token as flat primitive fields rather than a
|
|
// nested `tok` struct; `refill` copies a freshly lexed token in.
|
|
|
|
package syntax;
|
|
|
|
// Sibling files (expr, stmt, decl) are the same `package syntax`,
|
|
// auto-resolved via task #22 dir-enum of lib/ww/syntax/.
|
|
import os;
|
|
import strings;
|
|
|
|
export type parser = struct {
|
|
l: *lex,
|
|
errs: i32,
|
|
// nocast: while inside `[...]` we treat ':' as the slice
|
|
// separator, not the cast operator. Mirrors parse.c's flag.
|
|
nocast: i32,
|
|
curkind: tkind,
|
|
curfile: str,
|
|
curline: i32,
|
|
curcol: i32,
|
|
curtext: str,
|
|
curuval: u64,
|
|
curfval: f64,
|
|
// curtsuffix: typed numeric literal suffix ("i32", "u64", ...) on
|
|
// the current TK_INT / TK_FLOAT token, or empty. Parseprimary
|
|
// copies this onto the N_INTLIT / N_FLOATLIT node so cgen's
|
|
// rhstargetname can map `42i64` to the i64 variant of a tagged
|
|
// union without falling back to "first non-str variant" (which
|
|
// silently picked tag 0 for typed-int literals; see #10).
|
|
curtsuffix: str,
|
|
// curmod: the most-recent `module foo;` declaration. Each
|
|
// top-level decl is stamped with this value; on concatenated
|
|
// 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,
|
|
// #57: active `//ww:module-reset <path>` dotted path. Mangles decls on
|
|
// the path WITHOUT imported=1 (primary-ness for -c and the #32 bare-main
|
|
// rule stay intact), and the in-file `package` clause asserts (leaf ==
|
|
// last component) instead of overwriting curmod. "" means inactive.
|
|
resetmod: str,
|
|
};
|
|
|
|
fn refill(p: *parser) void = {
|
|
let t: tok;
|
|
lexnext(p.l, &t);
|
|
p.curkind = t.kind;
|
|
p.curfile = t.file;
|
|
p.curline = t.line;
|
|
p.curcol = t.col;
|
|
p.curtext = t.text;
|
|
p.curuval = t.uval;
|
|
p.curfval = t.fval;
|
|
p.curtsuffix = t.tsuffix;
|
|
};
|
|
|
|
export fn parserinit(p: *parser, l: *lex) void = {
|
|
p.l = l;
|
|
p.errs = 0;
|
|
p.nocast = 0;
|
|
p.pathmod = "";
|
|
p.resetmod = "";
|
|
refill(p);
|
|
};
|
|
|
|
fn advance(p: *parser) void = { refill(p); };
|
|
|
|
fn accepttok(p: *parser, k: tkind) bool = {
|
|
if (p.curkind == k) { advance(p); return true; };
|
|
return false;
|
|
};
|
|
|
|
fn errmsg(p: *parser, msg: str) void = {
|
|
let pre = "parse: ";
|
|
os.write(2, pre.ptr, pre.len: u64);
|
|
os.write(2, msg.ptr, msg.len: u64);
|
|
os.write(2, "\n".ptr, 1u64);
|
|
p.errs += 1;
|
|
};
|
|
|
|
fn expecttok(p: *parser, k: tkind, what: str) bool = {
|
|
if (p.curkind == k) { advance(p); return true; };
|
|
errmsg(p, what);
|
|
return false;
|
|
};
|
|
|
|
// expectident — consume the current tkind.TK_IDENT and return its text.
|
|
// Returns the empty str on error (and advances to make progress).
|
|
fn expectident(p: *parser, into: *str) bool = {
|
|
if (p.curkind != tkind.TK_IDENT) {
|
|
errmsg(p, "expected identifier");
|
|
advance(p);
|
|
return false;
|
|
};
|
|
*into = p.curtext;
|
|
advance(p);
|
|
return true;
|
|
};
|
|
|
|
// expectbindname — like expectident but also accepts a bare `_`
|
|
// discard marker. On `_`, returns "" so the checker skips
|
|
// scope_define for the binding.
|
|
fn expectbindname(p: *parser, into: *str) bool = {
|
|
if (p.curkind == tkind.TK_UNDER) {
|
|
*into = "";
|
|
advance(p);
|
|
return true;
|
|
};
|
|
return expectident(p, into);
|
|
};
|
|
|
|
// ---- type expressions ------------------------------------------------
|
|
//
|
|
// Currently: TNAME (single ident, no dotted path yet) and TPTR (`*T`).
|
|
// Other forms (slice, array, struct, fn, chan, tuple, tagged) will
|
|
// land in subsequent commits.
|
|
|
|
// joindotted — build "head.tail" for dotted type-name path
|
|
// collapse. Mirrors aprintf in C parser; pulled local to avoid a
|
|
// cross-module dependency.
|
|
fn joindotted(head: str, tail: str) str = {
|
|
let n: u64 = head.len: u64 + 1u64 + tail.len: u64;
|
|
let buf: []u8 = alloc([], n + 1u64)!;
|
|
let i: u64 = 0u64;
|
|
let j: i32 = 0;
|
|
for (j < head.len) { buf[i] = head[j]; i += 1u64; j += 1; };
|
|
buf[i] = '.';
|
|
i += 1u64;
|
|
j = 0;
|
|
for (j < tail.len) { buf[i] = tail[j]; i += 1u64; j += 1; };
|
|
buf[i] = 0u8;
|
|
let r: str;
|
|
r.ptr = buf.ptr;
|
|
r.len = n: i32;
|
|
return r;
|
|
};
|
|
|
|
fn parsetype(p: *parser) *node = {
|
|
let pf = p.curfile;
|
|
let pl = p.curline;
|
|
let pc = p.curcol;
|
|
|
|
if (p.curkind == tkind.TK_NOT) {
|
|
// `!T` — Hare error-flagged type wrapper.
|
|
advance(p);
|
|
let n = newnode(nkind.N_TBANG, pf, pl, pc);
|
|
n.lhs = parsetype(p);
|
|
return n;
|
|
};
|
|
|
|
if (p.curkind == tkind.TK_STAR) {
|
|
advance(p);
|
|
let n = newnode(nkind.N_TPTR, pf, pl, pc);
|
|
n.lhs = parsetype(p);
|
|
return n;
|
|
};
|
|
|
|
if (p.curkind == tkind.TK_LBRACK) {
|
|
advance(p);
|
|
if (p.curkind == tkind.TK_RBRACK) {
|
|
advance(p);
|
|
let n = newnode(nkind.N_TSLICE, pf, pl, pc);
|
|
n.lhs = parsetype(p);
|
|
return n;
|
|
};
|
|
let n = newnode(nkind.N_TARRAY, pf, pl, pc);
|
|
// `[_]T` — length inferred from initialiser. n.rhs stays nil
|
|
// as the sentinel; the cgen path for nkind.N_LET fills it from the
|
|
// array literal's element count.
|
|
if (p.curkind == tkind.TK_UNDER) {
|
|
advance(p);
|
|
} else {
|
|
n.rhs = parseexpr(p);
|
|
};
|
|
expecttok(p, tkind.TK_RBRACK, "expected ']' in array type");
|
|
n.lhs = parsetype(p);
|
|
return n;
|
|
};
|
|
|
|
if (p.curkind == tkind.TK_STRUCT) {
|
|
advance(p);
|
|
let n = newnode(nkind.N_TSTRUCT, pf, pl, pc);
|
|
// `@packed` is an inline struct TYPE attribute (harec ast.h:95),
|
|
// after `struct` and before `{` — NOT a fn-decl attr, so it does
|
|
// not route through parseattrs.
|
|
if (p.curkind == tkind.TK_AT) {
|
|
advance(p);
|
|
let an: str;
|
|
expectident(p, &an);
|
|
if (!streq(an, "packed")) {
|
|
errmsg(p, "unknown struct attribute");
|
|
} else {
|
|
n.packed = 1;
|
|
};
|
|
};
|
|
expecttok(p, tkind.TK_LBRACE, "expected '{' after struct");
|
|
let fhead: *node = nil;
|
|
let ftail: *node = nil;
|
|
for (p.curkind != tkind.TK_RBRACE) {
|
|
if (p.curkind == tkind.TK_EOF) { break; };
|
|
let fpf = p.curfile;
|
|
let fpl = p.curline;
|
|
let fpc = p.curcol;
|
|
let f = newnode(nkind.N_TFIELD, fpf, fpl, fpc);
|
|
// Three member forms (cstage parse.c:230-244):
|
|
// name: type — regular field
|
|
// struct { ... } — anonymous embedded struct
|
|
// Identifier — bare dotted-ident embedded type
|
|
// Embeds carry f.str == "" and the type in f.lhs. The
|
|
// cstage branches on peek≠':'; this parser has no peek,
|
|
// so consume the leading ident first and branch on ':'.
|
|
if (p.curkind == tkind.TK_STRUCT) {
|
|
f.lhs = parsetype(p);
|
|
} else {
|
|
let fid: str;
|
|
expectident(p, &fid);
|
|
if (p.curkind == tkind.TK_COLON) {
|
|
advance(p);
|
|
f.str = fid;
|
|
f.lhs = parsetype(p);
|
|
} else {
|
|
// Rebuild the dotted TNAME the consumed ident
|
|
// began (parsetype's TK_IDENT collapse, L280-285).
|
|
let tn = newnode(nkind.N_TNAME, fpf, fpl, fpc);
|
|
let acc = fid;
|
|
for (p.curkind == tkind.TK_DOT) {
|
|
advance(p);
|
|
if (p.curkind != tkind.TK_IDENT) { break; };
|
|
acc = joindotted(acc, p.curtext);
|
|
advance(p);
|
|
};
|
|
tn.str = acc;
|
|
f.lhs = tn;
|
|
};
|
|
};
|
|
if (fhead == nil) { fhead = f; ftail = f; }
|
|
else { ftail.next = f; ftail = f; };
|
|
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
|
};
|
|
expecttok(p, tkind.TK_RBRACE, "expected '}' after struct fields");
|
|
n.list = fhead;
|
|
return n;
|
|
};
|
|
|
|
if (p.curkind == tkind.TK_ENUM) {
|
|
// `enum [storage] { NAME [= expr], ... }`
|
|
// Storage defaults to i32 (lhs == nil). Each member is an
|
|
// nkind.N_TENUMMEMBER with str=name and lhs = value expr or nil
|
|
// (auto-increment when omitted).
|
|
advance(p);
|
|
let n = newnode(nkind.N_TENUM, pf, pl, pc);
|
|
if (p.curkind != tkind.TK_LBRACE) {
|
|
n.lhs = parsetype(p);
|
|
};
|
|
expecttok(p, tkind.TK_LBRACE, "expected '{' after enum");
|
|
let mhead: *node = nil;
|
|
let mtail: *node = nil;
|
|
for (p.curkind != tkind.TK_RBRACE) {
|
|
if (p.curkind == tkind.TK_EOF) { break; };
|
|
let mpf = p.curfile;
|
|
let mpl = p.curline;
|
|
let mpc = p.curcol;
|
|
let m = newnode(nkind.N_TENUMMEMBER, mpf, mpl, mpc);
|
|
let mid: str;
|
|
expectident(p, &mid);
|
|
m.str = mid;
|
|
if (accepttok(p, tkind.TK_ASSIGN)) {
|
|
m.lhs = parseexpr(p);
|
|
};
|
|
if (mhead == nil) { mhead = m; mtail = m; }
|
|
else { mtail.next = m; mtail = m; };
|
|
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
|
};
|
|
expecttok(p, tkind.TK_RBRACE, "expected '}' after enum members");
|
|
n.list = mhead;
|
|
return n;
|
|
};
|
|
|
|
if (p.curkind == tkind.TK_VOID) {
|
|
// `void` keyword in type-expr context — emit as nkind.N_TNAME so
|
|
// resolution treats it like any other primitive name.
|
|
let n = newnode(nkind.N_TNAME, pf, pl, pc);
|
|
n.str = "void";
|
|
advance(p);
|
|
return n;
|
|
};
|
|
|
|
if (p.curkind == tkind.TK_IDENT) {
|
|
let n = newnode(nkind.N_TNAME, pf, pl, pc);
|
|
let acc = p.curtext;
|
|
advance(p);
|
|
// Dotted path collapse: pkg.Type → single TNAME with the
|
|
// joined string. Mirrors C parsetype's loop.
|
|
for (p.curkind == tkind.TK_DOT) {
|
|
advance(p);
|
|
if (p.curkind != tkind.TK_IDENT) { break; };
|
|
acc = joindotted(acc, p.curtext);
|
|
advance(p);
|
|
};
|
|
n.str = acc;
|
|
return n;
|
|
};
|
|
|
|
if (p.curkind == tkind.TK_LPAREN) {
|
|
// (T) or (T, T, ...) or (T | T | ...)
|
|
//
|
|
// Each tagged variant may be prefixed with `...` to mark a
|
|
// spread — when the variant resolves to another tagged union
|
|
// its variants are flattened into the enclosing union. We
|
|
// tag the spread on node.op = TK_ELLIPSIS so resolve_type
|
|
// can distinguish intent. Mirrors C parsetype.
|
|
advance(p);
|
|
let firstspread = accepttok(p, tkind.TK_ELLIPSIS);
|
|
let first = parsetype(p);
|
|
if (firstspread) { first.op = tkind.TK_ELLIPSIS; };
|
|
if (accepttok(p, tkind.TK_PIPE)) {
|
|
let n = newnode(nkind.N_TTAGGED, pf, pl, pc);
|
|
let head = first;
|
|
let tail = first;
|
|
for (true) {
|
|
let spread = accepttok(p, tkind.TK_ELLIPSIS);
|
|
let e = parsetype(p);
|
|
if (spread) { e.op = tkind.TK_ELLIPSIS; };
|
|
tail.next = e;
|
|
tail = e;
|
|
if (!accepttok(p, tkind.TK_PIPE)) { break; };
|
|
};
|
|
expecttok(p, tkind.TK_RPAREN, "expected ')' in tagged-union type");
|
|
n.list = head;
|
|
return n;
|
|
};
|
|
if (firstspread) {
|
|
errmsg(p, "spread '...' only valid before tagged-union variants");
|
|
};
|
|
if (!accepttok(p, tkind.TK_COMMA)) {
|
|
expecttok(p, tkind.TK_RPAREN, "expected ')' after parenthesised type");
|
|
return first;
|
|
};
|
|
// Wrap each element in N_TPARAM so the chain owns its .next.
|
|
// Mirrors cstage's Tparam (cmd/wcc/check.c:1437-1451); lifted
|
|
// to the AST layer here because wwstage has no separate type
|
|
// layer, and exprtype must return shared element-type nodes
|
|
// (sym.decl.lhs, struct field's .lhs, another N_TTUPLE element)
|
|
// without corrupting source ASTs.
|
|
let n = newnode(nkind.N_TTUPLE, pf, pl, pc);
|
|
let firstwrap = newnode(nkind.N_TPARAM, pf, pl, pc);
|
|
firstwrap.lhs = first;
|
|
let head = firstwrap;
|
|
let tail = firstwrap;
|
|
for (true) {
|
|
let e = parsetype(p);
|
|
let w = newnode(nkind.N_TPARAM, e.file, e.line, e.col);
|
|
w.lhs = e;
|
|
tail.next = w;
|
|
tail = w;
|
|
if (!accepttok(p, tkind.TK_COMMA)) { break; };
|
|
if (p.curkind == tkind.TK_RPAREN) { break; };
|
|
};
|
|
expecttok(p, tkind.TK_RPAREN, "expected ')' in tuple type");
|
|
n.list = head;
|
|
return n;
|
|
};
|
|
|
|
if (p.curkind == tkind.TK_FN) {
|
|
advance(p);
|
|
expecttok(p, tkind.TK_LPAREN, "expected '(' after fn in type");
|
|
let n = newnode(nkind.N_TFN, pf, pl, pc);
|
|
// Anonymous-or-named params: parseparams handles named only;
|
|
// for fn-type expressions the C parser allows IDENT-less
|
|
// (anonymous) params. Stub: only named params for now.
|
|
n.list = parseparams(p);
|
|
expecttok(p, tkind.TK_RPAREN, "expected ')' after fn type params");
|
|
n.lhs = parsetype(p);
|
|
return n;
|
|
};
|
|
|
|
errmsg(p, "expected type");
|
|
advance(p);
|
|
return newnode(nkind.N_TNAME, pf, pl, pc);
|
|
};
|
|
|
|
// ---- expressions (Pratt) ---------------------------------------------
|
|
//
|
|
// Forwards: parseexpr → parsebin → parseunary → parsepostfix(parseprimary).
|
|
// Tuple literals, match expressions, struct literals, slice [lo:hi],
|
|
// and the ?/! try operators are not yet wired — they'll arrive as the
|
|
// AST diff fixture grows to need them.
|
|
|
|
fn bprec(k: tkind) i32 = {
|
|
if (k == tkind.TK_OR) { return 1; };
|
|
if (k == tkind.TK_AND) { return 2; };
|
|
if (k == tkind.TK_EQ) { return 3; };
|
|
if (k == tkind.TK_NEQ) { return 3; };
|
|
if (k == tkind.TK_LT) { return 4; };
|
|
if (k == tkind.TK_LE) { return 4; };
|
|
if (k == tkind.TK_GT) { return 4; };
|
|
if (k == tkind.TK_GE) { return 4; };
|
|
if (k == tkind.TK_PIPE) { return 5; };
|
|
if (k == tkind.TK_CARET) { return 6; };
|
|
if (k == tkind.TK_AMP) { return 7; };
|
|
if (k == tkind.TK_LSHIFT) { return 8; };
|
|
if (k == tkind.TK_RSHIFT) { return 8; };
|
|
if (k == tkind.TK_PLUS) { return 9; };
|
|
if (k == tkind.TK_MINUS) { return 9; };
|
|
if (k == tkind.TK_STAR) { return 10; };
|
|
if (k == tkind.TK_SLASH) { return 10; };
|
|
if (k == tkind.TK_PERCENT) { return 10; };
|
|
return 0;
|
|
};
|
|
|
|
fn isassignop(k: tkind) bool = {
|
|
if (k == tkind.TK_ASSIGN) { return true; };
|
|
if (k == tkind.TK_PLUSEQ) { return true; };
|
|
if (k == tkind.TK_MINUSEQ) { return true; };
|
|
if (k == tkind.TK_STAREQ) { return true; };
|
|
if (k == tkind.TK_SLASHEQ) { return true; };
|
|
if (k == tkind.TK_PERCENTEQ) { return true; };
|
|
if (k == tkind.TK_AMPEQ) { return true; };
|
|
if (k == tkind.TK_PIPEEQ) { return true; };
|
|
if (k == tkind.TK_CARETEQ) { return true; };
|
|
if (k == tkind.TK_LSHIFTEQ) { return true; };
|
|
if (k == tkind.TK_RSHIFTEQ) { return true; };
|
|
return false;
|
|
};
|
|
|
|
// Forward references between parseunary/parseexpr/parsebin/parsepostfix
|
|
// are resolved by the two-pass checker — no body-less prototypes needed.
|
|
|
|
export fn parsefile(p: *parser) *node = {
|
|
let f = newnode(nkind.N_FILE, p.curfile, p.curline, p.curcol);
|
|
let head: *node = nil;
|
|
let tail: *node = nil;
|
|
let sawpackage: i32 = 0;
|
|
for (p.curkind != tkind.TK_EOF) {
|
|
// `package foo;` — directory-as-module declaration. Every
|
|
// primary section opens with one (`package main;` for an
|
|
// executable); a missing clause on the first real decl is a
|
|
// hard error (strict-package, #24a). Imported (pathmod) and
|
|
// sep primary-reset (resetmod) regions carry identity
|
|
// out-of-band and are exempt.
|
|
if (p.curkind == tkind.TK_MODULE) {
|
|
sawpackage = 1;
|
|
advance(p);
|
|
let name: str;
|
|
expectident(p, &name);
|
|
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
|
|
if (p.pathmod.len != 0 || p.resetmod.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. #57 extends this
|
|
// to the sep primary-reset path (resetmod): the dotted
|
|
// reset path is authoritative, the clause asserts.
|
|
let active: str = p.pathmod;
|
|
if (p.pathmod.len == 0) { active = p.resetmod; };
|
|
let (pre, post) = strings.rcut(active, ".");
|
|
let last: str = post;
|
|
if (post.len == 0) { last = active; };
|
|
if (strings.compare(name, last) != 0) {
|
|
errmsg(p, "package does not match import path");
|
|
};
|
|
} else {
|
|
p.curmod = name;
|
|
// #11: stamp the primary module identity on the
|
|
// N_FILE node so wwi_emit can derive the `package`
|
|
// leaf even when the body carries zero module-tagged
|
|
// decls. Primary identity only; never the imported
|
|
// boundary (TK_MODPATH).
|
|
if (f.nmod.len == 0) { f.nmod = 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) {
|
|
sawpackage = 0;
|
|
p.pathmod = p.curtext;
|
|
p.curmod = p.curtext;
|
|
p.resetmod = "";
|
|
advance(p);
|
|
continue;
|
|
};
|
|
// `//ww:module-reset` — bundle boundary before a package-less
|
|
// file. Reset curmod to "" so the file's decls (and its own
|
|
// `import os;`) are attributed to the primary module, not the
|
|
// preceding bundled package. Codegen-neutral: "" curmod keeps
|
|
// bare symbols. (#16 option-B; closes task #11.)
|
|
// Driver-emitted ONLY before package-less files; a hand-placed
|
|
// directive after a mid-file `package` would strip subsequent
|
|
// decls to bare — that usage is deliberate-only.
|
|
if (p.curkind == tkind.TK_MODRESET) {
|
|
sawpackage = 0;
|
|
// #57: a path-carrying reset (sep primary body) mangles decls
|
|
// on the dotted path so definer == importer, but leaves
|
|
// imported==0 (curmod set, pathmod "") so -c primary-ness and
|
|
// the #32 bare-main rule are intact; the body's `package`
|
|
// clause then asserts (resetmod). A bare reset is the
|
|
// root/package-less boundary: curmod "" → bare, today's path.
|
|
let rp: str = p.curtext;
|
|
advance(p);
|
|
p.pathmod = "";
|
|
if (rp.len != 0) {
|
|
p.curmod = rp;
|
|
p.resetmod = rp;
|
|
// #11: path-carrying reset is a primary body identity
|
|
// (sep); stamp it for the wwi leaf fallback. A bare
|
|
// reset (rp empty) is the root/package-less boundary
|
|
// and MUST keep the "main" default — so don't stamp.
|
|
if (f.nmod.len == 0) { f.nmod = rp; };
|
|
} else {
|
|
let empty: str;
|
|
empty.ptr = nil;
|
|
empty.len = 0;
|
|
p.curmod = empty;
|
|
p.resetmod = "";
|
|
};
|
|
continue;
|
|
};
|
|
// strict-package: a primary section's first real decl must be
|
|
// preceded by a `package` clause. Imported (pathmod) and sep
|
|
// primary-reset (resetmod) regions carry identity out-of-band,
|
|
// so they are exempt. Wording mirrors Go's missing-`package`
|
|
// diagnostic (rule 5; Hare has no clause to port).
|
|
if (p.pathmod.len == 0 && p.resetmod.len == 0 && sawpackage == 0) {
|
|
errmsg(p, "missing package clause");
|
|
sawpackage = 1;
|
|
};
|
|
let attrs = parseattrs(p);
|
|
let exported: i32 = 0;
|
|
if (p.curkind == tkind.TK_EXPORT) { exported = 1; advance(p); };
|
|
|
|
let d: *node = nil;
|
|
if (p.curkind == tkind.TK_USE) {
|
|
d = parseuse(p);
|
|
} else { if (p.curkind == tkind.TK_DEF) {
|
|
d = parsedef(p, exported);
|
|
} else { if (p.curkind == tkind.TK_TYPE) {
|
|
d = parsetypedecl(p, exported);
|
|
} else { if (p.curkind == tkind.TK_LET) {
|
|
d = parselet(p, exported);
|
|
} else { if (p.curkind == tkind.TK_CONST) {
|
|
d = parselet(p, exported);
|
|
} else { if (p.curkind == tkind.TK_FN) {
|
|
d = parsefn(p, exported, attrs);
|
|
} else {
|
|
// cstage parse.c:1395-1400 errorf+p->errs++ on the
|
|
// default arm: an unknown top-level construct is a loud
|
|
// reject, not a silent skip. ww chews the whole decl at
|
|
// once (below), so one error per fallback entry.
|
|
errmsg(p, "expected top-level decl");
|
|
// Recovery: chew tokens until next ';' or EOF, balancing
|
|
// '{' '}' pairs so internal ';'s in unfamiliar forms don't
|
|
// derail us.
|
|
for (p.curkind != tkind.TK_SEMI) {
|
|
if (p.curkind == tkind.TK_EOF) { break; };
|
|
if (p.curkind == tkind.TK_LBRACE) {
|
|
let depth: i32 = 0;
|
|
for (true) {
|
|
if (p.curkind == tkind.TK_EOF) { break; };
|
|
if (p.curkind == tkind.TK_LBRACE) { depth += 1; advance(p); continue; };
|
|
if (p.curkind == tkind.TK_RBRACE) {
|
|
depth -= 1;
|
|
advance(p);
|
|
if (depth == 0) { break; };
|
|
continue;
|
|
};
|
|
advance(p);
|
|
};
|
|
continue;
|
|
};
|
|
advance(p);
|
|
};
|
|
if (p.curkind == tkind.TK_SEMI) { advance(p); };
|
|
};};};};};};
|
|
|
|
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;
|
|
} else {
|
|
tail.next = d;
|
|
tail = d;
|
|
};
|
|
};
|
|
};
|
|
f.list = head;
|
|
return f;
|
|
};
|