`match (e) { ... }` can now sit in expression position, with each
arm using `yield expr;` to produce the match's value:
let v = match (r) {
case let n: i32 => yield n + 1;
case let s: str => yield s.len: i32 + 100;
};
TK_YIELD keyword + N_YIELD AST node, both appended at the tail of
their enums to keep prior numeric values byte-stable for the
wwdump-diff gates.
Checker: cexpr for N_MATCH walks each arm's body looking for the
first N_YIELD; the match's type is the unified yield type (or
ty_void if no yield, preserving the statement-form semantics).
Mismatched arm yields are flagged.
Cgen: a yield-target stack (separate from the loop break stack)
holds each enclosing match's end label. N_YIELD evaluates its
expression into AX (and BX for str) and JMPs to the topmost entry.
cgmatch pushes its end label on entry and pops on exit.
Selfhost mirror: lib/ww/lex/tok.ww kwtab+name, lib/ww/ast.ww
N_YIELD def+print, lib/ww/parse/stmt.ww yield-stmt; selfhost cgen
adds a yieldbuf to the cgen struct and a cgyield helper. Verified
end-to-end: a yield-using program compiled via the wwstage cgen
matches the C-cgen build's exit code.
499 lines
13 KiB
C
499 lines
13 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_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_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
|
|
} 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 */
|