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:
2026-05-11 13:49:27 +09:00
parent e217cd32d1
commit 2c33228b7e
96 changed files with 2129 additions and 973 deletions

189
cmd/wcc/ast.c Normal file
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

74
cmd/wcc/sym.c Normal file
View 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
View 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
View 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
View 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 */