Files
ww/cmd/wcc/ww.h

621 lines
19 KiB
C

/*
* 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"
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 <dotted-path>` — driver import boundary:
* the following file's decls mangle on the full import
* path, not the leaf `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;
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 <path>` 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 <path>` 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 *usepath; /* M1 #22: on an N_USE node, the full
* dotted IMPORT path (`encoding.utf8`)
* vs the leaf alias in `str`. Drives
* the path-keyed decl_mod match and the
* qualified-ref codegen hint. */
int imported; /* M1 #22: decl reached through an
* `//ww:module <path>` 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 <path>` 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 <path>` dotted
* path; mangles decls on the path WITHOUT
* imported=1 (primary-ness for -c and the #32
* bare-main rule stay intact), and the in-file
* `package` clause asserts (leaf == last
* component) instead of overwriting curmod. */
const char *testmodule; /* hidden package-driver alias for toolchain
* `package test`; NULL outside that compile */
};
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. */
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. */
const char *test_module; /* generated dispatcher support qualifier */
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. */
};
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, u64*);
#endif /* WW_H */