Hare-style `@test fn check_foo() void = { ... }` now parses. The
attribute is recognised by making the args list optional in
parseattrs: `@symbol("rt_syscall")` still requires the parens;
`@test` doesn't. Same change mirrored in lib/ww/parse/decl.ww.
The runner (test/wcc/910_at_test.c) scans a fixture for
`@test fn IDENT(`, synthesises a wrapper `main()` that calls each
test fn, builds it via `ww run`, and asserts exit 0. A failing
@test would either explicitly call abort or trip a runtime trap
(div-by-zero, etc.) and the whole driver exits non-zero.
The 910_at_test target sits alongside the existing C-side test
binaries; `make test` now runs 20 tests instead of 19.
Fixture: test/wcc/data/attest_pass.ww exercises two passing tests
(simple arithmetic and a match-with-yield).
162 lines
4.2 KiB
Plaintext
162 lines
4.2 KiB
Plaintext
// lib/ww/parse/decl.ww — declaration parsing, split out of parse.ww.
|
|
|
|
use os;
|
|
use mem;
|
|
use tok;
|
|
|
|
fn parseuse(p: *parser) *node = {
|
|
let pf: str = p.curfile;
|
|
let pl: i32 = p.curline;
|
|
let pc: i32 = p.curcol;
|
|
advance(p); // past `use`
|
|
let n: *node = newnode(p.a, N_USE, pf, pl, pc);
|
|
let id: str;
|
|
expectident(p, &id);
|
|
n.str = id;
|
|
expecttok(p, TK_SEMI, "expected ';' after use");
|
|
return n;
|
|
};
|
|
|
|
fn parsedef(p: *parser, exported: i32) *node = {
|
|
let pf: str = p.curfile;
|
|
let pl: i32 = p.curline;
|
|
let pc: i32 = p.curcol;
|
|
advance(p); // past `def`
|
|
let n: *node = newnode(p.a, N_DEF, pf, pl, pc);
|
|
n.module = p.l.module;
|
|
let id: str;
|
|
expectident(p, &id);
|
|
n.str = id;
|
|
expecttok(p, TK_COLON, "expected ':' in def");
|
|
n.lhs = parsetype(p);
|
|
expecttok(p, TK_ASSIGN, "expected '=' in def");
|
|
n.rhs = parseexpr(p);
|
|
expecttok(p, TK_SEMI, "expected ';' after def");
|
|
n.exported = exported;
|
|
return n;
|
|
};
|
|
|
|
fn parselet(p: *parser, exported: i32) *node = {
|
|
let pf: str = p.curfile;
|
|
let pl: i32 = p.curline;
|
|
let pc: i32 = p.curcol;
|
|
// 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;
|
|
expectbindname(p, &id);
|
|
n.str = id;
|
|
if (accepttok(p, TK_COLON)) {
|
|
n.lhs = parsetype(p);
|
|
};
|
|
if (accepttok(p, TK_ASSIGN)) {
|
|
n.rhs = parseexpr(p);
|
|
};
|
|
expecttok(p, TK_SEMI, "expected ';' after let");
|
|
n.exported = exported;
|
|
if (is_const != 0) { n.op = TK_CONST; };
|
|
return n;
|
|
};
|
|
|
|
fn parseattrs(p: *parser) *node = {
|
|
let head: *node = nil;
|
|
let tail: *node = nil;
|
|
for (p.curkind == TK_AT) {
|
|
let pf: str = p.curfile;
|
|
let pl: i32 = p.curline;
|
|
let pc: i32 = p.curcol;
|
|
advance(p);
|
|
let a: *node = newnode(p.a, N_ATTR, pf, pl, pc);
|
|
let id: str;
|
|
expectident(p, &id);
|
|
a.str = id;
|
|
// `@name(args...)` for FFI-style attrs; `@name` for marker-
|
|
// only attrs like @test (no parens).
|
|
if (accepttok(p, TK_LPAREN)) {
|
|
let arghead: *node = nil;
|
|
parsearglist(p, TK_RPAREN, &arghead);
|
|
a.list = arghead;
|
|
expecttok(p, TK_RPAREN, "expected ')' after attribute args");
|
|
};
|
|
if (head == nil) { head = a; tail = a; }
|
|
else { tail.next = a; tail = a; };
|
|
};
|
|
return head;
|
|
};
|
|
|
|
fn parseparams(p: *parser) *node = {
|
|
if (p.curkind == TK_RPAREN) { return nil; };
|
|
let head: *node = nil;
|
|
let tail: *node = nil;
|
|
for (true) {
|
|
let pf: str = p.curfile;
|
|
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.
|
|
let id: str;
|
|
expectbindname(p, &id);
|
|
n.str = id;
|
|
expecttok(p, TK_COLON, "expected ':' in parameter");
|
|
n.lhs = parsetype(p);
|
|
if (head == nil) { head = n; tail = n; }
|
|
else { tail.next = n; tail = n; };
|
|
if (!accepttok(p, TK_COMMA)) { break; };
|
|
if (p.curkind == TK_RPAREN) { break; };
|
|
};
|
|
return head;
|
|
};
|
|
|
|
fn parsefn(p: *parser, exported: i32, attrs: *node) *node = {
|
|
let pf: str = p.curfile;
|
|
let pl: i32 = p.curline;
|
|
let pc: i32 = p.curcol;
|
|
advance(p); // past `fn`
|
|
let n: *node = newnode(p.a, N_FNDECL, pf, pl, pc);
|
|
n.module = p.l.module;
|
|
let id: str;
|
|
expectident(p, &id);
|
|
n.str = id;
|
|
expecttok(p, TK_LPAREN, "expected '(' after fn name");
|
|
n.list = parseparams(p);
|
|
expecttok(p, TK_RPAREN, "expected ')' after params");
|
|
if (p.curkind != TK_ASSIGN) {
|
|
if (p.curkind != TK_SEMI) {
|
|
n.lhs = parsetype(p);
|
|
};
|
|
};
|
|
if (accepttok(p, TK_ASSIGN)) {
|
|
n.body = parseblock(p);
|
|
expecttok(p, TK_SEMI, "expected ';' after fn body");
|
|
} else {
|
|
// Body-less fn: FFI declaration (`fn name(args) ret;`).
|
|
expecttok(p, TK_SEMI, "expected ';' after fn header");
|
|
};
|
|
n.exported = exported;
|
|
n.attr = attrs;
|
|
return n;
|
|
};
|
|
|
|
fn parsetypedecl(p: *parser, exported: i32) *node = {
|
|
let pf: str = p.curfile;
|
|
let pl: i32 = p.curline;
|
|
let pc: i32 = p.curcol;
|
|
advance(p); // past `type`
|
|
let n: *node = newnode(p.a, N_TYPEDECL, pf, pl, pc);
|
|
n.module = p.l.module;
|
|
let id: str;
|
|
expectident(p, &id);
|
|
n.str = id;
|
|
expecttok(p, TK_ASSIGN, "expected '=' in type decl");
|
|
n.lhs = parsetype(p);
|
|
expecttok(p, TK_SEMI, "expected ';' after type decl");
|
|
n.exported = exported;
|
|
return n;
|
|
};
|
|
|