1886 lines
46 KiB
C
1886 lines
46 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 int
|
|
advanceheaderimport(Parser *p)
|
|
{
|
|
Tok next;
|
|
if (!lexheaderimport(p->l, &next))
|
|
return 0;
|
|
p->cur = next;
|
|
p->hasla = 0;
|
|
return 1;
|
|
}
|
|
|
|
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);
|
|
|
|
static void
|
|
skipdecl(Parser *p)
|
|
{
|
|
int paren = 0, bracket = 0, brace = 0;
|
|
while (p->cur.kind != TK_EOF) {
|
|
switch (p->cur.kind) {
|
|
case TK_LPAREN: paren++; break;
|
|
case TK_RPAREN: if (paren > 0) paren--; break;
|
|
case TK_LBRACK: bracket++; break;
|
|
case TK_RBRACK: if (bracket > 0) bracket--; break;
|
|
case TK_LBRACE: brace++; break;
|
|
case TK_RBRACE: if (brace > 0) brace--; break;
|
|
case TK_SEMI:
|
|
advance(p);
|
|
if (paren == 0 && bracket == 0 && brace == 0)
|
|
return;
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
advance(p);
|
|
}
|
|
}
|
|
|
|
/* Consume attribute syntax without parsing its expressions or any following
|
|
* declaration. The imports-only pass needs only to recognize that the next
|
|
* declaration is an attributed import, which full parsing rejects. */
|
|
static void
|
|
skipimportattrs(Parser *p)
|
|
{
|
|
while (p->cur.kind == TK_AT) {
|
|
advance(p);
|
|
if (p->cur.kind == TK_IDENT)
|
|
advance(p);
|
|
else {
|
|
errorf(p->cur.pos, "expected identifier, got %s",
|
|
tokname(p->cur.kind));
|
|
p->errs++;
|
|
}
|
|
if (!accept(p, TK_LPAREN)) continue;
|
|
int depth = 1;
|
|
while (p->cur.kind != TK_EOF && depth > 0) {
|
|
if (p->cur.kind == TK_LPAREN) depth++;
|
|
else if (p->cur.kind == TK_RPAREN) depth--;
|
|
advance(p);
|
|
}
|
|
if (depth > 0) {
|
|
errorf(p->cur.pos, "expected ')' after attribute");
|
|
p->errs++;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
Node *n = newnode(p->a, N_TSTRUCT, pp);
|
|
/* `@packed` is an inline struct TYPE attribute (harec
|
|
* ast.h:95 `bool packed`), sitting after `struct` and
|
|
* before `{` — NOT a fn-decl attr, so it does not route
|
|
* through parseattrs. */
|
|
if (p->cur.kind == TK_AT) {
|
|
advance(p);
|
|
const char *an = expectident(p);
|
|
/* errorf only reports; p->errs gates the build
|
|
* (cmd/w6c/main.c:82). */
|
|
if (strcmp(an, "packed") != 0) {
|
|
errorf(p->cur.pos,
|
|
"unknown struct attribute '@%s'", an);
|
|
p->errs++;
|
|
} else
|
|
n->packed = 1;
|
|
}
|
|
expect(p, TK_LBRACE);
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
while (p->cur.kind == TK_DOT && peek(p).kind == TK_IDENT) {
|
|
advance(p);
|
|
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;
|
|
}
|
|
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);
|
|
}
|
|
|
|
static Node *
|
|
parselet(Parser *p)
|
|
{
|
|
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;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
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) {
|
|
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;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
if (accept(p, TK_ASSIGN))
|
|
first->rhs = parseexpr(p);
|
|
expect(p, TK_SEMI);
|
|
if (is_const) first->op = TK_CONST;
|
|
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) {
|
|
/* Range and three-clause forms share the `let` prefix. */
|
|
advance(p);
|
|
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) {
|
|
const char *nm = p->cur.text;
|
|
int isunder = p->cur.kind == TK_UNDER;
|
|
Tok la = peek(p);
|
|
if (la.kind == TK_DOTDOT) {
|
|
advance(p);
|
|
advance(p);
|
|
Node *rng = newnode(p->a, N_FORRANGE, pp);
|
|
rng->str = isunder ? "" : nm;
|
|
rng->lhs = parseexpr(p);
|
|
expect(p, TK_RPAREN);
|
|
rng->body = parseblock(p);
|
|
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);
|
|
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;
|
|
}
|
|
|
|
/* The optional alias is source-local; the dotted path remains the dependency
|
|
* identity supplied to the package driver. */
|
|
static Node *
|
|
parseuse(Parser *p)
|
|
{
|
|
Pos pp = p->cur.pos;
|
|
expect(p, TK_USE);
|
|
Node *n = newnode(p->a, N_USE, pp);
|
|
/* Keep n->pos at the import keyword for structural diagnostics. Go's
|
|
* import declaration position is the first spec token: the explicit alias
|
|
* when present, otherwise the path. */
|
|
n->usefile = p->cur.pos.file;
|
|
n->useline = p->cur.pos.line;
|
|
n->usecol = p->cur.pos.col;
|
|
const char *alias = NULL;
|
|
const char *first;
|
|
if (p->cur.kind == TK_UNDER) {
|
|
n->useblank = 1;
|
|
advance(p);
|
|
first = expectident(p);
|
|
} else {
|
|
first = expectident(p);
|
|
if (p->cur.kind == TK_IDENT) {
|
|
alias = first;
|
|
first = expectident(p);
|
|
}
|
|
}
|
|
const char *leaf = first;
|
|
const char *path = leaf;
|
|
while (accept(p, TK_DOT)) {
|
|
leaf = expectident(p);
|
|
path = aprintf(p->a, "%s.%s", path, leaf);
|
|
}
|
|
if (!n->useblank) {
|
|
n->str = alias ? alias : leaf;
|
|
n->strlen = strlen(n->str);
|
|
}
|
|
n->usesource = path;
|
|
n->usepath = path;
|
|
n->usealias = alias;
|
|
expect(p, TK_SEMI);
|
|
return n;
|
|
}
|
|
|
|
/* Parse one initial import without pulling the declaration that follows it
|
|
* into the header pass. This intentionally has stricter, single-error
|
|
* recovery than parseuse: named test-source build loading needs only decide
|
|
* whether the package/import header itself is valid. */
|
|
static Node *
|
|
parseheaderuse(Parser *p)
|
|
{
|
|
Pos pp = p->cur.pos;
|
|
advance(p);
|
|
Node *n = newnode(p->a, N_USE, pp);
|
|
n->usefile = p->cur.pos.file;
|
|
n->useline = p->cur.pos.line;
|
|
n->usecol = p->cur.pos.col;
|
|
const char *alias = NULL;
|
|
const char *first;
|
|
if (p->cur.kind == TK_UNDER) {
|
|
n->useblank = 1;
|
|
advance(p);
|
|
} else if (p->cur.kind != TK_IDENT) {
|
|
errorf(p->cur.pos, "expected identifier, got %s",
|
|
tokname(p->cur.kind));
|
|
p->errs++;
|
|
return n;
|
|
}
|
|
if (p->cur.kind != TK_IDENT) {
|
|
errorf(p->cur.pos, "expected identifier, got %s",
|
|
tokname(p->cur.kind));
|
|
p->errs++;
|
|
return n;
|
|
}
|
|
first = p->cur.text;
|
|
advance(p);
|
|
if (!n->useblank && p->cur.kind == TK_IDENT) {
|
|
alias = first;
|
|
first = p->cur.text;
|
|
advance(p);
|
|
}
|
|
const char *leaf = first;
|
|
const char *path = leaf;
|
|
while (p->cur.kind == TK_DOT) {
|
|
advance(p);
|
|
if (p->cur.kind != TK_IDENT) {
|
|
errorf(p->cur.pos, "expected identifier, got %s",
|
|
tokname(p->cur.kind));
|
|
p->errs++;
|
|
return n;
|
|
}
|
|
leaf = p->cur.text;
|
|
path = aprintf(p->a, "%s.%s", path, leaf);
|
|
advance(p);
|
|
}
|
|
if (!n->useblank) {
|
|
n->str = alias ? alias : leaf;
|
|
n->strlen = strlen(n->str);
|
|
}
|
|
n->usesource = path;
|
|
n->usepath = path;
|
|
n->usealias = alias;
|
|
if (p->cur.kind != TK_SEMI) {
|
|
errorf(p->cur.pos, "expected ';' after import");
|
|
p->errs++;
|
|
return n;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/* Go's named-file loader parses a valid source only through its initial
|
|
* package/import section. Keep the broader parseimports recovery pass for
|
|
* graph-bearing sources, but give actionless test-source omission a boundary
|
|
* that cannot diagnose late imports or an invalid ordinary declaration body. */
|
|
Node *
|
|
parsepackageheader(Parser *p)
|
|
{
|
|
Pos fp = { p->l->file, 1, 1 };
|
|
Node *file = newnode(p->a, N_FILE, fp);
|
|
Node *head = NULL, *tail = NULL;
|
|
|
|
while (p->cur.kind == TK_MODPATH || p->cur.kind == TK_MODRESET) {
|
|
if (p->cur.kind == TK_MODPATH) {
|
|
p->pathmod = p->cur.text;
|
|
p->curmod = p->cur.text;
|
|
p->resetmod = NULL;
|
|
} else {
|
|
p->pathmod = NULL;
|
|
p->curmod = p->cur.text;
|
|
p->resetmod = p->cur.text;
|
|
}
|
|
p->sourceid++;
|
|
advance(p);
|
|
}
|
|
if (p->cur.kind != TK_MODULE) {
|
|
errorf(p->cur.pos, "invalid or missing package clause");
|
|
p->errs++;
|
|
return file;
|
|
}
|
|
Pos pp = p->cur.pos;
|
|
advance(p);
|
|
if (p->cur.kind != TK_IDENT) {
|
|
errorf(p->cur.pos, "invalid or missing package clause");
|
|
p->errs++;
|
|
return file;
|
|
}
|
|
const char *name = p->cur.text;
|
|
advance(p);
|
|
if (p->cur.kind != TK_SEMI) {
|
|
errorf(p->cur.pos, "expected ';' after package name");
|
|
p->errs++;
|
|
return file;
|
|
}
|
|
p->curpkg = name;
|
|
if (p->pathmod == NULL && p->resetmod == NULL)
|
|
p->curmod = name;
|
|
file->module = name;
|
|
file->pkgname = name;
|
|
file->sourceid = p->sourceid;
|
|
file->pos = pp;
|
|
|
|
while (advanceheaderimport(p)) {
|
|
Node *d = parseheaderuse(p);
|
|
d->module = p->curmod;
|
|
d->pkgname = p->curpkg;
|
|
d->sourceid = p->sourceid;
|
|
if (head == NULL)
|
|
head = d;
|
|
else
|
|
tail->next = d;
|
|
tail = d;
|
|
if (p->errs != 0)
|
|
break;
|
|
}
|
|
file->list = head;
|
|
return file;
|
|
}
|
|
|
|
Node *
|
|
parseimports(Parser *p)
|
|
{
|
|
Pos fp = { p->l->file, 1, 1 };
|
|
Node *file = newnode(p->a, N_FILE, fp);
|
|
Node *head = NULL, *tail = NULL;
|
|
Node *packages = NULL, *packagetail = NULL;
|
|
int sawpackage = 0;
|
|
/* Go source files admit one import section before ordinary declarations.
|
|
* Keep recovery permissive, but diagnose the first import in each later
|
|
* section. Compiler-owned bundle markers begin a fresh source section. */
|
|
int previmport = 1;
|
|
|
|
while (p->cur.kind != 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->cur.kind == TK_MODPATH) {
|
|
sawpackage = 0;
|
|
previmport = 1;
|
|
p->sourceid++;
|
|
p->pathmod = p->cur.text;
|
|
p->curmod = p->cur.text;
|
|
p->resetmod = NULL;
|
|
p->curpkg = NULL;
|
|
advance(p);
|
|
continue;
|
|
}
|
|
if (p->cur.kind == TK_MODRESET) {
|
|
const char *rp = p->cur.text;
|
|
sawpackage = 0;
|
|
previmport = 1;
|
|
p->sourceid++;
|
|
advance(p);
|
|
p->pathmod = NULL;
|
|
p->curmod = rp;
|
|
p->resetmod = rp;
|
|
p->curpkg = NULL;
|
|
continue;
|
|
}
|
|
if (p->cur.kind == TK_MODULE) {
|
|
Pos pp = p->cur.pos;
|
|
previmport = 1;
|
|
advance(p);
|
|
if (p->cur.kind != TK_IDENT) {
|
|
errorf(p->cur.pos, "invalid or missing package clause");
|
|
p->errs++;
|
|
sawpackage = 1;
|
|
skipdecl(p);
|
|
continue;
|
|
}
|
|
const char *name = expectident(p);
|
|
expect(p, TK_SEMI);
|
|
p->curpkg = name;
|
|
if (p->pathmod == NULL && p->resetmod == NULL)
|
|
p->curmod = name;
|
|
Node *package = newnode(p->a, N_FILE, pp);
|
|
package->module = p->curmod;
|
|
package->pkgname = name;
|
|
package->sourceid = p->sourceid;
|
|
if (packages == NULL)
|
|
packages = package;
|
|
else
|
|
packagetail->next = package;
|
|
packagetail = package;
|
|
if (!sawpackage) {
|
|
file->module = name;
|
|
file->pkgname = name;
|
|
file->sourceid = p->sourceid;
|
|
file->pos = pp;
|
|
sawpackage = 1;
|
|
}
|
|
continue;
|
|
}
|
|
if (!sawpackage && p->pathmod == NULL && p->resetmod == NULL) {
|
|
errorf(p->cur.pos, "invalid or missing package clause");
|
|
p->errs++;
|
|
sawpackage = 1;
|
|
}
|
|
if (p->cur.kind == TK_USE) {
|
|
if (!previmport) {
|
|
errorf(p->cur.pos,
|
|
"imports must appear before other declarations");
|
|
p->errs++;
|
|
}
|
|
previmport = 1;
|
|
Node *d = parseuse(p);
|
|
d->module = p->curmod;
|
|
d->pkgname = p->curpkg;
|
|
d->sourceid = p->sourceid;
|
|
if (head == NULL)
|
|
head = d;
|
|
else
|
|
tail->next = d;
|
|
tail = d;
|
|
continue;
|
|
}
|
|
previmport = 0;
|
|
if (p->cur.kind == TK_AT) {
|
|
skipimportattrs(p);
|
|
if (p->cur.kind == TK_EXPORT) advance(p);
|
|
if (p->cur.kind == TK_USE) {
|
|
errorf(p->cur.pos,
|
|
"import cannot be exported or attributed");
|
|
p->errs++;
|
|
Node *d = parseuse(p);
|
|
d->module = p->curmod;
|
|
d->pkgname = p->curpkg;
|
|
d->sourceid = p->sourceid;
|
|
if (head == NULL)
|
|
head = d;
|
|
else
|
|
tail->next = d;
|
|
tail = d;
|
|
continue;
|
|
}
|
|
skipdecl(p);
|
|
continue;
|
|
}
|
|
if (p->cur.kind == TK_EXPORT && peek(p).kind == TK_USE) {
|
|
advance(p);
|
|
errorf(p->cur.pos,
|
|
"import cannot be exported or attributed");
|
|
p->errs++;
|
|
Node *d = parseuse(p);
|
|
d->module = p->curmod;
|
|
d->pkgname = p->curpkg;
|
|
d->sourceid = p->sourceid;
|
|
if (head == NULL)
|
|
head = d;
|
|
else
|
|
tail->next = d;
|
|
tail = d;
|
|
continue;
|
|
}
|
|
skipdecl(p);
|
|
}
|
|
file->list = head;
|
|
/* The package-clause chain lets a directory loader validate every
|
|
* selected file without inventing a second header grammar. It lives in
|
|
* body because list is the public imports chain. */
|
|
file->body = packages;
|
|
return file;
|
|
}
|
|
|
|
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;
|
|
Node *packages = NULL, *packagetail = NULL;
|
|
int sawpackage = 0;
|
|
/* Semantic twin of parseimports: full/direct parsing reports the same
|
|
* per-source import-section ordering error while retaining the AST. */
|
|
int previmport = 1;
|
|
while (p->cur.kind != 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->cur.kind == TK_MODULE) {
|
|
Pos packagepos = p->cur.pos;
|
|
sawpackage = 1;
|
|
previmport = 1;
|
|
advance(p);
|
|
const char *name = expectident(p);
|
|
expect(p, TK_SEMI);
|
|
p->curpkg = name;
|
|
if (p->pathmod == NULL && p->resetmod == NULL) {
|
|
p->curmod = name;
|
|
}
|
|
Node *package = newnode(p->a, N_FILE, packagepos);
|
|
package->module = p->curmod;
|
|
package->pkgname = name;
|
|
package->sourceid = p->sourceid;
|
|
package->imported = p->pathmod != NULL;
|
|
if (packages == NULL) packages = package;
|
|
else packagetail->next = package;
|
|
packagetail = package;
|
|
if (file->pkgname == NULL) {
|
|
file->pkgname = name;
|
|
file->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->cur.kind == TK_MODPATH) {
|
|
sawpackage = 0;
|
|
previmport = 1;
|
|
p->sourceid++;
|
|
p->pathmod = p->cur.text;
|
|
p->curmod = p->cur.text;
|
|
p->resetmod = NULL;
|
|
p->curpkg = NULL;
|
|
if (file->module == NULL) file->module = p->cur.text;
|
|
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) {
|
|
sawpackage = 0;
|
|
previmport = 1;
|
|
p->sourceid++;
|
|
/* #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;
|
|
p->curpkg = NULL;
|
|
if (rp != NULL) {
|
|
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==NULL) is the
|
|
* root/package-less boundary and MUST keep the
|
|
* "main" default — so do NOT stamp there. */
|
|
if (file->module == NULL)
|
|
file->module = rp;
|
|
} else {
|
|
p->curmod = NULL;
|
|
p->resetmod = NULL;
|
|
}
|
|
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 == NULL && p->resetmod == NULL && !sawpackage) {
|
|
errorf(p->cur.pos, "missing package clause");
|
|
p->errs++;
|
|
sawpackage = 1;
|
|
}
|
|
int thisimport = p->cur.kind == TK_USE;
|
|
if (thisimport && !previmport) {
|
|
errorf(p->cur.pos,
|
|
"imports must appear before other declarations");
|
|
p->errs++;
|
|
}
|
|
previmport = thisimport;
|
|
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); 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->pkgname = p->curpkg;
|
|
d->sourceid = p->sourceid;
|
|
d->imported = (p->pathmod != NULL);
|
|
}
|
|
if (head == NULL) head = d;
|
|
else tail->next = d;
|
|
tail = d;
|
|
}
|
|
file->list = head;
|
|
file->body = packages;
|
|
return file;
|
|
}
|