/* * 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 #include #include #include /* 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; int variadic; /* Hare-style `T...` — `type` is []T, * call site gathers / forwards. Distinct * from Type.variadic (C-style FFI `...`). */ }; 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) */ int use_alias; /* also bound as a `use` module name. Set * when a `use foo;` directive collides * with a same-named SK_TYPE/SK_FN/etc. * Lets resolve_typename treat `foo.x` * as module-qualified even though the * primary kind isn't SK_USE. */ const char *mod; /* importing module's bareword for * symbols originating in a `use`- * imported module. NULL for primary * (root) compilation unit symbols. * Used by scope_lookup_in_module to * disambiguate same-leaf-name types * coming from different imports. */ 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_define_in_module(Scope*, const char *name, const char *mod, Skind, Type*, Node *decl); Sym *scope_lookup(Scope*, const char *name); /* walk up parents */ Sym *scope_lookup_local(Scope*, const char *name); Sym *scope_lookup_in_module(Scope*, const char *mod, const char *name); Sym *scope_lookup_prefer(Scope*, const char *mod, 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) */ const char *cur_mod; /* importing-module bareword for the decl * currently being checked; NULL for primary * compilation unit. Drives same-module * preference in bare-leaf lookups so a bare * `read` inside lib/os resolves to os.read * rather than colliding io.read. */ int loops; /* nesting count for break/continue */ int errs; }; void check_init(Checker*, Arena*); void check_file(Checker*, Node *file); /* fold_int_literal — fold an integer-literal-leaf expression to its * u64 value. Accepts int/rune literal, true/false/nil, and a unary * +/-/~ over the same (any depth). No sibling-ident, no binary op. * Returns 1 on success; the call site decides what a miss means * (eval_enum_value's leaf delegation, emit_defs's DATA-row gate). */ int fold_int_literal(Node*, u64*); #endif /* WW_H */