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

@@ -40,11 +40,15 @@ fn parselet(p: *parser, exported: i32) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
advance(p); // past `let`
// Accept `let` or `const`. Const-bound bindings are marked via
// n.op = TK_CONST so the checker can reject reassignment.
let is_const: i32 = 0;
if (p.curkind == TK_CONST) { is_const = 1; };
advance(p);
let n: *node = newnode(p.a, N_LET, pf, pl, pc);
n.module = p.l.module;
let id: str;
expectident(p, &id);
expectbindname(p, &id);
n.str = id;
if (accepttok(p, TK_COLON)) {
n.lhs = parsetype(p);
@@ -54,6 +58,7 @@ fn parselet(p: *parser, exported: i32) *node = {
};
expecttok(p, TK_SEMI, "expected ';' after let");
n.exported = exported;
if (is_const != 0) { n.op = TK_CONST; };
return n;
};
@@ -89,10 +94,10 @@ fn parseparams(p: *parser) *node = {
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
let n: *node = newnode(p.a, N_PARAM, pf, pl, pc);
// Param form: IDENT ':' type. Anonymous-type-only params (used
// in fn type expressions) aren't yet wired here.
// Param form: (IDENT|'_') ':' type. Anonymous-type-only params
// (used in fn type expressions) aren't yet wired here.
let id: str;
expectident(p, &id);
expectbindname(p, &id);
n.str = id;
expecttok(p, TK_COLON, "expected ':' in parameter");
n.lhs = parsetype(p);

View File

@@ -4,6 +4,18 @@ use os;
use mem;
use tok;
// streq_local — str-to-str compare. Inlined here to avoid a cross-
// module `use sym;` for one call site.
fn streq_local(a: str, b: str) bool = {
if (a.len != b.len) { return false; };
let i: i32 = 0;
for (i < a.len) {
if (a[i] != b[i]) { return false; };
i += 1;
};
return true;
};
fn parseprimary(p: *parser) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
@@ -40,6 +52,43 @@ fn parseprimary(p: *parser) *node = {
advance(p);
return newnode(p.a, N_NIL, pf, pl, pc);
};
if (p.curkind == TK_UNDER) {
// Bare `_` — valid only as a discard lvalue. Emit an N_IDENT
// with empty str; the checker rejects it outside lvalue
// positions.
advance(p);
let n: *node = newnode(p.a, N_IDENT, pf, pl, pc);
let empty: str;
n.str = empty;
return n;
};
if (p.curkind == TK_LBRACK) {
// Array literal `[a, b, c]` or `[v, w...]` (repeat suffix).
// The repeat marker is an N_FIELD node with str = "..."
// appended to the element list so cgen can detect it.
advance(p);
let n: *node = newnode(p.a, N_ARRLIT, pf, pl, pc);
let head: *node = nil;
let tail: *node = nil;
for (p.curkind != TK_RBRACK) {
if (p.curkind == TK_EOF) { break; };
let e: *node = parseexpr(p);
if (head == nil) { head = e; tail = e; }
else { tail.next = e; tail = e; };
if (accepttok(p, TK_ELLIPSIS)) {
let rep: *node = newnode(p.a, N_FIELD,
p.curfile, p.curline, p.curcol);
rep.str = "...";
tail.next = rep;
tail = rep;
break;
};
if (!accepttok(p, TK_COMMA)) { break; };
};
expecttok(p, TK_RBRACK, "expected ']' after array literal");
n.list = head;
return n;
};
if (p.curkind == TK_LPAREN) {
advance(p);
let e: *node = parseexpr(p);
@@ -79,6 +128,13 @@ fn parseprimary(p: *parser) *node = {
let tail: *node = nil;
for (p.curkind != TK_RBRACE) {
if (p.curkind == TK_EOF) { break; };
// Trailing `...` autofill marker. Stash on s.op so
// cgen can zero-fill the slot before per-field stores.
if (p.curkind == TK_ELLIPSIS) {
advance(p);
s.op = TK_ELLIPSIS;
break;
};
let fpf: str = p.curfile;
let fpl: i32 = p.curline;
let fpc: i32 = p.curcol;
@@ -164,9 +220,20 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
advance(p);
let n: *node = newnode(p.a, N_CALL, pf, pl, pc);
n.lhs = cur;
let arghead: *node = nil;
parsearglist(p, TK_RPAREN, &arghead);
n.list = arghead;
// size(T)/align(T): the single arg is a type expression,
// not a regular expression. Special-case at the parser.
let is_typeop: i32 = 0;
if (cur.kind == N_IDENT) {
if (streq_local(cur.str, "size")) { is_typeop = 1; };
if (streq_local(cur.str, "align")) { is_typeop = 1; };
};
if (is_typeop != 0) {
n.list = parsetype(p);
} else {
let arghead: *node = nil;
parsearglist(p, TK_RPAREN, &arghead);
n.list = arghead;
};
expecttok(p, TK_RPAREN, "expected ')' after args");
cur = n;
continue;

View File

@@ -85,6 +85,19 @@ fn expectident(p: *parser, into: *str) bool = {
return true;
};
// expectbindname — like expectident but also accepts a bare `_`
// discard marker. On `_`, returns "" so the checker skips
// scope_define for the binding.
fn expectbindname(p: *parser, into: *str) bool = {
if (p.curkind == TK_UNDER) {
let empty: str;
*into = empty;
advance(p);
return true;
};
return expectident(p, into);
};
// ---- type expressions ------------------------------------------------
//
// Currently: TNAME (single ident, no dotted path yet) and TPTR (`*T`).
@@ -112,7 +125,14 @@ fn parsetype(p: *parser) *node = {
return n;
};
let n: *node = newnode(p.a, N_TARRAY, pf, pl, pc);
n.rhs = parseexpr(p);
// `[_]T` — length inferred from initialiser. n.rhs stays nil
// as the sentinel; the cgen path for N_LET fills it from the
// array literal's element count.
if (p.curkind == TK_UNDER) {
advance(p);
} else {
n.rhs = parseexpr(p);
};
expecttok(p, TK_RBRACK, "expected ']' in array type");
n.lhs = parsetype(p);
return n;
@@ -274,6 +294,8 @@ export fn parsefile(p: *parser) *node = {
d = parsetypedecl(p, exported);
} else { if (p.curkind == TK_LET) {
d = parselet(p, exported);
} else { if (p.curkind == TK_CONST) {
d = parselet(p, exported);
} else { if (p.curkind == TK_FN) {
d = parsefn(p, exported, attrs);
} else {
@@ -300,7 +322,7 @@ export fn parsefile(p: *parser) *node = {
advance(p);
};
if (p.curkind == TK_SEMI) { advance(p); };
};};};};};
};};};};};};
if (d != nil) {
if (head == nil) {

View File

@@ -8,10 +8,13 @@ fn parseletlocal(p: *parser) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
advance(p); // past `let`
// `let` or `const`. Const-bound locals are marked via n.op = TK_CONST.
let is_const: i32 = 0;
if (p.curkind == TK_CONST) { is_const = 1; };
advance(p);
let n: *node = newnode(p.a, N_LET, pf, pl, pc);
let id: str;
expectident(p, &id);
expectbindname(p, &id);
n.str = id;
if (accepttok(p, TK_COLON)) {
n.lhs = parsetype(p);
@@ -20,6 +23,7 @@ fn parseletlocal(p: *parser) *node = {
n.rhs = parseexpr(p);
};
expecttok(p, TK_SEMI, "expected ';' after let");
if (is_const != 0) { n.op = TK_CONST; };
return n;
};
@@ -98,6 +102,11 @@ fn parsefor(p: *parser) *node = {
};
expecttok(p, TK_RPAREN, "expected ')' after for");
n.body = parseblock(p);
// Optional `else { ... }` — runs at normal cond-false exit; skipped
// by break. Hare's "did the loop find it?" idiom.
if (accepttok(p, TK_ELSE)) {
n.els = parseblock(p);
};
return n;
};
@@ -116,6 +125,7 @@ fn parsestmt(p: *parser) *node = {
return b;
};
if (p.curkind == TK_LET) { return parseletlocal(p); };
if (p.curkind == TK_CONST) { return parseletlocal(p); };
if (p.curkind == TK_IF) {
let n: *node = parseif(p);
expecttok(p, TK_SEMI, "expected ';' after if");