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

6
cmd/w6c/CLAUDE.md Normal file
View File

@@ -0,0 +1,6 @@
w6c — amd64 compiler. Reads `.ww`, writes Plan 9 amd64 assembly (`.s`).
- Plan 9 C style: tabs, K&R, short names. Mirror `ref/plan9front/sys/src/cmd/8c/` and `ref/plan9front/sys/src/cmd/cc/`. Do not invent new data shapes — reuse `Prog`/`Adr`/`Node`/`Sym`/`Type` conventions.
- Frontend (lex, parse, check) lives in `../wcc/` and links as `libwcc.a`. This directory owns codegen only.
- `gc.h` is the per-target header (Plan 9 cc convention). `cgen.c` walks the AST into `Prog` list; `txt.c` prints it. `peep.c` is peephole. `reg.c` is the register allocator. `swt.c` is switch lowering.
- The text output must be readable by `w6a` (the amd64 assembler under `../w6a/`). When in doubt about syntax, run `w6a` on the output.

View File

@@ -856,6 +856,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
case N_ASSIGN: {
/* Discard lvalue `_ = expr;` — evaluate rhs for side effects,
* write nothing. */
if (n->lhs && n->lhs->kind == N_IDENT &&
n->lhs->str && n->lhs->str[0] == '\0' &&
n->op == TK_ASSIGN) {
cgexpr(c, n->rhs, locals);
break;
}
/* p.x = v or p.x += v where p.x is a struct field
* (direct or via *struct). For compound ops we read-modify-
* write the field; for plain `=` we just write. */
@@ -1234,6 +1242,49 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
case N_CALL: {
/* abort([msg]) — call rt_abort. Empty msg becomes (NULL, 0).
* Only fires when the checker tagged the callee as a builtin
* (lhs->type == ty_err); a user-declared `abort` in scope is
* resolved through the regular call path. */
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
n->lhs->type == ty_err &&
strcmp(n->lhs->str, "abort") == 0) {
if (n->list) {
cgexpr(c, n->list, locals);
ins2(c, A_MOVQ, areg(D_AX), areg(D_DI));
ins2(c, A_MOVQ, areg(D_BX), areg(D_SI));
} else {
ins2(c, A_MOVQ, aimm(0), areg(D_DI));
ins2(c, A_MOVQ, aimm(0), areg(D_SI));
}
ins1(c, A_CALL, asym("rt_abort"));
break;
}
/* assert(cond[, msg]) — if !cond, call rt_abort. Compiles to:
* CMPQ $0, AX
* JNE skip
* <abort body>
* skip: */
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
n->lhs->type == ty_err &&
strcmp(n->lhs->str, "assert") == 0 && n->list) {
cgexpr(c, n->list, locals);
char *skip = mklabel(c, "as");
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
ins1(c, A_JNE, abranch(skip));
Node *msg = n->list->next;
if (msg) {
cgexpr(c, msg, locals);
ins2(c, A_MOVQ, areg(D_AX), areg(D_DI));
ins2(c, A_MOVQ, areg(D_BX), areg(D_SI));
} else {
ins2(c, A_MOVQ, aimm(0), areg(D_DI));
ins2(c, A_MOVQ, aimm(0), areg(D_SI));
}
ins1(c, A_CALL, asym("rt_abort"));
label(c, skip);
break;
}
/* Hare-style builtins: len(x) and append(s, v). */
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
strcmp(n->lhs->str, "len") == 0 && n->list) {
@@ -2257,9 +2308,34 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 16));
break;
}
/* struct literal initialiser: field-by-field store. */
/* struct literal initialiser: field-by-field store. The
* literal carries op == TK_ELLIPSIS when the source ends in
* `..., ...` — in that case zero-fill the entire slot first,
* so unmentioned fields read as 0. */
if (n->rhs && n->rhs->kind == N_STRUCTLIT && lu
&& lu->kind == TY_STRUCT) {
if (n->rhs->op == TK_ELLIPSIS) {
u64 sz = lu->size;
/* AX = 0 once, then store from AX. w6a doesn't
* accept MOVB imm,mem — use register stores. */
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
u64 i = 0;
while (i + 8 <= sz) {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, off + (int)i));
i += 8;
}
while (i + 4 <= sz) {
ins2(c, A_MOVL, areg(D_AX),
amem(D_BP, off + (int)i));
i += 4;
}
while (i < sz) {
ins2(c, A_MOVB, areg(D_AX),
amem(D_BP, off + (int)i));
i += 1;
}
}
for (Node *f = n->rhs->list; f; f = f->next) {
/* find offset of this field */
u64 foff = 0;
@@ -2315,6 +2391,46 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
}
break;
}
/* array literal initialiser: `let xs: [N]T = [a, b, c];`.
* Walk elements in declaration order, store each at off + i*esz
* using the right width for the element type. The trailing
* `...` repeat marker (an N_FIELD with str=="...") fills the
* remaining slots with the last value. */
if (n->rhs && n->rhs->kind == N_ARRLIT && lu
&& lu->kind == TY_ARRAY) {
int esz = lu->sub ? (int)lu->sub->size : 1;
int op = A_MOVQ;
if (esz == 1) op = A_MOVB;
else if (esz == 4) op = A_MOVL;
/* esz == 2 (i16/u16) falls through to MOVQ — over-writes
* by 6B; the next element store rewrites the high half.
* For the last element this trails 6 bytes into the next
* stack slot. Add MOVW to w6a if real i16 arrays land. */
int idx = 0;
Node *last = NULL;
int repeat = 0;
for (Node *e = n->rhs->list; e; e = e->next) {
if (e->kind == N_FIELD && e->str &&
strcmp(e->str, "...") == 0) {
repeat = 1;
break;
}
cgexpr(c, e, *locals);
ins2(c, op, areg(D_AX),
amem(D_BP, off + idx * esz));
last = e;
idx++;
}
if (repeat && last) {
/* fill remaining slots with the value already in AX. */
while (idx < (int)lu->alen) {
ins2(c, op, areg(D_AX),
amem(D_BP, off + idx * esz));
idx++;
}
}
break;
}
if (n->rhs && sz == 8) {
cgexpr(c, n->rhs, *locals);
if (isf) {
@@ -2488,6 +2604,8 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
char *loop = mklabel(c, "rloop");
char *end = mklabel(c, "rend");
char *natural_exit = end;
if (n->els) natural_exit = mklabel(c, "relseloop");
if (nloops < LOOP_MAX) {
loop_cont[nloops] = loop;
loop_brk[nloops] = end;
@@ -2497,7 +2615,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
ins2(c, A_MOVQ, amem(D_BP, ioff), areg(D_AX));
ins2(c, A_MOVQ, amem(D_BP, loff), areg(D_BX));
ins2(c, A_CMPQ, areg(D_BX), areg(D_AX));
ins1(c, A_JGE, abranch(end));
ins1(c, A_JGE, abranch(natural_exit));
/* compute element base: s.ptr + i*esz → BX */
if (esz > 1) {
ins2(c, A_MOVQ, aimm(esz), areg(D_CX));
@@ -2523,6 +2641,10 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
cgstmt(c, n->body, locals, frame);
ins2(c, A_ADDQ, aimm(1), amem(D_BP, ioff));
ins1(c, A_JMP, abranch(loop));
if (n->els) {
label(c, natural_exit);
cgstmt(c, n->els, locals, frame);
}
label(c, end);
if (nloops > 0) nloops--;
break;
@@ -2530,12 +2652,17 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
case N_FOR: {
char *loop = mklabel(c, "loop");
char *end = mklabel(c, "endloop");
/* `else` runs at normal cond-false exit; break skips it.
* Separate the natural exit label from the break target so
* the else block sits between them. */
char *natural_exit = end;
if (n->els) natural_exit = mklabel(c, "elseloop");
if (n->lhs) cgstmt(c, n->lhs, locals, frame);
label(c, loop);
if (n->cond) {
cgexpr(c, n->cond, *locals);
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
ins1(c, A_JE, abranch(end));
ins1(c, A_JE, abranch(natural_exit));
}
if (nloops < LOOP_MAX) {
loop_cont[nloops] = loop;
@@ -2546,6 +2673,10 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
if (nloops > 0) nloops--;
if (n->rhs) cgexpr(c, n->rhs, *locals);
ins1(c, A_JMP, abranch(loop));
if (n->els) {
label(c, natural_exit);
cgstmt(c, n->els, locals, frame);
}
label(c, end);
break;
}

View File

@@ -91,10 +91,15 @@ resolve_type(Checker *c, Node *n)
return type_slice(c->a, resolve_type(c, n->lhs));
case N_TARRAY: {
u64 len = 0;
if (n->rhs && n->rhs->kind == N_INTLIT)
if (n->rhs == NULL) {
/* `[_]T` — length inferred at the use site (currently
* only `let x: [_]T = arrlit;`). Leave alen=0 as a
* sentinel; clet patches it from the initialiser. */
} else if (n->rhs->kind == N_INTLIT) {
len = n->rhs->uval;
else
} else {
err(c, n->pos, "array length must be an integer literal");
}
return type_array(c->a, resolve_type(c, n->lhs), len);
}
case N_TCHAN:
@@ -304,6 +309,9 @@ cexpr(Checker *c, Node *n)
case N_FALSE: n->type = ty_untyped_bool; return n->type;
case N_NIL: n->type = ty_untyped_nil; return n->type;
case N_IDENT: {
if (n->str && n->str[0] == '\0')
return n->type = err(c, n->pos,
"`_` is only valid as a binding or discard lvalue");
Sym *s = scope_lookup(c->cur, n->str);
if (s == NULL)
return n->type = err(c, n->pos, "undefined: %s", n->str);
@@ -417,6 +425,59 @@ cexpr(Checker *c, Node *n)
n->lhs->type = ty_err; /* mark builtin: no real symbol */
return n->type;
}
/* size(T) / align(T): fold to an integer literal. The arg is
* a type-expr node (planted by the parser, not a regular
* expression). */
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
(strcmp(n->lhs->str, "size") == 0 ||
strcmp(n->lhs->str, "align") == 0) &&
n->list != NULL) {
int is_size = strcmp(n->lhs->str, "size") == 0;
Type *t = resolve_type(c, n->list);
u64 v = 0;
if (t && t != ty_err) v = is_size ? t->size : t->align;
n->kind = N_INTLIT;
n->uval = v;
n->str = aprintf(c->a, "%llu", (unsigned long long)v);
n->strlen = strlen(n->str);
n->lhs = NULL;
n->list = NULL;
n->tsuffix = NULL;
n->type = ty_untyped_int;
return n->type;
}
/* offset(e.f): the byte offset of `f` inside the struct type of
* `e`. Folded to an integer literal at check-time. */
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
strcmp(n->lhs->str, "offset") == 0 &&
n->list != NULL && n->list->next == NULL &&
n->list->kind == N_DOT) {
Node *dot = n->list;
Type *bt = cexpr(c, dot->lhs);
Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
if (u && u->kind == TY_PTR) u = u->sub;
if (u && u->kind == TY_NAMED) u = u->under;
u64 off = 0;
int found = 0;
if (u && u->kind == TY_STRUCT) {
for (Tfield *f = u->fields; f; f = f->next)
if (strcmp(f->name, dot->str) == 0) {
off = f->offset; found = 1; break;
}
}
if (!found)
err(c, n->pos, "offset: no field '%s'",
dot->str ? dot->str : "?");
n->kind = N_INTLIT;
n->uval = off;
n->str = aprintf(c->a, "%llu", (unsigned long long)off);
n->strlen = strlen(n->str);
n->lhs = NULL;
n->list = NULL;
n->tsuffix = NULL;
n->type = ty_untyped_int;
return n->type;
}
if (n->lhs && n->lhs->kind == N_IDENT &&
n->lhs->str && strcmp(n->lhs->str, "append") == 0 &&
n->list != NULL && n->list->next != NULL) {
@@ -443,6 +504,43 @@ cexpr(Checker *c, Node *n)
n->lhs->type = ty_err;
return n->type;
}
/* assert(cond[, msg]) / abort([msg]) — runtime checks that
* call into rt_abort. msg must be a str when present.
* Only treated as builtins when no user symbol shadows the
* name; existing code that declares its own `abort`/`assert`
* (e.g. lib/os/os.ww) keeps working unchanged. */
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
strcmp(n->lhs->str, "abort") == 0 &&
scope_lookup(c->cur, "abort") == NULL) {
if (n->list) {
Type *mt = cexpr(c, n->list);
if (mt != ty_err && !type_assignable(ty_str, mt))
err(c, n->pos, "abort: message must be str");
if (n->list->next)
err(c, n->pos, "abort: at most one arg");
}
n->type = ty_void;
n->lhs->type = ty_err;
return n->type;
}
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
strcmp(n->lhs->str, "assert") == 0 &&
n->list != NULL &&
scope_lookup(c->cur, "assert") == NULL) {
Type *ct = cexpr(c, n->list);
if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool)
err(c, n->pos, "assert: cond must be bool");
if (n->list->next) {
Type *mt = cexpr(c, n->list->next);
if (mt != ty_err && !type_assignable(ty_str, mt))
err(c, n->pos, "assert: message must be str");
if (n->list->next->next)
err(c, n->pos, "assert: at most two args");
}
n->type = ty_void;
n->lhs->type = ty_err;
return n->type;
}
/* alloc([], n) — Hare-style fresh slice with cap n. We pin
* the element type to u8 by default; the caller's declared
* slice type drives the actual element size at codegen. */
@@ -488,6 +586,19 @@ cexpr(Checker *c, Node *n)
return n->type = u->ret ? u->ret : ty_void;
}
case N_ASSIGN: {
/* `_` lvalue: discard the rhs. */
if (n->lhs && n->lhs->kind == N_IDENT &&
n->lhs->str && n->lhs->str[0] == '\0') {
(void)cexpr(c, n->rhs);
return n->type = ty_void;
}
/* Reject assignment to a const-bound name. */
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) {
Sym *s = scope_lookup(c->cur, n->lhs->str);
if (s && s->is_const)
err(c, n->pos, "cannot assign to const `%s`",
n->lhs->str);
}
Type *l = cexpr(c, n->lhs);
Type *r = cexpr(c, n->rhs);
if (l != ty_err && r != ty_err && !type_assignable(l, r))
@@ -641,19 +752,45 @@ clet(Checker *c, Node *n)
Type *declared = n->lhs ? resolve_type(c, n->lhs) : NULL;
Type *initt = NULL;
if (n->rhs) initt = cexpr(c, n->rhs);
/* `let xs: [_]T = arrlit;` — fill in the inferred length from the
* initialiser. `resolve_type` left alen=0 as a sentinel. */
if (declared && declared->kind == TY_ARRAY && declared->alen == 0 &&
initt) {
Type *iu = (initt->kind == TY_NAMED) ? initt->under : initt;
if (iu && iu->kind == TY_ARRAY)
declared = type_array(c->a, declared->sub, iu->alen);
else
err(c, n->pos, "[_]T needs an array-literal initialiser");
}
Type *t = declared;
if (t == NULL && initt) t = type_default(initt);
if (t == NULL) {
err(c, n->pos, "let needs a type or initialiser");
t = ty_err;
}
if (declared && initt && initt != ty_err &&
/* An array literal with a trailing `...` repeat marker has
* "flexible" length — the last value fills the remaining slots.
* The literal's type carries the explicit-element count, which
* may not match the declared length. Trust the declared type
* when the marker is present. */
int has_arr_repeat = 0;
if (n->rhs && n->rhs->kind == N_ARRLIT) {
for (Node *e = n->rhs->list; e; e = e->next)
if (e->kind == N_FIELD && e->str &&
strcmp(e->str, "...") == 0) {
has_arr_repeat = 1;
break;
}
}
if (declared && initt && initt != ty_err && !has_arr_repeat &&
!type_assignable(declared, initt))
err(c, n->pos, "init %s not assignable to declared %s",
type_name(c->a, initt), type_name(c->a, declared));
n->type = t;
if (n->str && n->str[0])
scope_define(c->cur, n->str, SK_VAR, t, n);
if (n->str && n->str[0]) {
Sym *s = scope_define(c->cur, n->str, SK_VAR, t, n);
if (s && n->op == TK_CONST) s->is_const = 1;
}
}
static void
@@ -721,6 +858,7 @@ cstmt(Checker *c, Node *n)
}
cstmt(c, n->body);
c->loops--;
if (n->els) cstmt(c, n->els);
c->cur = saved;
break;
}
@@ -738,6 +876,10 @@ cstmt(Checker *c, Node *n)
if (n->rhs) (void)cexpr(c, n->rhs);
cstmt(c, n->body);
c->loops--;
/* `else` block: runs at normal cond-false exit (skipped by
* break). Outside the loop count — break/continue inside the
* else target an enclosing loop, not this one. */
if (n->els) cstmt(c, n->els);
c->cur = saved;
break;
}
@@ -759,8 +901,10 @@ cstmt(Checker *c, Node *n)
l->str, type_name(c->a, elem),
type_name(c->a, declared));
l->type = t;
if (l->str && l->str[0])
scope_define(c->cur, l->str, SK_VAR, t, l);
if (l->str && l->str[0]) {
Sym *s = scope_define(c->cur, l->str, SK_VAR, t, l);
if (s && n->op == TK_CONST) s->is_const = 1;
}
if (tp) tp = tp->next;
}
if (u && tp != NULL)
@@ -776,6 +920,11 @@ cstmt(Checker *c, Node *n)
}
Tparam *tp = u ? u->params : NULL;
for (Node *lv = n->list; lv; lv = lv->next) {
/* `_` lvalue: skip type check, advance the tuple cursor. */
if (lv->kind == N_IDENT && lv->str && lv->str[0] == '\0') {
if (tp) tp = tp->next;
continue;
}
Type *lt = cexpr(c, lv);
Type *elem = tp ? tp->type : NULL;
if (lt && elem && !type_assignable(lt, elem))

View File

@@ -317,6 +317,11 @@ lexident(Lex *l, Pos start)
lget(l);
u64 n = l->pos - begin;
const char *p = l->src + begin;
/* bare '_' is the discard marker. `_x`, `_1` are normal idents. */
if (n == 1 && p[0] == '_') {
Tok t = (Tok){ TK_UNDER, start, astrndup(l->a, p, n), n, {0}, TK_NONE };
return t;
}
Tkind k = kwlookup(p, n);
Tok t = (Tok){ k != TK_NONE ? k : TK_IDENT, start,
astrndup(l->a, p, n), n, {0}, TK_NONE };

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));

View File

@@ -19,6 +19,7 @@ static const struct kwent kwtab[] = {
{ "break", TK_BREAK },
{ "case", TK_CASE },
{ "chan", TK_CHAN },
{ "const", TK_CONST },
{ "continue", TK_CONTINUE },
{ "def", TK_DEF },
{ "defer", TK_DEFER },
@@ -90,6 +91,8 @@ tokname(Tkind k)
case TK_AS: return "as";
case TK_STATIC: return "static";
case TK_MATCH: return "match";
case TK_CONST: return "const";
case TK_UNDER: return "_";
case TK_LPAREN: return "(";
case TK_RPAREN: return ")";

View File

@@ -114,6 +114,8 @@ typedef enum {
TK_AS, /* reserved for future cast spelling, not active */
TK_STATIC, /* Hare-style storage-class qualifier */
TK_MATCH, /* match expression head */
TK_CONST, /* const binding */
TK_UNDER, /* bare '_' discard */
/* punct + operators */
TK_LPAREN, /* ( */
@@ -433,6 +435,7 @@ struct Sym {
Type *type;
Node *decl;
int exported;
int is_const; /* const-bound (assignment rejected) */
Sym *next; /* iteration */
Sym *hashnext; /* bucket chain */
Scope *scope;