Files
ww/cmd/wcc/ww.h
Hojun-Cho 34817eedcd wcc: Hare-style enum [storage] { ... } type
`type Foo = enum [intT] { NAME [= expr], ... };`. Storage defaults
to i32; members auto-increment from 0 (or last+1) when `= expr` is
omitted, and value expressions can reference earlier siblings —
enough surface for io::mode-style flag enums (`RDWR = READ | WRITE`).

`Foo.MEMBER` folds to an N_INTLIT in the checker, typed as the
named enum. Binops on enum values yield the same enum (type_eq on
the named pointer), so `mode.R | mode.W` is a `mode`. Enum ↔ int
is a reinterpret-only `as` cast — same register, no tag wrap — so
`mode.RDWR as i32` and `1 as mode` both work without runtime ops.

`is`/`?`/`!` are still tagged-union-only. CSP runtime (chan/proc)
is unchanged; only the type-system slot is touched here.
2026-05-12 04:15:31 +09:00

509 lines
14 KiB
C

/*
* 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, /* Hare-style type assertion: e as T */
TK_STATIC, /* Hare-style storage-class qualifier */
TK_MATCH, /* match expression head */
TK_CONST, /* const binding */
TK_UNDER, /* bare '_' discard */
/* 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) */
/* Appended after TK_FATARROW (not grouped with the keyword block)
* to keep the numeric value of every existing kind unchanged —
* the selfhost wwdump-diff test (990) is byte-sensitive. */
TK_IS, /* Hare-style type test: e is T */
TK_VOID, /* `void` — both a type name and a zero-size value */
TK_YIELD, /* `yield expr;` — value-return from a match arm */
TK_ENUM, /* Hare-style `enum [storage] { ... }` type form */
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;
const char *module; /* current `// MODULE: foo` directive, or NULL */
};
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 */
/* Appended after N_MASSIGN — keeps the numeric value of every
* existing kind unchanged, so the selfhost AST dump still diffs
* byte-for-byte. lhs=value, rhs=variant type expr. */
N_TYPETEST, /* lhs is T → bool */
N_TYPEASSERT, /* lhs as T → T (abort if tag mismatch) */
N_VOIDLIT, /* `void` as expression — zero-size void value */
N_TBANG, /* `!T` — error-flagged type. lhs = inner type. */
N_YIELD, /* `yield expr;` — set the enclosing match's value-
* return and jump to its end label. lhs = value. */
N_TENUM, /* `enum [storage] { ... }` type form.
* lhs = storage type expr or NULL (default i32);
* list = chain of N_TENUMMEMBER. */
N_TENUMMEMBER, /* enum member. str=name, lhs=value expr or NULL
* (auto-increment when omitted). */
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 */
const char *module; /* `// MODULE: foo` directive at the
* decl's section in combined.ww.
* NULL for nested nodes; only top-
* level decls (fn/def/type/let)
* carry it. */
};
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,
TY_NEVER, /* bottom: assignable to anything; size 0 */
/* 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,
/* Appended at the tail to keep existing TY_* values stable —
* lib/ww/typ.ww mirrors them as explicit `def` numbers. */
TY_ENUM /* `enum [storage] { ... }`. sub=storage,
* fields=member list (Tfield.offset = u64 value). */
} 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 */
int iserror;/* Hare-style `!T` error mark; propagates
* through NAMED aliases. Variants with
* iserror=1 are the propagation target of
* the `?` operator. */
int nullable;/* TY_TAGGED with exactly `(*T | void)` —
* stored as a single 8-byte pointer; null
* is the void variant. Mirrors Hare's
* `(*T | null)` folding. */
};
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_never;
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;
int is_const; /* const-bound (assignment rejected) */
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 */