// Flash AST — node definitions for the Flash grammar. // // The shapes below mirror the grammar: a Flash program is a flat list of // top-level items (imports, link directives, function definitions), and a // function body is a flat list of statements over a small expression grammar. // The parser populates these nodes and the lowering stage walks them to Zig // text. Every string field is a byte-slice into the original source, so the // AST copies no text. This invariant is load-bearing beyond zero-copy: the // semantic checker recovers a diagnostic's line and column from a slice's // address — its offset into the source buffer (see sema.zig `locate`) — so a // string field MUST stay a real slice into the original source, never a // synthesized or reallocated string. Everything here grows as the language // gains syntax. pub const Program = struct { items: []Item, }; pub const Item = union(enum) { use_decl: UseDecl, link_decl: LinkDecl, fn_decl: FnDecl, const_decl: ConstDecl, // `comptime { … }` — a top-level comptime block. Its body is an ordinary // statement list (today a lone `@export(…)` that forces a symbol's // emission, e.g. the `_start` shim). Lowers to `comptime { … }` verbatim. comptime_block: []Stmt, test_decl: TestDecl, }; // `test "name" { … }` — a test block: a string-named statement body that runs // under the test harness (`zig build test-flash`), lowering one-to-one to a // Zig `test` block. The name is the verbatim string-literal lexeme, quotes // included, so it re-emits byte-for-byte (and stays a real source slice for // diagnostics and the formatter's anchors). A test declares no binding — the // name is not an identifier and cannot be referenced. pub const TestDecl = struct { name: []const u8, // the "…" lexeme, quotes included body: []Stmt, }; // A top-level named constant: `const NAME = expr` or `const NAME T = expr`. // The block-level `const`/`var` binding is a Stmt (see Bind); this is the // item-level form that lowers to a file-scope `const NAME: T = expr;` (or // `var NAME: T = expr;` when `is_mut`, a mutable global). pub const ConstDecl = struct { // Leading `///` doc-comment lines, each the verbatim bytes after the three // slashes; an empty slice means no doc comment. Re-emitted before the // declaration (see lower.zig), byte-for-byte as zig fmt keeps them. doc: []const []const u8, is_pub: bool, // `pub const` / `pub var` — public visibility for an importing module is_mut: bool, // `var` — a mutable file-scope binding (`const` is the immutable default) name: []const u8, type: ?TypeRef, value: Expr, }; pub const UseDecl = struct { is_pub: bool, // `pub use` — re-export the import to importing modules (`pub const … = @import(…)`) // The @import stem. A bare module name (`use flibc` → "flibc") passes through // verbatim; a quoted file import (`use "syscalls" as sys`) stores the // extensionless stem ("syscalls") and lowering appends the backend artifact // suffix (`.zig`). Frozen Flash source therefore never names a file // extension — `is_file` records which spelling produced this decl. module: []const u8, alias: ?[]const u8, // `use X as Y` / `use "X" as Y` is_file: bool, // a quoted file import (`use "X" as Y`, a sibling file) vs a bare module name }; pub const LinkDecl = struct { module: []const u8, // the string-literal payload }; pub const FnDecl = struct { doc: []const []const u8, // leading `///` doc lines, empty == none (see ConstDecl.doc) is_pub: bool, // `pub fn` — public visibility (precedes `export`/`extern`/`inline`) is_export: bool, // `export fn` — a C-ABI boundary, emitted with callconv(.c) is_extern: bool, // `extern fn` — a bodyless C-ABI prototype (body == null); shares the // `export`/`inline` prototype slot, so none of the three co-occur is_inline: bool, // `inline fn` — forced inlining; shares the prototype slot // with `export`, so the two never co-occur (Zig rejects `export inline fn`) name: []const u8, params: []Param, ret: ?TypeRef, // An explicit calling convention `callconv(.c)` written in the signature — // the inner expression (an inferred enum literal). Null when absent; an // `export fn` emits callconv(.c) without one. Lowered as ` callconv()`. call_conv: ?Expr, body: ?[]Stmt, // null == a bodyless prototype (`extern fn …;`) }; pub const Param = struct { is_comptime: bool, // `comptime x: T` — a compile-time parameter (lowered with a `comptime ` prefix) name: ?[]const u8, // null == the `_` placeholder type: TypeRef, }; pub const TypeRef = union(enum) { name: []const u8, // usize, u8, void, noreturn, argv, cstr, or an imported A.B slice: *TypeRef, // []T (const by default; see lower.zig) slice_mut: *TypeRef, // []mut T slice_sentinel: SentinelPtr, // [:s]T — a sentinel-terminated slice (const by default) slice_sentinel_mut: SentinelPtr, // [:s]mut T — its mutable form many_ptr: *TypeRef, // [*]T — a many-item pointer, const pointee by default (see lower.zig) many_ptr_mut: *TypeRef, // [*]mut T — a many-item pointer to a mutable pointee many_ptr_volatile: *TypeRef, // [*]volatile T — const+volatile pointee (see lower.zig) many_ptr_mut_volatile: *TypeRef, // [*]mut volatile T — mutable+volatile pointee many_ptr_sentinel: SentinelPtr, // [*:s]T — a sentinel-terminated many-item pointer (const by default) many_ptr_sentinel_mut: SentinelPtr, // [*:s]mut T — its mutable form ptr: *TypeRef, // *T — a single-item pointer, const pointee by default (see lower.zig) ptr_mut: *TypeRef, // *mut T — a single-item pointer to a mutable pointee ptr_volatile: *TypeRef, // *volatile T — const+volatile pointee (see lower.zig) ptr_mut_volatile: *TypeRef, // *mut volatile T — mutable+volatile pointee array: Array, // [N]T (N is a length expression) array_sentinel: ArraySentinel, // [N:s]T — a fixed-length sentinel-terminated array array_inferred: *TypeRef, // [_]T — an array whose length is inferred from its initializer array_inferred_sentinel: SentinelPtr, // [_:s]T — its sentinel-terminated form ([_:s]T{ … }) optional: *TypeRef, // ?T — an optional, unwrapped with `orelse` / capture errunion: ErrUnion, // E!T / !T — an error union, propagated with `try` / `catch` fn_type: FnType, // fn(P, …) R — a function type; wrap in `*`/`*mut` for a pointer generic: GenericType, // Name(args…) — a generic type applied in type position // `(A, B)` — a tuple type: parenthesized, comma-separated element types, // arity ≥ 2 (a one-element `(T)` is expression grouping, not a tuple). // Lowers to Zig's inline positional struct `struct { A, B }`; the value // spelling stays the anonymous literal `.{ … }` and element access the // postfix index `t[0]`. tuple: []TypeRef, }; // `E!T` / `!T` — an error union: a payload type `T` paired with an error set. // `set` names the set explicitly (the infix `E!T`, where `E` is an error-set // type — `error{…}` or a named alias). `set == null` is the prefix `!T`, whose // set the compiler *infers*; Zig allows an inferred set only on a function // *declaration's* return type, so the parser rejects it on a function *type* // (`*fn(…) !T`). Both lower verbatim: `set!payload`, or `!payload` inferred. pub const ErrUnion = struct { set: ?*TypeRef, payload: *TypeRef, }; pub const Array = struct { len: *Expr, // the length expression, e.g. 512 or pkg.BUF_LEN elem: *TypeRef, }; // `[N:s]T` — a fixed-length array terminated by a sentinel value: `len` elements // of `elem` with a trailing `sentinel` (Zig guarantees `array[len] == sentinel`). // The inferred-length form `[_:s]T` drops `len` and reuses SentinelPtr below. pub const ArraySentinel = struct { len: *Expr, sentinel: *Expr, elem: *TypeRef, }; // `[*:s]T` — a many-item pointer terminated by a sentinel value. `sentinel` is // the terminator expression (commonly `0`), `elem` the pointee element type. // Like `[*]T`, the element is not const by default; the prefix composes under // `*` / `[N]` / `?` as any other element type does. The structurally-identical // sentinel-terminated *slice* forms (`[:s]T` / `[:s]mut T`) and the // inferred-length sentinel *array* (`[_:s]T`) reuse this payload. pub const SentinelPtr = struct { sentinel: *Expr, elem: *TypeRef, }; // `fn(P, …) R` — a function *type*: an ordered list of (unnamed) parameter // types and an optional return type. It is not itself a storable value; a // function *pointer* is this type behind a `*` (`*fn(…) R` lowers to // `*const fn (…) R`, const-pointee by default like any `*T`; `*mut fn(…)` for a // mutable one). The pointer-ness lives in the surrounding `ptr`/`ptr_mut`, never // here. Parameters are bare types — a function type names no bindings — so the // surface mirrors Go's `func(int, string) error`, not a named parameter list. pub const FnType = struct { params: []TypeRef, // parameter types, in source order; empty == `fn () R` ret: ?*TypeRef, // return type; null lowers to `void` }; // `Name(args…)` — a generic type applied in type position (`List(u8)`, // `Map(K, V)`, `pkg.Ring(64)`), so a generic instance can be named where a // type is expected (parameter, field, return). The arguments are full // expressions, exactly as a value-position call `List(u8)` parses them — a // type-name argument is an identifier expression, a value argument (`Ring(64)`) // is a literal, and a composite-type argument (`List([]u8)`, `Map(u8, *fn() u8)`) // is a `type_lit` expression (a `[`/`?`/`*`/`fn`-led type in value position). // `name` is the verbatim (possibly dotted) generic name; it lowers as // `name(args…)`. pub const GenericType = struct { name: []const u8, args: []Expr, }; pub const Stmt = union(enum) { discard: Expr, // `_ = expr` bind: Bind, // `:=` / `var` / `const` destructure: Destructure, // `a, b := e` / `var a, b = e` — a multi-name bind assign: Assign, // `target op= value` (op is "=", "+=", …) destructure_assign: DestructureAssign, // `a, b = e` — store to existing lvalues if_stmt: If, // `if cond { … } else { … }` while_stmt: While, // `while cond { … }` / `while cond |x| { … }` for_stmt: For, // `for item in iter { … }` defer_stmt: *Stmt, // `defer ` — runs the inner statement on scope exit errdefer_stmt: *Stmt, // `errdefer ` — runs it only on an error exit defer_block: []Stmt, // `defer { … }` — runs the block on scope exit errdefer_block: []Stmt, // `errdefer { … }` — runs it only on an error exit expr: Expr, // a bare call, break, continue, return, etc. }; // `a, b := expr` (immutable, the `:=` canon) or `var a, b = expr` / // `const a, b = expr` (the keyword form) — a destructuring bind over a // tuple-valued expression. One keyword rules all names: every name shares // `is_mut`. A `_` entry (a null name) skips that position; at least one entry // is a real name (an all-underscore destructure is rejected — that is // `_ = expr`). No per-name type or `align` is spellable — annotate the // producer instead. Lowers to Zig's native destructure, the binding keyword // repeated per name: `const a, const b = expr;` / `var a, var b = expr;`, // a skip staying `_`. pub const Destructure = struct { is_mut: bool, names: []?[]const u8, // null == `_` (skip); len ≥ 2, at least one non-null value: Expr, }; // `a, b = expr` — a destructuring assignment onto existing lvalues (member, // index, and deref targets included). `=` is the only operator — a compound // op cannot destructure — and `_` is not a target (skipping is the bind // form's job). Lowers verbatim: `a, b = expr;`. pub const DestructureAssign = struct { targets: []Expr, // len ≥ 2, each an lvalue expression value: Expr, }; pub const Bind = struct { is_mut: bool, is_comptime: bool, // `comptime var` / `comptime const` — a compile-time local (lowered with a `comptime ` prefix) name: []const u8, type: ?TypeRef, // An `align(expr)` qualifier (`const x T align(16) = …`), between the type // and `=`; null when absent. Lowered verbatim as ` align()`. align_expr: ?Expr, value: Expr, }; // An assignment statement. `op` is the verbatim operator lexeme — "=" for a // plain store, or one of the compound forms "+=", "-=", "*=", "/=", "%=", "&=", // "|=", "^=", "<<=", ">>="; all of these are spelled identically in the emitted // Zig. pub const Assign = struct { target: Expr, op: []const u8, value: Expr, }; pub const If = struct { cond: Expr, // An optional-capture if: `if opt |x| { … }` binds `x` to the unwrapped // payload for the body, lowering to Zig's `if (opt) |x| { … }`. Null for a // plain boolean `if`. capture: ?[]const u8, body: []Stmt, // The `else` arm, if present. An `else if` chain is encoded as a one-element // body holding a nested `if_stmt`; lowering renders that as idiomatic Zig // `else if`. else_body: ?[]Stmt, // The error capture on the else arm: `if expr |x| { … } else |err| { … }` // binds the failed error union's error for the else body, lowering to Zig's // `else |err| { … }`. Only set together with `else_body`; a captured else // arm is always a block, never an `else if` chain. else_capture: ?[]const u8, }; pub const While = struct { is_inline: bool, // `inline while` — a compile-time-unrolled loop (lowered with an `inline ` prefix) cond: Expr, // An optional payload capture `while expr |x| { … }`: binds the unwrapped // optional / error-union payload for the body, lowering to Zig's iterator // `while (expr) |x| { … }`. Null for a plain boolean `while`. Shaped exactly // like the `if` capture — the same `| ident | {` lookahead stops the // condition parse before it. capture: ?[]const u8, body: []Stmt, // The loop `else` arm: runs when the loop ends without `break` (condition // false / payload exhausted), lowering to Zig's `while (…) … else { … }`. else_body: ?[]Stmt, // The error capture on the else arm — `while next() |x| { … } else |err| // { … }`, the error-union iterator's failure binding. Only set together // with `else_body`. else_capture: ?[]const u8, }; // `for cap in iter { … }` — iterate `iter`, binding each element to `cap`. Two // surface forms ride on the bare element loop: // * a range iterator `for i in lo..hi` sets `range_hi`, lowering to Zig's // `for (lo..hi) |i|` — the counted loop without the `while` ceremony; // * a second capture `for x, i in xs` indexes the iteration, lowering to // `for (xs, 0..) |x, i|` (the trailing `0..` index range is implicit, // supplied at lowering). // `captures` holds one name (the element) or two (element, then index). `iter` // is the iterable, or the range's low bound when `range_hi` is set. pub const For = struct { is_inline: bool, // `inline for` — a compile-time-unrolled loop (lowered with an `inline ` prefix) captures: [][]const u8, iter: Expr, range_hi: ?Expr, // set → `iter` is a range's low bound and this its high bound body: []Stmt, // The loop `else` arm: runs when the iteration completes without `break`, // lowering to Zig's `for (…) |…| { … } else { … }`. A `for` else takes no // capture (there is no error to bind — matching Zig). else_body: ?[]Stmt, }; pub const Expr = union(enum) { int: []const u8, // decimal, 0x hex, 0o octal, or 0b binary integer; verbatim float: []const u8, // decimal float, e.g. 3.14 or 1.5e-3; verbatim string: []const u8, // A `\\…` raw multiline string: one entry per source line, each the bytes // after that line's `\\` (no escape processing). Lowering re-emits them as a // Zig multiline string, joined by newlines. multiline_str: [][]const u8, char: []const u8, // 'c' (lexeme including quotes) ident: []const u8, // A reserved value keyword — `true`, `false`, `null`, `undefined`, or // `unreachable`. Held apart from `ident` so these words are never bound as // names (the lexer yields them as keywords, not identifiers) and so a later // check can forbid `undefined` as a const value. Lowers verbatim. value_word: []const u8, member: Member, // expr.field deref: *Expr, // expr.* — single-item pointer dereference (also a valid lvalue) optional_unwrap: *Expr, // expr.? — optional unwrap (assert non-null); same postfix slot as `.*` call: Call, // expr(args) index: Index, // expr[i] slice: Slice, // expr[lo..hi] / expr[lo..] / expr[lo..hi :s] (sentinel-terminated) builtin_call: BuiltinCall, // #name(args) unary: Unary, // prefix op: ! - & binary: Binary, // lhs op rhs struct_lit: []StructLitField, // .{ a, b } / .{ .x = 1 } — empty slice == .{} typed_lit: TypedLit, // Name{ .x = 1 } / Name{} — a type-prefixed initializer type_lit: *TypeRef, // a composite type in value position — a type-alias value // (`const F = *fn(u8) u8`), a generic argument (`List([]u8)`), or the head of // an array-typed literal (`[_]u8{ … }`); a `[`/`?`/`*`/`fn`-led type, distinct // from a named type that parses as `ident` enum_lit: []const u8, // .red — an inferred enum literal (the bare variant name) error_lit: []const u8, // error.Name — an error-value origination (the bare error name) error_set: [][]const u8, // error{ A, B } — a named error-set definition (member names) block_expr: BlockExpr, // label: { … } — a labeled block whose value is a `break :label v` struct_def: StructDef, // struct { field T, … } — a struct type definition enum_def: EnumDef, // enum { a, b } / enum(u8) { … } — an enum type definition union_def: UnionDef, // union(enum) { a, b T, … } — a tagged-union type definition group: *Expr, // ( expr ) — explicit parentheses, preserved so the // emitted Zig keeps the programmer's evaluation order verbatim if_expr: IfExpr, // `if cond a else b` — an if used for its value (both arms required) switch_expr: SwitchExpr, // `switch subj { pat => body, … }` — a switch over the subject try_expr: *Expr, // `try expr` — unwrap an error union, propagate on error catch_expr: Catch, // `expr catch [|e|] handler` — recover from an error asm_expr: AsmExpr, // `asm [volatile] (template [: out : in : clobbers])` — inline assembly // Control-transfer forms are expressions (as in Zig) so they compose on the // right of `orelse` — e.g. `argv[i] orelse break`. brk: Break, // break, optionally to a labelled block and/or with a value cont, // continue // `return` / `return v` / `return a, b` — null is a bare void return; // otherwise the same-line value list (len ≥ 1). A multi-value return is // statement-position sugar for returning a tuple: `return a, b` lowers to // `return .{ a, b };`, while a single value lowers verbatim — so // `return .{ a, b }` (one struct_lit value) round-trips as written. ret: ?[]Expr, }; // A `break`, in its three shapes: a bare loop `break` (both fields null), a // labelled `break :blk` (label set), and a value-carrying `break :blk v` / // `break v` (value set). A labelled break targets an enclosing block // expression and supplies that block's value; lowering renders `break`, // then ` :label` when labelled, then ` value` when a value is present. pub const Break = struct { label: ?[]const u8, value: ?*Expr, }; // `label: { … }` — a block used as an expression. When `label` is set its value // is whatever a `break :label v` inside it yields; an unlabelled block (`label == // null`) is a switch-prong body — a void `=> {}` arm or a multi-statement arm — // carrying no value. Lowering renders the block body like any other (statements // at depth + 1, closing brace at depth), prefixed by the label when present. pub const BlockExpr = struct { label: ?[]const u8, body: []Stmt, }; pub const Member = struct { base: *Expr, field: []const u8, }; pub const Call = struct { callee: *Expr, args: []Expr, }; pub const Index = struct { base: *Expr, index: *Expr, }; pub const Slice = struct { base: *Expr, lo: *Expr, hi: ?*Expr, // null == open-ended `[lo..]` // `[lo..hi :s]` — a sentinel-terminated slice: the result is asserted to end // at the sentinel value `s`. Independent of `hi` (an open-ended `[lo.. :s]` // is valid); null == no sentinel, the ordinary slice. sentinel: ?*Expr, }; pub const BuiltinCall = struct { name: []const u8, // the bare intrinsic name, no sigil, e.g. "intCast" args: []Expr, }; pub const Unary = struct { op: []const u8, // "!", "-", "&", or "~" operand: *Expr, }; // `lhs catch handler` / `lhs catch |name| handler`. The handler runs when // `lhs` yields an error and is one of two shapes: a recovery *expression* — a // fallback value or a control transfer (`catch 0`, `catch return e`) — or a // recovery *block* `catch { … }` (a label-less `block_expr`), the multi- // statement void-recovery idiom (`device.flush() catch {}`). `capture` is the // error binding `|name|`, in scope for the handler only; null when unbound. pub const Catch = struct { lhs: *Expr, capture: ?[]const u8, handler: *Expr, }; // `asm [volatile] (template : outputs : inputs : clobbers)` — an inline-assembly // expression, mirroring Zig's (Tier 0 lowers it byte-for-byte). `is_volatile` // records the `volatile` modifier (every kernel-side use carries it). `template` // is the instruction text — a string or a `\\` multiline string, lowered // verbatim. The three operand sections are optional and positional: `outputs` // and `inputs` are `[name] "constraint" (body)` lists; `clobbers` is the single // trailing expression (`.{ .memory = true }`), null when absent. The constraint // strings and the template are an irreducible foreign (LLVM/assembler) // sublanguage that passes through unchanged — Flash adds no spelling of its own. pub const AsmExpr = struct { is_volatile: bool, template: *Expr, outputs: []AsmOperand, inputs: []AsmOperand, clobbers: ?*Expr, }; // One asm operand: `[name] "constraint" (body)`. `name` is the bracketed // symbolic name, `constraint` the verbatim quoted constraint-string lexeme. The // body is either a return-type output (`-> T`) or an expression operand — an // lvalue for an output, a value for an input. pub const AsmOperand = struct { name: []const u8, constraint: []const u8, // the "…" lexeme, quotes included body: AsmOperandBody, }; pub const AsmOperandBody = union(enum) { ret_type: TypeRef, // `(-> T)` — a value-producing output expr: Expr, // `(lvalue)` / `(value)` }; // `if cond thenExpr else elseExpr` — an `if` used for its value (the // expression form, distinct from the `if` statement). Both arms are // expressions and both are required: an if-expression always yields a value, // so there is no else-less form. The condition carries no parentheses (as in // the statement form); lowering renders the idiomatic Zig `if (cond) a else b`. pub const IfExpr = struct { cond: *Expr, then: *Expr, else_: *Expr, }; // `switch subject { prong, … }` — a switch over a scrutinee. The subject is // paren-less (like the other control-flow headers); each prong matches it and // yields a body expression. Lowering renders the idiomatic Zig // `switch (subject) { … }`. pub const SwitchExpr = struct { subject: *Expr, prongs: []SwitchProng, }; // One switch prong: `patterns => body`, or the default `else => body`. `is_else` // marks the default (its `patterns` is empty). `capture`, when set, is the // payload binding a `=> |x|` introduces — the active union variant's payload, in // scope for `body` only. `body` is any expression — an inline value, an // `if`-expression, a nested `switch`, or a `{ … }` block (an unlabelled block // expression) for a void or multi-statement arm. pub const SwitchProng = struct { is_else: bool, patterns: []SwitchPattern, capture: ?[]const u8, body: Expr, }; // One match item in a prong's pattern list: a single value (`hi == null`) or an // inclusive range `lo...hi` (`hi` set). Prongs comma-separate their patterns // (`'\r', '\n' => …`), so a prong holds one or more of these. pub const SwitchPattern = struct { lo: Expr, hi: ?Expr, }; pub const Binary = struct { // The Flash operator lexeme: "+", "-", "*", "/", "%", "==", "!=", "<", // "<=", ">", ">=", "&&", "||", "orelse". Lowering maps "&&"/"||" to Zig's // `and`/`or`; the rest pass through unchanged. op: []const u8, lhs: *Expr, rhs: *Expr, }; // One element of a `.{ … }` literal. A named element is `.name = value` (a // struct-init field); a positional element leaves `name` null (a tuple element), // e.g. the single positional field in `.{flibc.sys.dump_free()}`. pub const StructLitField = struct { name: ?[]const u8, value: Expr, }; // `Type{ .x = 1, … }` / `Type{}` — a type-prefixed initializer (a struct or // union literal whose type is named rather than inferred from context, unlike // the anonymous `.{ … }`). `type` is the prefix expression (an identifier, or a // dotted `pkg.Type`); the fields reuse StructLitField, so the same named / // positional spelling and zig fmt brace-spacing apply. It lowers verbatim: // `Type{ .x = 1 }`. pub const TypedLit = struct { type: *Expr, fields: []StructLitField, }; // `struct { name T, …, }` — a struct type definition, the value of a // `const Name = struct { … }`. Data fields come first (each `name T`, // comma-terminated), then any associated declarations — methods (`fn`) and // constants (`const`) — in source order. The fields-then-decls split mirrors // the idiomatic Zig layout the lowering emits: the field block, a blank line, // then the declarations one blank line apart. pub const StructDef = struct { fields: []Field, decls: []ContainerDecl, }; // One associated declaration inside a container body (struct, enum, or union): // a method, a constant, or an import. All three reuse the top-level node types — // a method is an ordinary FnDecl whose receiver (when it takes one) is a plain // first parameter, so there is no implicit `self`; an associated constant is an // ordinary ConstDecl; an associated import is an ordinary UseDecl (`use // "syscalls" as sys` lowering to a container-level `const sys = // @import("syscalls.zig")`), so `use` is the one import spelling at every scope // instead of an in-container `const … = #import(…)`. pub const ContainerDecl = union(enum) { method: FnDecl, constant: ConstDecl, use_import: UseDecl, }; pub const Field = struct { doc: []const []const u8, // leading `///` doc lines, empty == none (see ConstDecl.doc) name: []const u8, type: TypeRef, // A default field value: `name T = expr` initialises the field when the // container is constructed with that field omitted (`HistSlot{}`). The // expression is any value — a literal, `undefined`, an empty `.{}` — lowered // verbatim after the type (`name: T = expr,`). Null when the field has no // default (`name T,`). default: ?Expr, }; // `enum { a, b }` or `enum(u8) { a, b }` — an enum type definition. `tag_type` // carries the explicit backing integer type of `enum(T)`, null for a bare enum. // Variants come first, then any associated declarations — methods, constants, // imports — exactly as in a struct body (the fields-then-decls layout rule). pub const EnumDef = struct { tag_type: ?[]const u8, variants: []EnumVariant, decls: []ContainerDecl, }; // One enum variant: a bare name, optionally with an explicit discriminant // (`perm = 1`). `value` is the discriminant expression, null for an implicit // variant. Mixing is allowed (`a, b = 5, c`), exactly as Zig permits. pub const EnumVariant = struct { doc: []const []const u8, // leading `///` doc lines, empty == none (see ConstDecl.doc) name: []const u8, value: ?*Expr, }; // `union(enum) { a, b T, … }` — a tagged-union type definition. `tag` carries // the verbatim tag selector inside `union(…)`: "enum" for the inferred-tag form // `union(enum)`, an explicit enum type name for `union(MyTag)`, or null for a // bare untagged `union`. Variants are name-first like struct fields; a payload // type is optional (a bare name is a void variant). // Variants come first, then any associated declarations, as in a struct body. pub const UnionDef = struct { tag: ?[]const u8, variants: []UnionVariant, decls: []ContainerDecl, }; // One union variant: a name and an optional payload type. `payload` is null for // a void variant (the bare `none`), set for a typed variant (`echo u8` → // `echo: u8`). Mixing is allowed, exactly as Zig permits. pub const UnionVariant = struct { doc: []const []const u8, // leading `///` doc lines, empty == none (see ConstDecl.doc) name: []const u8, payload: ?TypeRef, };