ww: hare-feature batch (_, const, [_]T, ..., size/offset, assert, for-else)

This commit is contained in:
2026-05-11 19:38:12 +09:00
parent 579cc39f9b
commit 6219a47c6f
25 changed files with 1496 additions and 1049 deletions

View File

@@ -81,6 +81,19 @@ expectident(Parser *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);
@@ -108,11 +121,13 @@ parseparams(Parser *p)
break;
}
Node *n = newnode(p->a, N_PARAM, pp);
/* IDENT ':' type OR type-only (fn-type params).
* Disambiguate: if current is IDENT and next is ':' it's named.
* Otherwise treat as anonymous (type-only). */
if (p->cur.kind == TK_IDENT && peek(p).kind == TK_COLON) {
n->str = expectident(p);
/* 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 {
@@ -150,7 +165,11 @@ parsetype(Parser *p)
return n;
}
Node *n = newnode(p->a, N_TARRAY, pp);
n->rhs = parseexpr(p);
/* `[_]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;
@@ -332,6 +351,14 @@ parsestructlit(Parser *p, Node *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);
@@ -416,6 +443,16 @@ parseprimary(Parser *p)
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_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);
@@ -535,7 +572,16 @@ parsepostfix(Parser *p, Node *lhs)
advance(p);
Node *n = newnode(p->a, N_CALL, pp);
n->lhs = lhs;
n->list = parsearglist(p, TK_RPAREN);
/* 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;
@@ -715,7 +761,13 @@ static Node *
parselet(Parser *p, int top)
{
Pos pp = p->cur.pos;
expect(p, TK_LET);
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) {
@@ -725,7 +777,7 @@ parselet(Parser *p, int top)
for (;;) {
Pos lpp = p->cur.pos;
Node *l = newnode(p->a, N_LET, lpp);
l->str = expectident(p);
l->str = expectbindname(p);
if (accept(p, TK_COLON)) l->lhs = parsetype(p);
if (head == NULL) head = l;
else tail->next = l;
@@ -737,6 +789,10 @@ parselet(Parser *p, int top)
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;
}
@@ -744,7 +800,7 @@ parselet(Parser *p, int top)
/* parse first binding */
Pos lp = p->cur.pos;
Node *first = newnode(p->a, N_LET, lp);
first->str = expectident(p);
first->str = expectbindname(p);
if (accept(p, TK_COLON))
first->lhs = parsetype(p);
@@ -755,7 +811,7 @@ parselet(Parser *p, int top)
while (accept(p, TK_COMMA)) {
Pos lpp = p->cur.pos;
Node *l = newnode(p->a, N_LET, lpp);
l->str = expectident(p);
l->str = expectbindname(p);
if (accept(p, TK_COLON))
l->lhs = parsetype(p);
tail->next = l;
@@ -765,6 +821,10 @@ parselet(Parser *p, int top)
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;
}
@@ -772,6 +832,7 @@ parselet(Parser *p, int top)
if (accept(p, TK_ASSIGN))
first->rhs = parseexpr(p);
expect(p, TK_SEMI);
if (is_const) first->op = TK_CONST;
(void)top;
return first;
}
@@ -795,6 +856,17 @@ parseif(Parser *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)
{
@@ -803,7 +875,7 @@ parsefor(Parser *p)
Node *n = newnode(p->a, N_FOR, pp);
if (p->cur.kind == TK_LBRACE) {
n->body = parseblock(p);
return n;
return parse_for_else(p, n);
}
expect(p, TK_LPAREN);
if (p->cur.kind == TK_RPAREN) {
@@ -826,7 +898,7 @@ parsefor(Parser *p)
for (;;) {
Pos np = p->cur.pos;
Node *e = newnode(p->a, N_IDENT, np);
e->str = expectident(p);
e->str = expectbindname(p);
if (names == NULL) names = e;
else tail->next = e;
tail = e;
@@ -840,29 +912,30 @@ parsefor(Parser *p)
rng->lhs = parseexpr(p);
expect(p, TK_RPAREN);
rng->body = parseblock(p);
return rng;
return parse_for_else(p, rng);
}
if (p->cur.kind == TK_IDENT) {
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 */
advance(p); /* consume IDENT/UNDER */
advance(p); /* consume DOTDOT */
Node *rng = newnode(p->a, N_FORRANGE, pp);
rng->str = nm;
rng->str = isunder ? "" : nm;
rng->lhs = parseexpr(p);
expect(p, TK_RPAREN);
rng->body = parseblock(p);
(void)ip;
return rng;
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 = expectident(p);
first->str = expectbindname(p);
if (accept(p, TK_COLON))
first->lhs = parsetype(p);
if (accept(p, TK_ASSIGN))
@@ -885,7 +958,7 @@ parsefor(Parser *p)
}
expect(p, TK_RPAREN);
n->body = parseblock(p);
return n;
return parse_for_else(p, n);
}
static Node *
@@ -953,7 +1026,8 @@ parsestmt(Parser *p)
expect(p, TK_SEMI);
return b;
}
case TK_LET: return parselet(p, 0);
case TK_LET:
case TK_CONST: return parselet(p, 0);
case TK_IF: {
Node *n = parseif(p);
expect(p, TK_SEMI);
@@ -1166,7 +1240,8 @@ parsefile(Parser *p)
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: d = parselet(p, 1); d->export = exp; 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));