ww: hare-feature batch (_, const, [_]T, ..., size/offset, assert, for-else)
This commit is contained in:
6
cmd/w6c/CLAUDE.md
Normal file
6
cmd/w6c/CLAUDE.md
Normal 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.
|
||||
137
cmd/w6c/cgen.c
137
cmd/w6c/cgen.c
@@ -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;
|
||||
}
|
||||
|
||||
163
cmd/wcc/check.c
163
cmd/wcc/check.c
@@ -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))
|
||||
|
||||
@@ -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 };
|
||||
|
||||
119
cmd/wcc/parse.c
119
cmd/wcc/parse.c
@@ -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));
|
||||
|
||||
@@ -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 ")";
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user