Files
ww/lib/ww/syntax/parse.ww

972 lines
29 KiB
Plaintext

// Port of cmd/wcc/parse.c (entry + plumbing).
//
// 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>` canonical identity. Decls
// mangle on it without imported=1; the package clause independently
// supplies the declared name. "" means inactive.
resetmod: str,
// Declared package name and file scope are deliberately orthogonal to
// curmod/pathmod. Every driver module boundary advances sourceid.
curpkg: str,
sourceid: i32,
// Hidden package-driver alias for the toolchain `package test` source.
// Empty outside that one separate-compilation edge.
testmodule: str,
// Selected command-family validation: main/main_test classifies the
// package without replacing the resetmod canonical identity.
commandpackage: bool,
};
fn installtok(p: *parser, t: *tok) void = {
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;
};
fn refill(p: *parser) void = {
let t: tok;
lexnext(p.l, &t);
installtok(p, &t);
};
fn advanceheaderimport(p: *parser) bool = {
let t: tok;
if (!lexheaderimport(p.l, &t)) { return false; };
installtok(p, &t);
return true;
};
export fn parserinit(p: *parser, l: *lex) void = {
p.l = l;
p.errs = 0;
p.nocast = 0;
p.pathmod = "";
p.resetmod = "";
p.curpkg = "";
p.sourceid = 0;
p.testmodule = "";
p.commandpackage = false;
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 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 = {
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;
};
fn expecttok(p: *parser, k: tkind, what: str) bool = {
if (p.curkind == k) { advance(p); return true; };
errmsg(p, what);
return false;
};
// 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, strings.concat("expected identifier, got ",
tokname(p.curkind)));
return false;
};
*into = p.curtext;
advance(p);
return true;
};
// Package declarations admit the blank identifier syntactically. Keep this
// private to the package-name slot; the checker owns BlankPkgName rejection.
fn expectpackagename(p: *parser, into: *str) bool = {
if (p.curkind != tkind.TK_IDENT && p.curkind != tkind.TK_UNDER) {
errmsg(p, strings.concat("expected identifier, got ",
tokname(p.curkind)));
return false;
};
*into = p.curtext;
advance(p);
return true;
};
// 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);
};
// Mirrors aprintf in the 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);
};
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;
};
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"); };
};
};
};
// Parse one initial import for the named-source header pass. Unlike the full
// recovery scanner, this stops on the first malformed header token and never
// consumes an ordinary declaration body.
fn parseheaderuse(p: *parser) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
advance(p);
let n: *node = newnode(nkind.N_USE, pf, pl, pc);
n.usefile = p.curfile;
n.useline = p.curline;
n.usecol = p.curcol;
let pathfile: str = p.curfile;
let pathline: i32 = p.curline;
let pathcol: i32 = p.curcol;
let alias: str;
let first: str;
if (p.curkind == tkind.TK_UNDER) {
n.useblank = 1;
advance(p);
pathfile = p.curfile;
pathline = p.curline;
pathcol = p.curcol;
};
if (p.curkind != tkind.TK_IDENT) {
errmsg(p, strings.concat("expected identifier, got ",
tokname(p.curkind)));
return n;
};
first = p.curtext;
advance(p);
if (n.useblank == 0 && p.curkind == tkind.TK_IDENT) {
alias = first;
pathfile = p.curfile;
pathline = p.curline;
pathcol = p.curcol;
first = p.curtext;
advance(p);
};
n.usepathfile = pathfile;
n.usepathline = pathline;
n.usepathcol = pathcol;
let leaf: str = first;
let path: str = leaf;
for (p.curkind == tkind.TK_DOT) {
advance(p);
if (p.curkind != tkind.TK_IDENT) {
errmsg(p, strings.concat("expected identifier, got ",
tokname(p.curkind)));
return n;
};
leaf = p.curtext;
path = strings.concat(path, ".", leaf);
advance(p);
};
if (n.useblank != 0) { n.str = ""; }
else { if (alias.len > 0) { n.str = alias; } else { n.str = leaf; }; };
n.usesource = path;
n.usepath = path;
n.usealias = alias;
if (p.curkind != tkind.TK_SEMI) {
errmsg(p, "expected ';' after import");
return n;
};
return n;
};
// A valid named source is loader-visible only through its initial package and
// contiguous import section. Keep parseimports for graph-bearing source
// recovery; this boundary deliberately ignores every ordinary declaration.
export fn parsepackageheader(p: *parser) *node = {
let f: *node = newnode(nkind.N_FILE, p.curfile, 1, 1);
let head: *node = nil;
let tail: *node = nil;
for (p.curkind == tkind.TK_MODPATH
|| p.curkind == tkind.TK_MODRESET) {
if (p.curkind == tkind.TK_MODPATH) {
p.pathmod = p.curtext;
p.curmod = p.curtext;
p.resetmod = "";
} else {
p.pathmod = "";
p.curmod = p.curtext;
p.resetmod = p.curtext;
};
p.sourceid += 1;
advance(p);
};
if (p.curkind != tkind.TK_MODULE) {
errmsg(p, "invalid or missing package clause");
return f;
};
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
advance(p);
if (p.curkind != tkind.TK_IDENT && p.curkind != tkind.TK_UNDER) {
errmsg(p, "invalid or missing package clause");
return f;
};
let name: str;
expectpackagename(p, &name);
if (p.curkind != tkind.TK_SEMI) {
errmsg(p, "expected ';' after package name");
return f;
};
p.curpkg = name;
if (p.pathmod.len == 0 && p.resetmod.len == 0) { p.curmod = name; };
f.nmod = name;
f.pkgname = name;
f.sourceid = p.sourceid;
f.file = pf;
f.line = pl;
f.col = pc;
for (advanceheaderimport(p)) {
let d: *node = parseheaderuse(p);
d.nmod = p.curmod;
d.pkgname = p.curpkg;
d.sourceid = p.sourceid;
if (head == nil) { head = d; } else { tail.next = d; };
tail = d;
if (p.errs != 0) { break; };
};
f.list = head;
return f;
};
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;
// One import section precedes ordinary declarations. Keep parsing for
// recovery, diagnosing the first import in each later source section.
let previmport: bool = true;
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;
previmport = true;
p.sourceid += 1;
p.pathmod = p.curtext;
p.curmod = p.curtext;
p.resetmod = "";
p.curpkg = "";
advance(p);
continue;
};
if (p.curkind == tkind.TK_MODRESET) {
let rp: str = p.curtext;
sawpackage = false;
previmport = true;
p.sourceid += 1;
advance(p);
p.pathmod = "";
p.curmod = rp;
p.resetmod = rp;
p.curpkg = "";
continue;
};
if (p.curkind == tkind.TK_MODULE) {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
previmport = true;
advance(p);
if (p.curkind != tkind.TK_IDENT
&& p.curkind != tkind.TK_UNDER) {
errmsg(p, "invalid or missing package clause");
sawpackage = true;
skipimportdecl(p);
continue;
};
let name: str;
expectpackagename(p, &name);
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
p.curpkg = name;
if (p.pathmod.len == 0 && p.resetmod.len == 0) {
p.curmod = name;
};
let pm = newnode(nkind.N_FILE, pf, pl, pc);
pm.nmod = p.curmod;
pm.pkgname = name;
pm.sourceid = p.sourceid;
if (packages == nil) { packages = pm; }
else { packagetail.next = pm; };
packagetail = pm;
if (!sawpackage) {
f.nmod = name;
f.pkgname = name;
f.sourceid = p.sourceid;
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) {
if (!previmport) {
errmsg(p, "imports must appear before other declarations");
};
previmport = true;
let d: *node = parseuse(p);
d.nmod = p.curmod;
d.pkgname = p.curpkg;
d.sourceid = p.sourceid;
if (head == nil) { head = d; } else { tail.next = d; };
tail = d;
continue;
};
previmport = false;
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;
d.pkgname = p.curpkg;
d.sourceid = p.sourceid;
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;
d.pkgname = p.curpkg;
d.sourceid = p.sourceid;
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.
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 packages: *node = nil;
let packagetail: *node = nil;
let sawpackage: i32 = 0;
// Full/direct parsing is the semantic twin of the imports-only pass.
let previmport: bool = true;
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) {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
sawpackage = 1;
previmport = true;
advance(p);
let nf: str = p.curfile;
let nl: i32 = p.curline;
let nc: i32 = p.curcol;
let name: str;
expectpackagename(p, &name);
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
p.curpkg = name;
if (p.pathmod.len == 0 && p.resetmod.len == 0) {
p.curmod = name;
};
let mf: str = pf;
let ml: i32 = pl;
let mc: i32 = pc;
if (streq(name, "_")) {
mf = nf;
ml = nl;
mc = nc;
};
let pm: *node = newnode(nkind.N_FILE, mf, ml, mc);
pm.nmod = p.curmod;
pm.pkgname = name;
pm.sourceid = p.sourceid;
if (p.pathmod.len != 0) { pm.imported = 1; };
if (packages == nil) { packages = pm; }
else { packagetail.next = pm; };
packagetail = pm;
if (f.pkgname.len == 0) {
f.pkgname = name;
f.sourceid = p.sourceid;
};
continue;
};
// `//ww:module <path>` — M1 #22 import boundary. The following
// file's decls mangle on the full dotted import path independently
// of its `package` clause, and are flagged imported (gates the
// root-only bare-`main` rule, #32).
if (p.curkind == tkind.TK_MODPATH) {
sawpackage = 0;
previmport = true;
p.sourceid += 1;
p.pathmod = p.curtext;
p.curmod = p.curtext;
p.resetmod = "";
p.curpkg = "";
if (f.nmod.len == 0) { f.nmod = p.curtext; };
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;
previmport = true;
p.sourceid += 1;
// #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 = "";
p.curpkg = "";
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 thisimport: bool = p.curkind == tkind.TK_USE;
if (thisimport && !previmport) {
errmsg(p, "imports must appear before other declarations");
};
previmport = thisimport;
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) {
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);
} 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; };
d.nmod = p.curmod;
d.pkgname = p.curpkg;
d.sourceid = p.sourceid;
if (head == nil) {
head = d;
tail = d;
} else {
tail.next = d;
tail = d;
};
};
};
f.list = head;
f.body = packages;
return f;
};