Files
ww/cmd/wcc/parse.c
Hojun-Cho 01b657a7ff wcc,lib/ww/syntax: resolve qualified struct-literal pkg.Type{...} (#76)
The parser folded a qualified type pkg.Type into two different node shapes by position: declaration position collapsed it into one N_TNAME (resolved via the strrchr-leaf path), but literal position left an N_DOT chain that the struct-literal typeref handoff had no resolver arm for, so pkg.Type{...} rejected with "expected type expression".

Normalize the literal-position N_DOT chain into the same source-order N_TNAME the declaration path emits, reusing the existing resolver; no new checker arm. cstage flattens at parseprimary struct-lit handoff; wwstage (no token peek) folds dots in parsepostfix and normalizes there, guarding numeric tuple components and staying in the postfix loop so trailing ops still chain. Both stages emit identical N_STRUCTLIT(N_TNAME). Prereq for qualifying wcc syntax refs (#75).
2026-06-16 22:20:42 +09:00

1504 lines
37 KiB
C

/*
* parse.c — recursive descent + Pratt expression parser.
*
* Plan 9 style: hand-rolled, no yacc, errors are reported via errorf
* but we keep going where possible so the user gets multiple
* diagnostics from one run.
*
* Module navigation and field access both use '.'. We disambiguate
* structurally: the parser eats `IDENT('.'IDENT)*` greedily for type
* names and `use` paths; in expressions, postfix `.IDENT` becomes
* N_DOT and the checker decides if it's a module-qualified ref or a
* struct field.
*/
#include "ww.h"
#include <string.h>
#include <stdlib.h>
void
parserinit(Parser *p, Arena *a, Lex *l)
{
memset(p, 0, sizeof *p);
p->l = l;
p->a = a;
p->cur = lexnext(l);
}
static void
advance(Parser *p)
{
if (p->hasla) {
p->cur = p->la;
p->hasla = 0;
} else {
p->cur = lexnext(p->l);
}
}
static Tok
peek(Parser *p)
{
if (!p->hasla) {
p->la = lexnext(p->l);
p->hasla = 1;
}
return p->la;
}
static int
accept(Parser *p, Tkind k)
{
if (p->cur.kind == k) {
advance(p);
return 1;
}
return 0;
}
static int
expect(Parser *p, Tkind k)
{
if (p->cur.kind == k) {
advance(p);
return 1;
}
errorf(p->cur.pos, "expected %s, got %s", tokname(k), tokname(p->cur.kind));
p->errs++;
return 0;
}
static const char *
expectident(Parser *p)
{
if (p->cur.kind != TK_IDENT) {
errorf(p->cur.pos, "expected identifier, got %s",
tokname(p->cur.kind));
p->errs++;
return "<err>";
}
const char *s = p->cur.text;
advance(p);
return s;
}
/* Like expectident but also accepts a bare `_` discard marker. The
* returned string is the empty string "" so the checker skips
* scope_define. Callers that care can detect this with `s[0] == '\0'`. */
static const char *
expectbindname(Parser *p)
{
if (p->cur.kind == TK_UNDER) {
advance(p);
return "";
}
return expectident(p);
}
static Node *parseexpr(Parser *p);
static Node *parseunary(Parser *p);
static Node *parsetype(Parser *p);
static Node *parseblock(Parser *p);
static Node *parsestmt(Parser *p);
/* ------- type expressions ------------------------------------------ */
static Node *
parseparams(Parser *p)
{
Node *head = NULL, *tail = NULL;
if (p->cur.kind == TK_RPAREN)
return NULL;
for (;;) {
Pos pp = p->cur.pos;
if (p->cur.kind == TK_ELLIPSIS) {
advance(p);
Node *n = newnode(p->a, N_PARAM, pp);
n->str = "...";
n->strlen = 3;
if (head == NULL) head = n;
else tail->next = n;
tail = n;
break;
}
Node *n = newnode(p->a, N_PARAM, pp);
/* IDENT ':' type OR `_' ':' type OR type-only.
* Disambiguate: if current is IDENT or '_' and next is ':',
* it's a named param. Otherwise treat as anonymous. */
int named = (p->cur.kind == TK_IDENT || p->cur.kind == TK_UNDER)
&& peek(p).kind == TK_COLON;
if (named) {
n->str = expectbindname(p);
expect(p, TK_COLON);
n->lhs = parsetype(p);
} else {
n->str = "";
n->strlen = 0;
n->lhs = parsetype(p);
}
/* Hare-style variadic: `name: T...`. The trailing `...`
* after the type promotes the param's type to []T at type-
* resolution time; call sites gather N args or forward a
* `xs...` spread. Marked on n->op so check.c and selfhost
* recognise it without needing a new Node kind. */
if (accept(p, TK_ELLIPSIS))
n->op = TK_ELLIPSIS;
if (head == NULL) head = n;
else tail->next = n;
tail = n;
if (n->op == TK_ELLIPSIS)
break; /* Hare-style variadic must be the last param */
if (!accept(p, TK_COMMA))
break;
if (p->cur.kind == TK_RPAREN) /* trailing comma */
break;
}
return head;
}
static Node *
parsetype(Parser *p)
{
Pos pp = p->cur.pos;
switch (p->cur.kind) {
case TK_NOT: {
/* `!T` — error-flagged type. The flag propagates through
* NAMED aliases and lives on the underlying Type, not on
* a wrapper. The AST keeps an N_TBANG wrapper so prints
* and selfhost can recognise the marker. */
advance(p);
Node *n = newnode(p->a, N_TBANG, pp);
n->lhs = parsetype(p);
return n;
}
case TK_STAR: {
advance(p);
Node *n = newnode(p->a, N_TPTR, pp);
n->lhs = parsetype(p);
return n;
}
case TK_LBRACK: {
advance(p);
if (accept(p, TK_RBRACK)) {
Node *n = newnode(p->a, N_TSLICE, pp);
n->lhs = parsetype(p);
return n;
}
Node *n = newnode(p->a, N_TARRAY, pp);
/* `[_]T` — length inferred from the initialiser. Marked by
* leaving n->rhs == NULL; check.c fills in the length from
* the array literal's element count. */
if (!accept(p, TK_UNDER))
n->rhs = parseexpr(p);
expect(p, TK_RBRACK);
n->lhs = parsetype(p);
return n;
}
case TK_FN: {
advance(p);
expect(p, TK_LPAREN);
Node *n = newnode(p->a, N_TFN, pp);
n->list = parseparams(p);
expect(p, TK_RPAREN);
n->lhs = parsetype(p);
return n;
}
case TK_STRUCT: {
advance(p);
expect(p, TK_LBRACE);
Node *n = newnode(p->a, N_TSTRUCT, pp);
Node *head = NULL, *tail = NULL;
while (p->cur.kind != TK_RBRACE && p->cur.kind != TK_EOF) {
Pos fp = p->cur.pos;
Node *f = newnode(p->a, N_TFIELD, fp);
/* Three member forms:
* name: type — regular field
* struct { ... } — anonymous embedded struct
* Identifier — bare-name embedded type
* Embeds carry f->str == NULL and the type in f->lhs. */
if (p->cur.kind == TK_STRUCT) {
f->lhs = parsetype(p);
} else if (p->cur.kind == TK_IDENT
&& peek(p).kind != TK_COLON) {
f->lhs = parsetype(p);
} else {
f->str = expectident(p);
expect(p, TK_COLON);
f->lhs = parsetype(p);
}
if (head == NULL) head = f;
else tail->next = f;
tail = f;
if (!accept(p, TK_COMMA))
break;
}
expect(p, TK_RBRACE);
n->list = head;
return n;
}
case TK_CHAN: {
advance(p);
Node *n = newnode(p->a, N_TCHAN, pp);
n->lhs = parsetype(p);
return n;
}
case TK_ENUM: {
/* `enum [storage] { NAME [= expr] [, ...] }`
* storage defaults to i32 (lhs == NULL).
* Each member is N_TENUMMEMBER with str=name and
* lhs=value-expr (NULL means auto-increment from previous). */
advance(p);
Node *n = newnode(p->a, N_TENUM, pp);
if (p->cur.kind != TK_LBRACE)
n->lhs = parsetype(p);
expect(p, TK_LBRACE);
Node *head = NULL, *tail = NULL;
while (p->cur.kind != TK_RBRACE && p->cur.kind != TK_EOF) {
Pos mp = p->cur.pos;
Node *m = newnode(p->a, N_TENUMMEMBER, mp);
m->str = expectident(p);
m->strlen = strlen(m->str);
if (accept(p, TK_ASSIGN))
m->lhs = parseexpr(p);
if (head == NULL) head = m;
else tail->next = m;
tail = m;
if (!accept(p, TK_COMMA))
break;
}
expect(p, TK_RBRACE);
n->list = head;
return n;
}
case TK_VOID: {
/* `void` keyword in type-expr context. Synthesise an
* N_TNAME so type-resolution treats it like any other
* primitive name. */
Node *n = newnode(p->a, N_TNAME, pp);
n->str = "void";
n->strlen = 4;
advance(p);
return n;
}
case TK_IDENT: {
Node *n = newnode(p->a, N_TNAME, pp);
n->str = p->cur.text;
n->strlen = p->cur.tlen;
advance(p);
/* dotted path: pkg.Type — collapse into a single TNAME */
while (accept(p, TK_DOT)) {
if (p->cur.kind != TK_IDENT) {
errorf(p->cur.pos, "expected identifier after '.'");
p->errs++;
break;
}
n->str = aprintf(p->a, "%s.%s", n->str, p->cur.text);
n->strlen = strlen(n->str);
advance(p);
}
return n;
}
case TK_LPAREN: {
/* Three forms inside the parens:
* (T) — parenthesised single type
* (T, T2, ...) — tuple type
* (T | T2 | ...) — tagged-union type (Hare-style sum)
*
* 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 (today the checker
* flattens any nested tagged unconditionally, matching
* Hare's structural-equivalence rule, but the marker is
* preserved for future nominal handling). */
advance(p);
int first_spread = accept(p, TK_ELLIPSIS);
Node *first = parsetype(p);
if (first_spread) first->op = TK_ELLIPSIS;
if (accept(p, TK_PIPE)) {
Node *t = newnode(p->a, N_TTAGGED, pp);
Node *head = first, *tail = first;
for (;;) {
int spread = accept(p, TK_ELLIPSIS);
Node *e = parsetype(p);
if (spread) e->op = TK_ELLIPSIS;
tail->next = e;
tail = e;
if (!accept(p, TK_PIPE)) break;
}
expect(p, TK_RPAREN);
t->list = head;
return t;
}
if (first_spread) {
errorf(pp, "spread '...' only valid before tagged-union variants");
p->errs++;
}
if (!accept(p, TK_COMMA)) {
expect(p, TK_RPAREN);
return first;
}
Node *t = newnode(p->a, N_TTUPLE, pp);
Node *head = first, *tail = first;
for (;;) {
Node *e = parsetype(p);
tail->next = e;
tail = e;
if (!accept(p, TK_COMMA)) break;
if (p->cur.kind == TK_RPAREN) break;
}
expect(p, TK_RPAREN);
t->list = head;
return t;
}
default:
errorf(pp, "expected type, got %s", tokname(p->cur.kind));
p->errs++;
advance(p);
return newnode(p->a, N_TNAME, pp);
}
}
/* ------- expressions (Pratt) ---------------------------------------- */
/* binary precedence; 0 = not a binary op */
static int
bprec(Tkind k)
{
switch (k) {
case TK_OR: return 1;
case TK_AND: return 2;
case TK_EQ:
case TK_NEQ: return 3;
case TK_LT:
case TK_LE:
case TK_GT:
case TK_GE: return 4;
case TK_PIPE: return 5;
case TK_CARET: return 6;
case TK_AMP: return 7;
case TK_LSHIFT:
case TK_RSHIFT: return 8;
case TK_PLUS:
case TK_MINUS: return 9;
case TK_STAR:
case TK_SLASH:
case TK_PERCENT: return 10;
default: return 0;
}
}
static int
isassignop(Tkind k)
{
switch (k) {
case TK_ASSIGN: case TK_PLUSEQ: case TK_MINUSEQ:
case TK_STAREQ: case TK_SLASHEQ: case TK_PERCENTEQ:
case TK_AMPEQ: case TK_PIPEEQ: case TK_CARETEQ:
case TK_LSHIFTEQ: case TK_RSHIFTEQ:
return 1;
default: return 0;
}
}
static Node *
parsearglist(Parser *p, Tkind close)
{
Node *head = NULL, *tail = NULL;
if (p->cur.kind == close)
return NULL;
for (;;) {
Node *e = parseexpr(p);
/* Hare-style spread: `expr...` in an arg slot becomes a
* marker node that the callee/builtin can iterate. */
if (accept(p, TK_ELLIPSIS)) {
Node *sp = newnode(p->a, N_SPREAD, e->pos);
sp->lhs = e;
e = sp;
}
if (head == NULL) head = e;
else tail->next = e;
tail = e;
if (!accept(p, TK_COMMA))
break;
if (p->cur.kind == close)
break;
}
return head;
}
/* #76: a qualified path `pkg.Type` reaches a struct-literal handoff as an
* N_DOT chain (parseprimary's dotted fold). Flatten it into one N_TNAME
* whose str joins the chain in SOURCE order — byte-identical to parsetype's
* dotted collapse (parse.c: aprintf("%s.%s", ...)) — so resolve_type's
* existing N_TNAME arm resolves it with no new checker arm. */
static const char *
flattendotstr(Parser *p, Node *n)
{
if (n->kind == N_IDENT)
return n->str;
return aprintf(p->a, "%s.%s", flattendotstr(p, n->lhs), n->str);
}
static Node *
flattendot(Parser *p, Node *n)
{
Node *t = newnode(p->a, N_TNAME, n->pos);
t->str = flattendotstr(p, n);
t->strlen = strlen(t->str);
return t;
}
static Node *
parsestructlit(Parser *p, Node *typeref)
{
Pos pp = p->cur.pos;
expect(p, TK_LBRACE);
Node *n = newnode(p->a, N_STRUCTLIT, pp);
n->lhs = typeref;
Node *head = NULL, *tail = NULL;
while (p->cur.kind != TK_RBRACE && p->cur.kind != TK_EOF) {
Pos fp = p->cur.pos;
/* Trailing `...` after the last comma (or as the only entry)
* means "zero-init all unmentioned fields". Marked on the
* literal node via op = TK_ELLIPSIS; cgen consumes it. */
if (p->cur.kind == TK_ELLIPSIS) {
advance(p);
n->op = TK_ELLIPSIS;
break;
}
const char *name = expectident(p);
expect(p, TK_ASSIGN);
Node *val = parseexpr(p);
Node *f = newnode(p->a, N_FIELD, fp);
f->str = name;
f->lhs = val;
if (head == NULL) head = f;
else tail->next = f;
tail = f;
if (!accept(p, TK_COMMA))
break;
}
expect(p, TK_RBRACE);
n->list = head;
return n;
}
static Node *
parsearrlit(Parser *p)
{
Pos pp = p->cur.pos;
expect(p, TK_LBRACK);
Node *n = newnode(p->a, N_ARRLIT, pp);
Node *head = NULL, *tail = NULL;
while (p->cur.kind != TK_RBRACK && p->cur.kind != TK_EOF) {
Node *e = parseexpr(p);
if (head == NULL) head = e;
else tail->next = e;
tail = e;
if (accept(p, TK_ELLIPSIS)) {
/* repeat suffix marker; encode as a flag node */
Node *rep = newnode(p->a, N_FIELD, p->cur.pos);
rep->str = "...";
rep->strlen = 3;
tail->next = rep;
tail = rep;
break;
}
if (!accept(p, TK_COMMA))
break;
}
expect(p, TK_RBRACK);
n->list = head;
return n;
}
static Node *
parseprimary(Parser *p)
{
Pos pp = p->cur.pos;
Tok t = p->cur;
switch (t.kind) {
case TK_INT: {
Node *n = newnode(p->a, N_INTLIT, pp);
n->uval = t.v.uval;
n->str = t.text;
n->tsuffix = t.tsuffix;
advance(p);
return n;
}
case TK_FLOAT: {
Node *n = newnode(p->a, N_FLOATLIT, pp);
n->fval = t.v.fval;
n->str = t.text;
n->tsuffix = t.tsuffix;
advance(p);
return n;
}
case TK_STR: {
Node *n = newnode(p->a, N_STRLIT, pp);
n->str = t.text;
n->strlen = t.tlen;
advance(p);
return n;
}
case TK_RUNE: {
Node *n = newnode(p->a, N_RUNELIT, pp);
n->uval = t.v.uval;
advance(p);
return n;
}
case TK_TRUE: advance(p); return newnode(p->a, N_TRUE, pp);
case TK_FALSE: advance(p); return newnode(p->a, N_FALSE, pp);
case TK_NIL: advance(p); return newnode(p->a, N_NIL, pp);
case TK_VOID: advance(p); return newnode(p->a, N_VOIDLIT, pp);
case TK_UNDER: {
/* Bare `_` — valid only as a discard lvalue. We yield an N_IDENT
* with empty str; the checker rejects it outside assignment
* lvalue positions. */
advance(p);
Node *n = newnode(p->a, N_IDENT, pp);
n->str = "";
n->strlen = 0;
return n;
}
case TK_LPAREN: {
advance(p);
Node *e = parseexpr(p);
/* tuple literal: (e1, e2, ...) — at least 2 elements;
* a single (e) is a parenthesised expression */
if (accept(p, TK_COMMA)) {
Node *t = newnode(p->a, N_TUPLE, pp);
t->list = e;
Node *tt = e;
for (;;) {
Node *en = parseexpr(p);
tt->next = en;
tt = en;
if (!accept(p, TK_COMMA)) break;
if (p->cur.kind == TK_RPAREN) break;
}
expect(p, TK_RPAREN);
return t;
}
expect(p, TK_RPAREN);
return e;
}
case TK_LBRACK:
return parsearrlit(p);
case TK_MATCH: {
/* match (e) {
* case let v: T => stmt;
* case T => stmt;
* case => stmt; — default
* };
*/
advance(p);
expect(p, TK_LPAREN);
Node *m = newnode(p->a, N_MATCH, pp);
m->lhs = parseexpr(p);
expect(p, TK_RPAREN);
expect(p, TK_LBRACE);
Node *head = NULL, *tail = NULL;
while (p->cur.kind == TK_CASE) {
Pos cp = p->cur.pos;
advance(p);
Node *cs = newnode(p->a, N_MCASE, cp);
if (p->cur.kind == TK_LET) {
/* `case let v: T =>` — bind variant to v.
* Single-type only: multi-pattern with a
* binding would force the binding to take the
* union type, which we don't support. */
advance(p);
cs->str = expectident(p);
expect(p, TK_COLON);
cs->lhs = parsetype(p);
} else if (p->cur.kind == TK_FATARROW) {
/* `case =>` — default arm (cs->lhs left NULL) */
} else if (p->cur.kind != TK_ELSE && p->cur.kind != TK_LBRACE) {
/* `case T =>` — match variant by type.
* `case T1 | T2 | T3 =>` — match if scrutinee
* is any of the listed variants. The first type
* goes on cs->lhs (so single-pattern callers
* still work); additional types chain through
* cs->list. */
cs->lhs = parsetype(p);
Node *tail = NULL;
while (accept(p, TK_PIPE)) {
Node *more = parsetype(p);
if (cs->list == NULL) cs->list = more;
else tail->next = more;
tail = more;
}
}
expect(p, TK_FATARROW);
cs->body = parsestmt(p);
if (head == NULL) head = cs;
else tail->next = cs;
tail = cs;
}
expect(p, TK_RBRACE);
m->list = head;
return m;
}
case TK_IDENT: {
Node *n = newnode(p->a, N_IDENT, pp);
n->str = t.text;
n->strlen = t.tlen;
advance(p);
/* dotted ident chain folded into one IDENT for type-ish refs */
while (p->cur.kind == TK_DOT && peek(p).kind == TK_IDENT) {
advance(p);
n = (Node*)n; /* keep stable */
Node *mr = newnode(p->a, N_DOT, pp);
mr->lhs = n;
mr->str = p->cur.text;
mr->strlen = p->cur.tlen;
advance(p);
n = mr;
}
/* struct literal: ident '{' ... '}' (only if ident-shaped) */
if (p->cur.kind == TK_LBRACE) {
/* #76: bare `Foo{}` keeps the N_IDENT fast-path; a
* qualified `pkg.Type{}` (N_DOT chain) flattens first. */
if (n->kind == N_DOT)
n = flattendot(p, n);
return parsestructlit(p, n);
}
return n;
}
default:
errorf(pp, "unexpected token in expression: %s",
tokname(t.kind));
p->errs++;
advance(p);
return newnode(p->a, N_NONE, pp);
}
}
static Node *
parsepostfix(Parser *p, Node *lhs)
{
for (;;) {
Pos pp = p->cur.pos;
switch (p->cur.kind) {
case TK_LPAREN: {
advance(p);
Node *n = newnode(p->a, N_CALL, pp);
n->lhs = lhs;
/* size(T)/align(T): the single arg is a type expression,
* not a regular expression — types like []u8 cannot parse
* as expressions. Special-case at the parser. */
if (lhs->kind == N_IDENT && lhs->str &&
(strcmp(lhs->str, "size") == 0 ||
strcmp(lhs->str, "align") == 0)) {
n->list = parsetype(p);
} else {
n->list = parsearglist(p, TK_RPAREN);
}
expect(p, TK_RPAREN);
lhs = n;
break;
}
case TK_LBRACK: {
advance(p);
/* Three forms inside the brackets:
* [hi] — slice of [0:hi] (rare)
* [lo:hi] — slice
* [expr] — index
* We disambiguate by checking for ':' after the first
* expr (suppressing cast there since ':' is the slicer). */
if (p->cur.kind == TK_COLON) {
advance(p);
Node *n = newnode(p->a, N_SLICE, pp);
n->lhs = lhs;
if (p->cur.kind != TK_RBRACK)
n->cond = parseexpr(p);
expect(p, TK_RBRACK);
lhs = n;
break;
}
int prev_nocast = p->nocast;
p->nocast = 1;
Node *e = parseexpr(p);
p->nocast = prev_nocast;
if (p->cur.kind == TK_COLON) {
advance(p);
Node *n = newnode(p->a, N_SLICE, pp);
n->lhs = lhs;
n->rhs = e;
if (p->cur.kind != TK_RBRACK)
n->cond = parseexpr(p);
expect(p, TK_RBRACK);
lhs = n;
break;
}
Node *n = newnode(p->a, N_INDEX, pp);
n->lhs = lhs;
n->rhs = e;
expect(p, TK_RBRACK);
lhs = n;
break;
}
case TK_DOT: {
advance(p);
Node *n = newnode(p->a, N_DOT, pp);
n->lhs = lhs;
if (p->cur.kind == TK_INT) {
/* Hare-style tuple field access: t.0, t.1 */
n->str = aprintf(p->a, "%llu",
(unsigned long long)p->cur.v.uval);
advance(p);
} else {
n->str = expectident(p);
}
lhs = n;
break;
}
case TK_COLON: {
/* cast: expr ':' type — suppressed inside case-selectors,
* where ':' separates the selector from the body. */
if (p->nocast)
return lhs;
advance(p);
Node *n = newnode(p->a, N_CAST, pp);
n->lhs = lhs;
n->rhs = parsetype(p);
lhs = n;
break;
}
case TK_QUESTION: {
advance(p);
Node *n = newnode(p->a, N_TRYPROP, pp);
n->lhs = lhs;
lhs = n;
break;
}
case TK_NOT: {
/* Could be unary '!' starting a new expression, but as
* a *postfix* it's the Hare error-unwrap. We're inside
* parsepostfix, so it's postfix. */
advance(p);
Node *n = newnode(p->a, N_TRYUNW, pp);
n->lhs = lhs;
lhs = n;
break;
}
case TK_AS:
case TK_IS: {
/* Hare-style:
* e as T — assert lhs is variant T of its tagged
* union; abort if not. Yields T.
* e is T — bool: does lhs currently hold variant T?
* Postfix, same level as `:` cast. */
Tkind k = p->cur.kind;
advance(p);
Node *n = newnode(p->a,
k == TK_AS ? N_TYPEASSERT : N_TYPETEST, pp);
n->lhs = lhs;
n->rhs = parsetype(p);
lhs = n;
break;
}
default:
return lhs;
}
}
}
static Node *
parseunary(Parser *p)
{
Pos pp = p->cur.pos;
switch (p->cur.kind) {
case TK_MINUS:
case TK_PLUS:
case TK_NOT:
case TK_TILDE:
case TK_STAR: /* deref */
case TK_AMP: { /* address-of */
Tkind op = p->cur.kind;
advance(p);
Node *n = newnode(p->a, N_UN, pp);
n->op = op;
n->lhs = parseunary(p);
return n;
}
case TK_LARROW: { /* chan recv */
advance(p);
Node *n = newnode(p->a, N_RECV, pp);
n->lhs = parseunary(p);
return n;
}
default:
return parsepostfix(p, parseprimary(p));
}
}
static Node *
parsebin(Parser *p, Node *lhs, int min)
{
for (;;) {
Tkind op = p->cur.kind;
int pr = bprec(op);
if (pr == 0 || pr < min)
return lhs;
Pos pp = p->cur.pos;
advance(p);
Node *rhs = parseunary(p);
while (bprec(p->cur.kind) > pr)
rhs = parsebin(p, rhs, bprec(p->cur.kind));
Node *n = newnode(p->a, N_BIN, pp);
n->op = op;
n->lhs = lhs;
n->rhs = rhs;
lhs = n;
}
}
static Node *
parseexpr_noassign(Parser *p)
{
return parsebin(p, parseunary(p), 1);
}
static Node *
parseexpr(Parser *p)
{
Node *e = parsebin(p, parseunary(p), 1);
if (isassignop(p->cur.kind)) {
Pos pp = p->cur.pos;
Tkind op = p->cur.kind;
advance(p);
Node *n = newnode(p->a, N_ASSIGN, pp);
n->op = op;
n->lhs = e;
n->rhs = parseexpr(p); /* right-assoc */
return n;
}
return e;
}
Node *
parseexpr_top(Parser *p)
{
return parseexpr(p);
}
/* ------- statements ------------------------------------------------- */
static Node *
parselet(Parser *p, int top)
{
Pos pp = p->cur.pos;
int is_const = 0;
if (p->cur.kind == TK_CONST) {
is_const = 1;
advance(p);
} else {
expect(p, TK_LET);
}
/* Hare-style tuple destructure: `let (a, b) = expr;` */
if (p->cur.kind == TK_LPAREN) {
advance(p);
Node *m = newnode(p->a, N_MLET, pp);
Node *head = NULL, *tail = NULL;
for (;;) {
Pos lpp = p->cur.pos;
Node *l = newnode(p->a, N_LET, lpp);
l->str = expectbindname(p);
if (accept(p, TK_COLON)) l->lhs = parsetype(p);
if (head == NULL) head = l;
else tail->next = l;
tail = l;
if (!accept(p, TK_COMMA)) break;
}
expect(p, TK_RPAREN);
expect(p, TK_ASSIGN);
m->rhs = parseexpr(p);
expect(p, TK_SEMI);
m->list = head;
if (is_const) {
m->op = TK_CONST;
for (Node *l = head; l; l = l->next) l->op = TK_CONST;
}
(void)top;
return m;
}
/* parse first binding */
Pos lp = p->cur.pos;
Node *first = newnode(p->a, N_LET, lp);
first->str = expectbindname(p);
if (accept(p, TK_COLON))
first->lhs = parsetype(p);
if (p->cur.kind == TK_COMMA) {
/* multi-let: collect (name, type) pairs, then '=' rhs */
Node *m = newnode(p->a, N_MLET, pp);
Node *head = first, *tail = first;
while (accept(p, TK_COMMA)) {
Pos lpp = p->cur.pos;
Node *l = newnode(p->a, N_LET, lpp);
l->str = expectbindname(p);
if (accept(p, TK_COLON))
l->lhs = parsetype(p);
tail->next = l;
tail = l;
}
expect(p, TK_ASSIGN);
m->rhs = parseexpr(p);
expect(p, TK_SEMI);
m->list = head;
if (is_const) {
m->op = TK_CONST;
for (Node *l = head; l; l = l->next) l->op = TK_CONST;
}
(void)top;
return m;
}
if (accept(p, TK_ASSIGN))
first->rhs = parseexpr(p);
expect(p, TK_SEMI);
if (is_const) first->op = TK_CONST;
(void)top;
return first;
}
static Node *
parseif(Parser *p)
{
Pos pp = p->cur.pos;
expect(p, TK_IF);
expect(p, TK_LPAREN);
Node *n = newnode(p->a, N_IF, pp);
n->cond = parseexpr(p);
expect(p, TK_RPAREN);
n->body = parseblock(p);
if (accept(p, TK_ELSE)) {
if (p->cur.kind == TK_IF)
n->els = parseif(p);
else
n->els = parseblock(p);
}
return n;
}
static Node *
parse_for_else(Parser *p, Node *n)
{
/* `for (cond) { body } else { else_body }` — the else block runs
* when the loop exits normally (cond → false) and is skipped by
* `break`. Hare-style "did the loop find anything" idiom. */
if (accept(p, TK_ELSE))
n->els = parseblock(p);
return n;
}
static Node *
parsefor(Parser *p)
{
Pos pp = p->cur.pos;
expect(p, TK_FOR);
Node *n = newnode(p->a, N_FOR, pp);
if (p->cur.kind == TK_LBRACE) {
n->body = parseblock(p);
return parse_for_else(p, n);
}
expect(p, TK_LPAREN);
if (p->cur.kind == TK_RPAREN) {
advance(p);
n->body = parseblock(p);
return n;
}
/* First clause may be 'let' init or expr. If 'let' or terminated
* by ';', it's a C-for. Otherwise it's just `for (cond)`. */
if (p->cur.kind == TK_LET) {
/* Disambiguate range form: `let IDENT .. expr`. We peek
* for IDENT then DOTDOT before committing to parselet.
* Tuple destructure: `let (a, b) .. expr`. */
Tok save_cur = p->cur;
(void)save_cur;
advance(p); /* consume LET */
if (p->cur.kind == TK_LPAREN) {
advance(p);
Node *names = NULL, *tail = NULL;
for (;;) {
Pos np = p->cur.pos;
Node *e = newnode(p->a, N_IDENT, np);
e->str = expectbindname(p);
if (names == NULL) names = e;
else tail->next = e;
tail = e;
if (!accept(p, TK_COMMA)) break;
}
expect(p, TK_RPAREN);
expect(p, TK_DOTDOT);
Node *rng = newnode(p->a, N_FORRANGE, pp);
rng->str = NULL;
rng->list = names;
rng->lhs = parseexpr(p);
expect(p, TK_RPAREN);
rng->body = parseblock(p);
return parse_for_else(p, rng);
}
if (p->cur.kind == TK_IDENT || p->cur.kind == TK_UNDER) {
Pos ip = p->cur.pos;
const char *nm = p->cur.text;
int isunder = p->cur.kind == TK_UNDER;
Tok la = peek(p);
if (la.kind == TK_DOTDOT) {
advance(p); /* consume IDENT/UNDER */
advance(p); /* consume DOTDOT */
Node *rng = newnode(p->a, N_FORRANGE, pp);
rng->str = isunder ? "" : nm;
rng->lhs = parseexpr(p);
expect(p, TK_RPAREN);
rng->body = parseblock(p);
(void)ip;
return parse_for_else(p, rng);
}
}
/* Not a range. Build a synthetic LET stmt manually
* since we already consumed `let`. */
Pos lp = p->cur.pos;
Node *first = newnode(p->a, N_LET, lp);
first->str = expectbindname(p);
if (accept(p, TK_COLON))
first->lhs = parsetype(p);
if (accept(p, TK_ASSIGN))
first->rhs = parseexpr(p);
expect(p, TK_SEMI);
n->lhs = first;
if (p->cur.kind != TK_SEMI && p->cur.kind != TK_RPAREN)
n->cond = parseexpr(p);
if (accept(p, TK_SEMI))
n->rhs = parseexpr(p);
} else {
n->cond = parseexpr(p);
if (accept(p, TK_SEMI)) {
/* second clause: just consumed ';' but actually
* we need cond/init pattern. Simplify: only support
* `for (cond)` and `for (let init; cond; post)`.
* Treat the first form as having no init.
*/
}
}
expect(p, TK_RPAREN);
n->body = parseblock(p);
return parse_for_else(p, n);
}
static Node *
parseswitch(Parser *p)
{
Pos pp = p->cur.pos;
expect(p, TK_SWITCH);
expect(p, TK_LPAREN);
Node *n = newnode(p->a, N_SWITCH, pp);
n->lhs = parseexpr(p);
expect(p, TK_RPAREN);
expect(p, TK_LBRACE);
Node *head = NULL, *tail = NULL;
while (p->cur.kind == TK_CASE) {
Pos cp = p->cur.pos;
advance(p);
Node *c = newnode(p->a, N_CASE, cp);
Node *eh = NULL, *et = NULL;
if (p->cur.kind != TK_COLON) {
p->nocast = 1;
for (;;) {
Node *e = parseexpr(p);
if (eh == NULL) eh = e;
else et->next = e;
et = e;
if (!accept(p, TK_COMMA))
break;
}
p->nocast = 0;
}
c->list = eh;
expect(p, TK_COLON);
Node *bh = NULL, *bt = NULL;
while (p->cur.kind != TK_CASE && p->cur.kind != TK_RBRACE
&& p->cur.kind != TK_EOF) {
Node *s = parsestmt(p);
if (bh == NULL) bh = s;
else bt->next = s;
bt = s;
}
Node *blk = newnode(p->a, N_BLOCK, cp);
blk->list = bh;
c->body = blk;
if (head == NULL) head = c;
else tail->next = c;
tail = c;
}
expect(p, TK_RBRACE);
n->list = head;
return n;
}
static Node *
parsestmt(Parser *p)
{
/* Hare's `static` qualifies a statement to mean "the storage
* grown by this statement comes from the function's stack
* frame, not the heap". We don't optimise around it yet, so
* parse-and-ignore: the inner statement is unchanged. */
if (p->cur.kind == TK_STATIC) advance(p);
Pos pp = p->cur.pos;
switch (p->cur.kind) {
case TK_LBRACE: {
Node *b = parseblock(p);
expect(p, TK_SEMI);
return b;
}
case TK_LET:
case TK_CONST: return parselet(p, 0);
case TK_IF: {
Node *n = parseif(p);
expect(p, TK_SEMI);
return n;
}
case TK_FOR: {
Node *n = parsefor(p);
expect(p, TK_SEMI);
return n;
}
case TK_SWITCH: {
Node *n = parseswitch(p);
expect(p, TK_SEMI);
return n;
}
case TK_RETURN: {
advance(p);
Node *n = newnode(p->a, N_RETURN, pp);
if (p->cur.kind != TK_SEMI) {
Node *first = parseexpr(p);
if (p->cur.kind == TK_COMMA) {
Node *t = newnode(p->a, N_TUPLE, first->pos);
t->list = first;
Node *tail = first;
while (accept(p, TK_COMMA)) {
Node *e = parseexpr(p);
tail->next = e;
tail = e;
}
n->lhs = t;
} else {
n->lhs = first;
}
}
expect(p, TK_SEMI);
return n;
}
case TK_DEFER: {
advance(p);
Node *n = newnode(p->a, N_DEFER, pp);
n->lhs = parseexpr(p);
expect(p, TK_SEMI);
return n;
}
case TK_YIELD: {
advance(p);
Node *n = newnode(p->a, N_YIELD, pp);
n->lhs = parseexpr(p);
expect(p, TK_SEMI);
return n;
}
case TK_BREAK: {
advance(p);
Node *n = newnode(p->a, N_BREAK, pp);
expect(p, TK_SEMI);
return n;
}
case TK_CONTINUE: {
advance(p);
Node *n = newnode(p->a, N_CONTINUE, pp);
expect(p, TK_SEMI);
return n;
}
default: {
Node *e = parseexpr(p);
if (p->cur.kind == TK_COMMA) {
/* multi-assign: a, b, ... = expr;
* lvalues must not consume the trailing '=' as an
* assignment-chain (parseexpr would). */
Node *m = newnode(p->a, N_MASSIGN, pp);
Node *head = e, *tail = e;
while (accept(p, TK_COMMA)) {
Node *lv = parseexpr_noassign(p);
tail->next = lv;
tail = lv;
}
expect(p, TK_ASSIGN);
m->rhs = parseexpr(p);
m->list = head;
expect(p, TK_SEMI);
return m;
}
Node *n = newnode(p->a, N_EXPRSTMT, pp);
n->lhs = e;
expect(p, TK_SEMI);
return n;
}
}
}
static Node *
parseblock(Parser *p)
{
Pos pp = p->cur.pos;
expect(p, TK_LBRACE);
Node *n = newnode(p->a, N_BLOCK, pp);
Node *head = NULL, *tail = NULL;
while (p->cur.kind != TK_RBRACE && p->cur.kind != TK_EOF) {
Node *s = parsestmt(p);
if (head == NULL) head = s;
else tail->next = s;
tail = s;
}
expect(p, TK_RBRACE);
n->list = head;
return n;
}
/* ------- top-level decls ------------------------------------------- */
/* `import encoding.utf8;` — the driver resolves the dotted path to a
* directory; the checker only needs the leaf (`utf8`) as the module
* bareword for n_use→decl disambiguation, mirroring Hare's
* `use encoding::utf8;` → `utf8::name` (ref/hare/hare/ast/import.ha:7
* stores `ident: []str` but identifier-resolution uses the last
* component). */
static Node *
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);
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;
}
static Node *
parsedef(Parser *p, int exp)
{
Pos pp = p->cur.pos;
expect(p, TK_DEF);
Node *n = newnode(p->a, N_DEF, pp);
n->str = expectident(p);
expect(p, TK_COLON);
n->lhs = parsetype(p);
/* A value-LESS `def X: T;` is an interface prototype (an aggregate-
* init `.wwi` def whose DATA lives in the defining package — BUG-2 /
* task #71), mirroring parsefn's bodyless-prototype arm. rhs stays
* NULL; the checker's fold pass (`if (d->rhs)`) and cgen's emit_defs/
* emit_lets (`d->rhs == NULL` continue) already expect this shape. */
if (accept(p, TK_ASSIGN))
n->rhs = parseexpr(p);
expect(p, TK_SEMI);
n->export = exp;
return n;
}
static Node *
parsetypedecl(Parser *p, int exp)
{
Pos pp = p->cur.pos;
expect(p, TK_TYPE);
Node *n = newnode(p->a, N_TYPEDECL, pp);
n->str = expectident(p);
expect(p, TK_ASSIGN);
n->lhs = parsetype(p);
expect(p, TK_SEMI);
n->export = exp;
return n;
}
static Node *
parseattrs(Parser *p)
{
Node *head = NULL, *tail = NULL;
while (p->cur.kind == TK_AT) {
Pos pp = p->cur.pos;
advance(p);
Node *a = newnode(p->a, N_ATTR, pp);
a->str = expectident(p);
/* `@name(args...)` for FFI-style attributes;
* `@name` (no parens) for marker-only attributes like
* `@test`. */
if (accept(p, TK_LPAREN)) {
a->list = parsearglist(p, TK_RPAREN);
expect(p, TK_RPAREN);
}
if (head == NULL) head = a;
else tail->next = a;
tail = a;
}
return head;
}
static Node *
parsefn(Parser *p, int exp, Node *attrs)
{
Pos pp = p->cur.pos;
expect(p, TK_FN);
Node *n = newnode(p->a, N_FNDECL, pp);
n->str = expectident(p);
expect(p, TK_LPAREN);
n->list = parseparams(p);
expect(p, TK_RPAREN);
/* return type is required: void if absent only via explicit name */
if (p->cur.kind != TK_ASSIGN && p->cur.kind != TK_SEMI)
n->lhs = parsetype(p);
if (accept(p, TK_ASSIGN)) {
n->body = parseblock(p);
expect(p, TK_SEMI);
} else {
expect(p, TK_SEMI);
}
n->export = exp;
n->attr = attrs;
return n;
}
Node *
parsefile(Parser *p)
{
Pos pp = { p->l->file, 1, 1 };
Node *file = newnode(p->a, N_FILE, pp);
Node *head = NULL, *tail = NULL;
while (p->cur.kind != TK_EOF) {
/* `package foo;` — directory-as-module declaration. Each
* .ww file's section in a concatenated stream begins with
* one; a single-file or fragment input may omit it (curmod
* stays NULL and decls are treated as primary).
*
* Retained divergence from brief: the strict missing-`package`
* error was softened to silent-default to keep 63 inline-source
* test wrappers (200_parse, 100_lex, 300_check, ...) parsing.
* See task #23 for the wrapper migration that unblocks the
* strict check. Rule 7 + rule 8 documentation. */
if (p->cur.kind == TK_MODULE) {
advance(p);
const char *name = expectident(p);
expect(p, TK_SEMI);
if (p->pathmod != NULL || p->resetmod != 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.
* #57 extends this to the sep primary-reset path
* (resetmod): the dotted reset path is the
* authoritative identity, the clause asserts. */
const char *active =
p->pathmod ? p->pathmod : p->resetmod;
const char *dot = strrchr(active, '.');
const char *last = dot ? dot + 1 : active;
if (strcmp(name, last) != 0) {
errorf(p->cur.pos,
"package %s does not match import path %s",
name, active);
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;
p->resetmod = NULL;
advance(p);
continue;
}
/* `//ww:module-reset` — bundle boundary before a package-less
* file. Reset curmod to NULL so the file's decls (and its own
* `import os;`) are attributed to the primary module (""), not
* the preceding bundled package. Codegen-neutral: NULL 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->cur.kind == TK_MODRESET) {
/* #57: a path-carrying reset (sep primary body) mangles
* decls on the dotted path so definer == importer, but
* leaves imported==0 (curmod set, pathmod NULL) 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
* NULL → bare symbols, today's behavior. */
const char *rp = p->cur.text;
advance(p);
p->pathmod = NULL;
if (rp != NULL) {
p->curmod = rp;
p->resetmod = rp;
} else {
p->curmod = NULL;
p->resetmod = NULL;
}
continue;
}
Node *attrs = parseattrs(p);
int exp = accept(p, TK_EXPORT);
Node *d = NULL;
switch (p->cur.kind) {
case TK_USE:
if (attrs || exp) {
errorf(p->cur.pos, "import cannot be exported or attributed");
p->errs++;
}
d = parseuse(p);
break;
case TK_DEF: d = parsedef(p, exp); break;
case TK_TYPE: d = parsetypedecl(p, exp); break;
case TK_FN: d = parsefn(p, exp, attrs); break;
case TK_LET:
case TK_CONST: d = parselet(p, 1); d->export = exp; break;
default:
errorf(p->cur.pos, "expected top-level decl, got %s",
tokname(p->cur.kind));
p->errs++;
advance(p);
continue;
}
if (d != NULL) {
d->module = p->curmod;
d->imported = (p->pathmod != NULL);
}
if (head == NULL) head = d;
else tail->next = d;
tail = d;
}
file->list = head;
return file;
}