ww: rename toolchain to w-prefix + hare-style build/run/test driver
Plan 9-style w-prefix on the per-arch tools, disambiguating from the
real Plan 9 6c/6a/6l in ref/plan9front/:
cmd/wwc/ → cmd/wcc/ libwwc.a → libwcc.a
cmd/6{c,a,l} → cmd/w6{c,a,l} binary names too
test/wwc/ → test/wcc/ 6 test files w/ w6 prefix
selfhost/cmd mirror in lockstep
bootstrap/amd64/{w6c,w6a,w6l} snapshot binaries (gitignored)
WW_6{C,A,L} → WW_W6{C,A,L} env-var overrides
Plan 9 source-tree refs ("Plan 9 6c shape", ref/plan9front/, etc.)
preserved. Hare-style driver, both C and ww sides:
ww test [path] discover *_test.ww in a directory module, run
each; single-file mode for `ww test foo.ww`
Module-by-name `ww build foo` resolves to foo.ww or foo/foo.ww
via search path (cwd : -I dirs : $WW_LIB)
Default-to-cwd `ww build` / `ww test` build the cwd module
Run pass-through `ww run path arg1 arg2` reaches the program
lib/os: getcwd (79) and getdents64 (217) syscalls power `.` resolution
and directory enumeration on the ww side.
Makefile: wwstage tool deps now include lib/os/os.ww (+ lib/strconv
for wwdump_ww) so lib/* edits force their rebuild instead of leaving
stale binaries — surfaced when test 995 first failed against a stale
w6c_ww built before the lib/os additions.
Test 993 byte-identical parity gate (C-side ww vs ww-side ww_ww on a
build corpus) stays green; all 19 tests pass.
This commit is contained in:
189
cmd/wcc/ast.c
Normal file
189
cmd/wcc/ast.c
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* ast.c — Node constructor + s-expression printer.
|
||||
*
|
||||
* Constructor zeroes everything past kind/pos. Printer is rigid and
|
||||
* deterministic so golden tests can diff. One node per logical line,
|
||||
* children indented by 2 spaces.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <string.h>
|
||||
|
||||
Node *
|
||||
newnode(Arena *a, Nkind k, Pos p)
|
||||
{
|
||||
Node *n = amalloc(a, sizeof *n);
|
||||
n->kind = k;
|
||||
n->pos = p;
|
||||
return n;
|
||||
}
|
||||
|
||||
static const char *
|
||||
nkname(Nkind k)
|
||||
{
|
||||
switch (k) {
|
||||
case N_NONE: return "none";
|
||||
case N_INTLIT: return "int";
|
||||
case N_FLOATLIT: return "float";
|
||||
case N_STRLIT: return "str";
|
||||
case N_RUNELIT: return "rune";
|
||||
case N_TRUE: return "true";
|
||||
case N_FALSE: return "false";
|
||||
case N_NIL: return "nil";
|
||||
case N_IDENT: return "id";
|
||||
case N_BIN: return "bin";
|
||||
case N_UN: return "un";
|
||||
case N_CALL: return "call";
|
||||
case N_INDEX: return "index";
|
||||
case N_DOT: return "dot";
|
||||
case N_CAST: return "cast";
|
||||
case N_STRUCTLIT: return "structlit";
|
||||
case N_ARRLIT: return "arrlit";
|
||||
case N_FIELD: return "field";
|
||||
case N_ASSIGN: return "assign";
|
||||
case N_ALLOC: return "alloc";
|
||||
case N_FREE: return "free";
|
||||
case N_RECV: return "recv";
|
||||
case N_SLICE: return "slice";
|
||||
case N_SPREAD: return "spread";
|
||||
case N_BLOCK: return "block";
|
||||
case N_EXPRSTMT: return "exprstmt";
|
||||
case N_LET: return "let";
|
||||
case N_RETURN: return "return";
|
||||
case N_IF: return "if";
|
||||
case N_FOR: return "for";
|
||||
case N_FORRANGE: return "forrange";
|
||||
case N_DEFER: return "defer";
|
||||
case N_BREAK: return "break";
|
||||
case N_CONTINUE: return "continue";
|
||||
case N_SWITCH: return "switch";
|
||||
case N_CASE: return "case";
|
||||
case N_FILE: return "file";
|
||||
case N_USE: return "use";
|
||||
case N_DEF: return "def";
|
||||
case N_TYPEDECL: return "typedecl";
|
||||
case N_FNDECL: return "fn";
|
||||
case N_PARAM: return "param";
|
||||
case N_TNAME: return "tname";
|
||||
case N_TPTR: return "tptr";
|
||||
case N_TSLICE: return "tslice";
|
||||
case N_TARRAY: return "tarray";
|
||||
case N_TFN: return "tfn";
|
||||
case N_TSTRUCT: return "tstruct";
|
||||
case N_TFIELD: return "tfield";
|
||||
case N_TCHAN: return "tchan";
|
||||
case N_ATTR: return "attr";
|
||||
case N_TTUPLE: return "ttuple";
|
||||
case N_TTAGGED: return "ttagged";
|
||||
case N_TUPLE: return "tuple";
|
||||
case N_MATCH: return "match";
|
||||
case N_MCASE: return "mcase";
|
||||
case N_TRYPROP: return "tryprop";
|
||||
case N_TRYUNW: return "tryunw";
|
||||
case N_MLET: return "mlet";
|
||||
case N_MASSIGN: return "massign";
|
||||
case N_LAST: return "last";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
static void
|
||||
indent(FILE *f, int d)
|
||||
{
|
||||
for (int i = 0; i < d; i++) fputs(" ", f);
|
||||
}
|
||||
|
||||
static void
|
||||
printq(FILE *f, const char *s)
|
||||
{
|
||||
fputc('"', f);
|
||||
for (; *s; s++) {
|
||||
unsigned char c = (unsigned char)*s;
|
||||
switch (c) {
|
||||
case '"': fputs("\\\"", f); break;
|
||||
case '\\': fputs("\\\\", f); break;
|
||||
case '\n': fputs("\\n", f); break;
|
||||
case '\t': fputs("\\t", f); break;
|
||||
default:
|
||||
if (c < 0x20) fprintf(f, "\\x%02x", c);
|
||||
else fputc(c, f);
|
||||
}
|
||||
}
|
||||
fputc('"', f);
|
||||
}
|
||||
|
||||
static void pr(FILE*, Node*, int);
|
||||
|
||||
static void
|
||||
prlist(FILE *f, const char *tag, Node *head, int d)
|
||||
{
|
||||
indent(f, d);
|
||||
fprintf(f, "(%s\n", tag);
|
||||
for (Node *n = head; n; n = n->next)
|
||||
pr(f, n, d + 1);
|
||||
indent(f, d);
|
||||
fputs(")\n", f);
|
||||
}
|
||||
|
||||
static void
|
||||
pr(FILE *f, Node *n, int d)
|
||||
{
|
||||
if (n == NULL) {
|
||||
indent(f, d); fputs("()\n", f); return;
|
||||
}
|
||||
indent(f, d);
|
||||
fprintf(f, "(%s", nkname(n->kind));
|
||||
switch (n->kind) {
|
||||
case N_INTLIT:
|
||||
fprintf(f, " %llu", (unsigned long long)n->uval);
|
||||
break;
|
||||
case N_FLOATLIT:
|
||||
fprintf(f, " %g", n->fval);
|
||||
break;
|
||||
case N_RUNELIT:
|
||||
fprintf(f, " %llu", (unsigned long long)n->uval);
|
||||
break;
|
||||
case N_STRLIT:
|
||||
case N_IDENT:
|
||||
case N_USE:
|
||||
case N_DOT:
|
||||
case N_DEF:
|
||||
case N_TYPEDECL:
|
||||
case N_FNDECL:
|
||||
case N_PARAM:
|
||||
case N_LET:
|
||||
case N_TNAME:
|
||||
case N_TFIELD:
|
||||
case N_FIELD:
|
||||
case N_ATTR:
|
||||
if (n->str) { fputc(' ', f); printq(f, n->str); }
|
||||
break;
|
||||
case N_BIN:
|
||||
case N_UN:
|
||||
case N_ASSIGN:
|
||||
fprintf(f, " %s", tokname(n->op));
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
if (n->kind == N_FNDECL && n->export)
|
||||
fputs(" export", f);
|
||||
if (n->kind == N_DEF && n->export)
|
||||
fputs(" export", f);
|
||||
if (n->kind == N_TYPEDECL && n->export)
|
||||
fputs(" export", f);
|
||||
fputc('\n', f);
|
||||
if (n->attr)
|
||||
prlist(f, "@", n->attr, d + 1);
|
||||
if (n->lhs) pr(f, n->lhs, d + 1);
|
||||
if (n->rhs) pr(f, n->rhs, d + 1);
|
||||
if (n->cond) pr(f, n->cond, d + 1);
|
||||
if (n->body) pr(f, n->body, d + 1);
|
||||
if (n->els) pr(f, n->els, d + 1);
|
||||
if (n->list) prlist(f, "list", n->list, d + 1);
|
||||
indent(f, d); fputs(")\n", f);
|
||||
}
|
||||
|
||||
void
|
||||
astprint(FILE *f, Node *n)
|
||||
{
|
||||
pr(f, n, 0);
|
||||
}
|
||||
945
cmd/wcc/check.c
Normal file
945
cmd/wcc/check.c
Normal file
@@ -0,0 +1,945 @@
|
||||
/*
|
||||
* check.c — name resolution + type checking pass.
|
||||
*
|
||||
* Two-stage:
|
||||
* 1) collect: walk top-level decls and install Syms with stub types.
|
||||
* 2) resolve: expand types, check fn bodies and def initialisers.
|
||||
*
|
||||
* Errors do not stop the walk — we keep going so the user gets many
|
||||
* diagnostics from one run. Nodes get their resolved Type attached.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <string.h>
|
||||
|
||||
static void cstmt(Checker*, Node*);
|
||||
static Type *cexpr(Checker*, Node*);
|
||||
static Type *resolve_type(Checker*, Node*);
|
||||
|
||||
static Type *
|
||||
err(Checker *c, Pos p, const char *fmt, ...)
|
||||
{
|
||||
(void)c;
|
||||
va_list ap;
|
||||
fprintf(errout ? errout : stderr,
|
||||
"%s:%d:%d: error: ", p.file ? p.file : "?", p.line, p.col);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(errout ? errout : stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', errout ? errout : stderr);
|
||||
c->errs++;
|
||||
return ty_err;
|
||||
}
|
||||
|
||||
static Type *
|
||||
lookup_builtin(const char *name)
|
||||
{
|
||||
if (strcmp(name, "void") == 0) return ty_void;
|
||||
if (strcmp(name, "bool") == 0) return ty_bool;
|
||||
if (strcmp(name, "rune") == 0) return ty_rune;
|
||||
if (strcmp(name, "i8") == 0) return ty_i8;
|
||||
if (strcmp(name, "i16") == 0) return ty_i16;
|
||||
if (strcmp(name, "i32") == 0) return ty_i32;
|
||||
if (strcmp(name, "i64") == 0) return ty_i64;
|
||||
if (strcmp(name, "u8") == 0) return ty_u8;
|
||||
if (strcmp(name, "u16") == 0) return ty_u16;
|
||||
if (strcmp(name, "u32") == 0) return ty_u32;
|
||||
if (strcmp(name, "u64") == 0) return ty_u64;
|
||||
if (strcmp(name, "int") == 0) return ty_int;
|
||||
if (strcmp(name, "uint") == 0) return ty_uint;
|
||||
if (strcmp(name, "uintptr") == 0) return ty_uintptr;
|
||||
if (strcmp(name, "f32") == 0) return ty_f32;
|
||||
if (strcmp(name, "f64") == 0) return ty_f64;
|
||||
if (strcmp(name, "str") == 0) return ty_str;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static Type *
|
||||
resolve_typename(Checker *c, Node *n)
|
||||
{
|
||||
const char *nm = n->str;
|
||||
Type *bi = lookup_builtin(nm);
|
||||
if (bi) return bi;
|
||||
Sym *s = scope_lookup(c->cur, nm);
|
||||
if (s == NULL && nm) {
|
||||
/* module-qualified: io.stream → strip the last dot prefix
|
||||
* and look up the leaf if `io` is a `use`-imported name. */
|
||||
const char *dot = strrchr(nm, '.');
|
||||
if (dot) {
|
||||
char head[128] = {0};
|
||||
size_t hl = (size_t)(dot - nm);
|
||||
if (hl < sizeof head) memcpy(head, nm, hl);
|
||||
Sym *m = scope_lookup(c->cur, head);
|
||||
if (m && m->kind == SK_USE)
|
||||
s = scope_lookup(c->cur, dot + 1);
|
||||
}
|
||||
}
|
||||
if (s == NULL || s->kind != SK_TYPE)
|
||||
return err(c, n->pos, "unknown type '%s'", nm);
|
||||
return s->type;
|
||||
}
|
||||
|
||||
static Type *
|
||||
resolve_type(Checker *c, Node *n)
|
||||
{
|
||||
if (n == NULL) return ty_void;
|
||||
switch (n->kind) {
|
||||
case N_TNAME:
|
||||
return resolve_typename(c, n);
|
||||
case N_TPTR:
|
||||
return type_ptr(c->a, resolve_type(c, n->lhs));
|
||||
case N_TSLICE:
|
||||
return type_slice(c->a, resolve_type(c, n->lhs));
|
||||
case N_TARRAY: {
|
||||
u64 len = 0;
|
||||
if (n->rhs && n->rhs->kind == N_INTLIT)
|
||||
len = n->rhs->uval;
|
||||
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:
|
||||
return type_chan(c->a, resolve_type(c, n->lhs));
|
||||
case N_TTUPLE: {
|
||||
Type *t = newtype(c->a, TY_TUPLE);
|
||||
Tparam *head = NULL, *tail = NULL;
|
||||
u64 sz = 0, al = 1;
|
||||
for (Node *e = n->list; e; e = e->next) {
|
||||
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||
tp->type = resolve_type(c, e);
|
||||
if (tp->type && tp->type->align > al) al = tp->type->align;
|
||||
if (tp->type) sz += tp->type->size;
|
||||
if (head == NULL) head = tp;
|
||||
else tail->next = tp;
|
||||
tail = tp;
|
||||
}
|
||||
t->params = head;
|
||||
t->size = sz;
|
||||
t->align = al;
|
||||
return t;
|
||||
}
|
||||
case N_TTAGGED: {
|
||||
/* (T1 | T2 | ...) — tag (8B) followed by the largest variant. */
|
||||
Type *t = newtype(c->a, TY_TAGGED);
|
||||
Tparam *head = NULL, *tail = NULL;
|
||||
u64 maxsz = 0, al = 8;
|
||||
for (Node *e = n->list; e; e = e->next) {
|
||||
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||
tp->type = resolve_type(c, e);
|
||||
if (tp->type && tp->type->size > maxsz) maxsz = tp->type->size;
|
||||
if (tp->type && tp->type->align > al) al = tp->type->align;
|
||||
if (head == NULL) head = tp;
|
||||
else tail->next = tp;
|
||||
tail = tp;
|
||||
}
|
||||
t->params = head;
|
||||
t->size = 8 + maxsz;
|
||||
t->align = al;
|
||||
return t;
|
||||
}
|
||||
case N_TFN: {
|
||||
Type *t = newtype(c->a, TY_FN);
|
||||
t->ret = resolve_type(c, n->lhs);
|
||||
t->size = 8;
|
||||
t->align = 8;
|
||||
Tparam *head = NULL, *tail = NULL;
|
||||
for (Node *p = n->list; p; p = p->next) {
|
||||
if (strcmp(p->str ? p->str : "", "...") == 0) {
|
||||
t->variadic = 1;
|
||||
continue;
|
||||
}
|
||||
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||
tp->name = p->str;
|
||||
tp->type = resolve_type(c, p->lhs);
|
||||
if (head == NULL) head = tp;
|
||||
else tail->next = tp;
|
||||
tail = tp;
|
||||
}
|
||||
t->params = head;
|
||||
return t;
|
||||
}
|
||||
case N_TSTRUCT: {
|
||||
Type *t = newtype(c->a, TY_STRUCT);
|
||||
Tfield *head = NULL, *tail = NULL;
|
||||
u64 off = 0, maxalign = 1;
|
||||
for (Node *f = n->list; f; f = f->next) {
|
||||
Tfield *tf = amalloc(c->a, sizeof *tf);
|
||||
tf->name = f->str;
|
||||
tf->type = resolve_type(c, f->lhs);
|
||||
if (tf->type->align > maxalign) maxalign = tf->type->align;
|
||||
off = (off + tf->type->align - 1) & ~(tf->type->align - 1);
|
||||
tf->offset = off;
|
||||
off += tf->type->size;
|
||||
if (head == NULL) head = tf;
|
||||
else tail->next = tf;
|
||||
tail = tf;
|
||||
}
|
||||
t->fields = head;
|
||||
t->align = maxalign;
|
||||
t->size = (off + maxalign - 1) & ~(maxalign - 1);
|
||||
return t;
|
||||
}
|
||||
default:
|
||||
return err(c, n->pos, "expected type expression");
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- expressions -------------------------------------------------- */
|
||||
|
||||
static Type *
|
||||
unify_arith(Checker *c, Pos p, Type *a, Type *b)
|
||||
{
|
||||
if (a == ty_err || b == ty_err) return ty_err;
|
||||
/* untyped + untyped → untyped (prefer float over int) */
|
||||
if (type_isuntyped(a) && type_isuntyped(b)) {
|
||||
if (a->kind == TY_UNTYPED_FLOAT || b->kind == TY_UNTYPED_FLOAT)
|
||||
return ty_untyped_float;
|
||||
return ty_untyped_int;
|
||||
}
|
||||
/* untyped + typed → typed (if assignable) */
|
||||
if (type_isuntyped(a) && type_assignable(b, a)) return b;
|
||||
if (type_isuntyped(b) && type_assignable(a, b)) return a;
|
||||
if (type_eq(a, b)) return a;
|
||||
return err(c, p, "operands have differing types %s and %s",
|
||||
type_name(c->a, a), type_name(c->a, b));
|
||||
}
|
||||
|
||||
static Type *
|
||||
cbinop(Checker *c, Node *n)
|
||||
{
|
||||
Type *l = cexpr(c, n->lhs);
|
||||
Type *r = cexpr(c, n->rhs);
|
||||
switch (n->op) {
|
||||
case TK_PLUS: case TK_MINUS: case TK_STAR: case TK_SLASH:
|
||||
case TK_PERCENT:
|
||||
/* pointer arithmetic: ptr ± int → ptr; ptr - ptr → int */
|
||||
if ((n->op == TK_PLUS || n->op == TK_MINUS)
|
||||
&& l && l->kind == TY_PTR && type_isint(r))
|
||||
return l;
|
||||
if (n->op == TK_PLUS && type_isint(l) && r && r->kind == TY_PTR)
|
||||
return r;
|
||||
if (n->op == TK_MINUS && l && r && l->kind == TY_PTR
|
||||
&& r->kind == TY_PTR)
|
||||
return ty_i64;
|
||||
if (!type_isnum(l) || !type_isnum(r))
|
||||
return err(c, n->pos, "arithmetic on non-numeric type");
|
||||
return unify_arith(c, n->pos, l, r);
|
||||
case TK_AMP: case TK_PIPE: case TK_CARET: case TK_LSHIFT:
|
||||
case TK_RSHIFT:
|
||||
if (!type_isint(l) || !type_isint(r))
|
||||
return err(c, n->pos, "bitwise on non-integer type");
|
||||
return unify_arith(c, n->pos, l, r);
|
||||
case TK_EQ: case TK_NEQ:
|
||||
(void)unify_arith(c, n->pos, l, r);
|
||||
return ty_bool;
|
||||
case TK_LT: case TK_LE: case TK_GT: case TK_GE:
|
||||
if (!type_isnum(l) || !type_isnum(r))
|
||||
err(c, n->pos, "ordered comparison on non-numeric");
|
||||
(void)unify_arith(c, n->pos, l, r);
|
||||
return ty_bool;
|
||||
case TK_AND: case TK_OR:
|
||||
if (!(l == ty_bool || l == ty_untyped_bool || l == ty_err))
|
||||
err(c, n->pos, "left of %s is not bool", tokname(n->op));
|
||||
if (!(r == ty_bool || r == ty_untyped_bool || r == ty_err))
|
||||
err(c, n->pos, "right of %s is not bool", tokname(n->op));
|
||||
return ty_bool;
|
||||
default:
|
||||
return err(c, n->pos, "unsupported binary op %s", tokname(n->op));
|
||||
}
|
||||
}
|
||||
|
||||
static Type *
|
||||
cunop(Checker *c, Node *n)
|
||||
{
|
||||
Type *t = cexpr(c, n->lhs);
|
||||
switch (n->op) {
|
||||
case TK_MINUS: case TK_PLUS:
|
||||
if (!type_isnum(t))
|
||||
return err(c, n->pos, "%s on non-numeric", tokname(n->op));
|
||||
return t;
|
||||
case TK_NOT:
|
||||
if (!(t == ty_bool || t == ty_untyped_bool || t == ty_err))
|
||||
err(c, n->pos, "! on non-bool");
|
||||
return ty_bool;
|
||||
case TK_TILDE:
|
||||
if (!type_isint(t))
|
||||
return err(c, n->pos, "~ on non-integer");
|
||||
return t;
|
||||
case TK_STAR: /* deref */
|
||||
if (t == ty_err) return ty_err;
|
||||
if (t->kind != TY_PTR)
|
||||
return err(c, n->pos, "cannot deref non-pointer %s",
|
||||
type_name(c->a, t));
|
||||
return t->sub;
|
||||
case TK_AMP: /* address-of */
|
||||
return type_ptr(c->a, t);
|
||||
default:
|
||||
return err(c, n->pos, "unsupported unary %s", tokname(n->op));
|
||||
}
|
||||
}
|
||||
|
||||
static Type *
|
||||
cexpr(Checker *c, Node *n)
|
||||
{
|
||||
if (n == NULL) return ty_err;
|
||||
switch (n->kind) {
|
||||
case N_INTLIT:
|
||||
if (n->tsuffix) {
|
||||
Type *t = lookup_builtin(n->tsuffix);
|
||||
n->type = t ? t : ty_untyped_int;
|
||||
} else {
|
||||
n->type = ty_untyped_int;
|
||||
}
|
||||
return n->type;
|
||||
case N_FLOATLIT:
|
||||
if (n->tsuffix) {
|
||||
Type *t = lookup_builtin(n->tsuffix);
|
||||
n->type = t ? t : ty_untyped_float;
|
||||
} else {
|
||||
n->type = ty_untyped_float;
|
||||
}
|
||||
return n->type;
|
||||
case N_STRLIT: n->type = ty_untyped_str; return n->type;
|
||||
case N_RUNELIT: n->type = ty_untyped_rune; return n->type;
|
||||
case N_TRUE:
|
||||
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: {
|
||||
Sym *s = scope_lookup(c->cur, n->str);
|
||||
if (s == NULL)
|
||||
return n->type = err(c, n->pos, "undefined: %s", n->str);
|
||||
/* SK_USE has no concrete value type; the only legal use is
|
||||
* as the lhs of a DOT (module-qualified ref). Surface ty_err
|
||||
* here; the DOT case below resolves the qualified symbol. */
|
||||
if (s->kind == SK_USE)
|
||||
return n->type = ty_err;
|
||||
n->type = s->type;
|
||||
return s->type;
|
||||
}
|
||||
case N_PARAM:
|
||||
return n->type = ty_err; /* shouldn't appear in expr ctx */
|
||||
case N_BIN: n->type = cbinop(c, n); return n->type;
|
||||
case N_UN: n->type = cunop(c, n); return n->type;
|
||||
case N_CAST: {
|
||||
(void)cexpr(c, n->lhs);
|
||||
n->type = resolve_type(c, n->rhs);
|
||||
return n->type;
|
||||
}
|
||||
case N_DOT: {
|
||||
/* module-qualified: lhs is an N_IDENT bound as SK_USE.
|
||||
* Resolve to the symbol with the same leaf name. With
|
||||
* driver-side concatenation, all symbols live in flat
|
||||
* scope, so we lookup `n->str` directly. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT) {
|
||||
Sym *ms = scope_lookup(c->cur, n->lhs->str);
|
||||
if (ms && ms->kind == SK_USE) {
|
||||
Sym *fs = scope_lookup(c->cur, n->str);
|
||||
if (fs)
|
||||
return n->type = fs->type;
|
||||
/* Leaf isn't in scope here — treat as an
|
||||
* external declaration. The codegen will
|
||||
* still emit CALL/MOVQ by the leaf name; the
|
||||
* linker fails if the symbol is truly
|
||||
* missing. */
|
||||
return n->type = ty_err;
|
||||
}
|
||||
}
|
||||
Type *base = cexpr(c, n->lhs);
|
||||
if (base == NULL || base == ty_err) return n->type = ty_err;
|
||||
Type *u = (base->kind == TY_NAMED) ? base->under : base;
|
||||
if (u && u->kind == TY_PTR) u = u->sub;
|
||||
if (u && u->kind == TY_NAMED) u = u->under;
|
||||
/* built-in pseudo-fields on slice/str/array: .len, .cap, .ptr */
|
||||
if (u && (u->kind == TY_SLICE || u->kind == TY_ARRAY ||
|
||||
u->kind == TY_STR)) {
|
||||
if (strcmp(n->str, "len") == 0) return n->type = ty_i32;
|
||||
if (strcmp(n->str, "cap") == 0) return n->type = ty_i32;
|
||||
if (strcmp(n->str, "ptr") == 0) {
|
||||
Type *elem = (u->kind == TY_STR) ? ty_u8 : u->sub;
|
||||
return n->type = type_ptr(c->a, elem);
|
||||
}
|
||||
}
|
||||
if (u && u->kind == TY_STRUCT) {
|
||||
for (Tfield *f = u->fields; f; f = f->next)
|
||||
if (strcmp(f->name, n->str) == 0)
|
||||
return n->type = f->type;
|
||||
return n->type = err(c, n->pos, "no field '%s' in %s",
|
||||
n->str, type_name(c->a, base));
|
||||
}
|
||||
/* tuple positional access: t.0, t.1, ... */
|
||||
if (u && u->kind == TY_TUPLE && n->str) {
|
||||
int idx = 0;
|
||||
for (const char *q = n->str; *q; q++) {
|
||||
if (*q < '0' || *q > '9') { idx = -1; break; }
|
||||
idx = idx * 10 + (*q - '0');
|
||||
}
|
||||
if (idx < 0)
|
||||
return n->type = err(c, n->pos,
|
||||
"tuple field must be numeric");
|
||||
Tparam *tp = u->params;
|
||||
while (idx > 0 && tp) { tp = tp->next; idx--; }
|
||||
if (tp == NULL)
|
||||
return n->type = err(c, n->pos,
|
||||
"tuple index out of range");
|
||||
return n->type = tp->type;
|
||||
}
|
||||
/* module-qualified: lhs is IDENT bound as SK_USE */
|
||||
return n->type = ty_err;
|
||||
}
|
||||
case N_INDEX: {
|
||||
Type *base = cexpr(c, n->lhs);
|
||||
Type *idx = cexpr(c, n->rhs);
|
||||
if (idx != ty_err && !type_isint(idx))
|
||||
err(c, n->pos, "index must be integer");
|
||||
if (base == ty_err) return n->type = ty_err;
|
||||
Type *u = (base->kind == TY_NAMED) ? base->under : base;
|
||||
if (u && (u->kind == TY_SLICE || u->kind == TY_ARRAY))
|
||||
return n->type = u->sub;
|
||||
if (u && u->kind == TY_STR)
|
||||
return n->type = ty_u8;
|
||||
if (u && u->kind == TY_PTR && u->sub &&
|
||||
(u->sub->kind == TY_ARRAY || u->sub->kind == TY_SLICE))
|
||||
return n->type = u->sub->sub;
|
||||
/* C-style pointer indexing: p[i] → *(p+i) */
|
||||
if (u && u->kind == TY_PTR && u->sub)
|
||||
return n->type = u->sub;
|
||||
return n->type = err(c, n->pos, "indexing non-indexable %s",
|
||||
type_name(c->a, base));
|
||||
}
|
||||
case N_CALL: {
|
||||
/* Hare-style builtins: len(x), append(s, v), alloc(...).
|
||||
* Recognised by name with no scope binding; we type-check
|
||||
* the args ourselves and skip the normal call resolution. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT &&
|
||||
n->lhs->str && strcmp(n->lhs->str, "len") == 0 &&
|
||||
n->list != NULL && n->list->next == NULL) {
|
||||
(void)cexpr(c, n->list);
|
||||
n->type = ty_i32;
|
||||
n->lhs->type = ty_err; /* mark builtin: no real symbol */
|
||||
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) {
|
||||
for (Node *a = n->list; a; a = a->next)
|
||||
(void)cexpr(c, a);
|
||||
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, "alloc") == 0 &&
|
||||
n->list != NULL && n->list->next == NULL) {
|
||||
Type *t = cexpr(c, n->list);
|
||||
Type *def = type_default(t);
|
||||
n->type = type_ptr(c->a, def ? def : 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, "free") == 0 &&
|
||||
n->list != NULL && n->list->next == NULL) {
|
||||
(void)cexpr(c, n->list);
|
||||
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. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT &&
|
||||
n->lhs->str && strcmp(n->lhs->str, "alloc") == 0 &&
|
||||
n->list && n->list->kind == N_ARRLIT &&
|
||||
n->list->list == NULL &&
|
||||
n->list->next && n->list->next->next == NULL) {
|
||||
(void)cexpr(c, n->list->next);
|
||||
n->type = type_slice(c->a, ty_u8);
|
||||
n->lhs->type = ty_err;
|
||||
return n->type;
|
||||
}
|
||||
Type *ft = cexpr(c, n->lhs);
|
||||
if (ft == ty_err) {
|
||||
/* Walk args anyway so cgen sees real types. The
|
||||
* common case is a module-qualified call whose leaf
|
||||
* isn't in this scope (raw w6c on a single file with
|
||||
* `use mod;` but no driver concatenation). */
|
||||
for (Node *a = n->list; a; a = a->next)
|
||||
(void)cexpr(c, a);
|
||||
return n->type = ty_err;
|
||||
}
|
||||
Type *u = (ft->kind == TY_NAMED) ? ft->under : ft;
|
||||
if (u == NULL || u->kind != TY_FN)
|
||||
return n->type = err(c, n->pos, "calling non-function %s",
|
||||
type_name(c->a, ft));
|
||||
Tparam *p = u->params;
|
||||
for (Node *a = n->list; a; a = a->next) {
|
||||
Type *at = cexpr(c, a);
|
||||
if (p == NULL) {
|
||||
if (!u->variadic)
|
||||
err(c, n->pos, "too many arguments");
|
||||
continue;
|
||||
}
|
||||
if (!type_assignable(p->type, at) && at != ty_err && p->type != ty_err)
|
||||
err(c, a->pos, "argument type %s not assignable to %s",
|
||||
type_name(c->a, at), type_name(c->a, p->type));
|
||||
p = p->next;
|
||||
}
|
||||
if (p != NULL)
|
||||
err(c, n->pos, "not enough arguments");
|
||||
return n->type = u->ret ? u->ret : ty_void;
|
||||
}
|
||||
case N_ASSIGN: {
|
||||
Type *l = cexpr(c, n->lhs);
|
||||
Type *r = cexpr(c, n->rhs);
|
||||
if (l != ty_err && r != ty_err && !type_assignable(l, r))
|
||||
err(c, n->pos, "cannot assign %s to %s",
|
||||
type_name(c->a, r), type_name(c->a, l));
|
||||
return n->type = l;
|
||||
}
|
||||
case N_STRUCTLIT: {
|
||||
/* lhs may be an N_IDENT (the bare type name) or a real type
|
||||
* expression. Resolve via name lookup first; fall back to
|
||||
* resolve_type for the synthetic-type-expr case. */
|
||||
Type *t = NULL;
|
||||
if (n->lhs && n->lhs->kind == N_IDENT) {
|
||||
Sym *s = scope_lookup(c->cur, n->lhs->str);
|
||||
if (s == NULL || s->kind != SK_TYPE)
|
||||
t = err(c, n->pos, "unknown struct type '%s'",
|
||||
n->lhs->str);
|
||||
else
|
||||
t = s->type;
|
||||
} else {
|
||||
t = resolve_type(c, n->lhs);
|
||||
}
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
for (Node *f = n->list; f; f = f->next) {
|
||||
Type *vt = cexpr(c, f->lhs);
|
||||
if (u && u->kind == TY_STRUCT) {
|
||||
Tfield *match = NULL;
|
||||
for (Tfield *fl = u->fields; fl; fl = fl->next)
|
||||
if (strcmp(fl->name, f->str) == 0) {
|
||||
match = fl; break;
|
||||
}
|
||||
if (match == NULL)
|
||||
err(c, f->pos, "no field '%s' in %s",
|
||||
f->str, type_name(c->a, t));
|
||||
else if (vt != ty_err &&
|
||||
!type_assignable(match->type, vt))
|
||||
err(c, f->pos, "field %s: %s not assignable to %s",
|
||||
f->str, type_name(c->a, vt),
|
||||
type_name(c->a, match->type));
|
||||
}
|
||||
}
|
||||
return n->type = t;
|
||||
}
|
||||
case N_ARRLIT: {
|
||||
Type *elt = NULL;
|
||||
u64 count = 0;
|
||||
for (Node *e = n->list; e; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str &&
|
||||
strcmp(e->str, "...") == 0)
|
||||
continue;
|
||||
Type *t = cexpr(c, e);
|
||||
if (elt == NULL) elt = type_default(t);
|
||||
count++;
|
||||
}
|
||||
if (elt == NULL) elt = ty_i32;
|
||||
return n->type = type_array(c->a, elt, count);
|
||||
}
|
||||
case N_SPREAD:
|
||||
return n->type = cexpr(c, n->lhs);
|
||||
case N_SLICE: {
|
||||
Type *base = cexpr(c, n->lhs);
|
||||
if (n->rhs) (void)cexpr(c, n->rhs);
|
||||
if (n->cond) (void)cexpr(c, n->cond);
|
||||
Type *u = (base && base->kind == TY_NAMED) ? base->under : base;
|
||||
if (u && u->kind == TY_ARRAY)
|
||||
return n->type = type_slice(c->a, u->sub);
|
||||
if (u && u->kind == TY_SLICE)
|
||||
return n->type = base;
|
||||
if (u && u->kind == TY_STR)
|
||||
return n->type = ty_str;
|
||||
if (u && u->kind == TY_PTR && u->sub)
|
||||
return n->type = type_slice(c->a, u->sub);
|
||||
return n->type = err(c, n->pos, "cannot slice %s",
|
||||
type_name(c->a, base));
|
||||
}
|
||||
case N_RECV: {
|
||||
Type *t = cexpr(c, n->lhs);
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
if (u && u->kind == TY_CHAN) return n->type = u->sub;
|
||||
return n->type = err(c, n->pos, "<- expects chan, got %s",
|
||||
type_name(c->a, t));
|
||||
}
|
||||
case N_MATCH: {
|
||||
Type *st = cexpr(c, n->lhs);
|
||||
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||
if (u == NULL || u->kind != TY_TAGGED) {
|
||||
return n->type = err(c, n->pos,
|
||||
"match on non-tagged-union %s", type_name(c->a, st));
|
||||
}
|
||||
for (Node *cs = n->list; cs; cs = cs->next) {
|
||||
Scope *saved = c->cur;
|
||||
c->cur = newscope(c->a, saved);
|
||||
/* Resolve the case pattern's type so codegen can map it
|
||||
* to the variant tag. Both `case T =>` and `case let v: T
|
||||
* =>` get this — `case =>` (default) leaves cs->type NULL.
|
||||
* For multi-pattern `case T1 | T2 =>` each alternative in
|
||||
* cs->list also gets its type resolved in place. */
|
||||
if (cs->lhs) {
|
||||
Type *vt = resolve_type(c, cs->lhs);
|
||||
cs->type = vt;
|
||||
for (Node *alt = cs->list; alt; alt = alt->next)
|
||||
alt->type = resolve_type(c, alt);
|
||||
if (cs->str && cs->str[0])
|
||||
scope_define(c->cur, cs->str, SK_VAR, vt, cs);
|
||||
}
|
||||
cstmt(c, cs->body);
|
||||
c->cur = saved;
|
||||
}
|
||||
n->type = ty_void;
|
||||
return n->type;
|
||||
}
|
||||
case N_TRYPROP: case N_TRYUNW: {
|
||||
Type *t = cexpr(c, n->lhs);
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
if (u == NULL || u->kind != TY_TAGGED) {
|
||||
return n->type = err(c, n->pos,
|
||||
"%s on non-tagged-union %s",
|
||||
n->kind == N_TRYPROP ? "?" : "!",
|
||||
type_name(c->a, t));
|
||||
}
|
||||
/* Convention: first variant is the success type. */
|
||||
Tparam *first = u->params;
|
||||
return n->type = first ? first->type : ty_err;
|
||||
}
|
||||
case N_TUPLE: {
|
||||
/* keep untyped element types; assignability is checked
|
||||
* element-wise at the consumer (return / mlet / massign). */
|
||||
Type *t = newtype(c->a, TY_TUPLE);
|
||||
Tparam *head = NULL, *tail = NULL;
|
||||
for (Node *e = n->list; e; e = e->next) {
|
||||
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||
tp->type = cexpr(c, e);
|
||||
if (head == NULL) head = tp;
|
||||
else tail->next = tp;
|
||||
tail = tp;
|
||||
}
|
||||
t->params = head;
|
||||
return n->type = t;
|
||||
}
|
||||
default:
|
||||
return n->type = err(c, n->pos, "internal: unhandled expr kind %d",
|
||||
n->kind);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- statements --------------------------------------------------- */
|
||||
|
||||
static void
|
||||
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);
|
||||
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 &&
|
||||
!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);
|
||||
}
|
||||
|
||||
static void
|
||||
cstmt(Checker *c, Node *n)
|
||||
{
|
||||
if (n == NULL) return;
|
||||
switch (n->kind) {
|
||||
case N_BLOCK: {
|
||||
Scope *saved = c->cur;
|
||||
c->cur = newscope(c->a, saved);
|
||||
for (Node *s = n->list; s; s = s->next)
|
||||
cstmt(c, s);
|
||||
c->cur = saved;
|
||||
break;
|
||||
}
|
||||
case N_EXPRSTMT: (void)cexpr(c, n->lhs); break;
|
||||
case N_LET: clet(c, n); break;
|
||||
case N_RETURN: {
|
||||
Type *rt = n->lhs ? cexpr(c, n->lhs) : ty_void;
|
||||
if (c->ret == NULL) {
|
||||
err(c, n->pos, "return outside function");
|
||||
break;
|
||||
}
|
||||
if (c->ret == ty_void && n->lhs)
|
||||
err(c, n->pos, "return value in void function");
|
||||
else if (c->ret != ty_void && rt != ty_err && c->ret != ty_err
|
||||
&& !type_assignable(c->ret, rt))
|
||||
err(c, n->pos, "return %s not assignable to %s",
|
||||
type_name(c->a, rt), type_name(c->a, c->ret));
|
||||
break;
|
||||
}
|
||||
case N_IF: {
|
||||
Type *ct = cexpr(c, n->cond);
|
||||
if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool)
|
||||
err(c, n->pos, "if condition must be bool, got %s",
|
||||
type_name(c->a, ct));
|
||||
cstmt(c, n->body);
|
||||
cstmt(c, n->els);
|
||||
break;
|
||||
}
|
||||
case N_FORRANGE: {
|
||||
Scope *saved = c->cur;
|
||||
c->cur = newscope(c->a, saved);
|
||||
c->loops++;
|
||||
Type *st = cexpr(c, n->lhs);
|
||||
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||
Type *elem = NULL;
|
||||
if (u && (u->kind == TY_SLICE || u->kind == TY_ARRAY)) elem = u->sub;
|
||||
else if (u && u->kind == TY_STR) elem = ty_u8;
|
||||
else err(c, n->pos, "for-range needs slice/array/str");
|
||||
if (n->list != NULL) {
|
||||
/* tuple destructure: each name binds to a tuple field */
|
||||
Type *etu = (elem && elem->kind == TY_NAMED) ? elem->under : elem;
|
||||
Tparam *tp = (etu && etu->kind == TY_TUPLE) ? etu->params : NULL;
|
||||
for (Node *nm = n->list; nm; nm = nm->next) {
|
||||
Type *ft = tp ? tp->type : ty_err;
|
||||
if (nm->str && nm->str[0])
|
||||
scope_define(c->cur, nm->str,
|
||||
SK_VAR, ft, nm);
|
||||
if (tp) tp = tp->next;
|
||||
}
|
||||
} else if (n->str && n->str[0]) {
|
||||
scope_define(c->cur, n->str, SK_VAR,
|
||||
elem ? elem : ty_err, n);
|
||||
}
|
||||
cstmt(c, n->body);
|
||||
c->loops--;
|
||||
c->cur = saved;
|
||||
break;
|
||||
}
|
||||
case N_FOR: {
|
||||
Scope *saved = c->cur;
|
||||
c->cur = newscope(c->a, saved);
|
||||
c->loops++;
|
||||
if (n->lhs) cstmt(c, n->lhs); /* init may be a let or expr */
|
||||
if (n->cond) {
|
||||
Type *ct = cexpr(c, n->cond);
|
||||
if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool)
|
||||
err(c, n->pos, "for condition must be bool, got %s",
|
||||
type_name(c->a, ct));
|
||||
}
|
||||
if (n->rhs) (void)cexpr(c, n->rhs);
|
||||
cstmt(c, n->body);
|
||||
c->loops--;
|
||||
c->cur = saved;
|
||||
break;
|
||||
}
|
||||
case N_MLET: {
|
||||
Type *rt = cexpr(c, n->rhs);
|
||||
Type *u = (rt && rt->kind == TY_TUPLE) ? rt : NULL;
|
||||
if (u == NULL) {
|
||||
err(c, n->pos, "multi-let rhs is not a tuple (got %s)",
|
||||
type_name(c->a, rt));
|
||||
}
|
||||
Tparam *tp = u ? u->params : NULL;
|
||||
for (Node *l = n->list; l; l = l->next) {
|
||||
Type *declared = l->lhs ? resolve_type(c, l->lhs) : NULL;
|
||||
Type *elem = tp ? tp->type : NULL;
|
||||
Type *t = declared ? declared :
|
||||
(elem ? type_default(elem) : ty_err);
|
||||
if (declared && elem && !type_assignable(declared, elem))
|
||||
err(c, l->pos, "let %s: %s not assignable from %s",
|
||||
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 (tp) tp = tp->next;
|
||||
}
|
||||
if (u && tp != NULL)
|
||||
err(c, n->pos, "tuple has extra elements");
|
||||
break;
|
||||
}
|
||||
case N_MASSIGN: {
|
||||
Type *rt = cexpr(c, n->rhs);
|
||||
Type *u = (rt && rt->kind == TY_TUPLE) ? rt : NULL;
|
||||
if (u == NULL) {
|
||||
err(c, n->pos, "multi-assign rhs is not a tuple (got %s)",
|
||||
type_name(c->a, rt));
|
||||
}
|
||||
Tparam *tp = u ? u->params : NULL;
|
||||
for (Node *lv = n->list; lv; lv = lv->next) {
|
||||
Type *lt = cexpr(c, lv);
|
||||
Type *elem = tp ? tp->type : NULL;
|
||||
if (lt && elem && !type_assignable(lt, elem))
|
||||
err(c, lv->pos, "cannot assign %s to %s",
|
||||
type_name(c->a, elem), type_name(c->a, lt));
|
||||
if (tp) tp = tp->next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case N_DEFER: (void)cexpr(c, n->lhs); break;
|
||||
case N_BREAK:
|
||||
case N_CONTINUE:
|
||||
if (c->loops == 0)
|
||||
err(c, n->pos, "%s outside loop",
|
||||
n->kind == N_BREAK ? "break" : "continue");
|
||||
break;
|
||||
case N_SWITCH: {
|
||||
Type *st = cexpr(c, n->lhs);
|
||||
(void)st;
|
||||
for (Node *cs = n->list; cs; cs = cs->next) {
|
||||
for (Node *e = cs->list; e; e = e->next)
|
||||
(void)cexpr(c, e);
|
||||
cstmt(c, cs->body);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
err(c, n->pos, "internal: unhandled stmt kind %d", n->kind);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- top-level ---------------------------------------------------- */
|
||||
|
||||
static Type *
|
||||
build_fn_type(Checker *c, Node *fn)
|
||||
{
|
||||
Type *t = newtype(c->a, TY_FN);
|
||||
t->size = 8; t->align = 8;
|
||||
t->ret = fn->lhs ? resolve_type(c, fn->lhs) : ty_void;
|
||||
Tparam *head = NULL, *tail = NULL;
|
||||
for (Node *p = fn->list; p; p = p->next) {
|
||||
if (p->str && strcmp(p->str, "...") == 0) {
|
||||
t->variadic = 1;
|
||||
continue;
|
||||
}
|
||||
Tparam *tp = amalloc(c->a, sizeof *tp);
|
||||
tp->name = p->str;
|
||||
tp->type = resolve_type(c, p->lhs);
|
||||
if (head == NULL) head = tp;
|
||||
else tail->next = tp;
|
||||
tail = tp;
|
||||
}
|
||||
t->params = head;
|
||||
return t;
|
||||
}
|
||||
|
||||
void
|
||||
check_init(Checker *c, Arena *a)
|
||||
{
|
||||
memset(c, 0, sizeof *c);
|
||||
c->a = a;
|
||||
typesinit(a);
|
||||
c->top = newscope(a, NULL);
|
||||
c->cur = c->top;
|
||||
}
|
||||
|
||||
void
|
||||
check_file(Checker *c, Node *file)
|
||||
{
|
||||
if (file == NULL || file->kind != N_FILE) return;
|
||||
|
||||
/* pass 1: install names (types first, then defs/fns).
|
||||
* For self-referential types we install the named-type placeholder
|
||||
* BEFORE resolving its body; the body may legitimately mention
|
||||
* the type itself (`type stream = struct { read: fn(*stream)... }`).
|
||||
*/
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_TYPEDECL) continue;
|
||||
Type *named = type_named(c->a, d->str, NULL);
|
||||
if (!scope_define(c->cur, d->str, SK_TYPE, named, d))
|
||||
err(c, d->pos, "duplicate type %s", d->str);
|
||||
d->type = named;
|
||||
}
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_TYPEDECL) continue;
|
||||
Type *under = resolve_type(c, d->lhs);
|
||||
d->type->under = under;
|
||||
if (under) {
|
||||
d->type->size = under->size;
|
||||
d->type->align = under->align;
|
||||
}
|
||||
}
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
switch (d->kind) {
|
||||
case N_USE:
|
||||
scope_define(c->cur, d->str, SK_USE, NULL, d);
|
||||
break;
|
||||
case N_DEF: {
|
||||
Type *t = resolve_type(c, d->lhs);
|
||||
d->type = t;
|
||||
if (!scope_define(c->cur, d->str, SK_DEF, t, d))
|
||||
err(c, d->pos, "duplicate def %s", d->str);
|
||||
break;
|
||||
}
|
||||
case N_FNDECL: {
|
||||
Type *t = build_fn_type(c, d);
|
||||
d->type = t;
|
||||
if (!scope_define(c->cur, d->str, SK_FN, t, d))
|
||||
err(c, d->pos, "duplicate fn %s", d->str);
|
||||
break;
|
||||
}
|
||||
case N_LET: {
|
||||
Type *t = d->lhs ? resolve_type(c, d->lhs) : NULL;
|
||||
d->type = t;
|
||||
if (d->str && d->str[0])
|
||||
scope_define(c->cur, d->str, SK_VAR, t, d);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
/* pass 2: check def initialisers and fn bodies */
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
switch (d->kind) {
|
||||
case N_DEF: {
|
||||
if (d->rhs) {
|
||||
Type *rt = cexpr(c, d->rhs);
|
||||
if (d->type && rt != ty_err && d->type != ty_err
|
||||
&& !type_assignable(d->type, rt))
|
||||
err(c, d->pos, "def %s init %s not assignable to %s",
|
||||
d->str, type_name(c->a, rt),
|
||||
type_name(c->a, d->type));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case N_FNDECL: {
|
||||
if (d->body == NULL) break; /* extern decl */
|
||||
Scope *saved = c->cur;
|
||||
c->cur = newscope(c->a, saved);
|
||||
Type *fnt = d->type;
|
||||
for (Tparam *p = fnt->params; p; p = p->next) {
|
||||
if (p->name && p->name[0])
|
||||
scope_define(c->cur, p->name, SK_PARAM, p->type, d);
|
||||
}
|
||||
Type *prev = c->ret;
|
||||
c->ret = fnt->ret;
|
||||
cstmt(c, d->body);
|
||||
c->ret = prev;
|
||||
c->cur = saved;
|
||||
break;
|
||||
}
|
||||
case N_LET: {
|
||||
if (d->rhs) {
|
||||
Type *rt = cexpr(c, d->rhs);
|
||||
if (d->type == NULL) d->type = type_default(rt);
|
||||
if (d->type && rt != ty_err && d->type != ty_err
|
||||
&& !type_assignable(d->type, rt))
|
||||
err(c, d->pos, "let %s init not assignable",
|
||||
d->str);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
76
cmd/wcc/err.c
Normal file
76
cmd/wcc/err.c
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* err.c — diagnostics.
|
||||
*
|
||||
* fatal prints, sets exit(1).
|
||||
* errorf prints with source location, increments nerrors.
|
||||
* warnf prints with source location, increments nwarnings.
|
||||
*
|
||||
* Plan 9 style: short, no levels beyond fatal/error/warn, no colour.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
Pos noPos = { "<none>", 0, 0 };
|
||||
int nerrors;
|
||||
int nwarnings;
|
||||
FILE *errout; /* set by main; defaults to stderr */
|
||||
|
||||
static FILE *
|
||||
out(void)
|
||||
{
|
||||
return errout ? errout : stderr;
|
||||
}
|
||||
|
||||
static void
|
||||
prefix(Pos p)
|
||||
{
|
||||
FILE *f = out();
|
||||
if (p.file == NULL)
|
||||
p = noPos;
|
||||
if (p.line > 0)
|
||||
fprintf(f, "%s:%d:%d: ", p.file, p.line, p.col);
|
||||
else
|
||||
fprintf(f, "%s: ", p.file);
|
||||
}
|
||||
|
||||
void
|
||||
fatal(const char *fmt, ...)
|
||||
{
|
||||
FILE *f = out();
|
||||
va_list ap;
|
||||
fprintf(f, "ww: ");
|
||||
va_start(ap, fmt);
|
||||
vfprintf(f, fmt, ap);
|
||||
va_end(ap);
|
||||
fprintf(f, "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void
|
||||
errorf(Pos p, const char *fmt, ...)
|
||||
{
|
||||
FILE *f = out();
|
||||
va_list ap;
|
||||
prefix(p);
|
||||
fprintf(f, "error: ");
|
||||
va_start(ap, fmt);
|
||||
vfprintf(f, fmt, ap);
|
||||
va_end(ap);
|
||||
fprintf(f, "\n");
|
||||
nerrors++;
|
||||
}
|
||||
|
||||
void
|
||||
warnf(Pos p, const char *fmt, ...)
|
||||
{
|
||||
FILE *f = out();
|
||||
va_list ap;
|
||||
prefix(p);
|
||||
fprintf(f, "warning: ");
|
||||
va_start(ap, fmt);
|
||||
vfprintf(f, fmt, ap);
|
||||
va_end(ap);
|
||||
fprintf(f, "\n");
|
||||
nwarnings++;
|
||||
}
|
||||
476
cmd/wcc/lex.c
Normal file
476
cmd/wcc/lex.c
Normal file
@@ -0,0 +1,476 @@
|
||||
/*
|
||||
* lex.c — hand-rolled DFA. UTF-8 source, ASCII operators.
|
||||
*
|
||||
* Comments: //... and (slash-star ... star-slash). Both stripped.
|
||||
* Whitespace: space, tab, CR, NL.
|
||||
* Identifiers: [A-Za-z_][A-Za-z0-9_]* — also matches keywords; we
|
||||
* look up the kw table after lexing the run.
|
||||
* Integer: 0x[0-9a-fA-F_]+, 0o[0-7_]+, 0b[01_]+, [0-9][0-9_]*
|
||||
* Float: [0-9]+'.'[0-9]+([eE][+-]?[0-9]+)?
|
||||
* Rune: 'x' with C-like escapes
|
||||
* String: "..." with C-like escapes
|
||||
* Operators: longest match.
|
||||
*
|
||||
* No automatic semicolon insertion (Hare rule). The lexer only emits
|
||||
* what is in the source; the parser is responsible for non-empty rules.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
void
|
||||
lexinit(Lex *l, Arena *a, const char *file, const char *src, u64 len)
|
||||
{
|
||||
memset(l, 0, sizeof *l);
|
||||
l->file = file;
|
||||
l->src = src;
|
||||
l->srclen = len;
|
||||
l->line = 1;
|
||||
l->col = 1;
|
||||
l->a = a;
|
||||
}
|
||||
|
||||
static int
|
||||
lpeek(Lex *l, u64 ahead)
|
||||
{
|
||||
u64 p = l->pos + ahead;
|
||||
if (p >= l->srclen)
|
||||
return -1;
|
||||
return (unsigned char)l->src[p];
|
||||
}
|
||||
|
||||
static int
|
||||
lget(Lex *l)
|
||||
{
|
||||
if (l->pos >= l->srclen)
|
||||
return -1;
|
||||
int c = (unsigned char)l->src[l->pos++];
|
||||
if (c == '\n') {
|
||||
l->line++;
|
||||
l->col = 1;
|
||||
} else {
|
||||
l->col++;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static Pos
|
||||
lpos(Lex *l)
|
||||
{
|
||||
Pos p = { l->file, l->line, l->col };
|
||||
return p;
|
||||
}
|
||||
|
||||
static int
|
||||
isidstart(int c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
||||
}
|
||||
|
||||
static int
|
||||
isidcont(int c)
|
||||
{
|
||||
return isidstart(c) || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
static int
|
||||
ishex(int c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
|
||||
(c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
/* skip whitespace and comments. returns 0 on EOF, else 1. */
|
||||
static int
|
||||
skipws(Lex *l)
|
||||
{
|
||||
for (;;) {
|
||||
int c = lpeek(l, 0);
|
||||
if (c < 0)
|
||||
return 0;
|
||||
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
|
||||
lget(l);
|
||||
continue;
|
||||
}
|
||||
if (c == '/' && lpeek(l, 1) == '/') {
|
||||
while ((c = lpeek(l, 0)) >= 0 && c != '\n')
|
||||
lget(l);
|
||||
continue;
|
||||
}
|
||||
if (c == '/' && lpeek(l, 1) == '*') {
|
||||
lget(l); lget(l);
|
||||
int prev = -1;
|
||||
for (;;) {
|
||||
int x = lget(l);
|
||||
if (x < 0) {
|
||||
Pos p = lpos(l);
|
||||
errorf(p, "unterminated /* comment");
|
||||
l->errs++;
|
||||
return 0;
|
||||
}
|
||||
if (prev == '*' && x == '/')
|
||||
break;
|
||||
prev = x;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static u64
|
||||
parseint(const char *s, u64 n, int base, int *ok)
|
||||
{
|
||||
u64 v = 0;
|
||||
int got = 0;
|
||||
for (u64 i = 0; i < n; i++) {
|
||||
int c = (unsigned char)s[i];
|
||||
if (c == '_')
|
||||
continue;
|
||||
int d;
|
||||
if (c >= '0' && c <= '9') d = c - '0';
|
||||
else if (c >= 'a' && c <= 'f') d = c - 'a' + 10;
|
||||
else if (c >= 'A' && c <= 'F') d = c - 'A' + 10;
|
||||
else { *ok = 0; return 0; }
|
||||
if (d >= base) { *ok = 0; return 0; }
|
||||
/* overflow? cheap check */
|
||||
if (v > (u64)~0ULL / (u64)base) { *ok = 0; return 0; }
|
||||
v = v * (u64)base + (u64)d;
|
||||
got = 1;
|
||||
}
|
||||
*ok = got;
|
||||
return v;
|
||||
}
|
||||
|
||||
static int
|
||||
escape(Lex *l, int *out)
|
||||
{
|
||||
int c = lget(l);
|
||||
if (c < 0) return -1;
|
||||
switch (c) {
|
||||
case 'n': *out = '\n'; return 0;
|
||||
case 't': *out = '\t'; return 0;
|
||||
case 'r': *out = '\r'; return 0;
|
||||
case '\\': *out = '\\'; return 0;
|
||||
case '\'': *out = '\''; return 0;
|
||||
case '"': *out = '"'; return 0;
|
||||
case '0': *out = '\0'; return 0;
|
||||
case 'a': *out = '\a'; return 0;
|
||||
case 'b': *out = '\b'; return 0;
|
||||
case 'f': *out = '\f'; return 0;
|
||||
case 'v': *out = '\v'; return 0;
|
||||
case 'x': {
|
||||
int hi = lget(l), lo = lget(l);
|
||||
if (!ishex(hi) || !ishex(lo)) {
|
||||
Pos p = lpos(l);
|
||||
errorf(p, "bad \\x escape");
|
||||
l->errs++;
|
||||
return -1;
|
||||
}
|
||||
int h = (hi <= '9' ? hi - '0' : (hi | 0x20) - 'a' + 10);
|
||||
int o = (lo <= '9' ? lo - '0' : (lo | 0x20) - 'a' + 10);
|
||||
*out = (h << 4) | o;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
{ Pos p = lpos(l); errorf(p, "bad escape \\%c", c); l->errs++; }
|
||||
return -1;
|
||||
}
|
||||
|
||||
static Tok
|
||||
lexnum(Lex *l, Pos start)
|
||||
{
|
||||
Tok t = (Tok){ TK_INT, start, NULL, 0, {0}, TK_NONE };
|
||||
u64 begin = l->pos;
|
||||
int base = 10;
|
||||
int isfloat = 0;
|
||||
int c = lpeek(l, 0);
|
||||
|
||||
if (c == '0' && (lpeek(l, 1) == 'x' || lpeek(l, 1) == 'X')) {
|
||||
lget(l); lget(l);
|
||||
base = 16;
|
||||
while ((c = lpeek(l, 0)) >= 0 && (ishex(c) || c == '_'))
|
||||
lget(l);
|
||||
} else if (c == '0' && (lpeek(l, 1) == 'b' || lpeek(l, 1) == 'B')) {
|
||||
lget(l); lget(l);
|
||||
base = 2;
|
||||
while ((c = lpeek(l, 0)) >= 0 && (c == '0' || c == '1' || c == '_'))
|
||||
lget(l);
|
||||
} else if (c == '0' && (lpeek(l, 1) == 'o' || lpeek(l, 1) == 'O')) {
|
||||
lget(l); lget(l);
|
||||
base = 8;
|
||||
while ((c = lpeek(l, 0)) >= 0 && ((c >= '0' && c <= '7') || c == '_'))
|
||||
lget(l);
|
||||
} else {
|
||||
while ((c = lpeek(l, 0)) >= 0 && ((c >= '0' && c <= '9') || c == '_'))
|
||||
lget(l);
|
||||
if (lpeek(l, 0) == '.' && lpeek(l, 1) >= '0' && lpeek(l, 1) <= '9') {
|
||||
isfloat = 1;
|
||||
lget(l);
|
||||
while ((c = lpeek(l, 0)) >= 0 && ((c >= '0' && c <= '9') || c == '_'))
|
||||
lget(l);
|
||||
c = lpeek(l, 0);
|
||||
if (c == 'e' || c == 'E') {
|
||||
lget(l);
|
||||
if (lpeek(l, 0) == '+' || lpeek(l, 0) == '-')
|
||||
lget(l);
|
||||
while ((c = lpeek(l, 0)) >= 0 && c >= '0' && c <= '9')
|
||||
lget(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u64 n = l->pos - begin;
|
||||
t.text = astrndup(l->a, l->src + begin, n);
|
||||
t.tlen = n;
|
||||
|
||||
if (isfloat) {
|
||||
t.kind = TK_FLOAT;
|
||||
/* strdup with underscores stripped before strtod */
|
||||
char *clean = amalloc(l->a, n + 1);
|
||||
u64 j = 0;
|
||||
for (u64 i = 0; i < n; i++)
|
||||
if (l->src[begin + i] != '_')
|
||||
clean[j++] = l->src[begin + i];
|
||||
clean[j] = '\0';
|
||||
errno = 0;
|
||||
t.v.fval = strtod(clean, NULL);
|
||||
if (errno) {
|
||||
errorf(start, "bad float literal '%s'", t.text);
|
||||
l->errs++;
|
||||
}
|
||||
} else {
|
||||
const char *digs = l->src + begin;
|
||||
u64 dn = n;
|
||||
if (base != 10) {
|
||||
digs += 2;
|
||||
dn -= 2;
|
||||
}
|
||||
int ok = 0;
|
||||
t.v.uval = parseint(digs, dn, base, &ok);
|
||||
if (!ok) {
|
||||
errorf(start, "bad integer literal '%s'", t.text);
|
||||
l->errs++;
|
||||
t.kind = TK_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* Typed suffix: i8/i16/i32/i64, u8/u16/u32/u64, f32/f64.
|
||||
* Must be glued (no whitespace) to the digits. We grab the
|
||||
* adjacent identifier-like run and accept it only if it's one
|
||||
* of the recognised type names. */
|
||||
if (isidstart(lpeek(l, 0))) {
|
||||
u64 sb = l->pos;
|
||||
while (isidcont(lpeek(l, 0))) lget(l);
|
||||
u64 sl = l->pos - sb;
|
||||
const char *names[] = {
|
||||
"i8", "i16", "i32", "i64",
|
||||
"u8", "u16", "u32", "u64",
|
||||
"f32", "f64", NULL
|
||||
};
|
||||
const char *match = NULL;
|
||||
for (int i = 0; names[i]; i++) {
|
||||
u64 nl = strlen(names[i]);
|
||||
if (nl == sl && memcmp(names[i], l->src + sb, nl) == 0) {
|
||||
match = names[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
t.tsuffix = astrndup(l->a, l->src + sb, sl);
|
||||
} else {
|
||||
/* not a known suffix — rewind so the run becomes a
|
||||
* separate token. */
|
||||
l->pos = sb;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
static Tok
|
||||
lexident(Lex *l, Pos start)
|
||||
{
|
||||
u64 begin = l->pos;
|
||||
while (isidcont(lpeek(l, 0)))
|
||||
lget(l);
|
||||
u64 n = l->pos - begin;
|
||||
const char *p = l->src + begin;
|
||||
Tkind k = kwlookup(p, n);
|
||||
Tok t = (Tok){ k != TK_NONE ? k : TK_IDENT, start,
|
||||
astrndup(l->a, p, n), n, {0}, TK_NONE };
|
||||
return t;
|
||||
}
|
||||
|
||||
static Tok
|
||||
lexstr(Lex *l, Pos start)
|
||||
{
|
||||
/* opening quote already consumed by caller */
|
||||
u64 cap = 32, n = 0;
|
||||
char *buf = amalloc(l->a, cap);
|
||||
for (;;) {
|
||||
int c = lpeek(l, 0);
|
||||
if (c < 0) {
|
||||
errorf(start, "unterminated string");
|
||||
l->errs++;
|
||||
Tok t = (Tok){ TK_ERR, start, astrndup(l->a, "", 0), 0, {0}, TK_NONE };
|
||||
return t;
|
||||
}
|
||||
if (c == '"') { lget(l); break; }
|
||||
int ch;
|
||||
if (c == '\\') {
|
||||
lget(l);
|
||||
if (escape(l, &ch) < 0)
|
||||
ch = 0;
|
||||
} else {
|
||||
ch = lget(l);
|
||||
}
|
||||
if (n + 1 >= cap) {
|
||||
u64 ncap = cap * 2;
|
||||
char *nb = amalloc(l->a, ncap);
|
||||
memcpy(nb, buf, n);
|
||||
buf = nb;
|
||||
cap = ncap;
|
||||
}
|
||||
buf[n++] = (char)ch;
|
||||
}
|
||||
buf[n] = '\0';
|
||||
Tok t = (Tok){ TK_STR, start, buf, n, {0}, TK_NONE };
|
||||
return t;
|
||||
}
|
||||
|
||||
static Tok
|
||||
lexrune(Lex *l, Pos start)
|
||||
{
|
||||
int ch;
|
||||
int c = lpeek(l, 0);
|
||||
if (c < 0) {
|
||||
errorf(start, "unterminated rune");
|
||||
l->errs++;
|
||||
return (Tok){ TK_ERR, start, "", 0, {0}, TK_NONE };
|
||||
}
|
||||
if (c == '\\') {
|
||||
lget(l);
|
||||
if (escape(l, &ch) < 0)
|
||||
ch = 0;
|
||||
} else {
|
||||
ch = lget(l);
|
||||
}
|
||||
if (lpeek(l, 0) != '\'') {
|
||||
errorf(start, "rune literal missing closing '");
|
||||
l->errs++;
|
||||
return (Tok){ TK_ERR, start, "", 0, {0}, TK_NONE };
|
||||
}
|
||||
lget(l);
|
||||
Tok t = (Tok){ TK_RUNE, start, NULL, 0, {0}, TK_NONE };
|
||||
t.v.uval = (u64)(u32)ch;
|
||||
t.text = aprintf(l->a, "%d", ch);
|
||||
t.tlen = strlen(t.text);
|
||||
return t;
|
||||
}
|
||||
|
||||
#define EMIT(K) do { Tok _t = (Tok){ (K), start, NULL, 0, {0}, TK_NONE }; \
|
||||
_t.text = tokname(K); _t.tlen = strlen(_t.text); return _t; } while (0)
|
||||
|
||||
Tok
|
||||
lexnext(Lex *l)
|
||||
{
|
||||
if (!skipws(l)) {
|
||||
Pos p = lpos(l);
|
||||
Tok t = (Tok){ TK_EOF, p, "", 0, {0}, TK_NONE };
|
||||
return t;
|
||||
}
|
||||
Pos start = lpos(l);
|
||||
int c = lpeek(l, 0);
|
||||
|
||||
if (isidstart(c))
|
||||
return lexident(l, start);
|
||||
if (c >= '0' && c <= '9')
|
||||
return lexnum(l, start);
|
||||
|
||||
if (c == '"') { lget(l); return lexstr(l, start); }
|
||||
if (c == '\'') { lget(l); return lexrune(l, start); }
|
||||
|
||||
lget(l);
|
||||
switch (c) {
|
||||
case '(': EMIT(TK_LPAREN);
|
||||
case ')': EMIT(TK_RPAREN);
|
||||
case '{': EMIT(TK_LBRACE);
|
||||
case '}': EMIT(TK_RBRACE);
|
||||
case '[': EMIT(TK_LBRACK);
|
||||
case ']': EMIT(TK_RBRACK);
|
||||
case ',': EMIT(TK_COMMA);
|
||||
case ';': EMIT(TK_SEMI);
|
||||
case ':': EMIT(TK_COLON);
|
||||
case '@': EMIT(TK_AT);
|
||||
case '?': EMIT(TK_QUESTION);
|
||||
case '~': EMIT(TK_TILDE);
|
||||
case '.':
|
||||
if (lpeek(l, 0) == '.' && lpeek(l, 1) == '.') {
|
||||
lget(l); lget(l);
|
||||
EMIT(TK_ELLIPSIS);
|
||||
}
|
||||
if (lpeek(l, 0) == '.') {
|
||||
lget(l);
|
||||
EMIT(TK_DOTDOT);
|
||||
}
|
||||
EMIT(TK_DOT);
|
||||
case '+':
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_PLUSEQ); }
|
||||
EMIT(TK_PLUS);
|
||||
case '-':
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_MINUSEQ); }
|
||||
if (lpeek(l, 0) == '>') { lget(l); EMIT(TK_ARROW); }
|
||||
EMIT(TK_MINUS);
|
||||
case '*':
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_STAREQ); }
|
||||
EMIT(TK_STAR);
|
||||
case '/':
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_SLASHEQ); }
|
||||
EMIT(TK_SLASH);
|
||||
case '%':
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_PERCENTEQ); }
|
||||
EMIT(TK_PERCENT);
|
||||
case '&':
|
||||
if (lpeek(l, 0) == '&') { lget(l); EMIT(TK_AND); }
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_AMPEQ); }
|
||||
EMIT(TK_AMP);
|
||||
case '|':
|
||||
if (lpeek(l, 0) == '|') { lget(l); EMIT(TK_OR); }
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_PIPEEQ); }
|
||||
EMIT(TK_PIPE);
|
||||
case '^':
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_CARETEQ); }
|
||||
EMIT(TK_CARET);
|
||||
case '=':
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_EQ); }
|
||||
if (lpeek(l, 0) == '>') { lget(l); EMIT(TK_FATARROW); }
|
||||
EMIT(TK_ASSIGN);
|
||||
case '!':
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_NEQ); }
|
||||
EMIT(TK_NOT);
|
||||
case '<':
|
||||
if (lpeek(l, 0) == '<') {
|
||||
lget(l);
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_LSHIFTEQ); }
|
||||
EMIT(TK_LSHIFT);
|
||||
}
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_LE); }
|
||||
if (lpeek(l, 0) == '-') { lget(l); EMIT(TK_LARROW); }
|
||||
EMIT(TK_LT);
|
||||
case '>':
|
||||
if (lpeek(l, 0) == '>') {
|
||||
lget(l);
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_RSHIFTEQ); }
|
||||
EMIT(TK_RSHIFT);
|
||||
}
|
||||
if (lpeek(l, 0) == '=') { lget(l); EMIT(TK_GE); }
|
||||
EMIT(TK_GT);
|
||||
}
|
||||
errorf(start, "unexpected character 0x%02x", c);
|
||||
l->errs++;
|
||||
Tok t = (Tok){ TK_ERR, start, NULL, 0, {0}, TK_NONE };
|
||||
t.text = astrndup(l->a, (const char[]){ (char)c }, 1);
|
||||
t.tlen = 1;
|
||||
return t;
|
||||
}
|
||||
117
cmd/wcc/mem.c
Normal file
117
cmd/wcc/mem.c
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* mem.c — arena allocator. No free per allocation; freearena releases
|
||||
* the whole chain. Aligned to 16 so structs with 8-byte fields and
|
||||
* doubles are happy.
|
||||
*
|
||||
* Hot allocations in the compiler land in arenas: tokens, AST nodes,
|
||||
* symbols, types. The chunk size doubles up to a cap so we don't
|
||||
* fragment on huge inputs.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ALIGN 16
|
||||
#define INIT_CHUNK (64 * 1024)
|
||||
#define MAX_CHUNK (4 * 1024 * 1024)
|
||||
|
||||
static u64
|
||||
roundup(u64 n, u64 a)
|
||||
{
|
||||
return (n + a - 1) & ~(a - 1);
|
||||
}
|
||||
|
||||
Arena *
|
||||
newarena(void)
|
||||
{
|
||||
Arena *a = calloc(1, sizeof *a);
|
||||
if (a == NULL)
|
||||
fatal("newarena: out of memory");
|
||||
a->buf = malloc(INIT_CHUNK);
|
||||
if (a->buf == NULL)
|
||||
fatal("newarena: out of memory");
|
||||
a->cap = INIT_CHUNK;
|
||||
return a;
|
||||
}
|
||||
|
||||
static void
|
||||
grow(Arena *a, u64 need)
|
||||
{
|
||||
u64 ncap = a->cap * 2;
|
||||
if (ncap > MAX_CHUNK)
|
||||
ncap = MAX_CHUNK;
|
||||
if (ncap < need)
|
||||
ncap = roundup(need, ALIGN);
|
||||
|
||||
/* push current chunk onto chain, allocate fresh head */
|
||||
Arena *old = malloc(sizeof *old);
|
||||
if (old == NULL)
|
||||
fatal("arena: oom");
|
||||
*old = *a;
|
||||
a->next = old;
|
||||
a->buf = malloc(ncap);
|
||||
if (a->buf == NULL)
|
||||
fatal("arena: oom (chunk=%llu)", (unsigned long long)ncap);
|
||||
a->off = 0;
|
||||
a->cap = ncap;
|
||||
}
|
||||
|
||||
void *
|
||||
amalloc(Arena *a, u64 n)
|
||||
{
|
||||
n = roundup(n, ALIGN);
|
||||
if (n > a->cap - a->off)
|
||||
grow(a, n);
|
||||
void *p = a->buf + a->off;
|
||||
a->off += n;
|
||||
a->total += n;
|
||||
memset(p, 0, n);
|
||||
return p;
|
||||
}
|
||||
|
||||
char *
|
||||
astrdup(Arena *a, const char *s)
|
||||
{
|
||||
u64 n = strlen(s);
|
||||
char *p = amalloc(a, n + 1);
|
||||
memcpy(p, s, n);
|
||||
return p;
|
||||
}
|
||||
|
||||
char *
|
||||
astrndup(Arena *a, const char *s, u64 n)
|
||||
{
|
||||
char *p = amalloc(a, n + 1);
|
||||
memcpy(p, s, n);
|
||||
return p;
|
||||
}
|
||||
|
||||
char *
|
||||
aprintf(Arena *a, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int n = vsnprintf(NULL, 0, fmt, ap);
|
||||
va_end(ap);
|
||||
if (n < 0)
|
||||
fatal("aprintf: vsnprintf failed");
|
||||
char *p = amalloc(a, (u64)n + 1);
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(p, (size_t)n + 1, fmt, ap);
|
||||
va_end(ap);
|
||||
return p;
|
||||
}
|
||||
|
||||
void
|
||||
freearena(Arena *a)
|
||||
{
|
||||
while (a) {
|
||||
Arena *next = a->next;
|
||||
free(a->buf);
|
||||
/* The head Arena was returned by newarena() and is the only
|
||||
* one we should free as a struct; the linked older ones were
|
||||
* allocated by grow() and are also freeable. */
|
||||
free(a);
|
||||
a = next;
|
||||
}
|
||||
}
|
||||
1183
cmd/wcc/parse.c
Normal file
1183
cmd/wcc/parse.c
Normal file
File diff suppressed because it is too large
Load Diff
74
cmd/wcc/sym.c
Normal file
74
cmd/wcc/sym.c
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* sym.c — symbol table. Plan 9-flavoured: a per-scope hashtable
|
||||
* chained to the parent scope. Lookup walks up. Duplicate definitions
|
||||
* within the same scope are flagged by the caller (we just refuse the
|
||||
* insert and return the first one).
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <string.h>
|
||||
|
||||
#define INIT_BUCKETS 16
|
||||
|
||||
static u64
|
||||
hashstr(const char *s)
|
||||
{
|
||||
/* FNV-1a 64-bit; small fixed footprint, decent distribution */
|
||||
u64 h = 0xcbf29ce484222325ULL;
|
||||
for (; *s; s++) {
|
||||
h ^= (unsigned char)*s;
|
||||
h *= 0x100000001b3ULL;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
Scope *
|
||||
newscope(Arena *a, Scope *parent)
|
||||
{
|
||||
Scope *s = amalloc(a, sizeof *s);
|
||||
s->parent = parent;
|
||||
s->a = a;
|
||||
s->nbuckets = INIT_BUCKETS;
|
||||
s->buckets = amalloc(a, s->nbuckets * sizeof(Sym *));
|
||||
return s;
|
||||
}
|
||||
|
||||
Sym *
|
||||
scope_lookup_local(Scope *s, const char *name)
|
||||
{
|
||||
if (s == NULL) return NULL;
|
||||
u64 h = hashstr(name) % s->nbuckets;
|
||||
for (Sym *b = s->buckets[h]; b; b = b->hashnext)
|
||||
if (strcmp(b->name, name) == 0)
|
||||
return b;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Sym *
|
||||
scope_lookup(Scope *s, const char *name)
|
||||
{
|
||||
for (; s; s = s->parent) {
|
||||
Sym *r = scope_lookup_local(s, name);
|
||||
if (r) return r;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Sym *
|
||||
scope_define(Scope *s, const char *name, Skind k, Type *t, Node *decl)
|
||||
{
|
||||
if (scope_lookup_local(s, name) != NULL)
|
||||
return NULL;
|
||||
Sym *sy = amalloc(s->a, sizeof *sy);
|
||||
sy->name = name;
|
||||
sy->kind = k;
|
||||
sy->type = t;
|
||||
sy->decl = decl;
|
||||
sy->scope = s;
|
||||
u64 h = hashstr(name) % s->nbuckets;
|
||||
sy->hashnext = s->buckets[h];
|
||||
s->buckets[h] = sy;
|
||||
if (s->first == NULL) s->first = sy;
|
||||
else s->last->next = sy;
|
||||
s->last = sy;
|
||||
return sy;
|
||||
}
|
||||
199
cmd/wcc/tok.c
Normal file
199
cmd/wcc/tok.c
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* tok.c — token names, keyword lookup, debug printer.
|
||||
*
|
||||
* One table-of-records keyed by kind. The keyword subset is also
|
||||
* scanned linearly during lexing — fewer than 25 entries, a hash
|
||||
* isn't worth it.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <string.h>
|
||||
|
||||
struct kwent {
|
||||
const char *s;
|
||||
Tkind kind;
|
||||
};
|
||||
|
||||
/* keep alphabetised, so kwlookup is easy to read. */
|
||||
static const struct kwent kwtab[] = {
|
||||
{ "as", TK_AS },
|
||||
{ "break", TK_BREAK },
|
||||
{ "case", TK_CASE },
|
||||
{ "chan", TK_CHAN },
|
||||
{ "continue", TK_CONTINUE },
|
||||
{ "def", TK_DEF },
|
||||
{ "defer", TK_DEFER },
|
||||
{ "else", TK_ELSE },
|
||||
{ "export", TK_EXPORT },
|
||||
{ "false", TK_FALSE },
|
||||
{ "fn", TK_FN },
|
||||
{ "for", TK_FOR },
|
||||
{ "if", TK_IF },
|
||||
{ "let", TK_LET },
|
||||
{ "match", TK_MATCH },
|
||||
{ "nil", TK_NIL },
|
||||
{ "proc", TK_PROC },
|
||||
{ "return", TK_RETURN },
|
||||
{ "static", TK_STATIC },
|
||||
{ "struct", TK_STRUCT },
|
||||
{ "switch", TK_SWITCH },
|
||||
{ "true", TK_TRUE },
|
||||
{ "type", TK_TYPE },
|
||||
{ "use", TK_USE }
|
||||
};
|
||||
|
||||
Tkind
|
||||
kwlookup(const char *s, u64 n)
|
||||
{
|
||||
/* linear scan: small N, predictable, branchy fall-through is fine. */
|
||||
for (u64 i = 0; i < nelem(kwtab); i++) {
|
||||
const char *k = kwtab[i].s;
|
||||
if (strlen(k) == n && memcmp(k, s, n) == 0)
|
||||
return kwtab[i].kind;
|
||||
}
|
||||
return TK_NONE;
|
||||
}
|
||||
|
||||
const char *
|
||||
tokname(Tkind k)
|
||||
{
|
||||
switch (k) {
|
||||
case TK_NONE: return "<none>";
|
||||
case TK_EOF: return "EOF";
|
||||
case TK_ERR: return "ERR";
|
||||
case TK_IDENT: return "IDENT";
|
||||
case TK_INT: return "INT";
|
||||
case TK_FLOAT: return "FLOAT";
|
||||
case TK_RUNE: return "RUNE";
|
||||
case TK_STR: return "STR";
|
||||
|
||||
case TK_FN: return "fn";
|
||||
case TK_LET: return "let";
|
||||
case TK_DEF: return "def";
|
||||
case TK_IF: return "if";
|
||||
case TK_ELSE: return "else";
|
||||
case TK_FOR: return "for";
|
||||
case TK_SWITCH: return "switch";
|
||||
case TK_CASE: return "case";
|
||||
case TK_RETURN: return "return";
|
||||
case TK_USE: return "use";
|
||||
case TK_TYPE: return "type";
|
||||
case TK_STRUCT: return "struct";
|
||||
case TK_DEFER: return "defer";
|
||||
case TK_BREAK: return "break";
|
||||
case TK_CONTINUE: return "continue";
|
||||
case TK_EXPORT: return "export";
|
||||
case TK_PROC: return "proc";
|
||||
case TK_CHAN: return "chan";
|
||||
case TK_NIL: return "nil";
|
||||
case TK_TRUE: return "true";
|
||||
case TK_FALSE: return "false";
|
||||
case TK_AS: return "as";
|
||||
case TK_STATIC: return "static";
|
||||
case TK_MATCH: return "match";
|
||||
|
||||
case TK_LPAREN: return "(";
|
||||
case TK_RPAREN: return ")";
|
||||
case TK_LBRACE: return "{";
|
||||
case TK_RBRACE: return "}";
|
||||
case TK_LBRACK: return "[";
|
||||
case TK_RBRACK: return "]";
|
||||
case TK_COMMA: return ",";
|
||||
case TK_SEMI: return ";";
|
||||
case TK_COLON: return ":";
|
||||
case TK_DOT: return ".";
|
||||
case TK_ELLIPSIS: return "...";
|
||||
case TK_DOTDOT: return "..";
|
||||
case TK_AT: return "@";
|
||||
case TK_QUESTION: return "?";
|
||||
|
||||
case TK_ASSIGN: return "=";
|
||||
case TK_PLUSEQ: return "+=";
|
||||
case TK_MINUSEQ: return "-=";
|
||||
case TK_STAREQ: return "*=";
|
||||
case TK_SLASHEQ: return "/=";
|
||||
case TK_PERCENTEQ: return "%=";
|
||||
case TK_AMPEQ: return "&=";
|
||||
case TK_PIPEEQ: return "|=";
|
||||
case TK_CARETEQ: return "^=";
|
||||
case TK_LSHIFTEQ: return "<<=";
|
||||
case TK_RSHIFTEQ: return ">>=";
|
||||
|
||||
case TK_PLUS: return "+";
|
||||
case TK_MINUS: return "-";
|
||||
case TK_STAR: return "*";
|
||||
case TK_SLASH: return "/";
|
||||
case TK_PERCENT: return "%";
|
||||
case TK_AMP: return "&";
|
||||
case TK_PIPE: return "|";
|
||||
case TK_CARET: return "^";
|
||||
case TK_TILDE: return "~";
|
||||
case TK_LSHIFT: return "<<";
|
||||
case TK_RSHIFT: return ">>";
|
||||
|
||||
case TK_EQ: return "==";
|
||||
case TK_NEQ: return "!=";
|
||||
case TK_LT: return "<";
|
||||
case TK_LE: return "<=";
|
||||
case TK_GT: return ">";
|
||||
case TK_GE: return ">=";
|
||||
|
||||
case TK_AND: return "&&";
|
||||
case TK_OR: return "||";
|
||||
case TK_NOT: return "!";
|
||||
|
||||
case TK_LARROW: return "<-";
|
||||
case TK_ARROW: return "->";
|
||||
case TK_FATARROW: return "=>";
|
||||
|
||||
case TK_LAST: return "<last>";
|
||||
}
|
||||
return "<?>";
|
||||
}
|
||||
|
||||
static void
|
||||
fputq(FILE *f, const char *s, u64 n)
|
||||
{
|
||||
fputc('"', f);
|
||||
for (u64 i = 0; i < n; i++) {
|
||||
unsigned char c = (unsigned char)s[i];
|
||||
switch (c) {
|
||||
case '\\': fputs("\\\\", f); break;
|
||||
case '"': fputs("\\\"", f); break;
|
||||
case '\n': fputs("\\n", f); break;
|
||||
case '\t': fputs("\\t", f); break;
|
||||
case '\r': fputs("\\r", f); break;
|
||||
default:
|
||||
if (c < 0x20 || c == 0x7f)
|
||||
fprintf(f, "\\x%02x", c);
|
||||
else
|
||||
fputc(c, f);
|
||||
}
|
||||
}
|
||||
fputc('"', f);
|
||||
}
|
||||
|
||||
void
|
||||
tokprint(FILE *f, Tok t)
|
||||
{
|
||||
fprintf(f, "%s:%d:%d %s",
|
||||
t.pos.file ? t.pos.file : "<none>", t.pos.line, t.pos.col,
|
||||
tokname(t.kind));
|
||||
switch (t.kind) {
|
||||
case TK_IDENT:
|
||||
case TK_STR:
|
||||
case TK_ERR:
|
||||
fputc(' ', f);
|
||||
fputq(f, t.text, t.tlen);
|
||||
break;
|
||||
case TK_INT:
|
||||
case TK_RUNE:
|
||||
fprintf(f, " %llu", (unsigned long long)t.v.uval);
|
||||
break;
|
||||
case TK_FLOAT:
|
||||
fprintf(f, " %g", t.v.fval);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
fputc('\n', f);
|
||||
}
|
||||
370
cmd/wcc/type.c
Normal file
370
cmd/wcc/type.c
Normal file
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* type.c — Type values and structural equality.
|
||||
*
|
||||
* Built-in types are constructed once and exposed as globals so the
|
||||
* rest of the compiler can `==`-compare them. Compound types (ptr,
|
||||
* slice, array, fn, struct, chan) are constructed on demand and
|
||||
* de-duplicated when equality is cheap (only ptr/slice for now).
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <string.h>
|
||||
|
||||
Type *ty_void, *ty_bool, *ty_rune;
|
||||
Type *ty_i8, *ty_i16, *ty_i32, *ty_i64;
|
||||
Type *ty_u8, *ty_u16, *ty_u32, *ty_u64;
|
||||
Type *ty_int, *ty_uint, *ty_uintptr;
|
||||
Type *ty_f32, *ty_f64, *ty_str;
|
||||
Type *ty_err;
|
||||
Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str;
|
||||
Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil;
|
||||
|
||||
Type *
|
||||
newtype(Arena *a, TypeKind k)
|
||||
{
|
||||
Type *t = amalloc(a, sizeof *t);
|
||||
t->kind = k;
|
||||
return t;
|
||||
}
|
||||
|
||||
static Type *
|
||||
prim(Arena *a, TypeKind k, const char *nm, u64 sz, u64 al)
|
||||
{
|
||||
Type *t = newtype(a, k);
|
||||
t->name = nm;
|
||||
t->size = sz;
|
||||
t->align = al ? al : sz;
|
||||
return t;
|
||||
}
|
||||
|
||||
void
|
||||
typesinit(Arena *a)
|
||||
{
|
||||
/* Always re-init: callers create a fresh arena per compilation unit
|
||||
* and free it; old globals point at freed memory. */
|
||||
ty_void = prim(a, TY_VOID, "void", 0, 1);
|
||||
ty_bool = prim(a, TY_BOOL, "bool", 1, 1);
|
||||
ty_rune = prim(a, TY_RUNE, "rune", 4, 4);
|
||||
|
||||
ty_i8 = prim(a, TY_I8, "i8", 1, 1);
|
||||
ty_i16 = prim(a, TY_I16, "i16", 2, 2);
|
||||
ty_i32 = prim(a, TY_I32, "i32", 4, 4);
|
||||
ty_i64 = prim(a, TY_I64, "i64", 8, 8);
|
||||
ty_u8 = prim(a, TY_U8, "u8", 1, 1);
|
||||
ty_u16 = prim(a, TY_U16, "u16", 2, 2);
|
||||
ty_u32 = prim(a, TY_U32, "u32", 4, 4);
|
||||
ty_u64 = prim(a, TY_U64, "u64", 8, 8);
|
||||
ty_int = prim(a, TY_INT, "int", 8, 8); /* amd64 */
|
||||
ty_uint = prim(a, TY_UINT, "uint", 8, 8);
|
||||
ty_uintptr= prim(a, TY_UINTPTR,"uintptr", 8, 8);
|
||||
ty_f32 = prim(a, TY_F32, "f32", 4, 4);
|
||||
ty_f64 = prim(a, TY_F64, "f64", 8, 8);
|
||||
/* str is { *u8, len } — 16 bytes on amd64. ABI: pointer + u64. */
|
||||
ty_str = prim(a, TY_STR, "str", 16, 8);
|
||||
ty_err = prim(a, TY_ERR, "<err>", 0, 1);
|
||||
|
||||
ty_untyped_int = prim(a, TY_UNTYPED_INT, "untyped_int", 0, 1);
|
||||
ty_untyped_float = prim(a, TY_UNTYPED_FLOAT, "untyped_float", 0, 1);
|
||||
ty_untyped_str = prim(a, TY_UNTYPED_STR, "untyped_str", 0, 1);
|
||||
ty_untyped_rune = prim(a, TY_UNTYPED_RUNE, "untyped_rune", 0, 1);
|
||||
ty_untyped_bool = prim(a, TY_UNTYPED_BOOL, "untyped_bool", 0, 1);
|
||||
ty_untyped_nil = prim(a, TY_UNTYPED_NIL, "untyped_nil", 0, 1);
|
||||
}
|
||||
|
||||
Type *
|
||||
type_ptr(Arena *a, Type *sub)
|
||||
{
|
||||
Type *t = newtype(a, TY_PTR);
|
||||
t->sub = sub;
|
||||
t->size = 8;
|
||||
t->align = 8;
|
||||
return t;
|
||||
}
|
||||
|
||||
Type *
|
||||
type_slice(Arena *a, Type *sub)
|
||||
{
|
||||
Type *t = newtype(a, TY_SLICE);
|
||||
t->sub = sub;
|
||||
t->size = 24; /* { *T, len, cap } */
|
||||
t->align = 8;
|
||||
return t;
|
||||
}
|
||||
|
||||
Type *
|
||||
type_array(Arena *a, Type *sub, u64 len)
|
||||
{
|
||||
Type *t = newtype(a, TY_ARRAY);
|
||||
t->sub = sub;
|
||||
t->alen = len;
|
||||
t->size = sub ? sub->size * len : 0;
|
||||
t->align = sub ? sub->align : 1;
|
||||
return t;
|
||||
}
|
||||
|
||||
Type *
|
||||
type_chan(Arena *a, Type *sub)
|
||||
{
|
||||
Type *t = newtype(a, TY_CHAN);
|
||||
t->sub = sub;
|
||||
t->size = 8; /* opaque ptr */
|
||||
t->align = 8;
|
||||
return t;
|
||||
}
|
||||
|
||||
Type *
|
||||
type_named(Arena *a, const char *name, Type *under)
|
||||
{
|
||||
Type *t = newtype(a, TY_NAMED);
|
||||
t->name = name;
|
||||
t->under = under;
|
||||
if (under) {
|
||||
t->size = under->size;
|
||||
t->align = under->align;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
int
|
||||
type_isint(Type *t)
|
||||
{
|
||||
if (t == NULL) return 0;
|
||||
switch (t->kind) {
|
||||
case TY_I8: case TY_I16: case TY_I32: case TY_I64:
|
||||
case TY_U8: case TY_U16: case TY_U32: case TY_U64:
|
||||
case TY_INT: case TY_UINT: case TY_UINTPTR:
|
||||
case TY_RUNE:
|
||||
case TY_UNTYPED_INT:
|
||||
case TY_UNTYPED_RUNE:
|
||||
return 1;
|
||||
case TY_NAMED: return type_isint(t->under);
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
type_isfloat(Type *t)
|
||||
{
|
||||
if (t == NULL) return 0;
|
||||
switch (t->kind) {
|
||||
case TY_F32: case TY_F64: case TY_UNTYPED_FLOAT:
|
||||
return 1;
|
||||
case TY_NAMED: return type_isfloat(t->under);
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
type_isnum(Type *t)
|
||||
{
|
||||
return type_isint(t) || type_isfloat(t);
|
||||
}
|
||||
|
||||
int
|
||||
type_isunsigned(Type *t)
|
||||
{
|
||||
if (t == NULL) return 0;
|
||||
switch (t->kind) {
|
||||
case TY_U8: case TY_U16: case TY_U32: case TY_U64:
|
||||
case TY_UINT: case TY_UINTPTR:
|
||||
return 1;
|
||||
case TY_NAMED: return type_isunsigned(t->under);
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
type_isuntyped(Type *t)
|
||||
{
|
||||
if (t == NULL) return 0;
|
||||
switch (t->kind) {
|
||||
case TY_UNTYPED_INT: case TY_UNTYPED_FLOAT: case TY_UNTYPED_STR:
|
||||
case TY_UNTYPED_RUNE: case TY_UNTYPED_BOOL: case TY_UNTYPED_NIL:
|
||||
return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Type *
|
||||
type_default(Type *t)
|
||||
{
|
||||
if (t == NULL) return NULL;
|
||||
switch (t->kind) {
|
||||
case TY_UNTYPED_INT: return ty_i32;
|
||||
case TY_UNTYPED_FLOAT: return ty_f64;
|
||||
case TY_UNTYPED_STR: return ty_str;
|
||||
case TY_UNTYPED_RUNE: return ty_rune;
|
||||
case TY_UNTYPED_BOOL: return ty_bool;
|
||||
case TY_UNTYPED_NIL: return NULL; /* needs context */
|
||||
default: return t;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
type_eq(Type *a, Type *b)
|
||||
{
|
||||
if (a == b) return 1;
|
||||
if (a == NULL || b == NULL) return 0;
|
||||
if (a->kind != b->kind) return 0;
|
||||
switch (a->kind) {
|
||||
case TY_PTR: case TY_SLICE: case TY_CHAN:
|
||||
return type_eq(a->sub, b->sub);
|
||||
case TY_ARRAY:
|
||||
return a->alen == b->alen && type_eq(a->sub, b->sub);
|
||||
case TY_FN: {
|
||||
if (a->variadic != b->variadic) return 0;
|
||||
if (!type_eq(a->ret, b->ret)) return 0;
|
||||
Tparam *pa = a->params, *pb = b->params;
|
||||
while (pa && pb) {
|
||||
if (!type_eq(pa->type, pb->type)) return 0;
|
||||
pa = pa->next; pb = pb->next;
|
||||
}
|
||||
return pa == NULL && pb == NULL;
|
||||
}
|
||||
case TY_STRUCT: {
|
||||
Tfield *fa = a->fields, *fb = b->fields;
|
||||
while (fa && fb) {
|
||||
if (strcmp(fa->name, fb->name) != 0) return 0;
|
||||
if (!type_eq(fa->type, fb->type)) return 0;
|
||||
fa = fa->next; fb = fb->next;
|
||||
}
|
||||
return fa == NULL && fb == NULL;
|
||||
}
|
||||
case TY_NAMED:
|
||||
return a == b; /* nominally equal only when same node */
|
||||
case TY_TUPLE: {
|
||||
Tparam *pa = a->params, *pb = b->params;
|
||||
while (pa && pb) {
|
||||
if (!type_eq(pa->type, pb->type)) return 0;
|
||||
pa = pa->next; pb = pb->next;
|
||||
}
|
||||
return pa == NULL && pb == NULL;
|
||||
}
|
||||
default: return 1; /* primitives */
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
type_assignable(Type *dst, Type *src)
|
||||
{
|
||||
if (dst == NULL || src == NULL) return 0;
|
||||
if (dst == ty_err || src == ty_err) return 1; /* swallow */
|
||||
if (type_eq(dst, src)) return 1;
|
||||
|
||||
/* Tagged-union variant inclusion: src is one of dst's variants.
|
||||
* Checked before the untyped branch so untyped literals (e.g.
|
||||
* 0, "msg") flow through to a variant's typed slot. Unwraps a
|
||||
* named alias on either side so `type result = (T | E);` also
|
||||
* accepts variants and the inverse. */
|
||||
{
|
||||
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
|
||||
Type *su = (src->kind == TY_NAMED) ? src->under : src;
|
||||
if (du && du->kind == TY_TAGGED &&
|
||||
!(su && su->kind == TY_TAGGED)) {
|
||||
for (Tparam *p = du->params; p; p = p->next)
|
||||
if (type_assignable(p->type, src)) return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Untyped → typed: only if the typed kind can hold the value. */
|
||||
if (type_isuntyped(src)) {
|
||||
if (src->kind == TY_UNTYPED_INT && type_isnum(dst)) return 1;
|
||||
if (src->kind == TY_UNTYPED_FLOAT && type_isfloat(dst)) return 1;
|
||||
if (src->kind == TY_UNTYPED_STR && (dst->kind == TY_STR ||
|
||||
(dst->kind == TY_NAMED && dst->under && dst->under->kind == TY_STR))) return 1;
|
||||
if (src->kind == TY_UNTYPED_RUNE && (type_isint(dst) || dst->kind == TY_RUNE)) return 1;
|
||||
if (src->kind == TY_UNTYPED_BOOL && (dst->kind == TY_BOOL ||
|
||||
(dst->kind == TY_NAMED && dst->under && dst->under->kind == TY_BOOL))) return 1;
|
||||
if (src->kind == TY_UNTYPED_NIL) {
|
||||
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
|
||||
if (du && (du->kind == TY_PTR || du->kind == TY_SLICE ||
|
||||
du->kind == TY_CHAN || du->kind == TY_FN))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Named on either side: compare to the underlying. NAMED is a
|
||||
* distinct type from its under; but assignment from under to
|
||||
* named (and vice-versa) is allowed in this minimal checker. */
|
||||
if (dst->kind == TY_NAMED && type_eq(dst->under, src)) return 1;
|
||||
if (src->kind == TY_NAMED && type_eq(dst, src->under)) return 1;
|
||||
|
||||
/* Tuple-to-tuple: element-wise assignable. */
|
||||
if (dst->kind == TY_TUPLE && src->kind == TY_TUPLE) {
|
||||
Tparam *pa = dst->params, *pb = src->params;
|
||||
while (pa && pb) {
|
||||
if (!type_assignable(pa->type, pb->type)) return 0;
|
||||
pa = pa->next; pb = pb->next;
|
||||
}
|
||||
return pa == NULL && pb == NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *
|
||||
type_name(Arena *a, Type *t)
|
||||
{
|
||||
if (t == NULL) return "<nil>";
|
||||
switch (t->kind) {
|
||||
case TY_NONE: return "<none>";
|
||||
case TY_VOID: return "void";
|
||||
case TY_BOOL: return "bool";
|
||||
case TY_RUNE: return "rune";
|
||||
case TY_I8: return "i8";
|
||||
case TY_I16: return "i16";
|
||||
case TY_I32: return "i32";
|
||||
case TY_I64: return "i64";
|
||||
case TY_U8: return "u8";
|
||||
case TY_U16: return "u16";
|
||||
case TY_U32: return "u32";
|
||||
case TY_U64: return "u64";
|
||||
case TY_INT: return "int";
|
||||
case TY_UINT: return "uint";
|
||||
case TY_UINTPTR: return "uintptr";
|
||||
case TY_F32: return "f32";
|
||||
case TY_F64: return "f64";
|
||||
case TY_STR: return "str";
|
||||
case TY_ERR: return "<err>";
|
||||
case TY_UNTYPED_INT: return "untyped_int";
|
||||
case TY_UNTYPED_FLOAT: return "untyped_float";
|
||||
case TY_UNTYPED_STR: return "untyped_str";
|
||||
case TY_UNTYPED_RUNE: return "untyped_rune";
|
||||
case TY_UNTYPED_BOOL: return "untyped_bool";
|
||||
case TY_UNTYPED_NIL: return "untyped_nil";
|
||||
case TY_PTR: return aprintf(a, "*%s", type_name(a, t->sub));
|
||||
case TY_SLICE: return aprintf(a, "[]%s", type_name(a, t->sub));
|
||||
case TY_ARRAY: return aprintf(a, "[%llu]%s",
|
||||
(unsigned long long)t->alen, type_name(a, t->sub));
|
||||
case TY_CHAN: return aprintf(a, "chan %s", type_name(a, t->sub));
|
||||
case TY_FN: {
|
||||
const char *r = t->ret ? type_name(a, t->ret) : "void";
|
||||
const char *acc = "";
|
||||
for (Tparam *p = t->params; p; p = p->next) {
|
||||
const char *pn = type_name(a, p->type);
|
||||
acc = acc[0] ? aprintf(a, "%s, %s", acc, pn) : pn;
|
||||
}
|
||||
return aprintf(a, "fn(%s) %s", acc, r);
|
||||
}
|
||||
case TY_STRUCT: return t->name ? t->name : "struct{...}";
|
||||
case TY_NAMED: return t->name ? t->name : "<named>";
|
||||
case TY_TUPLE: {
|
||||
const char *acc = "";
|
||||
for (Tparam *p = t->params; p; p = p->next) {
|
||||
const char *pn = type_name(a, p->type);
|
||||
acc = acc[0] ? aprintf(a, "%s, %s", acc, pn) : pn;
|
||||
}
|
||||
return aprintf(a, "(%s)", acc);
|
||||
}
|
||||
case TY_TAGGED: {
|
||||
const char *acc = "";
|
||||
for (Tparam *p = t->params; p; p = p->next) {
|
||||
const char *pn = type_name(a, p->type);
|
||||
acc = acc[0] ? aprintf(a, "%s | %s", acc, pn) : pn;
|
||||
}
|
||||
return aprintf(a, "(%s)", acc);
|
||||
}
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
462
cmd/wcc/ww.h
Normal file
462
cmd/wcc/ww.h
Normal file
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
* ww.h — central header for libwcc.a (the ww frontend library).
|
||||
*
|
||||
* Plan 9 in spirit. This file mirrors cc/cc.h's role: one shared
|
||||
* header that declares everything every translation unit in the
|
||||
* frontend cares about.
|
||||
*
|
||||
* Phases add to this file (lexer/parser/checker), they do not branch
|
||||
* a sibling header. There is one frontend; there is one ww.h.
|
||||
*/
|
||||
#ifndef WW_H
|
||||
#define WW_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* version banner — printed by `ww -V` */
|
||||
#define WW_VERSION "0.0"
|
||||
|
||||
/* short integer aliases, Plan 9 / Hare-flavoured */
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
/* forward decls — concrete shapes appear in their phases. */
|
||||
typedef struct Tok Tok;
|
||||
typedef struct Lex Lex;
|
||||
typedef struct Node Node;
|
||||
typedef struct Sym Sym;
|
||||
typedef struct Type Type;
|
||||
typedef struct Scope Scope;
|
||||
typedef struct Arena Arena;
|
||||
|
||||
/* mem.c — bump arena (no free; reset/destroy at end of phase) */
|
||||
struct Arena {
|
||||
u8 *buf; /* base of current chunk */
|
||||
u64 off; /* bytes used in current chunk */
|
||||
u64 cap; /* capacity of current chunk */
|
||||
struct Arena *next; /* older chunks (linked list, head = current) */
|
||||
u64 total; /* across all chunks, debug only */
|
||||
};
|
||||
|
||||
Arena *newarena(void);
|
||||
void *amalloc(Arena*, u64); /* zeroed, aligned to 16 */
|
||||
char *astrdup(Arena*, const char*);
|
||||
char *astrndup(Arena*, const char*, u64);
|
||||
char *aprintf(Arena*, const char*, ...);
|
||||
void freearena(Arena*);
|
||||
|
||||
/* err.c — diagnostics. Phase 0 has only fatal/warn; later phases add
|
||||
* source-location-bearing variants. */
|
||||
typedef struct Pos Pos;
|
||||
struct Pos {
|
||||
const char *file;
|
||||
i32 line;
|
||||
i32 col;
|
||||
};
|
||||
|
||||
extern Pos noPos;
|
||||
extern int nerrors;
|
||||
extern int nwarnings;
|
||||
extern FILE *errout;
|
||||
|
||||
void fatal(const char*, ...) __attribute__((noreturn, format(printf, 1, 2)));
|
||||
void errorf(Pos, const char*, ...) __attribute__((format(printf, 2, 3)));
|
||||
void warnf(Pos, const char*, ...) __attribute__((format(printf, 2, 3)));
|
||||
|
||||
/* tiny helpers */
|
||||
#define nelem(a) ((sizeof(a) / sizeof((a)[0])))
|
||||
|
||||
/* ---- lexer (lex.c, tok.c) ----------------------------------------- */
|
||||
typedef enum {
|
||||
/* zero is "no token" so memset-zero structs read sane */
|
||||
TK_NONE = 0,
|
||||
|
||||
/* trivial */
|
||||
TK_EOF,
|
||||
TK_ERR,
|
||||
TK_IDENT,
|
||||
TK_INT,
|
||||
TK_FLOAT,
|
||||
TK_RUNE,
|
||||
TK_STR,
|
||||
|
||||
/* keywords — stay grouped, used by tok.c kwtab */
|
||||
TK_FN,
|
||||
TK_LET,
|
||||
TK_DEF,
|
||||
TK_IF,
|
||||
TK_ELSE,
|
||||
TK_FOR,
|
||||
TK_SWITCH,
|
||||
TK_CASE,
|
||||
TK_RETURN,
|
||||
TK_USE,
|
||||
TK_TYPE,
|
||||
TK_STRUCT,
|
||||
TK_DEFER,
|
||||
TK_BREAK,
|
||||
TK_CONTINUE,
|
||||
TK_EXPORT,
|
||||
TK_PROC,
|
||||
TK_CHAN,
|
||||
TK_NIL,
|
||||
TK_TRUE,
|
||||
TK_FALSE,
|
||||
TK_AS, /* reserved for future cast spelling, not active */
|
||||
TK_STATIC, /* Hare-style storage-class qualifier */
|
||||
TK_MATCH, /* match expression head */
|
||||
|
||||
/* punct + operators */
|
||||
TK_LPAREN, /* ( */
|
||||
TK_RPAREN, /* ) */
|
||||
TK_LBRACE, /* { */
|
||||
TK_RBRACE, /* } */
|
||||
TK_LBRACK, /* [ */
|
||||
TK_RBRACK, /* ] */
|
||||
TK_COMMA, /* , */
|
||||
TK_SEMI, /* ; */
|
||||
TK_COLON, /* : */
|
||||
TK_DOT, /* . */
|
||||
TK_ELLIPSIS, /* ... */
|
||||
TK_DOTDOT, /* .. (range op) */
|
||||
TK_AT, /* @ */
|
||||
TK_QUESTION, /* ? */
|
||||
|
||||
TK_ASSIGN, /* = */
|
||||
TK_PLUSEQ, /* += */
|
||||
TK_MINUSEQ, /* -= */
|
||||
TK_STAREQ, /* *= */
|
||||
TK_SLASHEQ, /* /= */
|
||||
TK_PERCENTEQ, /* %= */
|
||||
TK_AMPEQ, /* &= */
|
||||
TK_PIPEEQ, /* |= */
|
||||
TK_CARETEQ, /* ^= */
|
||||
TK_LSHIFTEQ, /* <<= */
|
||||
TK_RSHIFTEQ, /* >>= */
|
||||
|
||||
TK_PLUS, /* + */
|
||||
TK_MINUS, /* - */
|
||||
TK_STAR, /* * */
|
||||
TK_SLASH, /* / */
|
||||
TK_PERCENT, /* % */
|
||||
TK_AMP, /* & */
|
||||
TK_PIPE, /* | */
|
||||
TK_CARET, /* ^ */
|
||||
TK_TILDE, /* ~ */
|
||||
TK_LSHIFT, /* << */
|
||||
TK_RSHIFT, /* >> */
|
||||
|
||||
TK_EQ, /* == */
|
||||
TK_NEQ, /* != */
|
||||
TK_LT, /* < */
|
||||
TK_LE, /* <= */
|
||||
TK_GT, /* > */
|
||||
TK_GE, /* >= */
|
||||
|
||||
TK_AND, /* && */
|
||||
TK_OR, /* || */
|
||||
TK_NOT, /* ! */
|
||||
|
||||
TK_LARROW, /* <- (chan recv) */
|
||||
TK_ARROW, /* -> (reserved) */
|
||||
TK_FATARROW, /* => (match arms) */
|
||||
|
||||
TK_LAST /* sentinel for tables */
|
||||
} Tkind;
|
||||
|
||||
struct Tok {
|
||||
Tkind kind;
|
||||
Pos pos;
|
||||
const char *text; /* lexeme (arena-owned, NUL-terminated) */
|
||||
u64 tlen; /* byte length of lexeme (sans NUL) */
|
||||
/* numeric values pre-parsed; string/rune unescaped */
|
||||
union {
|
||||
u64 uval; /* TK_INT, TK_RUNE */
|
||||
double fval; /* TK_FLOAT */
|
||||
} v;
|
||||
/* for typed numeric literals: "i32", "u8", "f64", ... or NULL. */
|
||||
const char *tsuffix;
|
||||
};
|
||||
|
||||
struct Lex {
|
||||
const char *file;
|
||||
const char *src; /* full source, NUL-terminated */
|
||||
u64 srclen;
|
||||
u64 pos; /* current byte offset */
|
||||
i32 line;
|
||||
i32 col;
|
||||
Arena *a; /* token-text arena */
|
||||
int errs;
|
||||
};
|
||||
|
||||
void lexinit(Lex*, Arena*, const char *file, const char *src, u64 len);
|
||||
Tok lexnext(Lex*);
|
||||
const char *tokname(Tkind); /* canonical spelling, e.g. "fn", "+=" */
|
||||
void tokprint(FILE*, Tok); /* one line, "%s:%d:%d: %s %q" */
|
||||
Tkind kwlookup(const char *s, u64 n); /* TK_NONE if not a keyword */
|
||||
|
||||
/* ---- AST (ast.c, parse.c) ----------------------------------------- */
|
||||
typedef enum {
|
||||
N_NONE = 0,
|
||||
|
||||
/* literals */
|
||||
N_INTLIT,
|
||||
N_FLOATLIT,
|
||||
N_STRLIT,
|
||||
N_RUNELIT,
|
||||
N_TRUE,
|
||||
N_FALSE,
|
||||
N_NIL,
|
||||
N_IDENT,
|
||||
|
||||
/* expressions */
|
||||
N_BIN, /* op, lhs, rhs */
|
||||
N_UN, /* op, lhs */
|
||||
N_CALL, /* lhs=callee, list=args */
|
||||
N_INDEX, /* lhs=base, rhs=index */
|
||||
N_DOT, /* lhs=base, str=field */
|
||||
N_CAST, /* lhs=expr, rhs=type-expr */
|
||||
N_STRUCTLIT, /* lhs=type-expr, list=N_FIELD */
|
||||
N_ARRLIT, /* list=elements (for [a,b,...]) */
|
||||
N_FIELD, /* str=name, lhs=value */
|
||||
N_ASSIGN, /* op, lhs, rhs */
|
||||
N_ALLOC, /* lhs=expr, rhs=size-or-null */
|
||||
N_FREE, /* lhs=expr */
|
||||
N_RECV, /* lhs (chan recv: <-c) */
|
||||
N_SLICE, /* lhs=base, rhs=lo or NULL, cond=hi or NULL */
|
||||
N_SPREAD, /* lhs (variadic spread in arg position: e...) */
|
||||
|
||||
/* statements */
|
||||
N_BLOCK, /* list=stmts */
|
||||
N_EXPRSTMT, /* lhs=expr */
|
||||
N_LET, /* str=name, lhs=type-expr|NULL, rhs=init|NULL */
|
||||
N_RETURN, /* lhs=expr|NULL */
|
||||
N_IF, /* cond, body, els */
|
||||
N_FOR, /* lhs=init, cond, rhs=post, body */
|
||||
N_FORRANGE, /* str=elem name, lhs=slice expr, body=block */
|
||||
N_DEFER, /* lhs=expr */
|
||||
N_BREAK,
|
||||
N_CONTINUE,
|
||||
N_SWITCH, /* lhs=scrutinee, list=cases */
|
||||
N_CASE, /* list=exprs (empty=default), body */
|
||||
|
||||
/* declarations */
|
||||
N_FILE, /* list=top decls */
|
||||
N_USE, /* str=path */
|
||||
N_DEF, /* str=name, lhs=type|NULL, rhs=init */
|
||||
N_TYPEDECL, /* str=name, lhs=type-expr */
|
||||
N_FNDECL, /* str=name, list=params, lhs=ret-type, body|NULL */
|
||||
N_PARAM, /* str=name, lhs=type-expr */
|
||||
|
||||
/* type expressions */
|
||||
N_TNAME, /* str */
|
||||
N_TPTR, /* lhs=inner */
|
||||
N_TSLICE, /* lhs=inner */
|
||||
N_TARRAY, /* lhs=element, rhs=len-expr */
|
||||
N_TFN, /* list=params, lhs=ret */
|
||||
N_TSTRUCT, /* list=fields */
|
||||
N_TFIELD, /* str=name, lhs=type */
|
||||
N_TCHAN, /* lhs=element */
|
||||
|
||||
/* attribute on a decl */
|
||||
N_ATTR, /* str=name, list=args */
|
||||
|
||||
/* multi-value (tuple) plumbing */
|
||||
N_TTUPLE, /* type expr: (T1, T2, ...). list = element type exprs */
|
||||
N_TTAGGED, /* type expr: (T1 | T2 | ...). list = variant type exprs */
|
||||
N_TUPLE, /* expr: (e1, e2, ...). list = element exprs */
|
||||
N_MATCH, /* match (lhs) { list of cases }; cases are N_MCASE */
|
||||
N_MCASE, /* str=binding name (or NULL), lhs=variant type expr or NULL, body */
|
||||
N_TRYPROP, /* lhs? — propagate error variant */
|
||||
N_TRYUNW, /* lhs! — abort on error variant */
|
||||
N_MLET, /* let a, b = expr; list = N_LET stubs (str, lhs=type), rhs = expr */
|
||||
N_MASSIGN, /* a, b = expr; list = lvalue exprs, rhs = expr */
|
||||
|
||||
N_LAST
|
||||
} Nkind;
|
||||
|
||||
struct Node {
|
||||
Nkind kind;
|
||||
Pos pos;
|
||||
Tkind op; /* for N_BIN/N_UN/N_ASSIGN */
|
||||
const char *str; /* identifier/literal/name/path */
|
||||
u64 strlen;
|
||||
u64 uval; /* int/rune lit */
|
||||
double fval; /* float lit */
|
||||
Node *lhs;
|
||||
Node *rhs;
|
||||
Node *cond;
|
||||
Node *body;
|
||||
Node *els;
|
||||
Node *list; /* head of singly-linked sibling chain */
|
||||
Node *next; /* sibling link inside `list` */
|
||||
Node *attr; /* @attribute chain (N_ATTR list) */
|
||||
int export;
|
||||
Type *type; /* filled in by checker */
|
||||
const char *tsuffix; /* typed numeric literal suffix */
|
||||
};
|
||||
|
||||
Node *newnode(Arena*, Nkind, Pos);
|
||||
void astprint(FILE*, Node*); /* s-expr, deterministic, one-line per node */
|
||||
|
||||
typedef struct Parser Parser;
|
||||
struct Parser {
|
||||
Lex *l;
|
||||
Arena *a;
|
||||
Tok cur;
|
||||
Tok la; /* one-token lookahead buffer */
|
||||
int hasla;
|
||||
int errs;
|
||||
int nocast; /* in case-selector ctx, ':' is a separator */
|
||||
};
|
||||
|
||||
void parserinit(Parser*, Arena*, Lex*);
|
||||
Node *parsefile(Parser*);
|
||||
Node *parseexpr_top(Parser*); /* for testing: parse one expression */
|
||||
|
||||
/* ---- types (type.c) ----------------------------------------------- */
|
||||
typedef enum {
|
||||
TY_NONE = 0,
|
||||
TY_VOID,
|
||||
TY_BOOL,
|
||||
TY_RUNE,
|
||||
TY_I8, TY_I16, TY_I32, TY_I64,
|
||||
TY_U8, TY_U16, TY_U32, TY_U64,
|
||||
TY_UINT, TY_INT,
|
||||
TY_UINTPTR,
|
||||
TY_F32, TY_F64,
|
||||
TY_STR,
|
||||
TY_PTR,
|
||||
TY_SLICE,
|
||||
TY_ARRAY,
|
||||
TY_STRUCT,
|
||||
TY_FN,
|
||||
TY_CHAN,
|
||||
TY_NAMED,
|
||||
TY_TUPLE,
|
||||
TY_TAGGED, /* (T1 | T2 | ...) — Hare-style sum type */
|
||||
TY_ERR,
|
||||
/* untyped constants (not surfaced to users; checker-internal) */
|
||||
TY_UNTYPED_INT,
|
||||
TY_UNTYPED_FLOAT,
|
||||
TY_UNTYPED_STR,
|
||||
TY_UNTYPED_RUNE,
|
||||
TY_UNTYPED_BOOL,
|
||||
TY_UNTYPED_NIL
|
||||
} TypeKind;
|
||||
|
||||
typedef struct Tfield Tfield;
|
||||
struct Tfield {
|
||||
const char *name;
|
||||
Type *type;
|
||||
u64 offset;
|
||||
Tfield *next;
|
||||
};
|
||||
|
||||
typedef struct Tparam Tparam;
|
||||
struct Tparam {
|
||||
const char *name;
|
||||
Type *type;
|
||||
Tparam *next;
|
||||
};
|
||||
|
||||
struct Type {
|
||||
TypeKind kind;
|
||||
u64 size;
|
||||
u64 align;
|
||||
Type *sub; /* ptr/slice/array/chan element */
|
||||
u64 alen; /* array length */
|
||||
Tfield *fields;/* struct */
|
||||
Tparam *params;/* fn */
|
||||
Type *ret; /* fn */
|
||||
int variadic;
|
||||
const char *name; /* named alias / debug */
|
||||
Type *under; /* underlying resolved type for NAMED */
|
||||
};
|
||||
|
||||
extern Type *ty_void, *ty_bool, *ty_rune;
|
||||
extern Type *ty_i8, *ty_i16, *ty_i32, *ty_i64;
|
||||
extern Type *ty_u8, *ty_u16, *ty_u32, *ty_u64;
|
||||
extern Type *ty_int, *ty_uint, *ty_uintptr;
|
||||
extern Type *ty_f32, *ty_f64, *ty_str;
|
||||
extern Type *ty_err;
|
||||
extern Type *ty_untyped_int, *ty_untyped_float, *ty_untyped_str;
|
||||
extern Type *ty_untyped_rune, *ty_untyped_bool, *ty_untyped_nil;
|
||||
|
||||
void typesinit(Arena*);
|
||||
Type *newtype(Arena*, TypeKind);
|
||||
Type *type_ptr(Arena*, Type *sub);
|
||||
Type *type_slice(Arena*, Type *sub);
|
||||
Type *type_array(Arena*, Type *sub, u64 len);
|
||||
Type *type_chan(Arena*, Type *sub);
|
||||
Type *type_named(Arena*, const char *name, Type *under);
|
||||
const char *type_name(Arena*, Type*); /* arena'd debug string */
|
||||
int type_eq(Type *a, Type *b); /* structural equality */
|
||||
int type_isint(Type *t);
|
||||
int type_isfloat(Type *t);
|
||||
int type_isnum(Type *t);
|
||||
int type_isunsigned(Type *t);
|
||||
int type_isuntyped(Type *t);
|
||||
int type_assignable(Type *dst, Type *src);
|
||||
Type *type_default(Type *t); /* untyped → default concrete */
|
||||
|
||||
/* ---- symbols (sym.c) ---------------------------------------------- */
|
||||
typedef enum {
|
||||
SK_NONE = 0,
|
||||
SK_VAR,
|
||||
SK_PARAM,
|
||||
SK_DEF,
|
||||
SK_TYPE,
|
||||
SK_FN,
|
||||
SK_USE,
|
||||
SK_FIELD /* not stored in scope; used by check internally */
|
||||
} Skind;
|
||||
|
||||
struct Sym {
|
||||
const char *name;
|
||||
Skind kind;
|
||||
Type *type;
|
||||
Node *decl;
|
||||
int exported;
|
||||
Sym *next; /* iteration */
|
||||
Sym *hashnext; /* bucket chain */
|
||||
Scope *scope;
|
||||
};
|
||||
|
||||
struct Scope {
|
||||
Scope *parent;
|
||||
Sym *first, *last;
|
||||
Sym **buckets;
|
||||
u64 nbuckets;
|
||||
Arena *a;
|
||||
};
|
||||
|
||||
Scope *newscope(Arena*, Scope *parent);
|
||||
Sym *scope_define(Scope*, const char *name, Skind, Type*, Node *decl);
|
||||
Sym *scope_lookup(Scope*, const char *name); /* walk up parents */
|
||||
Sym *scope_lookup_local(Scope*, const char *name);
|
||||
|
||||
/* ---- checker (check.c) -------------------------------------------- */
|
||||
typedef struct Checker Checker;
|
||||
struct Checker {
|
||||
Arena *a;
|
||||
Scope *top; /* file scope */
|
||||
Scope *cur; /* current scope */
|
||||
Type *ret; /* expected return type of current fn (or NULL) */
|
||||
int loops; /* nesting count for break/continue */
|
||||
int errs;
|
||||
};
|
||||
|
||||
void check_init(Checker*, Arena*);
|
||||
void check_file(Checker*, Node *file);
|
||||
|
||||
#endif /* WW_H */
|
||||
Reference in New Issue
Block a user