/* * 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" 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; 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*); typedef struct Pos Pos; struct Pos { const char *file; i32 line; i32 col; }; extern Pos noPos; extern FILE *errout; void fatal(const char*, ...) __attribute__((noreturn, format(printf, 1, 2))); void errorf(Pos, const char*, ...) __attribute__((format(printf, 2, 3))); #define nelem(a) ((sizeof(a) / sizeof((a)[0]))) typedef enum { /* zero is "no token" so memset-zero structs read sane */ TK_NONE = 0, 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 */ 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_MODULE, /* `module foo;` — directory-as-module declaration */ TK_MODRESET, /* `//ww:module-reset` — driver bundle boundary: reset * curmod to NULL before a package-less file's bytes * (#16 option-B; the package-less-entry attribution fix * that replaces the withdrawn `package main` inject). */ TK_MODPATH, /* `//ww:module ` — driver import boundary: * the following file's decls mangle on the full import * path independently of the `package` clause (M1 #22). Token * text carries the dotted path. */ 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; u64 nulcount; /* raw NUL bytes diagnosed by the source decoder */ int modreset; /* a `//ww:module-reset` directive was seen in * the last skipped run; lexnext emits TK_MODRESET * before the next real token. */ const char *modpath; /* a `//ww:module ` directive was seen in * the last skipped run; lexnext emits TK_MODPATH * carrying this dotted path (M1 #22). */ const char *modresetpath; /* a `//ww:module-reset ` directive * was seen; the next TK_MODRESET carries this * dotted path so the sep primary body mangles * on the path, not its leaf clause (#57). */ }; 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 */ 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; int packed; /* N_TSTRUCT: `struct @packed` — no field/ * trailing padding (harec ast.h:95). */ Type *type; /* filled in by checker */ const char *tsuffix; /* typed numeric literal suffix */ const char *module; /* `// MODULE: foo` directive at the * decl's section of a resolved * compilation unit (the sep driver's * .unit.ww). NULL for nested nodes; * only top-level decls (fn/def/type/ * let) carry it. On an N_USE node * this is the importing (owning) * module. */ const char *usesource; /* N_USE: immutable dotted source spelling. */ const char *usepath; /* N_USE: canonical, vendor-expanded identity; * initially equal to `usesource`. */ const char *usealias; /* N_USE: explicit file-local alias, or NULL. */ const char *usepkgname; /* N_USE: imported declared package name, * independent of the visible binding in `str`. */ int useblank; /* N_USE: `_` spelling; no source binding. */ const char *pkgname; /* declared package name for this source/export * section; independent of canonical `module`. */ int sourceid; /* lexical source-file scope within the parsed * owner unit; module-reset/module boundaries * advance it deterministically. */ int used; /* N_USE: checker observed this file-local binding. */ int initfn; /* special source `fn init`, absent from scope/API. */ int initsynthetic; /* compiler-owned variable helper/package task. */ int runtimeinit; /* module let lowered through an init helper. */ u64 initorder; /* 1-based variable or init-function order. */ const char *linksym; /* raw compiler-private assembler symbol. */ Node *refdecl; /* checker-resolved value declaration. */ u64 initmark; /* checker-private initializer walk mark. */ int imported; /* M1 #22: decl reached through an * `//ww:module ` import boundary * (vs root/primary). Gates the root-only * bare-`main` rule (#32). */ }; 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 */ const char *curmod; /* most-recent `module foo;` declaration — * stamped onto each top-level decl that * follows. */ const char *pathmod; /* M1 #22: active `//ww:module ` dotted * import path; while set, decls stamp * module=pathmod and imported=1, and the * in-file `package` clause is an assertion. */ const char *resetmod; /* #57: active `//ww:module-reset ` canonical * identity; decls mangle on it without becoming * imported. The package clause independently * supplies the declared name. */ const char *curpkg; /* declared name of the active source section. */ int sourceid; /* deterministic lexical source-section ordinal. */ const char *testmodule; /* hidden package-driver alias for toolchain * `package test`; NULL outside that compile */ int commandpackage; /* selected command family: package main/main_test * validates kind without replacing resetmod identity */ }; void parserinit(Parser*, Arena*, Lex*); Node *parsefile(Parser*); /* Imports-only N_FILE: module/pos identify the first package clause, * list holds N_USE declarations, and body holds package-clause markers. */ Node *parseimports(Parser*); Node *parseexpr_top(Parser*); /* for testing: parse one expression */ 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). */ TY_SIZE, /* platform-width unsigned int; mirrors TY_UINTPTR * (8/8 on amd64). Hare: `size`, SIZE_MAX=U64_MAX * (ref/hare/types/arch+x86_64.ha:17,20). #85 fold-1. */ TY_OPAQUE /* abstract + unsized; legal only behind indirection * (`*opaque`, `[]opaque`). size=align=SIZE_UNDEFINED. * Hare: `opaque` (ref/harec/src/types.c:1446). #108(a). */ } TypeKind; /* Unsized sentinel for abstract types (Type.size / Type.align). Mirrors * harec's SIZE_UNDEFINED = (size_t)-1 (ref/harec/include/types.h:58); * chosen over 0 so a 0-byte local can never silently slip through. */ #define SIZE_UNDEFINED ((u64)-1) 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 resolving;/* TY_NAMED demand-resolution cycle guard * (#62): a self-reference re-entering * resolve while the body is open gets the * placeholder, exactly as the old file-order * pass handed it out — sound for pointer * fields, which never read the target's * size. */ 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. */ int packed; /* TY_STRUCT laid out with no padding; part * of type identity (harec types.c:517/621). */ }; 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, *ty_size; extern Type *ty_f32, *ty_f64, *ty_str; extern Type *ty_err; extern Type *ty_never; extern Type *ty_nomem; /* task #29: predeclared `!void` alias */ extern Type *ty_opaque; /* #108(a): abstract unsized; behind indirection only */ 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 */ Type *type_chase_named(Type *t); /* transitive TY_NAMED peel */ 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 */ 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); Sym *scope_lookup_type(Scope*, const char *mod, const char *name); 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 cur_source; /* lexical source-file scope of the declaration * currently being checked. */ Node *file; /* current N_FILE root; used by check_module_shadow * to consult the declaring source file's own `use` * directives when refusing param/let names that * would shadow an imported module bareword. */ int loops; /* nesting count for break/continue */ int matcharms; /* nesting count for yield */ int errs; int is_test; /* #15: `w6c -T` — collect @test fns + synth * the entry; loud-reject a user main. */ int is_test_package; /* package-test variant: validate/retain @test * bodies and export compiler-private metadata, * but do not synthesize an entry. */ const char *test_module; /* generated dispatcher support qualifier */ const char **test_targets; /* canonical generated-main target paths */ int n_test_targets; int sep_mode; /* -c package compilation: imported interfaces are * present, so absent members are hard export errors. */ Node *synth_test_run; /* exact compiler-generated support.run DOT; * its unresolved external hook is intentional */ Node *alloc_octx; /* #3/B': the one empty `alloc([], n)` call node * that has let-declared slice context this walk; * any OTHER empty alloc has no element-type hint * and must fail to infer (harec check.c:1801). * Set by clet around its cexpr, NULL elsewhere. */ const char *package_init_symbol; /* canonical action-owned hidden task. */ }; 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*); /* Re-evaluate a checked integer constant under the declaration owner's * import scope. The compiler export writer uses this to canonicalize array * dimensions without serializing source-level constant dependencies. */ int check_eval_const(Checker*, Node*, const char *owner, int source, u64*); #endif /* WW_H */