972 lines
29 KiB
Plaintext
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;
|
|
};
|