// 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. use "support" as sup pub const Program = struct { items []mut 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 []mut 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 []u8, // the "…" lexeme, quotes included body []mut 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). A file-scope `var` // also takes the fn-modifier matrix: `export var` defines a cross-object // symbol, `extern var` consumes one (typed, with no initializer). 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 [][]u8, is_pub bool, // `pub const` / `pub var` — public visibility for an importing module is_export bool, // `export var` — the binding defines a cross-object symbol is_extern bool, // `extern var` — storage defined in another object (value == null) is_mut bool, // `var` — a mutable file-scope binding (`const` is the immutable default) name []u8, type ?TypeRef, // An explicit `align(expr)` qualifier between the type and any // `linksection` — the symbol's storage alignment (`var pool [N]u8 // align(4096) = …`), Zig's slot order. The inner expression is lowered // verbatim. Null when absent. Valid on an `extern var` too (the // alignment is a promise about the externally-defined symbol, as in Zig). align_expr ?Expr = null, // An explicit `linksection("…")` attribute between any `align` and `=` — // the section the symbol is placed in (a NOLOAD scratch region, a // .rodata pad). The inner expression is lowered verbatim, in Zig's slot // order (after `align`). Null when absent; never set on an // `extern var` (the defining object owns the section). link_section ?Expr, value ?Expr, // null only for `extern var` (bodyless/valueless, like `extern fn`) } 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 []u8, alias ?[]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 []u8, // the string-literal payload } pub const FnDecl = struct { doc [][]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 []u8, params []mut Param, ret ?TypeRef, // An explicit `linksection("…")` attribute between the parameter list and // any `callconv(…)` (Zig's slot order) — the section the function is // placed in. The inner expression is lowered verbatim; null when absent. link_section ?Expr, // 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 ?[]mut 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 ?[]u8, // null == the `_` placeholder type TypeRef, } pub const TypeRef = union(enum) { name []u8, // usize, u8, void, noreturn, argv, cstr, or an imported A.B slice PtrElem, // []T / []align(A) T (const by default; see lower.zig) slice_mut PtrElem, // []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 PtrElem, // [*]T — a many-item pointer, const pointee by default (see lower.zig) many_ptr_mut PtrElem, // [*]mut T — a many-item pointer to a mutable pointee many_ptr_volatile PtrElem, // [*]volatile T — const+volatile pointee (see lower.zig) many_ptr_mut_volatile PtrElem, // [*]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 PtrElem, // *T — a single-item pointer, const pointee by default (see lower.zig) ptr_mut PtrElem, // *mut T — a single-item pointer to a mutable pointee ptr_volatile PtrElem, // *volatile T — const+volatile pointee (see lower.zig) ptr_mut_volatile PtrElem, // *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 *mut TypeRef, // [_]T — an array whose length is inferred from its initializer array_inferred_sentinel SentinelPtr, // [_:s]T — its sentinel-terminated form ([_:s]T{ … }) optional *mut 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 []mut 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 ?*mut TypeRef, payload *mut TypeRef, } // The payload of the plain (non-sentinel) pointer/slice forms: the pointee // type plus an optional `align(expr)` qualifier directly after the prefix — // `[]align(16) u8`, `*align(4) mut T` — Zig's slot (before `const`/`volatile` // there, before `mut`/`volatile` here). The expression is lowered verbatim. pub const PtrElem = struct { align_expr ?*mut Expr = null, elem *mut TypeRef, } pub const Array = struct { len *mut Expr, // the length expression, e.g. 512 or pkg.BUF_LEN elem *mut 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 *mut Expr, sentinel *mut Expr, elem *mut 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. // `align_expr` is the optional `align(expr)` qualifier after the closing `]` // (`[*:0]align(16) u8` — Zig's slot, before `mut`); it is never set on the // array form (an array type carries no `align` — align the binding instead). pub const SentinelPtr = struct { sentinel *mut Expr, align_expr ?*mut Expr = null, elem *mut 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 []mut TypeRef, // parameter types, in source order; empty == `fn () R` // An explicit calling convention `callconv(.c)` between the parameter list // and the return type — Zig's slot order, mirroring FnDecl.call_conv. Boxed: // an Expr held by value here would close a by-value cycle through // `Expr.asm_expr` (AsmExpr carries a TypeRef by value). Null when absent. call_conv ?*mut Expr, ret ?*mut 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 []u8, args []mut 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 *mut Stmt, // `defer ` — runs the inner statement on scope exit errdefer_stmt Errdefer, // `errdefer [|err|] ` — runs it only on an error exit defer_block []mut Stmt, // `defer { … }` — runs the block on scope exit errdefer_block ErrdeferBlock, // `errdefer [|err|] { … }` — runs it only on an error exit expr Expr, // a bare call, break, continue, return, etc. } // `errdefer ` with an optional `|err|` capture (`errdefer |err| // log(err)`) — binds the error that is unwinding, in scope for the deferred // statement only. The error binds by value (`|*err|` is rejected, as in Zig); // `capture` is null on the plain form. Plain `defer` takes no capture — there // is no error on a normal exit. pub const Errdefer = struct { capture ?[]u8, body *mut Stmt, } // The block form, `errdefer [|err|] { … }` — the same optional capture, in // scope for the whole deferred block. pub const ErrdeferBlock = struct { capture ?[]u8, body []mut Stmt, } // `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 []mut ?[]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 []mut 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 []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 []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 ?[]u8, // `if opt |*x| { … }` — a pointer capture: `x` binds a pointer to the // payload so the body can write through it (`x.* = v`). The name stays the // bare identifier (a diagnostic anchor must be a real source slice), the // flag carries the `*`. The else arm's error capture binds by value only // (matching Zig), so it takes no flag. capture_is_ptr bool = false, body []mut 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 ?[]mut 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 ?[]u8, } pub const While = struct { is_inline bool, // `inline while` — a compile-time-unrolled loop (lowered with an `inline ` prefix) // `outer: while cond { … }` — an optional loop label, the target of a // `break :outer` / `continue :outer` inside the body. The bare lexeme (the // declaring source slice — it doubles as the Diag anchor); lowering renders // it as Zig's `outer: while (…)` prefix. label ?[]u8 = null, 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 ?[]u8, // `while expr |*x| { … }` — a pointer capture on the payload; same shape // and rules as the `if` flag (the else arm's error capture takes none). capture_is_ptr bool = false, body []mut Stmt, // The loop `else` arm: runs when the loop ends without `break` (condition // false / payload exhausted), lowering to Zig's `while (…) … else { … }`. else_body ?[]mut 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 ?[]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) // `outer: for … { … }` — an optional loop label, exactly as on `While`. label ?[]u8 = null, captures []mut []u8, // `for *p in &arr { … }` — a pointer capture on the ELEMENT: `p` binds a // pointer to each element so the body can write through it, lowering to // Zig's `for (&arr) |*p|`. Only the element capture can be a pointer (the // index is a value — matching Zig); `captures` keeps the bare names. elem_is_ptr bool = false, iter Expr, range_hi ?Expr, // set → `iter` is a range's low bound and this its high bound body []mut 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 ?[]mut Stmt, } pub const Expr = union(enum) { int []u8, // decimal, 0x hex, 0o octal, or 0b binary integer; verbatim float []u8, // decimal float, e.g. 3.14 or 1.5e-3; verbatim string []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 []mut []u8, char []u8, // 'c' (lexeme including quotes) ident []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 []u8, member Member, // expr.field deref *mut Expr, // expr.* — single-item pointer dereference (also a valid lvalue) optional_unwrap *mut 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 []mut StructLitField, // .{ a, b } / .{ .x = 1 } — empty slice == .{} typed_lit TypedLit, // Name{ .x = 1 } / Name{} — a type-prefixed initializer type_lit *mut 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 []u8, // .red — an inferred enum literal (the bare variant name) error_lit []u8, // error.Name — an error-value origination (the bare error name) error_set []mut []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 *mut 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 *mut 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 / loop and/or with a value cont ?[]u8, // `continue`, optionally to a labelled loop (`continue :outer`) // `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 ?[]mut 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 labelled block // expression (supplying that block's value) or an enclosing labelled loop; // lowering renders `break`, then ` :label` when labelled, then ` value` when a // value is present. pub const Break = struct { label ?[]u8, value ?*mut 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 ?[]u8, body []mut Stmt, } pub const Member = struct { base *mut Expr, field []u8, } pub const Call = struct { callee *mut Expr, args []mut Expr, } pub const Index = struct { base *mut Expr, index *mut Expr, } pub const Slice = struct { base *mut Expr, lo *mut Expr, hi ?*mut 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 ?*mut Expr, } pub const BuiltinCall = struct { name []u8, // the bare intrinsic name, no sigil, e.g. "intCast" args []mut Expr, } pub const Unary = struct { op []u8, // "!", "-", "&", or "~" operand *mut 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 *mut Expr, capture ?[]u8, handler *mut 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 *mut Expr, outputs []mut AsmOperand, inputs []mut AsmOperand, clobbers ?*mut 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 []u8, constraint []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 *mut Expr, then *mut Expr, else_ *mut 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 *mut Expr, prongs []mut 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 []mut SwitchPattern, capture ?[]u8, // `=> |*x|` — a pointer capture on the payload; same shape as the loop // flags (`capture` keeps the bare name, the flag carries the `*`). capture_is_ptr bool = false, 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 []u8, lhs *mut Expr, rhs *mut 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 ?[]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 *mut Expr, fields []mut 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 { // The layout modifier before `struct`: "packed" (bit-exact field packing, // an on-disk FAT directory entry) or "extern" (C ABI layout, a type that // crosses a copy_to_user or assembly boundary), emitted verbatim as a // prefix. Null for an ordinary auto-layout struct. The backing-integer // form `packed struct(uN)` is not part of the grammar — the field widths // define the layout. layout ?[]u8, fields []mut Field, decls []mut 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 [][]u8, // leading `///` doc lines, empty == none (see ConstDecl.doc) name []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 ?[]u8, variants []mut EnumVariant, decls []mut 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 [][]u8, // leading `///` doc lines, empty == none (see ConstDecl.doc) name []u8, value ?*mut 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 ?[]u8, variants []mut UnionVariant, decls []mut 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 [][]u8, // leading `///` doc lines, empty == none (see ConstDecl.doc) name []u8, payload ?TypeRef, } // --- tests --------------------------------------------------------------- // The handwritten ast.zig carries no tests (pure definitions); these smoke // tests assert the ported shapes stay constructible and tag-addressable from // Flash, through the generated Zig. test "an expression node constructs and reads back" { e := Expr{ .int = "42" } try sup.expectEqualStrings("42", e.int) } test "an item routes by tag" { it := Item{ .link_decl = .{ .module = "flibc" } } switch it { .link_decl => |l| try sup.expectEqualStrings("flibc", l.module), else => try sup.expect(false), } } test "a bind statement carries its mutability and optional slots" { s := Stmt{ .bind = .{ .is_mut = false, .is_comptime = false, .name = "x", .type = null, .align_expr = null, .value = Expr{ .int = "1" } } } try sup.expect(!s.bind.is_mut) try sup.expect(s.bind.type == null) } test "a switch pattern spans an inclusive range" { pat := SwitchPattern{ .lo = Expr{ .char = "'a'" }, .hi = Expr{ .char = "'z'" } } try sup.expect(pat.hi != null) try sup.expectEqualStrings("'a'", pat.lo.char) } test "a use declaration distinguishes file imports" { u := UseDecl{ .is_pub = false, .module = "token", .alias = null, .is_file = true } try sup.expect(u.is_file) try sup.expectEqualStrings("token", u.module) }