ajhahn.de
← Flash
Flash 1917 lines
// Flash lowering — AST to Zig source text for Flash (the Tier 0 backend).
//
// This is the whole Tier-0 bet: instead of emitting machine code, Flash emits
// Zig and lets the existing FlashOS toolchain do code generation, linking,
// +strict-align handling, and comptime. The mapping is intentionally close to
// 1:1 so a ported module is readable Zig a human can diff against the original
// during the migration:
//
//   use X            -> const X = @import("X");           (a bare module/package)
//   use X as Y       -> const Y = @import("X");
//   use "X" as Y     -> const Y = @import("X.zig");       (a sibling file import;
//                        lowering owns the backend '.zig' suffix — the Flash
//                        source names the stem only, no file extension)
//   pub use X as Y   -> pub const Y = @import("X");       (a re-exported import)
//   link "M"         -> comptime { _ = @import("M"); }   (consecutive links fold
//                                                          into one comptime block)
//   #name(a, …)      -> @name(a, …)   (a compiler intrinsic: the Flash '#'
//                        sigil maps 1:1 to Zig's '@')
//   fn f(a T) R      -> fn f(a: T) R { ... }   (the return type follows the
//                        parameter list directly — no arrow; a missing return
//                        lowers to void)
//   fn f(a T)        -> fn f(a: T) void { ... }
//   export fn        -> export fn ... callconv(.c)        (a C-ABI boundary)
//   inline fn        -> inline fn ...                     (always-inline marker)
//   /// text         -> /// text          (a `///` doc comment: content kept
//                        byte-for-byte and re-emitted before its declaration —
//                        const, fn, struct field/member, enum/union variant)
//   []T              -> []const T        ([]mut T -> []T)
//   [*]T             -> [*]const T       (const-pointee default; [*]mut T -> [*]T)
//   [*:s]T           -> [*:s]const T     (sentinel many-ptr; [*:s]mut T -> [*:s]T)
//   [N]T             -> [N]T
//   argv             -> [*]const ?[*:0]const u8   (builtin alias; suppressed when
//   cstr             -> [*:0]const u8              the program declares the name)
//   fn(P, …) R    -> fn (P, …) R   (a function *type*; zig fmt spaces the
//                        anonymous `fn (`; a missing return -> void)
//   *fn(P, …) R   -> *const fn (P, …) R   (a function *pointer*: `*` over the
//                        function type, const-pointee by default like any *T;
//                        *mut fn(…) R -> *fn (…) R for a mutable one)
//   const N T = e    -> const N: T = e;                   (top-level constant)
//   ?T / !T          -> ?T / !T            (optional / inferred error-union types)
//   E!T              -> E!T                 (explicit error union: a named set E)
//   const N = struct {f T} -> const N = struct { f: T };   (fields, then any
//                        `fn` / `const` decls, blank-line separated like zig fmt)
//   const N = enum {a, b}  -> const N = enum { a, b };      (enum(T) + explicit
//                        `= discriminant` per variant both supported)
//   const U = union(enum) {a, b T} -> union(enum) { a, b: T };  (tagged union;
//                        optional payload type per variant, bare == void)
//   const U = \\line  -> const U =\n    \\line ;   (multiline / raw string;
//                        byte-exact in const/binding/discard value position)
//
// Top-level items are emitted in source order, separated by a single blank
// line, where a "unit" is a run of consecutive `use` declarations, a run of
// consecutive `link` declarations (one comptime block), one top-level constant,
// one function, or one test block. The emitted file ends with a trailing
// newline, as hand-written Zig does.
//
// Anchored emission (`emitAnchored`; flashc's `--anchors`) prefixes every
// top-level constant and function with a `// <file>:<line>` comment naming
// the Flash source line it lowered from, so a Zig error in the emitted file
// reads back to its .flash origin by eye. Strictly opt-in: default emission
// stays byte-identical with the flag absent.

use "ast"
use "parser"
use "sema"
use "support" as sup

pub const Error = error{OutOfMemory}

// The backend artifact suffix appended to a quoted file import: `use "syscalls"`
// lowers to @import("syscalls.zig"). Lowering owns this — frozen Flash source
// names no file extension, so the suffix changes here (not across the corpus)
// when the Tier-0 Zig backend is replaced.
const backend_ext = ".zig"

pub fn emit(arena sup.Allocator, program ast.Program) Error![]u8 {
    return emitWith(arena, program, null, "")
}

// Anchored emission: as `emit`, but every top-level constant and function is
// preceded by a `// <file>:<line>` comment locating its Flash source line.
// `src` is the original source buffer — declaration names are slices into it
// (the ast.flash invariant), which is how the line is recovered. `file` is
// the name stamped into each anchor; callers pass a basename, so the output
// never encodes the invoking directory layout.
pub fn emitAnchored(arena sup.Allocator, program ast.Program, src []u8, file []u8) Error![]u8 {
    return emitWith(arena, program, src, file)
}

fn emitWith(arena sup.Allocator, program ast.Program, anchor_src ?[]u8, anchor_file []u8) Error![]u8 {
    var e Emitter = .{ .arena = arena, .anchor_src = anchor_src, .anchor_file = anchor_file }
    items := program.items
    // A top-level declaration (const or var) named `argv` / `cstr` shadows the
    // builtin type alias of that name for the whole file (emitType then emits it
    // verbatim instead of expanding it). The corpus declares neither, so this
    // changes no byte.
    for it in items {
        if it == .const_decl {
            if sup.eql(u8, it.const_decl.name, "argv") {
                e.argv_shadowed = true
            }
            if sup.eql(u8, it.const_decl.name, "cstr") {
                e.cstr_shadowed = true
            }
        }
    }
    var i usize = 0
    var first = true
    while i < items.len {
        if !first {
            // A blank line between units.
            try e.raw("\n")
        }
        first = false
        switch items[i] {
            .use_decl => {
                while i < items.len && items[i] == .use_decl {
                    try e.emitUseDeclAt(items[i].use_decl)
                    try e.raw("\n")
                    i += 1
                }
            },
            .link_decl => {
                try e.raw("comptime {\n")
                while i < items.len && items[i] == .link_decl {
                    try e.raw("    _ = @import(\"")
                    try e.raw(items[i].link_decl.module)
                    try e.raw("\");\n")
                    i += 1
                }
                try e.raw("}\n")
            },
            .const_decl => |c| {
                try e.emitAnchor(c.name)
                try e.emitConstDecl(c)
                i += 1
            },
            .fn_decl => |f| {
                try e.emitAnchor(f.name)
                try e.emitFn(f)
                i += 1
            },
            // `comptime { … }` at file scope — the body reuses the shared
            // block layout (statements at depth 1, closing brace at 0).
            .comptime_block => |stmts| {
                try e.raw("comptime ")
                try e.emitBlockBody(stmts, 0)
                try e.raw("\n")
                i += 1
            },
            // `test "name" { … }` lowers one-to-one to a Zig test block;
            // the name lexeme (quotes included) re-emits verbatim.
            .test_decl => |t| {
                try e.raw("test ")
                try e.raw(t.name)
                try e.raw(" ")
                try e.emitBlockBody(t.body, 0)
                try e.raw("\n")
                i += 1
            },
        }
    }
    return e.buf.toOwnedSlice(arena)
}

const Emitter = struct {
    arena sup.Allocator,
    buf sup.List(u8) = .empty,
    // The builtin type aliases `argv` / `cstr` (see emitType) are suppressed when
    // the program declares a top-level binding of that name, so a user — or a
    // future standard library — alias wins instead of being silently overridden.
    // Set once, before emission, from the top-level declarations.
    argv_shadowed bool = false,
    cstr_shadowed bool = false,
    // Anchored emission (emitAnchored): the source buffer the declaration
    // names slice into — null when anchors are off — and the file name
    // stamped into each anchor line.
    anchor_src ?[]u8 = null,
    anchor_file []u8 = "",

    fn raw(self *mut Emitter, s []u8) Error!void {
        try self.buf.appendSlice(self.arena, s)
    }

    // The `// <file>:<line>` comment above a top-level declaration, emitted
    // only under anchored emission. `name` is the declaration's name lexeme —
    // a slice into the anchored source — so the line is recovered exactly the
    // way sema locates a diagnostic. Sits above any doc comment, keeping the
    // `///` block attached to its declaration in the emitted Zig.
    fn emitAnchor(self *mut Emitter, name []u8) Error!void {
        if self.anchor_src |src| {
            try self.raw("// ")
            try self.raw(self.anchor_file)
            try self.raw(":")
            try self.rawDecimal(sema.locate(src, name).line)
            try self.raw("\n")
        }
    }

    // Append `n` in decimal; ten digits cover any u32.
    fn rawDecimal(self *mut Emitter, n u32) Error!void {
        var buf [10]u8 = undefined
        var i usize = buf.len
        var v = n
        while true {
            i -= 1
            buf[i] = '0' + #as(u8, #intCast(v % 10))
            v /= 10
            if v == 0 {
                break
            }
        }
        try self.raw(buf[i..])
    }

    // A top-level function: emitted at depth 0 and terminated with a newline.
    // The depth-aware body lives in emitFnAt, which a struct method reuses.
    fn emitFn(self *mut Emitter, f ast.FnDecl) Error!void {
        try self.emitDoc(f.doc, 0)
        try self.emitFnAt(f, 0)
        try self.raw("\n")
    }

    // Emit a function whose signature starts at the current column (the caller
    // supplies any leading indent) and whose closing brace returns to `depth` —
    // 0 for a top-level function, the struct's field indent for a method. The
    // body is one level deeper; no trailing newline is emitted.
    fn emitFnAt(self *mut Emitter, f ast.FnDecl, depth usize) Error!void {
        if f.is_pub {
            try self.raw("pub ")
        }
        if f.is_export {
            try self.raw("export ")
        }
        if f.is_extern {
            try self.raw("extern ")
        }
        if f.is_inline {
            try self.raw("inline ")
        }
        try self.raw("fn ")
        try self.raw(f.name)
        try self.raw("(")
        for p, idx in f.params {
            if idx != 0 {
                try self.raw(", ")
            }
            if p.is_comptime {
                try self.raw("comptime ")
            }
            try self.raw(p.name orelse "_")
            try self.raw(": ")
            try self.emitType(p.type)
        }
        try self.raw(")")
        // `linksection(…)` precedes any `callconv(…)`, Zig's slot order; the
        // inner expression passes through verbatim.
        if f.link_section |ls| {
            try self.raw(" linksection(")
            try self.emitExpr(ls)
            try self.raw(")")
        }
        // An explicit `callconv(.c)` from the signature wins; otherwise an
        // `export fn` is canonicalised with the implicit C ABI marker.
        if f.call_conv |cc| {
            try self.raw(" callconv(")
            try self.emitExpr(cc)
            try self.raw(")")
        } else if f.is_export {
            try self.raw(" callconv(.c)")
        }
        try self.raw(" ")
        if f.ret |r| {
            try self.emitType(r)
        } else {
            try self.raw("void")
        }
        // A bodyless prototype (`extern fn`) closes with `;`; a defined
        // function emits its brace body one space after the return type.
        if f.body |body| {
            try self.raw(" ")
            try self.emitBlockBody(body, depth)
        } else {
            try self.raw(";")
        }
    }

    // A top-level constant: emitted at depth 0 and terminated with a newline.
    // emitConstDeclAt carries the depth so an associated constant inside a
    // struct reuses it.
    fn emitConstDecl(self *mut Emitter, c ast.ConstDecl) Error!void {
        try self.emitDoc(c.doc, 0)
        try self.emitConstDeclAt(c, 0)
        try self.raw("\n")
    }

    // Emit `[pub ][export |extern ](const|var) NAME[: T][ align(A)][ linksection(S)][ = value];` starting at
    // the current column (the caller supplies any leading indent), ending at the
    // `;` with no trailing newline.
    // `depth` is the statement's own indent, threaded into the value so a
    // multiline string or a nested type definition lays out one level deeper.
    fn emitConstDeclAt(self *mut Emitter, c ast.ConstDecl, depth usize) Error!void {
        if c.is_pub {
            try self.raw("pub ")
        }
        if c.is_export {
            try self.raw("export ")
        }
        if c.is_extern {
            try self.raw("extern ")
        }
        try self.raw(if (c.is_mut) "var " else "const ")
        try self.raw(c.name)
        if c.type |ty| {
            try self.raw(": ")
            try self.emitType(ty)
        }
        // `align(…)` then `linksection(…)` sit between the type and `=`,
        // Zig's slot order; each inner expression passes through verbatim.
        if c.align_expr |ae| {
            try self.raw(" align(")
            try self.emitExpr(ae)
            try self.raw(")")
        }
        if c.link_section |ls| {
            try self.raw(" linksection(")
            try self.emitExpr(ls)
            try self.raw(")")
        }
        // An `extern var` is valueless — the declaration ends at the type.
        if c.value |value| {
            if value == .multiline_str {
                try self.raw(" ")
                try self.emitMultilineRhs(value.multiline_str, depth)
            } else {
                try self.raw(" = ")
                try self.emitValue(value, depth)
                try self.raw(";")
            }
        } else {
            try self.raw(";")
        }
    }

    // Emit one import as `[pub ]const NAME = @import("TARGET");` at the current
    // column (the caller supplies any leading indent), ending at the `;` with no
    // trailing newline. A quoted file import names the module stem only — lowering
    // appends the backend artifact suffix here — so the same `use` form lowers
    // identically at the top level and inside a struct body.
    fn emitUseDeclAt(self *mut Emitter, u ast.UseDecl) Error!void {
        if u.is_pub {
            try self.raw("pub ")
        }
        try self.raw("const ")
        try self.raw(u.alias orelse u.module)
        try self.raw(" = @import(\"")
        try self.raw(u.module)
        if u.is_file {
            try self.raw(backend_ext)
        }
        try self.raw("\");")
    }

    // Lay out a multiline-string value in assignment-RHS position. The caller
    // has emitted the left-hand side and a trailing space, up to but not
    // including the `=`. Produces, matching zig fmt:
    //     =\n  <\\lines at depth+1>  indent(depth) ;
    // `depth` is the statement's own indent (0 for a top-level constant).
    fn emitMultilineRhs(self *mut Emitter, lines []mut []u8, depth usize) Error!void {
        try self.raw("=\n")
        for ln in lines {
            try self.indent(depth + 1)
            try self.raw("\\\\")
            try self.raw(ln)
            try self.raw("\n")
        }
        try self.indent(depth)
        try self.raw(";")
    }

    // Emit the value of a binding or constant. A struct/enum type definition
    // lays out across multiple lines with its closing brace at `depth` (the
    // statement's own indent); every other value is a single-line expression.
    fn emitValue(self *mut Emitter, value ast.Expr, depth usize) Error!void {
        switch value {
            .struct_def, .enum_def, .union_def => try self.emitTypeDef(value, depth),
            else => try self.emitExprAt(value, depth),
        }
    }

    // Lay out a `struct { … }` / `enum { … }` type definition. Fields/variants
    // sit one per line at `depth + 1` with a trailing comma (the zig fmt form);
    // the closing brace returns to `depth`. The caller supplies the opening
    // `const Name = ` and the closing `;`.
    fn emitTypeDef(self *mut Emitter, x ast.Expr, depth usize) Error!void {
        switch x {
            .struct_def => |sd| {
                // The layout modifier — `packed` / `extern` — prefixes the
                // keyword verbatim; Zig spells the modifier identically.
                if sd.layout |l| {
                    try self.raw(l)
                    try self.raw(" ")
                }
                // A container with no members at all is zig fmt's one-line
                // form: `struct {}`.
                if sd.fields.len == 0 && sd.decls.len == 0 {
                    try self.raw("struct {}")
                    return
                }
                try self.raw("struct {\n")
                for f in sd.fields {
                    try self.emitDoc(f.doc, depth + 1)
                    try self.indent(depth + 1)
                    try self.raw(f.name)
                    try self.raw(": ")
                    try self.emitType(f.type)
                    // A default value renders after the type: `name: T = expr,`.
                    if f.default |d| {
                        try self.raw(" = ")
                        try self.emitExpr(d)
                    }
                    try self.raw(",\n")
                }
                try self.emitContainerDecls(sd.decls, sd.fields.len != 0, depth)
                try self.indent(depth)
                try self.raw("}")
            },
            .enum_def => |ed| {
                try self.raw("enum")
                if ed.tag_type |t| {
                    try self.raw("(")
                    try self.raw(t)
                    try self.raw(")")
                }
                if ed.variants.len == 0 && ed.decls.len == 0 {
                    try self.raw(" {}")
                    return
                }
                try self.raw(" {\n")
                for v in ed.variants {
                    try self.emitDoc(v.doc, depth + 1)
                    try self.indent(depth + 1)
                    try self.raw(v.name)
                    if v.value |val| {
                        try self.raw(" = ")
                        try self.emitExpr(val.*)
                    }
                    try self.raw(",\n")
                }
                try self.emitContainerDecls(ed.decls, ed.variants.len != 0, depth)
                try self.indent(depth)
                try self.raw("}")
            },
            // `union(enum) { … }` — like the enum layout, but each variant may
            // carry a payload type (`name: T`); a bare name is a void variant.
            .union_def => |ud| {
                try self.raw("union")
                if ud.tag |t| {
                    try self.raw("(")
                    try self.raw(t)
                    try self.raw(")")
                }
                if ud.variants.len == 0 && ud.decls.len == 0 {
                    try self.raw(" {}")
                    return
                }
                try self.raw(" {\n")
                for v in ud.variants {
                    try self.emitDoc(v.doc, depth + 1)
                    try self.indent(depth + 1)
                    try self.raw(v.name)
                    if v.payload |ty| {
                        try self.raw(": ")
                        try self.emitType(ty)
                    }
                    try self.raw(",\n")
                }
                try self.emitContainerDecls(ud.decls, ud.variants.len != 0, depth)
                try self.indent(depth)
                try self.raw("}")
            },
            else => unreachable,
        }
    }

    // Associated declarations follow a container's fields/variants, each
    // preceded by a blank line (one after the member block, one between decls) —
    // the idiomatic container layout zig fmt preserves. A container whose first
    // member is a declaration gets no leading blank.
    fn emitContainerDecls(self *mut Emitter, decls []mut ast.ContainerDecl, has_members bool, depth usize) Error!void {
        for d, idx in decls {
            if idx != 0 || has_members {
                try self.raw("\n")
            }
            // The decl's doc block precedes its own indented signature line.
            switch d {
                .method => |m| {
                    try self.emitDoc(m.doc, depth + 1)
                    try self.indent(depth + 1)
                    try self.emitFnAt(m, depth + 1)
                },
                .constant => |c| {
                    try self.emitDoc(c.doc, depth + 1)
                    try self.indent(depth + 1)
                    try self.emitConstDeclAt(c, depth + 1)
                },
                // No doc (the parser forbids it); a container-level import
                // lowers exactly like a top-level one, just indented.
                .use_import => |u| {
                    try self.indent(depth + 1)
                    try self.emitUseDeclAt(u)
                },
            }
            try self.raw("\n")
        }
    }

    // Emit a brace-delimited block body, opening at the current column. An empty
    // statement list collapses to `{}` on one line (matching zig fmt); a
    // non-empty one opens `{`, lays out one statement per line at `depth + 1`,
    // and closes `}` back at `depth`. The single source of the empty-block rule —
    // every block-emitting site (function body, `if`/`else`, `while`, `for`)
    // routes through here, so the collapse is consistent. The caller supplies any
    // leading space before the `{`; no trailing newline is emitted.
    fn emitBlockBody(self *mut Emitter, stmts []mut ast.Stmt, depth usize) Error!void {
        if stmts.len == 0 {
            try self.raw("{}")
            return
        }
        try self.raw("{\n")
        try self.emitBlock(stmts, depth + 1)
        try self.indent(depth)
        try self.raw("}")
    }

    // Emit a block's statements, each on its own line at the given indent depth
    // (depth 1 == one level inside a function body). Block-structured statements
    // recurse at depth + 1 for their inner statements.
    fn emitBlock(self *mut Emitter, stmts []mut ast.Stmt, depth usize) Error!void {
        for s in stmts {
            try self.indent(depth)
            try self.emitStmt(s, depth)
            try self.raw("\n")
        }
    }

    fn indent(self *mut Emitter, depth usize) Error!void {
        var k usize = 0
        while k < depth {
            try self.raw("    ")
            k += 1
        }
    }

    // Emit a `///` doc-comment block: one line per entry at `depth`, each the
    // three slashes plus its preserved content. The caller emits it immediately
    // before the declaration's own indented line, where zig fmt keeps it
    // byte-for-byte. An empty `doc` emits nothing.
    fn emitDoc(self *mut Emitter, doc [][]u8, depth usize) Error!void {
        for line in doc {
            try self.indent(depth)
            try self.raw("///")
            try self.raw(line)
            try self.raw("\n")
        }
    }

    // The optional `align(expr)` qualifier inside a pointer/slice type —
    // rendered directly after the prefix, before `const`/`volatile`, with one
    // trailing space (`[]align(16) const u8`, zig fmt's layout). Nothing when
    // absent.
    fn emitTypeAlign(self *mut Emitter, align_expr ?*mut ast.Expr) Error!void {
        if align_expr |ae| {
            try self.raw("align(")
            try self.emitExpr(ae.*)
            try self.raw(") ")
        }
    }

    fn emitType(self *mut Emitter, t ast.TypeRef) Error!void {
        switch t {
            .name => |n| {
                // `argv` and `cstr` are builtin spelling aliases for the two
                // pointer types the coreutils need but the surface gives no syntax
                // for. They are suppressed when the program declares a top-level
                // binding of the same name (argv_shadowed / cstr_shadowed), so a
                // user — or a future standard library — alias wins instead of
                // being silently overridden; the name then lowers verbatim.
                if !self.argv_shadowed && sup.eql(u8, n, "argv") {
                    try self.raw("[*]const ?[*:0]const u8")
                } else if !self.cstr_shadowed && sup.eql(u8, n, "cstr") {
                    try self.raw("[*:0]const u8")
                } else {
                    try self.raw(n)
                }
            },
            .slice => |p| {
                try self.raw("[]")
                try self.emitTypeAlign(p.align_expr)
                try self.raw("const ")
                try self.emitType(p.elem.*)
            },
            .slice_mut => |p| {
                try self.raw("[]")
                try self.emitTypeAlign(p.align_expr)
                try self.emitType(p.elem.*)
            },
            // `[:s]const T` — the sentinel-terminated slice; const-pointee by
            // default like a plain `[]T`. The sentinel sits between `:` and `]`.
            .slice_sentinel => |sp| {
                try self.raw("[:")
                try self.emitExpr(sp.sentinel.*)
                try self.raw("]")
                try self.emitTypeAlign(sp.align_expr)
                try self.raw("const ")
                try self.emitType(sp.elem.*)
            },
            // `[:s]T` — its mutable form (no `const`), as `[]mut T` is to `[]T`.
            .slice_sentinel_mut => |sp| {
                try self.raw("[:")
                try self.emitExpr(sp.sentinel.*)
                try self.raw("]")
                try self.emitTypeAlign(sp.align_expr)
                try self.emitType(sp.elem.*)
            },
            // `[*]T` is const-pointee by default, like `[]T` and `*T`; the
            // `const` is implicit in the Flash surface and explicit in Zig,
            // spaced from the element type as zig fmt lays it out.
            .many_ptr => |p| {
                try self.raw("[*]")
                try self.emitTypeAlign(p.align_expr)
                try self.raw("const ")
                try self.emitType(p.elem.*)
            },
            // `[*]mut T` — a writable many-item pointer; no `const` in Zig.
            .many_ptr_mut => |p| {
                try self.raw("[*]")
                try self.emitTypeAlign(p.align_expr)
                try self.emitType(p.elem.*)
            },
            // `[*]volatile T` — const+volatile pointee, const by default like
            // every pointer family; the implicit `const` renders before
            // `volatile`, both spaced from the element type as zig fmt does.
            .many_ptr_volatile => |p| {
                try self.raw("[*]")
                try self.emitTypeAlign(p.align_expr)
                try self.raw("const volatile ")
                try self.emitType(p.elem.*)
            },
            // `[*]mut volatile T` — the writable+volatile form; no `const`.
            .many_ptr_mut_volatile => |p| {
                try self.raw("[*]")
                try self.emitTypeAlign(p.align_expr)
                try self.raw("volatile ")
                try self.emitType(p.elem.*)
            },
            // `[*:s]T` — const-pointee by default like `[*]T`; the sentinel
            // expr is kept verbatim and the implicit `const` renders after the
            // `]`, before the element type.
            .many_ptr_sentinel => |sp| {
                try self.raw("[*:")
                try self.emitExpr(sp.sentinel.*)
                try self.raw("]")
                try self.emitTypeAlign(sp.align_expr)
                try self.raw("const ")
                try self.emitType(sp.elem.*)
            },
            // `[*:s]mut T` — the writable sentinel form; no `const` in Zig.
            .many_ptr_sentinel_mut => |sp| {
                try self.raw("[*:")
                try self.emitExpr(sp.sentinel.*)
                try self.raw("]")
                try self.emitTypeAlign(sp.align_expr)
                try self.emitType(sp.elem.*)
            },
            // `*T` is const-pointee by default, like `[]T` is a const slice.
            .ptr => |p| {
                try self.raw("*")
                try self.emitTypeAlign(p.align_expr)
                try self.raw("const ")
                try self.emitType(p.elem.*)
            },
            .ptr_mut => |p| {
                try self.raw("*")
                try self.emitTypeAlign(p.align_expr)
                try self.emitType(p.elem.*)
            },
            // `*volatile T` — const+volatile pointee, const by default; the
            // implicit `const` renders before `volatile`, as zig fmt lays it out.
            .ptr_volatile => |p| {
                try self.raw("*")
                try self.emitTypeAlign(p.align_expr)
                try self.raw("const volatile ")
                try self.emitType(p.elem.*)
            },
            // `*mut volatile T` — the writable+volatile form; no `const`.
            .ptr_mut_volatile => |p| {
                try self.raw("*")
                try self.emitTypeAlign(p.align_expr)
                try self.raw("volatile ")
                try self.emitType(p.elem.*)
            },
            .array => |arr| {
                try self.raw("[")
                try self.emitExpr(arr.len.*)
                try self.raw("]")
                try self.emitType(arr.elem.*)
            },
            // `[N:s]T` — a fixed-length sentinel-terminated array; length and
            // sentinel render verbatim, no spaces around the `:`, as zig fmt
            // lays it out.
            .array_sentinel => |a| {
                try self.raw("[")
                try self.emitExpr(a.len.*)
                try self.raw(":")
                try self.emitExpr(a.sentinel.*)
                try self.raw("]")
                try self.emitType(a.elem.*)
            },
            // `[_]T` — the inferred-length array; the `_` lowers verbatim.
            .array_inferred => |elem| {
                try self.raw("[_]")
                try self.emitType(elem.*)
            },
            // `[_:s]T` — the inferred-length sentinel array (`[_:0]u8{ … }`).
            .array_inferred_sentinel => |sp| {
                try self.raw("[_:")
                try self.emitExpr(sp.sentinel.*)
                try self.raw("]")
                try self.emitType(sp.elem.*)
            },
            .optional => |inner| {
                try self.raw("?")
                try self.emitType(inner.*)
            },
            // `E!T` names the error set, `!T` (set == null) infers it. The
            // set renders before the `!`, the payload after — both verbatim.
            .errunion => |eu| {
                if eu.set |s| {
                    try self.emitType(s.*)
                }
                try self.raw("!")
                try self.emitType(eu.payload.*)
            },
            // `fn(P, …) R` — a function type. zig fmt writes the anonymous
            // form with a space after `fn` (unlike a named `fn name(`), and a
            // missing return lowers to `void`. Any `*`/`*mut` around it is the
            // surrounding `.ptr`/`.ptr_mut` case, so this emits the bare
            // function type only — `*fn(…)` becomes `*const fn (…) R` for free.
            .fn_type => |ft| {
                try self.raw("fn (")
                for p, idx in ft.params {
                    if idx != 0 {
                        try self.raw(", ")
                    }
                    try self.emitType(p)
                }
                try self.raw(")")
                // An explicit `callconv(…)` sits between the parameter list
                // and the return type, exactly as on a declaration signature.
                if ft.call_conv |cc| {
                    try self.raw(" callconv(")
                    try self.emitExpr(cc.*)
                    try self.raw(")")
                }
                try self.raw(" ")
                if ft.ret |r| {
                    try self.emitType(r.*)
                } else {
                    try self.raw("void")
                }
            },
            // `Name(args…)` — a generic applied in type position lowers as the
            // verbatim call zig reads it: the name, then the argument
            // expressions comma-separated, exactly as a value-position call.
            .generic => |g| {
                try self.raw(g.name)
                try self.raw("(")
                for arg, idx in g.args {
                    if idx != 0 {
                        try self.raw(", ")
                    }
                    try self.emitExpr(arg)
                }
                try self.raw(")")
            },
            // `(A, B)` — a tuple type lowers to Zig's inline positional
            // struct, spaced as zig fmt lays the one-line form out:
            // `struct { A, B }`.
            .tuple => |elems| {
                try self.raw("struct { ")
                for e, idx in elems {
                    if idx != 0 {
                        try self.raw(", ")
                    }
                    try self.emitType(e)
                }
                try self.raw(" }")
            },
        }
    }

    fn emitStmt(self *mut Emitter, s ast.Stmt, depth usize) Error!void {
        switch s {
            .discard => |x| {
                if x == .multiline_str {
                    try self.raw("_ ")
                    try self.emitMultilineRhs(x.multiline_str, depth)
                } else {
                    try self.raw("_ = ")
                    try self.emitExprAt(x, depth)
                    try self.raw(";")
                }
            },
            // A `comptime var` keeps Zig's `comptime` prefix. A `comptime
            // const` cannot: Zig rejects `comptime const` as redundant and
            // directs the comptime-ness onto the initializer instead, so it
            // lowers to `const x = comptime e` — the force-comptime intent
            // rides the value, not a redundant binding keyword.
            .bind => |b| {
                force_value_comptime := b.is_comptime && !b.is_mut
                if b.is_comptime && b.is_mut {
                    try self.raw("comptime ")
                }
                try self.raw(if (b.is_mut) "var " else "const ")
                try self.raw(b.name)
                if b.type |ty| {
                    try self.raw(": ")
                    try self.emitType(ty)
                }
                if b.align_expr |ae| {
                    // `align(expr)` sits after the type, before `=` (Zig order).
                    try self.raw(" align(")
                    try self.emitExpr(ae)
                    try self.raw(")")
                }
                if b.value == .multiline_str {
                    // A multiline string literal is already comptime-known, so a
                    // `comptime const` over one needs no `comptime` wrap.
                    try self.raw(" ")
                    try self.emitMultilineRhs(b.value.multiline_str, depth)
                } else {
                    try self.raw(" = ")
                    if force_value_comptime {
                        try self.raw("comptime ")
                    }
                    try self.emitValue(b.value, depth)
                    try self.raw(";")
                    // "=", "+=", … are spelled the same in Zig
                }
            },
            .assign => |a| {
                try self.emitExprAt(a.target, depth)
                try self.raw(" ")
                try self.raw(a.op)
                try self.raw(" ")
                try self.emitExprAt(a.value, depth)
                try self.raw(";")
            },
            // A destructuring bind repeats the binding keyword per name —
            // Zig's native spelling: `const a, const b = e;` / `var a, var b
            // = e;` — and a `_` skip stays `_` (Zig's discard position).
            .destructure => |d| {
                for maybe, i in d.names {
                    if i != 0 {
                        try self.raw(", ")
                    }
                    if maybe |name| {
                        try self.raw(if (d.is_mut) "var " else "const ")
                        try self.raw(name)
                    } else {
                        try self.raw("_")
                    }
                }
                try self.raw(" = ")
                try self.emitValue(d.value, depth)
                try self.raw(";")
            },
            // A destructuring assignment lowers verbatim: `a, b = e;`.
            .destructure_assign => |da| {
                for t, i in da.targets {
                    if i != 0 {
                        try self.raw(", ")
                    }
                    try self.emitExprAt(t, depth)
                }
                try self.raw(" = ")
                try self.emitExprAt(da.value, depth)
                try self.raw(";")
            },
            .if_stmt => |iff| try self.emitIf(iff, depth),
            // `defer` / `errdefer` prefix their inner statement; the inner
            // statement emits its own trailing `;`.
            .defer_stmt => |inner| {
                try self.raw("defer ")
                try self.emitStmt(inner.*, depth)
            },
            // The `|err|` capture renders between the keyword and the body,
            // Zig's slot (`errdefer |err| …`).
            .errdefer_stmt => |ed| {
                try self.raw("errdefer ")
                if ed.capture |cap| {
                    try self.raw("|")
                    try self.raw(cap)
                    try self.raw("| ")
                }
                try self.emitStmt(ed.body.*, depth)
            },
            // The block forms render the brace body; like an `if` body, the
            // closing `}` carries no `;`.
            .defer_block => |stmts| {
                try self.raw("defer ")
                try self.emitBlockBody(stmts, depth)
            },
            .errdefer_block => |ed| {
                try self.raw("errdefer ")
                if ed.capture |cap| {
                    try self.raw("|")
                    try self.raw(cap)
                    try self.raw("| ")
                }
                try self.emitBlockBody(ed.body, depth)
            },
            // The capture `|x|` — the optional / error payload binding
            // `while (e) |x| {…}` — renders between the condition and the
            // body; the else arm renders its own error capture the same way.
            .while_stmt => |w| {
                // A loop label prefixes the whole loop — `outer: inline while`.
                if w.label |l| {
                    try self.raw(l)
                    try self.raw(": ")
                }
                if w.is_inline {
                    try self.raw("inline ")
                }
                try self.raw("while (")
                try self.emitExprAt(w.cond, depth)
                try self.raw(")")
                if w.capture |cap| {
                    try self.raw(" |")
                    if w.capture_is_ptr {
                        try self.raw("*")
                    }
                    try self.raw(cap)
                    try self.raw("|")
                }
                try self.raw(" ")
                try self.emitBlockBody(w.body, depth)
                if w.else_body |eb| {
                    // The loop else arm — ` else { … }`, the error capture
                    // rendered as ` else |err| { … }`.
                    try self.raw(" else ")
                    if w.else_capture |cap| {
                        try self.raw("|")
                        try self.raw(cap)
                        try self.raw("| ")
                    }
                    try self.emitBlockBody(eb, depth)
                }
            },
            // A range iterator prints its `..hi` bound; a second capture adds
            // the implicit index range `0..` as a parallel input. The loop
            // else arm runs when the iteration completes without `break`,
            // and is capture-less.
            .for_stmt => |fr| {
                if fr.label |l| {
                    try self.raw(l)
                    try self.raw(": ")
                }
                if fr.is_inline {
                    try self.raw("inline ")
                }
                try self.raw("for (")
                try self.emitExprAt(fr.iter, depth)
                if fr.range_hi |hi| {
                    try self.raw("..")
                    try self.emitExprAt(hi, depth)
                }
                if fr.captures.len == 2 {
                    try self.raw(", 0..")
                }
                try self.raw(") |")
                for c, i in fr.captures {
                    if i != 0 {
                        try self.raw(", ")
                    }
                    if i == 0 && fr.elem_is_ptr {
                        try self.raw("*")
                    }
                    try self.raw(c)
                }
                try self.raw("| ")
                try self.emitBlockBody(fr.body, depth)
                if fr.else_body |eb| {
                    try self.raw(" else ")
                    try self.emitBlockBody(eb, depth)
                }
                // A block-form expression used as a bare statement (a `switch`
                // or a labeled block whose value is discarded) is a complete
                // statement in Zig and takes no trailing `;`; every other
                // expression statement closes with one.
            },
            .expr => |x| {
                try self.emitExprAt(x, depth)
                if !isBlockFormStmt(x) {
                    try self.raw(";")
                }
            },
        }
    }

    // `if (cond) { … }`, with an `else { … }` arm or, when the else body is
    // exactly one nested if, an idiomatic `else if (…) { … }` chain. An
    // optional-capture if renders the payload binding (`if (opt) |x| { … }`)
    // between the condition and the body; an error capture on the else arm
    // renders as ` else |err| { … }`.
    fn emitIf(self *mut Emitter, iff ast.If, depth usize) Error!void {
        try self.raw("if (")
        try self.emitExprAt(iff.cond, depth)
        try self.raw(")")
        if iff.capture |cap| {
            try self.raw(" |")
            if iff.capture_is_ptr {
                try self.raw("*")
            }
            try self.raw(cap)
            try self.raw("|")
        }
        try self.raw(" ")
        try self.emitBlockBody(iff.body, depth)
        if iff.else_body |eb| {
            if eb.len == 1 && eb[0] == .if_stmt {
                try self.raw(" else ")
                try self.emitIf(eb[0].if_stmt, depth)
            } else {
                try self.raw(" else ")
                if iff.else_capture |cap| {
                    try self.raw("|")
                    try self.raw(cap)
                    try self.raw("| ")
                }
                try self.emitBlockBody(eb, depth)
            }
        }
    }

    // The depth-0 wrapper, for the inline-only callers (type length / sentinel
    // expressions, struct-field and enum-variant defaults) where an expression
    // never spans multiple lines.
    fn emitExpr(self *mut Emitter, x ast.Expr) Error!void {
        try self.emitExprAt(x, 0)
    }

    // Emit an expression whose indentation is `depth`. Most expression forms are
    // single-line and ignore `depth`, threading it unchanged through their
    // sub-expressions; the multi-line forms — a labeled block, and the `switch`
    // expression — lay their inner statements / prongs out at `depth + 1` and
    // close at `depth`, so a `return switch (…) { … }` or a `blk: { … }` prong
    // body indents correctly however deeply it nests.
    fn emitExprAt(self *mut Emitter, x ast.Expr, depth usize) Error!void {
        switch x {
            .int, .float, .string, .char, .ident, .value_word => |s| try self.raw(s),
            // Reached only outside a const/binding/discard value (e.g. a call
            // argument or `return`), where zig fmt's layout is
            // position-specific. Indentation before `\\` does not affect the
            // value, so this stays a semantically identical program; the
            // byte-exact layout is guaranteed for the routed value positions,
            // not here.
            .multiline_str => |lines| {
                try self.raw("\n")
                for ln in lines {
                    try self.raw("\\\\")
                    try self.raw(ln)
                    try self.raw("\n")
                }
            },
            .member => |m| {
                try self.emitExprAt(m.base.*, depth)
                try self.raw(".")
                try self.raw(m.field)
            },
            // `p.*` — a single-item pointer dereference, spelled identically
            // in Zig; valid in value and lvalue (assignment target) position.
            .deref => |d| {
                try self.emitExprAt(d.*, depth)
                try self.raw(".*")
            },
            // `opt.?` — optional unwrap (assert non-null), spelled identically
            // in Zig; sits in the same postfix slot as `.*`.
            .optional_unwrap => |u| {
                try self.emitExprAt(u.*, depth)
                try self.raw(".?")
            },
            .call => |c| {
                try self.emitExprAt(c.callee.*, depth)
                try self.emitArgs(c.args, depth)
            },
            .index => |ix| {
                try self.emitExprAt(ix.base.*, depth)
                try self.raw("[")
                try self.emitExprAt(ix.index.*, depth)
                try self.raw("]")
            },
            // zig fmt spaces the `..` when either bound is a binary operation
            // (`a[i .. j + 1]`); simple bounds — idents, literals, calls,
            // indexing, member access, deref — stay tight (`a[i..j]`). The
            // trailing space is emitted only when a high bound follows, so an
            // open-ended `a[i + 1 ..]` keeps the space only before `..`. A
            // `[lo..hi :s]` sentinel gets a space before the `:` and none
            // after; an open-ended `[lo.. :s]` keeps the same form.
            .slice => |s| {
                try self.emitExprAt(s.base.*, depth)
                try self.raw("[")
                try self.emitExprAt(s.lo.*, depth)
                spaced := sliceBoundSpaces(s.lo.*) || (s.hi != null && sliceBoundSpaces(s.hi.?.*))
                if spaced {
                    try self.raw(" ")
                }
                try self.raw("..")
                if s.hi |hi| {
                    if spaced {
                        try self.raw(" ")
                    }
                    try self.emitExprAt(hi.*, depth)
                }
                if s.sentinel |sen| {
                    try self.raw(" :")
                    try self.emitExprAt(sen.*, depth)
                }
                try self.raw("]")
            },
            // The AST holds the bare intrinsic name (the Flash '#' sigil is
            // stripped in the parser); Tier 0 emits Zig's '@'-prefixed form.
            .builtin_call => |b| {
                try self.raw("@")
                try self.raw(b.name)
                try self.emitArgs(b.args, depth)
            },
            .unary => |u| {
                try self.raw(u.op)
                try self.emitExprAt(u.operand.*, depth)
            },
            .binary => |b| {
                try self.emitExprAt(b.lhs.*, depth)
                try self.raw(" ")
                try self.raw(zigBinOp(b.op))
                try self.raw(" ")
                try self.emitExprAt(b.rhs.*, depth)
            },
            // zig fmt spaces the braces (`.{ … }`) for every literal except
            // the empty `.{}` and a single positional element (`.{x}`); a
            // single named field is a struct init and stays spaced.
            .struct_lit => |fields| {
                spaced := !(fields.len == 0 || (fields.len == 1 && fields[0].name == null))
                try self.raw(if (spaced) ".{ " else ".{")
                for f, idx in fields {
                    if idx != 0 {
                        try self.raw(", ")
                    }
                    if f.name |n| {
                        try self.raw(".")
                        try self.raw(n)
                        try self.raw(" = ")
                    }
                    try self.emitExprAt(f.value, depth)
                }
                try self.raw(if (spaced) " }" else "}")
            },
            // `Type{ .x = 1 }` — a typed initializer. The type prefix renders
            // first, then the field list with the same brace-spacing rule as the
            // anonymous `.{ … }` form (spaced unless empty / single positional).
            .typed_lit => |tl| {
                try self.emitExprAt(tl.type.*, depth)
                spaced := !(tl.fields.len == 0 || (tl.fields.len == 1 && tl.fields[0].name == null))
                try self.raw(if (spaced) "{ " else "{")
                for f, idx in tl.fields {
                    if idx != 0 {
                        try self.raw(", ")
                    }
                    if f.name |n| {
                        try self.raw(".")
                        try self.raw(n)
                        try self.raw(" = ")
                    }
                    try self.emitExprAt(f.value, depth)
                }
                try self.raw(if (spaced) " }" else "}")
            },
            // A type in value position (the head of an array-typed literal,
            // `[_]u8{ … }`); the type renders through the shared type emitter.
            .type_lit => |t| try self.emitType(t.*),
            .enum_lit => |v| {
                try self.raw(".")
                try self.raw(v)
            },
            // `error.Name` — an error-value origination, spelled identically in Zig.
            .error_lit => |n| {
                try self.raw("error.")
                try self.raw(n)
            },
            // `error{ A, B }` — a named error-set definition. zig fmt spaces the
            // braces only for two-or-more members (`error{ A, B }`); a single
            // member (`error{One}`) and the empty set (`error{}`) stay tight.
            .error_set => |names| {
                spaced := names.len > 1
                try self.raw(if (spaced) "error{ " else "error{")
                for n, idx in names {
                    if idx != 0 {
                        try self.raw(", ")
                    }
                    try self.raw(n)
                }
                try self.raw(if (spaced) " }" else "}")
            },
            // A struct/enum/union definition appearing mid-expression — e.g. the
            // arms of a `const X = if (cond) struct {…} else struct {…}` driver
            // select. It renders at the depth threaded in from the enclosing
            // expression (0 for a top-level const), so the body indents one level
            // past that and the closing brace returns to it.
            .struct_def, .enum_def, .union_def => try self.emitTypeDef(x, depth),
            .group => |g| {
                try self.raw("(")
                try self.emitExprAt(g.*, depth)
                try self.raw(")")
            },
            // `if cond a else b` — the value form. The condition is wrapped in
            // parentheses (as zig fmt requires); the arms render inline with a
            // single space around `else`: `if (cond) a else b`.
            .if_expr => |iff| {
                try self.raw("if (")
                try self.emitExprAt(iff.cond.*, depth)
                try self.raw(") ")
                try self.emitExprAt(iff.then.*, depth)
                try self.raw(" else ")
                try self.emitExprAt(iff.else_.*, depth)
            },
            // `switch (subject) { … }` — the subject is parenthesised; prongs lay
            // out one per line at depth + 1 (`patterns => body,`) and the closing
            // brace returns to depth. A prong body is an expression at the prong's
            // own depth, so a `label: { … }` arm indents its statements correctly.
            // An inclusive pattern range `lo...hi` renders without spaces, and a
            // `=> |x|` payload capture renders before the body — both as zig fmt.
            .switch_expr => |sw| {
                try self.raw("switch (")
                try self.emitExprAt(sw.subject.*, depth)
                try self.raw(") {\n")
                for prong in sw.prongs {
                    try self.indent(depth + 1)
                    if prong.is_else {
                        try self.raw("else")
                    } else {
                        for p, idx in prong.patterns {
                            if idx != 0 {
                                try self.raw(", ")
                            }
                            try self.emitExprAt(p.lo, depth + 1)
                            if p.hi |hi| {
                                try self.raw("...")
                                try self.emitExprAt(hi, depth + 1)
                            }
                        }
                    }
                    try self.raw(" => ")
                    if prong.capture |cap| {
                        try self.raw("|")
                        if prong.capture_is_ptr {
                            try self.raw("*")
                        }
                        try self.raw(cap)
                        try self.raw("| ")
                    }
                    try self.emitExprAt(prong.body, depth + 1)
                    try self.raw(",\n")
                }
                try self.indent(depth)
                try self.raw("}")
            },
            // A block expression. A labelled `label: { … }` prefixes the block
            // body with its label (its value comes from a `break :label v`
            // inside); an unlabelled block — a switch-prong `=> { … }` arm — emits
            // the body alone. Statements lay out at depth + 1, brace back at depth.
            .block_expr => |blk| {
                if blk.label |label| {
                    try self.raw(label)
                    try self.raw(": ")
                }
                try self.emitBlockBody(blk.body, depth)
            },
            .try_expr => |t| {
                try self.raw("try ")
                try self.emitExprAt(t.*, depth)
            },
            .catch_expr => |c| {
                try self.emitExprAt(c.lhs.*, depth)
                try self.raw(" catch ")
                if c.capture |cap| {
                    try self.raw("|")
                    try self.raw(cap)
                    try self.raw("| ")
                }
                try self.emitExprAt(c.handler.*, depth)
            },
            .asm_expr => |a| try self.emitAsm(a, depth),
            // `break`, optionally to a labelled block (`break :blk`) and/or with a
            // value (`break :blk v`). zig fmt spaces both: `break :blk v`.
            .brk => |b| {
                try self.raw("break")
                if b.label |l| {
                    try self.raw(" :")
                    try self.raw(l)
                }
                if b.value |v| {
                    try self.raw(" ")
                    try self.emitExprAt(v.*, depth)
                }
            },
            // `continue`, optionally to a labelled loop (`continue :outer`).
            .cont => |maybe| {
                try self.raw("continue")
                if maybe |l| {
                    try self.raw(" :")
                    try self.raw(l)
                }
            },
            // `return a, b` — the multi-return sugar: a value list of two or
            // more lowers to one anonymous tuple literal, `return .{ a, b };`,
            // zig fmt's one-line layout; a single value lowers verbatim.
            .ret => |maybe| {
                try self.raw("return")
                if maybe |vals| {
                    try self.raw(" ")
                    if vals.len == 1 {
                        try self.emitExprAt(vals[0], depth)
                    } else {
                        try self.raw(".{ ")
                        for v, idx in vals {
                            if idx != 0 {
                                try self.raw(", ")
                            }
                            try self.emitExprAt(v, depth)
                        }
                        try self.raw(" }")
                    }
                }
            },
        }
    }

    fn emitArgs(self *mut Emitter, args []mut ast.Expr, depth usize) Error!void {
        try self.raw("(")
        for a, idx in args {
            if idx != 0 {
                try self.raw(", ")
            }
            try self.emitExprAt(a, depth)
        }
        try self.raw(")")
    }

    // `asm [volatile] (…)` — inline assembly, laid out exactly as zig fmt renders
    // it. The closing `)` is emitted here; the surrounding statement supplies the
    // `;`. zig fmt keeps the expression on one line when it has no output and no
    // input operands and a single-string template — a bare `asm (T)` or a
    // clobber-only `asm (T ::: C)`; any output/input operand, or a `\\` multiline
    // template, breaks it across lines. The colon sections are positional, so an
    // empty earlier section still occupies its own `:` line, and the trailing
    // clobber expression hugs the closing `)` (`: C)`), whereas an output/input
    // last section closes the `)` on its own line at the statement's depth.
    fn emitAsm(self *mut Emitter, a ast.AsmExpr, depth usize) Error!void {
        try self.raw("asm ")
        if a.is_volatile {
            try self.raw("volatile ")
        }
        try self.raw("(")

        ml_template := a.template.* == .multiline_str
        multiline := ml_template || a.outputs.len > 0 || a.inputs.len > 0

        if !multiline {
            try self.emitExprAt(a.template.*, depth)
            if a.clobbers |c| {
                try self.raw(" ::: ")
                try self.emitExprAt(c.*, depth)
            }
            try self.raw(")")
            return
        }

        // Multi-line: the template heads its own line(s), the sections follow.
        if ml_template {
            try self.raw("\n")
            for ln in a.template.*.multiline_str {
                try self.indent(depth + 1)
                try self.raw("\\\\")
                try self.raw(ln)
                try self.raw("\n")
            }
        } else {
            try self.emitExprAt(a.template.*, depth)
            try self.raw("\n")
        }

        // The highest non-empty section fixes how many positional colons appear:
        // a clobber forces all three, an input forces outputs+inputs, an output
        // forces outputs alone. A multiline template with no operands has none.
        var n_sections usize = 0
        if a.clobbers != null {
            n_sections = 3
        } else if a.inputs.len > 0 {
            n_sections = 2
        } else if a.outputs.len > 0 {
            n_sections = 1
        }

        if n_sections >= 1 {
            try self.indent(depth + 1)
            try self.raw(":")
            try self.emitAsmOperandList(a.outputs, depth)
        }
        if n_sections >= 2 {
            try self.indent(depth + 1)
            try self.raw(":")
            try self.emitAsmOperandList(a.inputs, depth)
        }
        if a.clobbers |c| {
            try self.indent(depth + 1)
            try self.raw(": ")
            try self.emitExprAt(c.*, depth)
            try self.raw(")")
            return
        }
        // No clobber section: the last operand carried a trailing comma + newline,
        // so the `)` closes on a fresh line at the statement's own depth.
        try self.indent(depth)
        try self.raw(")")
    }

    // The operands of one asm section. The first follows its colon on the same
    // line (`: op,`); each later one sits on its own line aligned two columns
    // past the colon. Every operand carries a trailing comma. An empty section
    // is just its colon, so the line is closed with a bare newline.
    fn emitAsmOperandList(self *mut Emitter, ops []mut ast.AsmOperand, depth usize) Error!void {
        if ops.len == 0 {
            try self.raw("\n")
            return
        }
        for op, idx in ops {
            if idx == 0 {
                try self.raw(" ")
            } else {
                try self.indent(depth + 1)
                try self.raw("  ")
            }
            try self.emitAsmOperand(op, depth)
            try self.raw(",\n")
        }
    }

    // `[name] "constraint" (body)` — the body is `-> T` for a value-producing
    // output, or an expression for an lvalue output / an input value.
    fn emitAsmOperand(self *mut Emitter, op ast.AsmOperand, depth usize) Error!void {
        try self.raw("[")
        try self.raw(op.name)
        try self.raw("] ")
        try self.raw(op.constraint)
        try self.raw(" (")
        switch op.body {
            .ret_type => |t| {
                try self.raw("-> ")
                try self.emitType(t)
            },
            .expr => |e| try self.emitExprAt(e, depth),
        }
        try self.raw(")")
    }
}

// Whether an expression, used as a bare statement, is a block-form that
// closes on `}` and so takes no trailing `;` (Zig's BlockExpr statement).
fn isBlockFormStmt(x ast.Expr) bool {
    return switch x {
        .switch_expr, .block_expr => true,
        else => false,
    }
}

// Whether a slice bound forces zig fmt to space the `..`. zig fmt spaces it
// when a bound is a binary operation or a `catch` — every other expression form
// (ident, literal, call, index, member, deref, unary, grouping) stays tight.
// Flash's `.binary` covers all binary operators (arithmetic, comparison,
// bitwise, shift, `&&`/`||`, `orelse`); `catch` is its own `.catch_expr` node.
fn sliceBoundSpaces(x ast.Expr) bool {
    return switch x {
        .binary, .catch_expr => true,
        else => false,
    }
}

// Map a Flash binary-operator lexeme to its Zig spelling. Only the two logical
// operators differ — Flash's ligature-friendly "&&"/"||" lower to Zig's
// `and`/`or`; every other operator is identical in both languages.
fn zigBinOp(op []u8) []u8 {
    if sup.eql(u8, op, "&&") {
        return "and"
    }
    if sup.eql(u8, op, "||") {
        return "or"
    }
    return op
}

// --- tests ---------------------------------------------------------------
// Each test parses a Flash source (no sema — lowering needs only the AST) and
// asserts the emitted Zig matches the handwritten emitter's output byte for
// byte; every `want` below was captured from the stage-0 compiler over the
// same input, so the staged half is licensed by output equality, not review.

// Parse `src` and assert the emitted Zig equals `want` exactly.
fn expectLower(src []u8, want []u8) !void {
    var arena = sup.ArenaAllocator.init(sup.testAlloc)
    defer arena.deinit()
    var p = parser.Parser.init(arena.allocator(), src)
    prog := try p.parseProgram()
    try sup.expectEqualStrings(want, try emit(arena.allocator(), prog))
}

// As expectLower, but through the anchored entry point, with `file` as the
// stamped name. (Default-path byte-identity is pinned by the existing
// expectLower goldens and the differential corpus, not re-proven here.)
fn expectLowerAnchored(src []u8, file []u8, want []u8) !void {
    var arena = sup.ArenaAllocator.init(sup.testAlloc)
    defer arena.deinit()
    var p = parser.Parser.init(arena.allocator(), src)
    prog := try p.parseProgram()
    try sup.expectEqualStrings(want, try emitAnchored(arena.allocator(), prog, src, file))
}

test "use forms lower to imports and consecutive links fold into one comptime block" {
    try expectLower("use flibc\nuse console_ui as ui\npub use other\nuse \"syscalls\" as sys\npub use \"screen\" as scr\n\nlink \"flibc_start\"\nlink \"flibc_mem\"", "const flibc = @import(\"flibc\");\nconst ui = @import(\"console_ui\");\npub const other = @import(\"other\");\nconst sys = @import(\"syscalls.zig\");\npub const scr = @import(\"screen.zig\");\n\ncomptime {\n    _ = @import(\"flibc_start\");\n    _ = @import(\"flibc_mem\");\n}\n")
}

test "anchored emission stamps file:line above consts and fns, skipping imports and tests" {
    // The anchor names the declaration's own line (the doc comment above
    // `twice` is line 5; the anchor says 6 and sits above the doc block, so
    // the `///` stays attached to its declaration in the emitted Zig).
    try expectLowerAnchored("use flibc\n\nconst N = 4\n\n/// doubles\nfn twice(x u8) u8 {\n    return x * 2\n}\n\nvar COUNT usize = 0\n\ntest \"t\" {\n    _ = N\n}", "t.flash", "const flibc = @import(\"flibc\");\n\n// t.flash:3\nconst N = 4;\n\n// t.flash:6\n/// doubles\nfn twice(x: u8) u8 {\n    return x * 2;\n}\n\n// t.flash:10\nvar COUNT: usize = 0;\n\ntest \"t\" {\n    _ = N;\n}\n")
}

test "fn signatures: the modifier matrix, callconv, comptime and `_` params, void default" {
    try expectLower("extern fn write(fd i32, buf [*]u8, n usize) isize\n\nexport fn main(_ usize, _ argv) noreturn {}\n\npub fn _start() callconv(.naked) noreturn {}\n\ninline fn bump(n usize) usize {}\n\npub fn fill(comptime n usize, v u8) {}", "extern fn write(fd: i32, buf: [*]const u8, n: usize) isize;\n\nexport fn main(_: usize, _: [*]const ?[*:0]const u8) callconv(.c) noreturn {}\n\npub fn _start() callconv(.naked) noreturn {}\n\ninline fn bump(n: usize) usize {}\n\npub fn fill(comptime n: usize, v: u8) void {}\n")
}

test "type-position align lowers into Zig's slot: after the prefix, before const/volatile" {
    // The implicit `const` renders AFTER the align (`[]align(16) const u8`),
    // zig fmt's layout; `mut` drops it, `volatile` follows it; on the
    // sentinel forms the align sits after the closing `]`.
    try expectLower("extern fn a0(x []align(16) u8, y []align(16) mut u8, z [:0]align(2) u8)\n\nextern fn a1(x [*]align(8) u8, y [*]align(8) mut volatile u8, z [*:0]align(2) mut u8)\n\nextern fn a2(x *align(4) u32, y *align(4) mut u32, z *align(4) volatile u32, w *align(4) mut volatile u32)\n\nextern fn a3(p ?*align(4096) u8)", "extern fn a0(x: []align(16) const u8, y: []align(16) u8, z: [:0]align(2) const u8) void;\n\nextern fn a1(x: [*]align(8) const u8, y: [*]align(8) volatile u8, z: [*:0]align(2) u8) void;\n\nextern fn a2(x: *align(4) const u32, y: *align(4) u32, z: *align(4) const volatile u32, w: *align(4) volatile u32) void;\n\nextern fn a3(p: ?*align(4096) const u8) void;\n")
}

test "file-scope bind align lowers between the type and any linksection" {
    // The A2 audit gap: file-scope `align` on a typed var, an untyped var
    // (no colon-type in Zig either), an extern var, and the align-then-
    // linksection slot order.
    try expectLower("var pool [4096]u8 align(4096) = undefined\n\nvar head align(64) = compute()\n\nextern var _vec u8 align(2048)\n\nvar pad [16]u8 align(4) linksection(\".rodata\") = undefined", "var pool: [4096]u8 align(4096) = undefined;\n\nvar head align(64) = compute();\n\nextern var _vec: u8 align(2048);\n\nvar pad: [16]u8 align(4) linksection(\".rodata\") = undefined;\n")
}

test "the pointer family: slices, many-item pointers, single pointers, sentinels, volatile" {
    try expectLower("extern fn a0(x []u8, y []mut u8, z [:0]u8, w [:0]mut u8)\n\nextern fn a1(x [*]u8, y [*]mut u8, z [*:0]u8, w [*:0]mut u8)\n\nextern fn a2(x [*]volatile u8, y [*]mut volatile u8)\n\nextern fn a3(x *u8, y *mut u8, z *volatile u8, w *mut volatile u8)", "extern fn a0(x: []const u8, y: []u8, z: [:0]const u8, w: [:0]u8) void;\n\nextern fn a1(x: [*]const u8, y: [*]u8, z: [*:0]const u8, w: [*:0]u8) void;\n\nextern fn a2(x: [*]const volatile u8, y: [*]volatile u8) void;\n\nextern fn a3(x: *const u8, y: *u8, z: *const volatile u8, w: *volatile u8) void;\n")
}

test "arrays, optionals, error unions, and top-level mutable globals" {
    try expectLower("var BUF [4]u8 = undefined\nvar TAIL [4:0]u8 = undefined\nvar COUNT usize = 0\npub var ready bool = false\nconst A = [_]u8\nconst B = [_:0]u8\n\nextern fn o0(x ?*u8, y ?[]u8) ?usize\n\nextern fn o1() !u8\n\nextern fn o2(e Err) Err!u8", "var BUF: [4]u8 = undefined;\n\nvar TAIL: [4:0]u8 = undefined;\n\nvar COUNT: usize = 0;\n\npub var ready: bool = false;\n\nconst A = [_]u8;\n\nconst B = [_:0]u8;\n\nextern fn o0(x: ?*const u8, y: ?[]const u8) ?usize;\n\nextern fn o1() !u8;\n\nextern fn o2(e: Err) Err!u8;\n")
}

test "function types, function pointers, generic applications, and tuple returns" {
    try expectLower("extern fn g0(cb *fn(u8) u8, f fn(u8, usize) bool, m *mut fn() u8)\n\nextern fn g1(xs List(u8), m Map(u8, *fn() u8), r pkg.Ring(64))\n\nextern fn g2() (u8, usize)", "extern fn g0(cb: *const fn (u8) u8, f: fn (u8, usize) bool, m: *fn () u8) void;\n\nextern fn g1(xs: List(u8), m: Map(u8, *const fn () u8), r: pkg.Ring(64)) void;\n\nextern fn g2() struct { u8, usize };\n")
}

test "a function-type callconv emits between the parameter list and the return" {
    // The v-table shape: optional C-ABI function-pointer fields, one with a
    // default, plus a void return (lowered `void` sits after the callconv)
    // and a non-c convention.
    try expectLower("pub const BlockDev = extern struct {\n    read_fn ?*fn(u32, *mut [512]u8) callconv(.c) i32,\n    write_fn ?*fn(u32, *[512]u8) callconv(.c) i32 = null,\n}\n\nconst flush *fn() callconv(.c) = doFlush\n\nconst vec *fn() callconv(.naked) noreturn = doVec", "pub const BlockDev = extern struct {\n    read_fn: ?*const fn (u32, *[512]u8) callconv(.c) i32,\n    write_fn: ?*const fn (u32, *const [512]u8) callconv(.c) i32 = null,\n};\n\nconst flush: *const fn () callconv(.c) void = doFlush;\n\nconst vec: *const fn () callconv(.naked) noreturn = doVec;\n")
}

test "the argv/cstr builtin type aliases expand in type position" {
    try expectLower("extern fn execvp(name cstr, argv argv) i32", "extern fn execvp(name: [*:0]const u8, argv: [*]const ?[*:0]const u8) i32;\n")
}

test "a same-named top-level declaration shadows the argv/cstr aliases" {
    try expectLower("const argv = u8\nconst cstr = u8\n\nextern fn f(a argv, b cstr)", "const argv = u8;\n\nconst cstr = u8;\n\nextern fn f(a: argv, b: cstr) void;\n")
}

test "a struct definition: doc comments, field defaults, associated const and method" {
    try expectLower("/// A point in screen space.\nconst Point = struct {\n    /// Horizontal cell.\n    x i32,\n    y i32 = 0,\n\n    pub const dim = 2\n\n    pub fn norm(self Point) i32 {}\n}", "/// A point in screen space.\nconst Point = struct {\n    /// Horizontal cell.\n    x: i32,\n    y: i32 = 0,\n\n    pub const dim = 2;\n\n    pub fn norm(self: Point) i32 {}\n};\n")
}

test "a declaration-only struct lowers without a leading blank line, with an indented import" {
    try expectLower("const Sys = struct {\n    use \"syscalls\" as sys\n\n    const max = 9\n}", "const Sys = struct {\n    const sys = @import(\"syscalls.zig\");\n\n    const max = 9;\n};\n")
}

test "enum, tagged-union, and bare-union definitions: tags, discriminants, payloads, methods" {
    try expectLower("const Color = enum {\n    red,\n    green = 3,\n}\n\nconst Tok = enum(u8) {\n    /// The end marker.\n    eof,\n\n    fn isEof(self Tok) bool {}\n}\n\nconst Result = union(enum) {\n    none,\n    byte u8,\n\n    pub fn ok(self Result) bool {}\n}\n\nconst Raw = union {\n    word usize,\n    bytes [8]u8,\n}", "const Color = enum {\n    red,\n    green = 3,\n};\n\nconst Tok = enum(u8) {\n    /// The end marker.\n    eof,\n\n    fn isEof(self: Tok) bool {}\n};\n\nconst Result = union(enum) {\n    none,\n    byte: u8,\n\n    pub fn ok(self: Result) bool {}\n};\n\nconst Raw = union {\n    word: usize,\n    bytes: [8]u8,\n};\n")
}

test "empty container definitions lower to zig fmt's one-line `{}` form" {
    try expectLower("const Empty = struct {}\n\nconst E = enum {}\n\nconst T = enum(u8) {}\n\nconst U = union {}\n\nconst V = union(enum) {}\n\nconst P = packed struct {}", "const Empty = struct {};\n\nconst E = enum {};\n\nconst T = enum(u8) {};\n\nconst U = union {};\n\nconst V = union(enum) {};\n\nconst P = packed struct {};\n")
}

test "a multiline-string const, an empty comptime block, and an empty test block" {
    try expectLower("/// Top doc.\nconst banner =\n    \\\\hello\n    \\\\world\n\ncomptime {}\n\ntest \"smoke\" {}", "/// Top doc.\nconst banner =\n    \\\\hello\n    \\\\world\n;\n\ncomptime {}\n\ntest \"smoke\" {}\n")
}

test "hello: bind, discard, call lower to diffable Zig" {
    try expectLower("use flibc\n\nlink \"flibc_start\"\nlink \"flibc_mem\"\n\nexport fn main(_ usize, _ argv) noreturn {\n    msg := \"hello from flash\\n\"\n    _ = flibc.sys.write_fd(1, msg.ptr, msg.len)\n    flibc.exit()\n}", "const flibc = @import(\"flibc\");\n\ncomptime {\n    _ = @import(\"flibc_start\");\n    _ = @import(\"flibc_mem\");\n}\n\nexport fn main(_: usize, _: [*]const ?[*:0]const u8) callconv(.c) noreturn {\n    const msg = \"hello from flash\\n\";\n    _ = flibc.sys.write_fd(1, msg.ptr, msg.len);\n    flibc.exit();\n}\n")
}

test "error model: origination, named sets, and infix unions lower byte-for-byte" {
    try expectLower("const AllocError = error{ OutOfMemory, Overflow }\nconst One = error{Bad}\n\nfn dup(path cstr) AllocError!i32 {\n    return error.OutOfMemory\n}\n\nfn run() !void {\n    return\n}\n\nconst VTable = struct {\n    alloc *fn(usize) AllocError![]u8,\n}", "const AllocError = error{ OutOfMemory, Overflow };\n\nconst One = error{Bad};\n\nfn dup(path: [*:0]const u8) AllocError!i32 {\n    return error.OutOfMemory;\n}\n\nfn run() !void {\n    return;\n}\n\nconst VTable = struct {\n    alloc: *const fn (usize) AllocError![]const u8,\n};\n")
}

test "sentinel slice: a[lo..hi :s] lowers byte-for-byte with zig fmt's space before the colon" {
    try expectLower("fn span(buf []mut u8, pos usize, n usize) [*:0]mut u8 {\n    return buf[pos..n :0].ptr\n}", "fn span(buf: []u8, pos: usize, n: usize) [*:0]u8 {\n    return buf[pos..n :0].ptr;\n}\n")
}

test "inferred sentinel array: [_:s]T{ … } lowers as the argv-style null-terminated vector" {
    try expectLower("fn run() {\n    const argv = [_:null]?[*:0]u8{ \"sh\" }\n    _ = argv\n}", "fn run() void {\n    const argv = [_:null]?[*:0]const u8{\"sh\"};\n    _ = argv;\n}\n")
}

test "slice bound spacing: zig fmt spaces `..` only when a bound is a binary op" {
    try expectLower("fn slices(buf []mut u8, lo usize, hi usize) {\n    _ = buf[lo..hi]\n    _ = buf[lo .. hi + 1]\n    _ = buf[lo + 1 .. hi]\n    _ = buf[lo + 1 ..]\n    _ = buf[lo..][0..hi]\n    _ = buf[lo .. hi + 1 :0]\n}", "fn slices(buf: []u8, lo: usize, hi: usize) void {\n    _ = buf[lo..hi];\n    _ = buf[lo .. hi + 1];\n    _ = buf[lo + 1 .. hi];\n    _ = buf[lo + 1 ..];\n    _ = buf[lo..][0..hi];\n    _ = buf[lo .. hi + 1 :0];\n}\n")
}

test "expressions: precedence, index/slice, unary, builtins, && -> and, grouping" {
    try expectLower("fn f(xs []u8, n usize, m usize) {\n    _ = n + 1 < m\n    _ = xs[n] != 0\n    _ = xs[0..n]\n    _ = -xs[n] + #intCast(n % 10)\n    _ = &xs\n    _ = n < m && m != 0\n    _ = n * (m + n)\n}", "fn f(xs: []const u8, n: usize, m: usize) void {\n    _ = n + 1 < m;\n    _ = xs[n] != 0;\n    _ = xs[0..n];\n    _ = -xs[n] + @intCast(n % 10);\n    _ = &xs;\n    _ = n < m and m != 0;\n    _ = n * (m + n);\n}\n")
}

test "control-flow shapes: while, for-in, if/else-if/else, compound assign, return" {
    try expectLower("fn f(xs []u8, n usize) usize {\n    var total usize = 0\n    for x in xs {\n        _ = x\n        total += 1\n    }\n    while n > 0 {\n        n -= 1\n    }\n    if n == 0 {\n        return total\n    } else if n == 1 {\n        return n\n    } else {\n        return 0\n    }\n}", "fn f(xs: []const u8, n: usize) usize {\n    var total: usize = 0;\n    for (xs) |x| {\n        _ = x;\n        total += 1;\n    }\n    while (n > 0) {\n        n -= 1;\n    }\n    if (n == 0) {\n        return total;\n    } else if (n == 1) {\n        return n;\n    } else {\n        return 0;\n    }\n}\n")
}

test "while optional-capture lowers to `while (expr) |x| { … }`" {
    try expectLower("fn drain(it Iter) {\n    while it.next() |x| {\n        _ = x\n    }\n}", "fn drain(it: Iter) void {\n    while (it.next()) |x| {\n        _ = x;\n    }\n}\n")
}

test "for surface: range iterator and indexed capture lower to Zig's `for (lo..hi)` / `for (xs, 0..)`" {
    try expectLower("fn f(xs []u8, n usize) {\n    for i in 0..n {\n        _ = i\n    }\n    for i in lo..hi {\n        _ = i\n    }\n    for x, i in xs {\n        _ = x\n        _ = i\n    }\n}", "fn f(xs: []const u8, n: usize) void {\n    for (0..n) |i| {\n        _ = i;\n    }\n    for (lo..hi) |i| {\n        _ = i;\n    }\n    for (xs, 0..) |x, i| {\n        _ = x;\n        _ = i;\n    }\n}\n")
}

test "inline for lowers to Zig's `inline for` across element, range, indexed, and else shapes" {
    try expectLower("fn f(xs []u8, n usize) {\n    inline for x in xs {\n        _ = x\n    }\n    inline for i in 0..n {\n        _ = i\n    }\n    inline for x, i in xs {\n        _ = x\n        _ = i\n    } else {\n        g()\n    }\n}", "fn f(xs: []const u8, n: usize) void {\n    inline for (xs) |x| {\n        _ = x;\n    }\n    inline for (0..n) |i| {\n        _ = i;\n    }\n    inline for (xs, 0..) |x, i| {\n        _ = x;\n        _ = i;\n    } else {\n        g();\n    }\n}\n")
}

test "for surface: a `_` capture lowers verbatim to Zig's `|_|` discard" {
    try expectLower("fn f(xs []u8, n usize) {\n    for _ in 0..n {\n        tick()\n    }\n    for x, _ in xs {\n        _ = x\n    }\n}", "fn f(xs: []const u8, n: usize) void {\n    for (0..n) |_| {\n        tick();\n    }\n    for (xs, 0..) |x, _| {\n        _ = x;\n    }\n}\n")
}

test "pointer captures lower verbatim: for element (indexed too), if and while payloads" {
    // `for *p in &arr` — the systems-code shape is the for-loop form
    // `for (&arr) |*p|` (often with the index); the if/while payload
    // pointers complete Zig's capture grid.
    try expectLower("fn f(arr *mut [4]u8, opt ?u8) {\n    for *p in arr {\n        p.* = 0\n    }\n    for *p, i in &buf {\n        p.* = #intCast(i)\n    }\n    if opt |*x| {\n        x.* += 1\n    }\n    while it.next() |*v| {\n        v.* = 0\n    }\n}", "fn f(arr: *[4]u8, opt: ?u8) void {\n    for (arr) |*p| {\n        p.* = 0;\n    }\n    for (&buf, 0..) |*p, i| {\n        p.* = @intCast(i);\n    }\n    if (opt) |*x| {\n        x.* += 1;\n    }\n    while (it.next()) |*v| {\n        v.* = 0;\n    }\n}\n")
}

test "a switch prong pointer capture lowers to `=> |*pay|`" {
    try expectLower("fn bump(u *mut U) {\n    switch u.* {\n        .a => |*pay| {\n            pay.* += 1\n        },\n        else => {},\n    }\n}", "fn bump(u: *U) void {\n    switch (u.*) {\n        .a => |*pay| {\n            pay.* += 1;\n        },\n        else => {},\n    }\n}\n")
}

test "if-expression lowers to a parenthesised-condition value if" {
    try expectLower("fn classify(n usize) usize {\n    x := if (n == 0) 1 else 2\n    return if (n > x) n else x\n}", "fn classify(n: usize) usize {\n    const x = if (n == 0) 1 else 2;\n    return if (n > x) n else x;\n}\n")
}

test "typed struct/union literal lowers, and a header brace stays the body" {
    try expectLower("const Action = union(enum) {\n    none,\n    echo u8,\n}\n\nfn step(buf []u8, byte u8) Action {\n    var i usize = 0\n    while i < buf.len {\n        i += 1\n    }\n    return Action{ .echo = byte }\n}", "const Action = union(enum) {\n    none,\n    echo: u8,\n};\n\nfn step(buf: []const u8, byte: u8) Action {\n    var i: usize = 0;\n    while (i < buf.len) {\n        i += 1;\n    }\n    return Action{ .echo = byte };\n}\n")
}

test "switch expression lowers prongs, value lists, ranges, and block arms" {
    try expectLower("fn classify(c u8) u8 {\n    return switch c {\n        ' ', '\\t' => 0,\n        '0'...'9' => 1,\n        '+' => if (c == 0) 2 else 3,\n        else => blk: {\n            if c == 0 {\n                break :blk 9\n            }\n            break :blk 4\n        },\n    }\n}", "fn classify(c: u8) u8 {\n    return switch (c) {\n        ' ', '\\t' => 0,\n        '0'...'9' => 1,\n        '+' => if (c == 0) 2 else 3,\n        else => blk: {\n            if (c == 0) {\n                break :blk 9;\n            }\n            break :blk 4;\n        },\n    };\n}\n")
}

test "switch prong payload captures and a void block arm lower like Zig" {
    try expectLower("fn dispatch(line []u8) {\n    var argv [tok.MAX_ARGS]?[*:0]mut u8 = undefined\n    var buf [TOK_BUF]u8 = undefined\n    switch tok.tokenize(line, &argv, &buf) {\n        .empty => {},\n        .err => |e| switch e {\n            .too_many_pipes => emit(2, \"fsh: only one pipe supported\\n\"),\n            .empty_side => emit(2, \"fsh: missing command around |\\n\"),\n        },\n        .single => |n| runSingle(&argv, n),\n        .piped => |p| runPiped(&argv, p),\n    }\n}", "fn dispatch(line: []const u8) void {\n    var argv: [tok.MAX_ARGS]?[*:0]u8 = undefined;\n    var buf: [TOK_BUF]u8 = undefined;\n    switch (tok.tokenize(line, &argv, &buf)) {\n        .empty => {},\n        .err => |e| switch (e) {\n            .too_many_pipes => emit(2, \"fsh: only one pipe supported\\n\"),\n            .empty_side => emit(2, \"fsh: missing command around |\\n\"),\n        },\n        .single => |n| runSingle(&argv, n),\n        .piped => |p| runPiped(&argv, p),\n    }\n}\n")
}

test "align(N) binding qualifier and a bare-return switch prong lower like Zig" {
    try expectLower("fn repl() {\n    const comp Completion align(16) = .{ .a = 1 }\n    switch read(comp) {\n        .eof => return,\n        .line => |l| {\n            handle(l)\n        },\n    }\n}", "fn repl() void {\n    const comp: Completion align(16) = .{ .a = 1 };\n    switch (read(comp)) {\n        .eof => return,\n        .line => |l| {\n            handle(l);\n        },\n    }\n}\n")
}

test "labeled block expression and valued break lower with correct depth" {
    try expectLower("fn pick(n usize) usize {\n    return blk: {\n        if n == 0 {\n            break :blk 1\n        }\n        break :blk 2\n    }\n}", "fn pick(n: usize) usize {\n    return blk: {\n        if (n == 0) {\n            break :blk 1;\n        }\n        break :blk 2;\n    };\n}\n")
}

test "empty blocks collapse to `{}` across fn, while, for, and if/else" {
    try expectLower("fn noop() {\n}\n\nfn loops(n usize) {\n    var i usize = 0\n    while i < n {\n    }\n    for x in n {\n    }\n}\n\nfn branch(c bool) {\n    if c {\n    } else {\n    }\n}", "fn noop() void {}\n\nfn loops(n: usize) void {\n    var i: usize = 0;\n    while (i < n) {}\n    for (n) |x| {}\n}\n\nfn branch(c: bool) void {\n    if (c) {} else {}\n}\n")
}

test "error-union surface: !T/?T, try, catch, defer, errdefer, optional-capture if" {
    try expectLower("fn open(p cstr) !i32 {\n    fd := try sys.open(p)\n    errdefer _ = sys.close(fd)\n    defer _ = sys.flush()\n    return fd\n}\n\nfn pick(xs []u8) ?u8 {\n    if find(xs) |hit| {\n        return hit\n    }\n    _ = run(xs) catch |e| report(e)\n    return none\n}", "fn open(p: [*:0]const u8) !i32 {\n    const fd = try sys.open(p);\n    errdefer _ = sys.close(fd);\n    defer _ = sys.flush();\n    return fd;\n}\n\nfn pick(xs: []const u8) ?u8 {\n    if (find(xs)) |hit| {\n        return hit;\n    }\n    _ = run(xs) catch |e| report(e);\n    return none;\n}\n")
}

test "catch block recovery: bare void block, captured block, value position" {
    try expectLower("fn run() !u8 {\n    flush() catch {}\n    _ = work() catch |e| {\n        log(e)\n        return 0\n    }\n    x := work() catch { return 0 }\n    return x\n}", "fn run() !u8 {\n    flush() catch {};\n    _ = work() catch |e| {\n        log(e);\n        return 0;\n    };\n    const x = work() catch {\n        return 0;\n    };\n    return x;\n}\n")
}

test "echo port: while + orelse break + nul-scan helper" {
    try expectLower("use flibc\n\nlink \"flibc_start\"\nlink \"flibc_mem\"\n\nfn emit(s []u8) {\n    _ = flibc.sys.write_fd(1, s.ptr, s.len)\n}\n\nfn emitz(s cstr) {\n    var n usize = 0\n    while s[n] != 0 {\n        n += 1\n    }\n    _ = flibc.sys.write_fd(1, s, n)\n}\n\nexport fn main(argc usize, argv argv) noreturn {\n    var i usize = 1\n    while i < argc {\n        s := argv[i] orelse break\n        emitz(s)\n        if i + 1 < argc {\n            emit(\" \")\n        }\n        i += 1\n    }\n    emit(\"\\n\")\n    flibc.exit()\n}", "const flibc = @import(\"flibc\");\n\ncomptime {\n    _ = @import(\"flibc_start\");\n    _ = @import(\"flibc_mem\");\n}\n\nfn emit(s: []const u8) void {\n    _ = flibc.sys.write_fd(1, s.ptr, s.len);\n}\n\nfn emitz(s: [*:0]const u8) void {\n    var n: usize = 0;\n    while (s[n] != 0) {\n        n += 1;\n    }\n    _ = flibc.sys.write_fd(1, s, n);\n}\n\nexport fn main(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn {\n    var i: usize = 1;\n    while (i < argc) {\n        const s = argv[i] orelse break;\n        emitz(s);\n        if (i + 1 < argc) {\n            emit(\" \");\n        }\n        i += 1;\n    }\n    emit(\"\\n\");\n    flibc.exit();\n}\n")
}

test "bitwise and shift expressions lower verbatim" {
    try expectLower("fn f(a u32, b u32) {\n    _ = a << 2\n    _ = a >> 1\n    _ = a & b\n    _ = a | b\n    _ = a ^ b\n}", "fn f(a: u32, b: u32) void {\n    _ = a << 2;\n    _ = a >> 1;\n    _ = a & b;\n    _ = a | b;\n    _ = a ^ b;\n}\n")
}

test "bitwise and shift compound assignment lowers verbatim" {
    try expectLower("fn f(flags u32) {\n    var f2 u32 = flags\n    f2 &= 0x0F\n    f2 |= 0x10\n    f2 ^= 0xFF\n    f2 <<= 1\n    f2 >>= 2\n}", "fn f(flags: u32) void {\n    var f2: u32 = flags;\n    f2 &= 0x0F;\n    f2 |= 0x10;\n    f2 ^= 0xFF;\n    f2 <<= 1;\n    f2 >>= 2;\n}\n")
}

test "wrapping compound assignment lowers verbatim, reference shapes included" {
    // The accumulator fold and the monotone ring head — the systems-code
    // shapes the form exists for; the want is the emitted Zig of
    // examples/register/wrapping.flash, byte for byte.
    try expectLower("pub fn fold(state *mut [8]u32, a u32, b u32) {\n    state[0] +%= a\n    state[1] +%= b\n}\n\npub const Ring = struct {\n    head u64 = 0,\n    tail u64 = 0,\n\n    pub fn push(self *mut Ring, size u64) {\n        self.head +%= 1\n        if self.head -% self.tail > size {\n            self.tail = self.head -% size\n        }\n    }\n}", "pub fn fold(state: *[8]u32, a: u32, b: u32) void {\n    state[0] +%= a;\n    state[1] +%= b;\n}\n\npub const Ring = struct {\n    head: u64 = 0,\n    tail: u64 = 0,\n\n    pub fn push(self: *Ring, size: u64) void {\n        self.head +%= 1;\n        if (self.head -% self.tail > size) {\n            self.tail = self.head -% size;\n        }\n    }\n};\n")
    try expectLower("fn f() {\n    var i u32 = 0\n    i +%= 1\n    i -%= 2\n    i *%= 3\n}", "fn f() void {\n    var i: u32 = 0;\n    i +%= 1;\n    i -%= 2;\n    i *%= 3;\n}\n")
}

test "array repetition `**` lowers verbatim, reference shapes included" {
    // The fixed-buffer zero-init shapes: a field default sized by a named
    // constant, the anonymous-tuple form, a typed non-u8 element, and a
    // parenthesized count; the want is the emitted Zig of
    // examples/register/repetition.flash, byte for byte.
    try expectLower("pub const SIZE u64 = 4096\n\npub const KlogRing = struct {\n    buf [SIZE]u8 = [_]u8{0} ** SIZE,\n    head u64 = 0,\n}\n\npub const Dirent = struct {\n    name [32]u8 = .{0} ** 32,\n    d_type u8 = 0,\n    _pad [7]u8 = .{0} ** 7,\n}\n\npub const TaskStruct = struct {\n    pid u32 = 0,\n}\n\npub var task [64]?*mut TaskStruct = [_]?*mut TaskStruct{null} ** 64\n\nvar pool [1024 * 1024]u8 = [_]u8{0} ** (1024 * 1024)", "pub const SIZE: u64 = 4096;\n\npub const KlogRing = struct {\n    buf: [SIZE]u8 = [_]u8{0} ** SIZE,\n    head: u64 = 0,\n};\n\npub const Dirent = struct {\n    name: [32]u8 = .{0} ** 32,\n    d_type: u8 = 0,\n    _pad: [7]u8 = .{0} ** 7,\n};\n\npub const TaskStruct = struct {\n    pid: u32 = 0,\n};\n\npub var task: [64]?*TaskStruct = [_]?*TaskStruct{null} ** 64;\n\nvar pool: [1024 * 1024]u8 = [_]u8{0} ** (1024 * 1024);\n")
}

test "export var and extern var lower verbatim, reference shapes included" {
    // The cross-object symbol pattern — definitions exported once, consumed
    // bodyless/valueless elsewhere, plus the linker-symbol idiom; the want is
    // the emitted Zig of examples/register/export_extern_var.flash, byte for
    // byte.
    try expectLower("pub const TaskStruct = struct {\n    pid i32 = 0,\n}\n\npub const NR_TASKS usize = 64\n\nexport var current ?*mut TaskStruct = null\nexport var task [NR_TASKS]?*mut TaskStruct = [_]?*mut TaskStruct{null} ** NR_TASKS\nexport var nr_tasks i32 = 0\nexport var next_pid i32 = 1\n\nextern var _kernel_pa_end u8\nextern var __initramfs_start u8", "pub const TaskStruct = struct {\n    pid: i32 = 0,\n};\n\npub const NR_TASKS: usize = 64;\n\nexport var current: ?*TaskStruct = null;\n\nexport var task: [NR_TASKS]?*TaskStruct = [_]?*TaskStruct{null} ** NR_TASKS;\n\nexport var nr_tasks: i32 = 0;\n\nexport var next_pid: i32 = 1;\n\nextern var _kernel_pa_end: u8;\n\nextern var __initramfs_start: u8;\n")
    try expectLower("pub export var head u64 = 0\n\npub extern var tail u64", "pub export var head: u64 = 0;\n\npub extern var tail: u64;\n")
}

test "linksection lowers verbatim, reference shapes included" {
    // A symbol placed in a named section — the NOLOAD scratch buffer and the
    // .rodata pad; the want is the emitted Zig of
    // examples/register/linksection.flash, byte for byte.
    try expectLower("const SCRATCH_BLOCKS u32 = 128 * 1024\n\nvar scratch [#as(usize, SCRATCH_BLOCKS) * 512]u8 linksection(\".sdscratch\") = undefined\n\nconst PAD [4096]u8 linksection(\".rodata\") = .{0xAB} ** 4096\n\npub fn keep() *[4096]u8 {\n    _ = &scratch\n    return &PAD\n}", "const SCRATCH_BLOCKS: u32 = 128 * 1024;\n\nvar scratch: [@as(usize, SCRATCH_BLOCKS) * 512]u8 linksection(\".sdscratch\") = undefined;\n\nconst PAD: [4096]u8 linksection(\".rodata\") = .{0xAB} ** 4096;\n\npub fn keep() *const [4096]u8 {\n    _ = &scratch;\n    return &PAD;\n}\n")
    // On a fn the attribute precedes any callconv — Zig's slot order — and an
    // inferred-type binding carries it the same way.
    try expectLower("const named linksection(SECTION) = 1\n\nfn fast() linksection(\".fast\") {}\n\nexport fn shim() linksection(\".vec\") callconv(.c) u32 {\n    return 0\n}", "const named linksection(SECTION) = 1;\n\nfn fast() linksection(\".fast\") void {}\n\nexport fn shim() linksection(\".vec\") callconv(.c) u32 {\n    return 0;\n}\n")
}

test "packed and extern struct layouts lower verbatim, reference shapes included" {
    // The on-disk FAT directory entry and the ABI-crossing Dirent — the want
    // is the emitted Zig of examples/register/packed_extern_struct.flash,
    // byte for byte.
    try expectLower("pub const DirEntry = packed struct {\n    name_lo u64,\n    name_hi u24,\n    attr u8,\n    nt_res u8,\n    crt_time_tenth u8,\n    crt_time u16,\n    crt_date u16,\n    lst_acc_date u16,\n    fst_clus_hi u16,\n    wrt_time u16,\n    wrt_date u16,\n    fst_clus_lo u16,\n    file_size u32,\n}\n\ncomptime {\n    if #sizeOf(DirEntry) != 32 {\n        #compileError(\"DirEntry size\")\n    }\n    if #bitOffsetOf(DirEntry, \"attr\") / 8 != 0x0B {\n        #compileError(\"DirEntry attr offset\")\n    }\n}\n\npub const DT_REG u8 = 0\n\npub const Dirent = extern struct {\n    name [32]u8 = .{0} ** 32,\n    d_type u8 = DT_REG,\n    _pad [7]u8 = .{0} ** 7,\n}", "pub const DirEntry = packed struct {\n    name_lo: u64,\n    name_hi: u24,\n    attr: u8,\n    nt_res: u8,\n    crt_time_tenth: u8,\n    crt_time: u16,\n    crt_date: u16,\n    lst_acc_date: u16,\n    fst_clus_hi: u16,\n    wrt_time: u16,\n    wrt_date: u16,\n    fst_clus_lo: u16,\n    file_size: u32,\n};\n\ncomptime {\n    if (@sizeOf(DirEntry) != 32) {\n        @compileError(\"DirEntry size\");\n    }\n    if (@bitOffsetOf(DirEntry, \"attr\") / 8 != 0x0B) {\n        @compileError(\"DirEntry attr offset\");\n    }\n}\n\npub const DT_REG: u8 = 0;\n\npub const Dirent = extern struct {\n    name: [32]u8 = .{0} ** 32,\n    d_type: u8 = DT_REG,\n    _pad: [7]u8 = .{0} ** 7,\n};\n")
    // An extern struct keeps its associated declarations exactly as a bare
    // struct does; the bare form stays modifier-free.
    try expectLower("const Ops = extern struct {\n    count u32,\n\n    pub fn empty() Ops {\n        return Ops{ .count = 0 }\n    }\n}\n\nconst Bare = struct {\n    n u8,\n}", "const Ops = extern struct {\n    count: u32,\n\n    pub fn empty() Ops {\n        return Ops{ .count = 0 };\n    }\n};\n\nconst Bare = struct {\n    n: u8,\n};\n")
}

test "multiline string in binding and discard value position" {
    try expectLower("fn show() {\n    msg :=\n        \\\\hi\n        \\\\there\n    _ = msg\n}", "fn show() void {\n    const msg =\n        \\\\hi\n        \\\\there\n    ;\n    _ = msg;\n}\n")
}

test "readline port: step's switch — value list, range, if-expr arm, block arms, typed literal" {
    try expectLower("pub const State = struct {\n    buf []mut u8,\n    len usize = 0,\n    pos usize = 0,\n}\n\npub const Action = union(enum) {\n    none,\n    echo u8,\n    backspace,\n    submit,\n    complete,\n    abandon,\n    eof,\n}\n\n/// One-byte state transition for the plain (append-only) editor. Pure: no\n/// syscalls, no allocator.\npub fn step(state *mut State, byte u8) Action {\n    return switch byte {\n        '\\r', '\\n' => .submit,\n        0x03 => .abandon,\n        0x04 => if (state.len == 0) Action.eof else Action.none,\n        0x09 => .complete,\n        0x08, 0x7f => blk: {\n            if state.len == 0 {\n                break :blk Action.none\n            }\n            state.len -= 1\n            break :blk Action.backspace\n        },\n        0x20...0x7e => blk: {\n            if state.len >= state.buf.len {\n                break :blk Action.none\n            }\n            state.buf[state.len] = byte\n            state.len += 1\n            break :blk Action{ .echo = byte }\n        },\n        else => .none,\n    }\n}", "pub const State = struct {\n    buf: []u8,\n    len: usize = 0,\n    pos: usize = 0,\n};\n\npub const Action = union(enum) {\n    none,\n    echo: u8,\n    backspace,\n    submit,\n    complete,\n    abandon,\n    eof,\n};\n\n/// One-byte state transition for the plain (append-only) editor. Pure: no\n/// syscalls, no allocator.\npub fn step(state: *State, byte: u8) Action {\n    return switch (byte) {\n        '\\r', '\\n' => .submit,\n        0x03 => .abandon,\n        0x04 => if (state.len == 0) Action.eof else Action.none,\n        0x09 => .complete,\n        0x08, 0x7f => blk: {\n            if (state.len == 0) {\n                break :blk Action.none;\n            }\n            state.len -= 1;\n            break :blk Action.backspace;\n        },\n        0x20...0x7e => blk: {\n            if (state.len >= state.buf.len) {\n                break :blk Action.none;\n            }\n            state.buf[state.len] = byte;\n            state.len += 1;\n            break :blk Action{ .echo = byte };\n        },\n        else => .none,\n    };\n}\n")
}

test "comptime binding: `comptime const` wraps its value, `comptime var` keeps the prefix" {
    try expectLower("pub fn f() {\n    comptime const N = 4\n    comptime var i usize = 0\n    i += N\n}", "pub fn f() void {\n    const N = comptime 4;\n    comptime var i: usize = 0;\n    i += N;\n}\n")
}

test "fsh port: `.?` optional unwrap — value-if fallback and pipe-stage name assert" {
    try expectLower("use flibc\n\nfn cd(argv *mut [16]?[*:0]mut u8, argc usize) {\n    const target [*:0]u8 = if (argc >= 2) argv[1].? else \"/\"\n    _ = flibc.chdir(target)\n}\n\nfn pipeLeft(argv *mut [16]?[*:0]mut u8) {\n    const left [*]?[*:0]u8 = #ptrCast(argv)\n    _ = flibc.execvp(left[0].?, left)\n}", "const flibc = @import(\"flibc\");\n\nfn cd(argv: *[16]?[*:0]u8, argc: usize) void {\n    const target: [*:0]const u8 = if (argc >= 2) argv[1].? else \"/\";\n    _ = flibc.chdir(target);\n}\n\nfn pipeLeft(argv: *[16]?[*:0]u8) void {\n    const left: [*]const ?[*:0]const u8 = @ptrCast(argv);\n    _ = flibc.execvp(left[0].?, left);\n}\n")
}

test "inline assembly: operands, positional sections, and the volatile modifier lower byte-for-byte" {
    try expectLower("fn spin() {\n    asm volatile (\"wfe\")\n}\n\nfn read_iar() u64 {\n    var iar u64 = undefined\n    asm volatile (\"mrs %[iar], S3_0_C12_C12_0\"\n        : [iar] \"=r\" (iar),\n    )\n    return iar\n}\n\nfn eoi(iar u64) {\n    asm volatile (\"msr S3_0_C12_C12_1, %[iar]\"\n        :\n        : [iar] \"r\" (iar),\n    )\n}\n\nfn exec_path(path usize, argv usize) i32 {\n    return asm volatile (\"svc #0\"\n        : [ret] \"={x0}\" (-> i32),\n        : [nr] \"{x8}\" (11),\n          [path] \"{x0}\" (path),\n          [argv] \"{x1}\" (argv),\n        : .{ .memory = true })\n}", "fn spin() void {\n    asm volatile (\"wfe\");\n}\n\nfn read_iar() u64 {\n    var iar: u64 = undefined;\n    asm volatile (\"mrs %[iar], S3_0_C12_C12_0\"\n        : [iar] \"=r\" (iar),\n    );\n    return iar;\n}\n\nfn eoi(iar: u64) void {\n    asm volatile (\"msr S3_0_C12_C12_1, %[iar]\"\n        :\n        : [iar] \"r\" (iar),\n    );\n}\n\nfn exec_path(path: usize, argv: usize) i32 {\n    return asm volatile (\"svc #0\"\n        : [ret] \"={x0}\" (-> i32),\n        : [nr] \"{x8}\" (11),\n          [path] \"{x0}\" (path),\n          [argv] \"{x1}\" (argv),\n        : .{ .memory = true });\n}\n")
}

test "inline assembly: multiline template, clobber-only single line, non-volatile, and a `#` intrinsic input" {
    try expectLower("export fn user_entry() {\n    asm volatile (\n        \\\\bl pid1_main\n        \\\\mov x8, #2\n        \\\\svc #0\n    )\n}\n\nfn flush(addr u64) {\n    asm volatile (\n        \\\\dc cvau, %[a]\n        \\\\dsb ish\n        \\\\isb\n        :\n        : [a] \"r\" (addr),\n        : .{ .memory = true })\n}\n\nfn barrier() {\n    asm volatile (\"dsb sy\" ::: .{ .memory = true })\n}\n\nfn one() i32 {\n    return asm (\"mov %[r], #1\"\n        : [r] \"=r\" (-> i32),\n    )\n}\n\nfn set_pri() {\n    asm volatile (\"msr S3_0_C4_C6_0, %[v]\"\n        :\n        : [v] \"r\" (#as(u64, 255)),\n    )\n}", "export fn user_entry() callconv(.c) void {\n    asm volatile (\n        \\\\bl pid1_main\n        \\\\mov x8, #2\n        \\\\svc #0\n    );\n}\n\nfn flush(addr: u64) void {\n    asm volatile (\n        \\\\dc cvau, %[a]\n        \\\\dsb ish\n        \\\\isb\n        :\n        : [a] \"r\" (addr),\n        : .{ .memory = true });\n}\n\nfn barrier() void {\n    asm volatile (\"dsb sy\" ::: .{ .memory = true });\n}\n\nfn one() i32 {\n    return asm (\"mov %[r], #1\"\n        : [r] \"=r\" (-> i32),\n    );\n}\n\nfn set_pri() void {\n    asm volatile (\"msr S3_0_C4_C6_0, %[v]\"\n        :\n        : [v] \"r\" (@as(u64, 255)),\n    );\n}\n")
}

test "float literals lower verbatim — byte-identical to zig fmt" {
    try expectLower("const pi = 3.14\nconst grav = 9.81e-2\nconst tiny = 1_000.5e+0", "const pi = 3.14;\n\nconst grav = 9.81e-2;\n\nconst tiny = 1_000.5e+0;\n")
}

test "wrapping operators `-%` / `*%` lower verbatim — byte-identical to zig fmt" {
    try expectLower("const d = x -% y\nconst p = x *% y\nconst m = a -% b *% c", "const d = x -% y;\n\nconst p = x *% y;\n\nconst m = a -% b *% c;\n")
}

test "defer/errdefer block form lowers to a Zig brace body — byte-identical to zig fmt" {
    try expectLower("fn run(fd i32) !void {\n    defer {\n        close(fd)\n        close(fd + 1)\n    }\n    errdefer {\n        close(0)\n    }\n    defer close(fd)\n    return\n}", "fn run(fd: i32) !void {\n    defer {\n        close(fd);\n        close(fd + 1);\n    }\n    errdefer {\n        close(0);\n    }\n    defer close(fd);\n    return;\n}\n")
}

test "errdefer capture lowers to Zig's |err| slot on both forms — byte-identical to zig fmt" {
    try expectLower("fn run(fd i32) !void {\n    errdefer |err| log(err)\n    errdefer |err| {\n        log(err)\n        close(fd)\n    }\n    errdefer close(fd)\n    return\n}", "fn run(fd: i32) !void {\n    errdefer |err| log(err);\n    errdefer |err| {\n        log(err);\n        close(fd);\n    }\n    errdefer close(fd);\n    return;\n}\n")
}

test "loop else arms and the if else-capture lower — byte-identical to zig fmt" {
    try expectLower("fn f(xs []u8, c bool) void {\n    if next() |v| {\n        consume(v)\n    } else |err| {\n        log(err)\n    }\n    while next() |v| {\n        consume(v)\n    } else |err| {\n        log(err)\n    }\n    while c {\n        step()\n    } else {\n        done()\n    }\n    for x in xs {\n        consume(x)\n    } else {\n        done()\n    }\n    if c {} else {}\n}", "fn f(xs: []const u8, c: bool) void {\n    if (next()) |v| {\n        consume(v);\n    } else |err| {\n        log(err);\n    }\n    while (next()) |v| {\n        consume(v);\n    } else |err| {\n        log(err);\n    }\n    while (c) {\n        step();\n    } else {\n        done();\n    }\n    for (xs) |x| {\n        consume(x);\n    } else {\n        done();\n    }\n    if (c) {} else {}\n}\n")
}

test "tuple types and multi-return lower to Zig tuples — byte-identical to zig fmt" {
    try expectLower("const Pair = (u8, bool)\nfn pair() (u8, bool) {\n    return 42, true\n}\nfn lit() Pair {\n    return .{ 7, false }\n}\nfn first(t (u8, (u8, bool))) u8 {\n    return t[0] + t[1][0]\n}\nfn three() (u8, u8, u8) {\n    return 1, 2, 3\n}", "const Pair = struct { u8, bool };\n\nfn pair() struct { u8, bool } {\n    return .{ 42, true };\n}\n\nfn lit() Pair {\n    return .{ 7, false };\n}\n\nfn first(t: struct { u8, struct { u8, bool } }) u8 {\n    return t[0] + t[1][0];\n}\n\nfn three() struct { u8, u8, u8 } {\n    return .{ 1, 2, 3 };\n}\n")
}

test "destructuring binds and assigns lower to Zig destructures — byte-identical to zig fmt" {
    try expectLower("fn pair() (u8, bool) {\n    return 42, true\n}\nfn demo() void {\n    a, b := pair()\n    _ = a\n    _ = b\n    tok, _ := pair()\n    _ = tok\n    _, ok := pair()\n    _ = ok\n    var x, y = pair()\n    x, y = pair()\n    var arr [3]u8 = .{ 0, 0, 0 }\n    arr[0], y = pair()\n    _ = x\n}", "fn pair() struct { u8, bool } {\n    return .{ 42, true };\n}\n\nfn demo() void {\n    const a, const b = pair();\n    _ = a;\n    _ = b;\n    const tok, _ = pair();\n    _ = tok;\n    _, const ok = pair();\n    _ = ok;\n    var x, var y = pair();\n    x, y = pair();\n    var arr: [3]u8 = .{ 0, 0, 0 };\n    arr[0], y = pair();\n    _ = x;\n}\n")
}

test "clear: cross-import, aliasless void fn, const slice param" {
    try expectLower("use flibc\nuse console_ui\n\nlink \"flibc_start\"\nlink \"flibc_mem\"\n\nfn sink(bytes []u8) {\n    _ = flibc.sys.write_fd(1, bytes.ptr, bytes.len)\n}\n\nexport fn main(_ usize, _ argv) noreturn {\n    console_ui.screen.clear(sink)\n    flibc.exit()\n}", "const flibc = @import(\"flibc\");\nconst console_ui = @import(\"console_ui\");\n\ncomptime {\n    _ = @import(\"flibc_start\");\n    _ = @import(\"flibc_mem\");\n}\n\nfn sink(bytes: []const u8) void {\n    _ = flibc.sys.write_fd(1, bytes.ptr, bytes.len);\n}\n\nexport fn main(_: usize, _: [*]const ?[*:0]const u8) callconv(.c) noreturn {\n    console_ui.screen.clear(sink);\n    flibc.exit();\n}\n")
}

test "function pointer v-table: the explicit-allocator interface shape lowers byte-for-byte" {
    try expectLower("const Allocator = struct {\n    ptr *mut anyopaque,\n    vtable *VTable,\n}\n\nconst VTable = struct {\n    alloc *fn(*mut anyopaque, usize) ?[*]mut u8,\n    free *fn(*mut anyopaque, []mut u8) void,\n}", "const Allocator = struct {\n    ptr: *anyopaque,\n    vtable: *const VTable,\n};\n\nconst VTable = struct {\n    alloc: *const fn (*anyopaque, usize) ?[*]u8,\n    free: *const fn (*anyopaque, []u8) void,\n};\n")
}

test "pointer dereference: p.* reads and stores through a single-item pointer" {
    try expectLower("fn store(p *mut u32, v u32) {\n    p.* = v\n    _ = p.*\n}", "fn store(p: *u32, v: u32) void {\n    p.* = v;\n    _ = p.*;\n}\n")
}

test "meminfo: an anonymous struct literal in a printf call" {
    try expectLower("use flibc\n\nlink \"flibc_start\"\nlink \"flibc_mem\"\n\nexport fn main(_ usize, _ argv) noreturn {\n    flibc.printf(\"free pages: %u\\n\", .{flibc.sys.dump_free()})\n    flibc.exit()\n}", "const flibc = @import(\"flibc\");\n\ncomptime {\n    _ = @import(\"flibc_start\");\n    _ = @import(\"flibc_mem\");\n}\n\nexport fn main(_: usize, _: [*]const ?[*:0]const u8) callconv(.c) noreturn {\n    flibc.printf(\"free pages: %u\\n\", .{flibc.sys.dump_free()});\n    flibc.exit();\n}\n")
}

test "dmesg port: array decl + if guard lower to diffable Zig" {
    try expectLower("use flibc\n\nlink \"flibc_start\"\nlink \"flibc_mem\"\n\nexport fn main(_ usize, _ argv) noreturn {\n    var buf [flibc.KLOG_SIZE]u8 = undefined\n    n := flibc.sys.klog_read(&buf, buf.len)\n    if n > 0 {\n        _ = flibc.sys.write_fd(1, &buf, #intCast(n))\n    }\n    flibc.exit()\n}", "const flibc = @import(\"flibc\");\n\ncomptime {\n    _ = @import(\"flibc_start\");\n    _ = @import(\"flibc_mem\");\n}\n\nexport fn main(_: usize, _: [*]const ?[*:0]const u8) callconv(.c) noreturn {\n    var buf: [flibc.KLOG_SIZE]u8 = undefined;\n    const n = flibc.sys.klog_read(&buf, buf.len);\n    if (n > 0) {\n        _ = flibc.sys.write_fd(1, &buf, @intCast(n));\n    }\n    flibc.exit();\n}\n")
}

test "cat port: top-const, if/else, nested while, continue, import alias" {
    try expectLower("use flibc\nuse syscall_defs as defs\n\nlink \"flibc_start\"\nlink \"flibc_mem\"\n\nconst BUF_LEN usize = 512\n\nfn drain(fd i32) {\n    var buf [BUF_LEN]u8 = undefined\n    while true {\n        n := flibc.sys.read(fd, &buf, buf.len)\n        if n <= 0 {\n            break\n        }\n        _ = flibc.sys.write_fd(1, &buf, #intCast(n))\n    }\n}\n\nexport fn main(argc usize, argv argv) noreturn {\n    if argc <= 1 {\n        drain(0)\n    } else {\n        var i usize = 1\n        while i < argc {\n            path := argv[i] orelse break\n            i += 1\n            fd := flibc.sys.open(path)\n            if fd < 0 {\n                var msg []u8 = \"cat: cannot open\\n\"\n                if fd == -defs.EACCES {\n                    msg = \"cat: Permission denied\\n\"\n                }\n                _ = flibc.sys.write_fd(2, msg.ptr, msg.len)\n                continue\n            }\n            drain(fd)\n            _ = flibc.sys.close(fd)\n        }\n    }\n    flibc.exit()\n}", "const flibc = @import(\"flibc\");\nconst defs = @import(\"syscall_defs\");\n\ncomptime {\n    _ = @import(\"flibc_start\");\n    _ = @import(\"flibc_mem\");\n}\n\nconst BUF_LEN: usize = 512;\n\nfn drain(fd: i32) void {\n    var buf: [BUF_LEN]u8 = undefined;\n    while (true) {\n        const n = flibc.sys.read(fd, &buf, buf.len);\n        if (n <= 0) {\n            break;\n        }\n        _ = flibc.sys.write_fd(1, &buf, @intCast(n));\n    }\n}\n\nexport fn main(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn {\n    if (argc <= 1) {\n        drain(0);\n    } else {\n        var i: usize = 1;\n        while (i < argc) {\n            const path = argv[i] orelse break;\n            i += 1;\n            const fd = flibc.sys.open(path);\n            if (fd < 0) {\n                var msg: []const u8 = \"cat: cannot open\\n\";\n                if (fd == -defs.EACCES) {\n                    msg = \"cat: Permission denied\\n\";\n                }\n                _ = flibc.sys.write_fd(2, msg.ptr, msg.len);\n                continue;\n            }\n            drain(fd);\n            _ = flibc.sys.close(fd);\n        }\n    }\n    flibc.exit();\n}\n")
}

test "ls port: struct literal, member address-of, && condition" {
    try expectLower("use flibc\n\nlink \"flibc_start\"\nlink \"flibc_mem\"\n\nfn emit(s []u8) {\n    _ = flibc.sys.write_fd(1, s.ptr, s.len)\n}\n\nfn listDir(path cstr) {\n    var d flibc.Dirent = .{}\n    var i u64 = 0\n    while flibc.sys.readdir(path, i, &d) == 0 {\n        var n usize = 0\n        while n < d.name.len && d.name[n] != 0 {\n            n += 1\n        }\n        _ = flibc.sys.write_fd(1, &d.name, n)\n        if d.d_type == flibc.DT_DIR {\n            emit(\"/\")\n        }\n        emit(\"\\n\")\n        i += 1\n    }\n}\n\nexport fn main(argc usize, argv argv) noreturn {\n    if argc <= 1 {\n        listDir(\".\")\n    } else {\n        var a usize = 1\n        while a < argc {\n            path := argv[a] orelse break\n            listDir(path)\n            a += 1\n        }\n    }\n    flibc.exit()\n}", "const flibc = @import(\"flibc\");\n\ncomptime {\n    _ = @import(\"flibc_start\");\n    _ = @import(\"flibc_mem\");\n}\n\nfn emit(s: []const u8) void {\n    _ = flibc.sys.write_fd(1, s.ptr, s.len);\n}\n\nfn listDir(path: [*:0]const u8) void {\n    var d: flibc.Dirent = .{};\n    var i: u64 = 0;\n    while (flibc.sys.readdir(path, i, &d) == 0) {\n        var n: usize = 0;\n        while (n < d.name.len and d.name[n] != 0) {\n            n += 1;\n        }\n        _ = flibc.sys.write_fd(1, &d.name, n);\n        if (d.d_type == flibc.DT_DIR) {\n            emit(\"/\");\n        }\n        emit(\"\\n\");\n        i += 1;\n    }\n}\n\nexport fn main(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn {\n    if (argc <= 1) {\n        listDir(\".\");\n    } else {\n        var a: usize = 1;\n        while (a < argc) {\n            const path = argv[a] orelse break;\n            listDir(path);\n            a += 1;\n        }\n    }\n    flibc.exit();\n}\n")
}

test "readfile port: error-union fns, try, defer, errdefer, catch capture" {
    try expectLower("use flibc\n\nlink \"flibc_start\"\nlink \"flibc_mem\"\n\nconst BUF_LEN usize = 512\n\nfn dup(path cstr) !i32 {\n    fd := try flibc.sys.open(path)\n    errdefer _ = flibc.sys.close(fd)\n    _ = try flibc.sys.fstat(fd)\n    return fd\n}\n\nfn copy(path cstr) !usize {\n    fd := try dup(path)\n    defer _ = flibc.sys.close(fd)\n    var buf [BUF_LEN]u8 = undefined\n    var total usize = 0\n    while true {\n        n := try flibc.sys.read(fd, &buf, buf.len)\n        if n == 0 {\n            break\n        }\n        _ = flibc.sys.write_fd(1, &buf, #intCast(n))\n        total += n\n    }\n    return total\n}\n\nfn report(e flibc.Error) usize {\n    _ = e\n    _ = flibc.sys.write_fd(2, \"readfile: I/O error\\n\", 20)\n    return 0\n}\n\nexport fn main(argc usize, argv argv) noreturn {\n    var i usize = 1\n    while i < argc {\n        path := argv[i] orelse break\n        _ = copy(path) catch |e| report(e)\n        i += 1\n    }\n    flibc.exit()\n}", "const flibc = @import(\"flibc\");\n\ncomptime {\n    _ = @import(\"flibc_start\");\n    _ = @import(\"flibc_mem\");\n}\n\nconst BUF_LEN: usize = 512;\n\nfn dup(path: [*:0]const u8) !i32 {\n    const fd = try flibc.sys.open(path);\n    errdefer _ = flibc.sys.close(fd);\n    _ = try flibc.sys.fstat(fd);\n    return fd;\n}\n\nfn copy(path: [*:0]const u8) !usize {\n    const fd = try dup(path);\n    defer _ = flibc.sys.close(fd);\n    var buf: [BUF_LEN]u8 = undefined;\n    var total: usize = 0;\n    while (true) {\n        const n = try flibc.sys.read(fd, &buf, buf.len);\n        if (n == 0) {\n            break;\n        }\n        _ = flibc.sys.write_fd(1, &buf, @intCast(n));\n        total += n;\n    }\n    return total;\n}\n\nfn report(e: flibc.Error) usize {\n    _ = e;\n    _ = flibc.sys.write_fd(2, \"readfile: I/O error\\n\", 20);\n    return 0;\n}\n\nexport fn main(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn {\n    var i: usize = 1;\n    while (i < argc) {\n        const path = argv[i] orelse break;\n        _ = copy(path) catch |e| report(e);\n        i += 1;\n    }\n    flibc.exit();\n}\n")
}

test "sysinfo port: optional-capture if, decimal helper, for-over-bytes" {
    try expectLower("use flibc\nuse pwfile\nuse console_ui\nuse build_options\n\nlink \"flibc_start\"\nlink \"flibc_mem\"\n\nconst PASSWD_MAX usize = build_options.passwd_max\n\nfn u64dec(out []mut u8, v u64) usize {\n    var x u64 = v\n    if x == 0 {\n        out[0] = '0'\n        return 1\n    }\n    var tmp [20]u8 = undefined\n    var n usize = 0\n    while x != 0 {\n        tmp[n] = '0' + #as(u8, #intCast(x % 10))\n        n += 1\n        x /= 10\n    }\n    var i usize = 0\n    while i < n {\n        out[i] = tmp[n - 1 - i]\n        i += 1\n    }\n    return n\n}\n\nfn currentUser(buf []mut u8) []u8 {\n    uid_raw := flibc.sys.getuid()\n    if uid_raw < 0 {\n        return \"?\"\n    }\n    uid := #as(u32, #intCast(uid_raw))\n    n := u64dec(buf, uid)\n    if pwfile.lookupByUid(buf[0..n], uid) |entry| {\n        return entry.user\n    }\n    return buf[0..n]\n}\n\nfn freePages(out []mut u8) usize {\n    pages := flibc.sys.dump_free()\n    var n usize = u64dec(out, pages)\n    suffix := \" free\"\n    for c in suffix {\n        out[n] = c\n        n += 1\n    }\n    return n\n}\n\nexport fn main(_ usize, _ argv) noreturn {\n    console_ui.banner(\"sysinfo\")\n    var ubuf [PASSWD_MAX]u8 = undefined\n    user := currentUser(&ubuf)\n    console_ui.screen.kv(\"user\", user)\n    var fbuf [32]u8 = undefined\n    m := freePages(&fbuf)\n    console_ui.screen.kv(\"memory\", fbuf[0..m])\n    flibc.exit()\n}", "const flibc = @import(\"flibc\");\nconst pwfile = @import(\"pwfile\");\nconst console_ui = @import(\"console_ui\");\nconst build_options = @import(\"build_options\");\n\ncomptime {\n    _ = @import(\"flibc_start\");\n    _ = @import(\"flibc_mem\");\n}\n\nconst PASSWD_MAX: usize = build_options.passwd_max;\n\nfn u64dec(out: []u8, v: u64) usize {\n    var x: u64 = v;\n    if (x == 0) {\n        out[0] = '0';\n        return 1;\n    }\n    var tmp: [20]u8 = undefined;\n    var n: usize = 0;\n    while (x != 0) {\n        tmp[n] = '0' + @as(u8, @intCast(x % 10));\n        n += 1;\n        x /= 10;\n    }\n    var i: usize = 0;\n    while (i < n) {\n        out[i] = tmp[n - 1 - i];\n        i += 1;\n    }\n    return n;\n}\n\nfn currentUser(buf: []u8) []const u8 {\n    const uid_raw = flibc.sys.getuid();\n    if (uid_raw < 0) {\n        return \"?\";\n    }\n    const uid = @as(u32, @intCast(uid_raw));\n    const n = u64dec(buf, uid);\n    if (pwfile.lookupByUid(buf[0..n], uid)) |entry| {\n        return entry.user;\n    }\n    return buf[0..n];\n}\n\nfn freePages(out: []u8) usize {\n    const pages = flibc.sys.dump_free();\n    var n: usize = u64dec(out, pages);\n    const suffix = \" free\";\n    for (suffix) |c| {\n        out[n] = c;\n        n += 1;\n    }\n    return n;\n}\n\nexport fn main(_: usize, _: [*]const ?[*:0]const u8) callconv(.c) noreturn {\n    console_ui.banner(\"sysinfo\");\n    var ubuf: [PASSWD_MAX]u8 = undefined;\n    const user = currentUser(&ubuf);\n    console_ui.screen.kv(\"user\", user);\n    var fbuf: [32]u8 = undefined;\n    const m = freePages(&fbuf);\n    console_ui.screen.kv(\"memory\", fbuf[0..m]);\n    flibc.exit();\n}\n")
}

test "doc comments lower verbatim before their declarations" {
    try expectLower("/// The maximum number of entries.\n/// Tunable at build time.\npub const MAX usize = 16\n\n/// Returns the smaller of two values.\ninline fn min(a usize, b usize) usize {\n    return a\n}\n\nconst Point = struct {\n    /// The horizontal coordinate.\n    x i32,\n    y i32,\n\n    /// Manhattan distance from the origin.\n    pub fn norm(self Point) i32 {\n        return self.x + self.y\n    }\n\n    /// The origin point.\n    const ZERO i32 = 0\n}\n\nconst Kind = enum(u8) {\n    /// A regular file.\n    file,\n    dir,\n}\n\nconst Tok = union(enum) {\n    /// End of input.\n    eof,\n    int usize,\n}", "/// The maximum number of entries.\n/// Tunable at build time.\npub const MAX: usize = 16;\n\n/// Returns the smaller of two values.\ninline fn min(a: usize, b: usize) usize {\n    return a;\n}\n\nconst Point = struct {\n    /// The horizontal coordinate.\n    x: i32,\n    y: i32,\n\n    /// Manhattan distance from the origin.\n    pub fn norm(self: Point) i32 {\n        return self.x + self.y;\n    }\n\n    /// The origin point.\n    const ZERO: i32 = 0;\n};\n\nconst Kind = enum(u8) {\n    /// A regular file.\n    file,\n    dir,\n};\n\nconst Tok = union(enum) {\n    /// End of input.\n    eof,\n    int: usize,\n};\n")
}

test "tokenize port: union(enum) result, union/enum-literal returns, sentinel slice, doc comments" {
    try expectLower("/// argv capacity, including the interleaved `null` separators (the pipe\n/// boundary and the trailing terminator). 16 covers a command plus a\n/// generous argument list for demoware; longer lines truncate.\npub const MAX_ARGS usize = 16\n\n/// Why the two sides of a `|` cannot both be commands, or why a second\n/// `|` appeared.\npub const Err = enum {\n    too_many_pipes,\n    empty_side,\n}\n\n/// A single-pipe decomposition. The right command's argv begins at\n/// `argv[left_argc + 1]` (the `+ 1` skips the `null` the tokenizer wrote\n/// at the pipe boundary); both vectors are NULL-terminated in place.\npub const Piped = struct {\n    left_argc usize,\n    right_argc usize,\n}\n\n/// How a line decomposed.\npub const Result = union(enum) {\n    /// Blank or whitespace-only line — fsh redraws the prompt.\n    empty,\n    /// One command; `argv[0..argc]` valid, `argv[argc] == null`.\n    single usize,\n    /// One pipe stage; see `Piped`.\n    piped Piped,\n    /// Malformed pipe usage.\n    err Err,\n}\n\ninline fn is_space(c u8) bool {\n    return c == ' ' || c == '\\t' || c == '\\r' || c == '\\n'\n}\n\n/// Split `line` into `argv` (pointers into `buf`). See the module header\n/// for the decomposition rules. `argv` and `buf` are caller-owned and\n/// reused per line; the returned pointers are valid until the next call\n/// that reuses them.\npub fn tokenize(line []u8, argv *mut [MAX_ARGS]?[*:0]mut u8, buf []mut u8) Result {\n    var argc usize = 0\n    var buf_pos usize = 0\n    var pipe_at ?usize = null\n    var pipes usize = 0\n\n    var i usize = 0\n    while i < line.len {\n        while i < line.len && is_space(line[i]) {\n            i += 1\n        }\n        if i >= line.len {\n            break\n        }\n\n        if argc >= MAX_ARGS - 1 {\n            break\n        }\n\n        if line[i] == '|' {\n            pipes += 1\n            if pipes > 1 {\n                return .{ .err = .too_many_pipes }\n            }\n            pipe_at = argc\n            argv[argc] = null\n            argc += 1\n            i += 1\n            continue\n        }\n\n        start := i\n        while i < line.len && !is_space(line[i]) && line[i] != '|' {\n            i += 1\n        }\n        tok := line[start..i]\n\n        if buf_pos + tok.len + 1 > buf.len {\n            break\n        }\n        #memcpy(buf[buf_pos..][0..tok.len], tok)\n        buf[buf_pos + tok.len] = 0\n        argv[argc] = buf[buf_pos .. buf_pos + tok.len :0].ptr\n        argc += 1\n        buf_pos += tok.len + 1\n    }\n\n    if argc < MAX_ARGS {\n        argv[argc] = null\n    }\n\n    if pipe_at |p| {\n        left_argc := p\n        right_argc := argc - p - 1\n        if left_argc == 0 || right_argc == 0 {\n            return .{ .err = .empty_side }\n        }\n        return .{ .piped = .{ .left_argc = left_argc, .right_argc = right_argc } }\n    }\n\n    if argc == 0 {\n        return .empty\n    }\n    return .{ .single = argc }\n}", "/// argv capacity, including the interleaved `null` separators (the pipe\n/// boundary and the trailing terminator). 16 covers a command plus a\n/// generous argument list for demoware; longer lines truncate.\npub const MAX_ARGS: usize = 16;\n\n/// Why the two sides of a `|` cannot both be commands, or why a second\n/// `|` appeared.\npub const Err = enum {\n    too_many_pipes,\n    empty_side,\n};\n\n/// A single-pipe decomposition. The right command's argv begins at\n/// `argv[left_argc + 1]` (the `+ 1` skips the `null` the tokenizer wrote\n/// at the pipe boundary); both vectors are NULL-terminated in place.\npub const Piped = struct {\n    left_argc: usize,\n    right_argc: usize,\n};\n\n/// How a line decomposed.\npub const Result = union(enum) {\n    /// Blank or whitespace-only line — fsh redraws the prompt.\n    empty,\n    /// One command; `argv[0..argc]` valid, `argv[argc] == null`.\n    single: usize,\n    /// One pipe stage; see `Piped`.\n    piped: Piped,\n    /// Malformed pipe usage.\n    err: Err,\n};\n\ninline fn is_space(c: u8) bool {\n    return c == ' ' or c == '\\t' or c == '\\r' or c == '\\n';\n}\n\n/// Split `line` into `argv` (pointers into `buf`). See the module header\n/// for the decomposition rules. `argv` and `buf` are caller-owned and\n/// reused per line; the returned pointers are valid until the next call\n/// that reuses them.\npub fn tokenize(line: []const u8, argv: *[MAX_ARGS]?[*:0]u8, buf: []u8) Result {\n    var argc: usize = 0;\n    var buf_pos: usize = 0;\n    var pipe_at: ?usize = null;\n    var pipes: usize = 0;\n    var i: usize = 0;\n    while (i < line.len) {\n        while (i < line.len and is_space(line[i])) {\n            i += 1;\n        }\n        if (i >= line.len) {\n            break;\n        }\n        if (argc >= MAX_ARGS - 1) {\n            break;\n        }\n        if (line[i] == '|') {\n            pipes += 1;\n            if (pipes > 1) {\n                return .{ .err = .too_many_pipes };\n            }\n            pipe_at = argc;\n            argv[argc] = null;\n            argc += 1;\n            i += 1;\n            continue;\n        }\n        const start = i;\n        while (i < line.len and !is_space(line[i]) and line[i] != '|') {\n            i += 1;\n        }\n        const tok = line[start..i];\n        if (buf_pos + tok.len + 1 > buf.len) {\n            break;\n        }\n        @memcpy(buf[buf_pos..][0..tok.len], tok);\n        buf[buf_pos + tok.len] = 0;\n        argv[argc] = buf[buf_pos .. buf_pos + tok.len :0].ptr;\n        argc += 1;\n        buf_pos += tok.len + 1;\n    }\n    if (argc < MAX_ARGS) {\n        argv[argc] = null;\n    }\n    if (pipe_at) |p| {\n        const left_argc = p;\n        const right_argc = argc - p - 1;\n        if (left_argc == 0 or right_argc == 0) {\n            return .{ .err = .empty_side };\n        }\n        return .{ .piped = .{ .left_argc = left_argc, .right_argc = right_argc } };\n    }\n    if (argc == 0) {\n        return .empty;\n    }\n    return .{ .single = argc };\n}\n")
}

test "mem port: C-ABI mem*/strlen providers — const many-ptr sources, sentinel-const scan" {
    try expectLower("/// memset(dst, c, n) — fill `n` bytes of `dst` with byte `c`. Byte\n/// granular; the C ABI returns `dst`.\nexport fn memset(dst [*]mut u8, c i32, n_in u64) [*]mut u8 {\n    var n = n_in\n    var p = dst\n    const byte u8 = #truncate(#as(u32, #bitCast(c)))\n    while n != 0 {\n        p[0] = byte\n        p += 1\n        n -= 1\n    }\n    return dst\n}\n\n/// memcpy(dst, src, bytes) — copy `bytes` bytes from `src` to `dst`\n/// (non-overlapping). Copies 8 bytes at a time when both operands are\n/// 8-aligned, then drains the tail byte-wise. The C ABI returns `dst`.\nexport fn memcpy(dst *mut anyopaque, src *anyopaque, bytes u64) *mut anyopaque {\n    var d [*]mut u8 = #ptrCast(dst)\n    var s [*]u8 = #ptrCast(src)\n    var n = bytes\n\n    if #intFromPtr(d) % 8 == 0 && #intFromPtr(s) % 8 == 0 {\n        var d64 [*]mut u64 = #ptrCast(#alignCast(d))\n        var s64 [*]u64 = #ptrCast(#alignCast(s))\n        while n >= 8 {\n            d64[0] = s64[0]\n            d64 += 1\n            s64 += 1\n            n -= 8\n        }\n        d = #ptrCast(d64)\n        s = #ptrCast(s64)\n    }\n\n    while n > 0 {\n        d[0] = s[0]\n        d += 1\n        s += 1\n        n -= 1\n    }\n    return dst\n}\n\n/// strlen(s) — length of the NUL-terminated string at `s`, excluding the\n/// terminator. The lone scan the idiom recognizer would otherwise route\n/// to an external `strlen`; defining it here closes the loop.\nexport fn strlen(s [*:0]u8) u64 {\n    var n u64 = 0\n    while s[n] != 0 {\n        n += 1\n    }\n    return n\n}", "/// memset(dst, c, n) — fill `n` bytes of `dst` with byte `c`. Byte\n/// granular; the C ABI returns `dst`.\nexport fn memset(dst: [*]u8, c: i32, n_in: u64) callconv(.c) [*]u8 {\n    var n = n_in;\n    var p = dst;\n    const byte: u8 = @truncate(@as(u32, @bitCast(c)));\n    while (n != 0) {\n        p[0] = byte;\n        p += 1;\n        n -= 1;\n    }\n    return dst;\n}\n\n/// memcpy(dst, src, bytes) — copy `bytes` bytes from `src` to `dst`\n/// (non-overlapping). Copies 8 bytes at a time when both operands are\n/// 8-aligned, then drains the tail byte-wise. The C ABI returns `dst`.\nexport fn memcpy(dst: *anyopaque, src: *const anyopaque, bytes: u64) callconv(.c) *anyopaque {\n    var d: [*]u8 = @ptrCast(dst);\n    var s: [*]const u8 = @ptrCast(src);\n    var n = bytes;\n    if (@intFromPtr(d) % 8 == 0 and @intFromPtr(s) % 8 == 0) {\n        var d64: [*]u64 = @ptrCast(@alignCast(d));\n        var s64: [*]const u64 = @ptrCast(@alignCast(s));\n        while (n >= 8) {\n            d64[0] = s64[0];\n            d64 += 1;\n            s64 += 1;\n            n -= 8;\n        }\n        d = @ptrCast(d64);\n        s = @ptrCast(s64);\n    }\n    while (n > 0) {\n        d[0] = s[0];\n        d += 1;\n        s += 1;\n        n -= 1;\n    }\n    return dst;\n}\n\n/// strlen(s) — length of the NUL-terminated string at `s`, excluding the\n/// terminator. The lone scan the idiom recognizer would otherwise route\n/// to an external `strlen`; defining it here closes the loop.\nexport fn strlen(s: [*:0]const u8) callconv(.c) u64 {\n    var n: u64 = 0;\n    while (s[n] != 0) {\n        n += 1;\n    }\n    return n;\n}\n")
}

test "start port: extern fn prototype, explicit callconv, and a comptime #export block" {
    try expectLower("extern fn main(argc usize, argv argv) callconv(.c) noreturn\n\nfn _start_shim(argc usize, argv argv) callconv(.c) noreturn {\n    main(argc, argv)\n}\n\ncomptime {\n    #export(&_start_shim, .{ .name = \"_start\", .linkage = .strong })\n}", "extern fn main(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn;\n\nfn _start_shim(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn {\n    main(argc, argv);\n}\n\ncomptime {\n    @export(&_start_shim, .{ .name = \"_start\", .linkage = .strong });\n}\n")
}

test "process port: flibc process glue over a sibling-file syscalls import" {
    try expectLower("use \"syscalls\" as sys\n\n/// fork() — clone the current process. Returns the child's pid in the\n/// parent and 0 in the child. -1 on failure (NR_TASKS exhausted,\n/// out-of-memory, etc.).\npub fn fork() i32 {\n    return sys.fork()\n}\n\n/// exit() — terminate the current process. Never returns. The kernel\n/// flips the task to TASK_ZOMBIE; the parent's wait reaps it (frees\n/// every user/kernel page tracked by `mm`).\npub fn exit() noreturn {\n    sys.exit()\n}\n\n/// execve(path, argv) — path-resolved exec on slot 31. `path` is a\n/// NUL-terminated UVA; `argv` points at a NULL-terminated array of\n/// `[*:0]u8`. Returns -1 on failure with the address space untouched.\npub fn execve(path cstr, argv argv) i32 {\n    return sys.exec_path(path, argv)\n}", "const sys = @import(\"syscalls.zig\");\n\n/// fork() — clone the current process. Returns the child's pid in the\n/// parent and 0 in the child. -1 on failure (NR_TASKS exhausted,\n/// out-of-memory, etc.).\npub fn fork() i32 {\n    return sys.fork();\n}\n\n/// exit() — terminate the current process. Never returns. The kernel\n/// flips the task to TASK_ZOMBIE; the parent's wait reaps it (frees\n/// every user/kernel page tracked by `mm`).\npub fn exit() noreturn {\n    sys.exit();\n}\n\n/// execve(path, argv) — path-resolved exec on slot 31. `path` is a\n/// NUL-terminated UVA; `argv` points at a NULL-terminated array of\n/// `[*:0]u8`. Returns -1 on failure with the address space untouched.\npub fn execve(path: [*:0]const u8, argv: [*]const ?[*:0]const u8) i32 {\n    return sys.exec_path(path, argv);\n}\n")
}

test "heap port: bump allocator — unary `~` alignment mask, optional many-ptr, empty no-op body" {
    try expectLower("use \"syscalls\" as sys\n\nconst ALIGN u64 = 8\n\n/// malloc(n) — return a pointer to a freshly-allocated region of at\n/// least `n` bytes (rounded up to 8). Returns null on failure\n/// (kernel rejects out-of-bounds break, propagated as a negative sbrk\n/// return). The memory is zeroed by the kernel's get_free_page on first\n/// touch via the do_data_abort demand-alloc path.\n///\n/// C `malloc(0)` is implementation-defined; flibc returns null.\n/// Callers must distinguish `len == 0` themselves before treating\n/// null as failure.\npub fn malloc(n u64) ?[*]mut u8 {\n    if n == 0 { return null }\n    const aligned u64 = (n + ALIGN - 1) & ~(ALIGN - 1)\n    const prev = sys.sbrk(#intCast(aligned))\n    if prev < 0 { return null }\n    return #ptrFromInt(#as(u64, #bitCast(prev)))\n}\n\n/// free — no-op. The bump allocator never reclaims individual\n/// allocations; the kernel reaps the entire heap on process exit\n/// (do_wait clears every page in `mm.user_pages`). Provided so consumers\n/// can keep the alloc/free pairing readable even though the call is\n/// inert.\npub fn free(_ ?[*]mut u8) {}", "const sys = @import(\"syscalls.zig\");\n\nconst ALIGN: u64 = 8;\n\n/// malloc(n) — return a pointer to a freshly-allocated region of at\n/// least `n` bytes (rounded up to 8). Returns null on failure\n/// (kernel rejects out-of-bounds break, propagated as a negative sbrk\n/// return). The memory is zeroed by the kernel's get_free_page on first\n/// touch via the do_data_abort demand-alloc path.\n///\n/// C `malloc(0)` is implementation-defined; flibc returns null.\n/// Callers must distinguish `len == 0` themselves before treating\n/// null as failure.\npub fn malloc(n: u64) ?[*]u8 {\n    if (n == 0) {\n        return null;\n    }\n    const aligned: u64 = (n + ALIGN - 1) & ~(ALIGN - 1);\n    const prev = sys.sbrk(@intCast(aligned));\n    if (prev < 0) {\n        return null;\n    }\n    return @ptrFromInt(@as(u64, @bitCast(prev)));\n}\n\n/// free — no-op. The bump allocator never reclaims individual\n/// allocations; the kernel reaps the entire heap on process exit\n/// (do_wait clears every page in `mm.user_pages`). Provided so consumers\n/// can keep the alloc/free pairing readable even though the call is\n/// inert.\npub fn free(_: ?[*]u8) void {}\n")
}

test "flibc port: `pub use` re-exports lower to `pub const … = @import`, interleaved with value re-exports" {
    try expectLower("pub use \"syscalls\" as sys\npub use \"io\" as io\nuse syscall_defs as defs\npub const Dirent = defs.Dirent\npub const printf = io.printf\npub use \"heap\" as heap\npub const malloc = heap.malloc", "pub const sys = @import(\"syscalls.zig\");\npub const io = @import(\"io.zig\");\nconst defs = @import(\"syscall_defs\");\n\npub const Dirent = defs.Dirent;\n\npub const printf = io.printf;\n\npub const heap = @import(\"heap.zig\");\n\npub const malloc = heap.malloc;\n")
}

test "execvp port: sentinel-slice return type and the if-expression driver select" {
    try expectLower("use builtin\n\nconst has_driver = builtin.cpu.arch == .aarch64 && builtin.target.os.tag == .freestanding\n\npub fn resolve(name []u8, out []mut u8) ?[:0]mut u8 {\n    if name.len == 0 { return null }\n    out[name.len] = 0\n    return out[0..name.len :0]\n}\n\npub const execvp = driver.execvp\n\nconst driver = if (has_driver) struct {\n    use \"syscalls\" as sys\n\n    pub fn execvp(name cstr, argv argv) i32 {\n        return sys.exec_path(name, argv)\n    }\n} else struct {\n    pub fn execvp(_ cstr, _ argv) i32 {\n        return -1\n    }\n}", "const builtin = @import(\"builtin\");\n\nconst has_driver = builtin.cpu.arch == .aarch64 and builtin.target.os.tag == .freestanding;\n\npub fn resolve(name: []const u8, out: []u8) ?[:0]u8 {\n    if (name.len == 0) {\n        return null;\n    }\n    out[name.len] = 0;\n    return out[0..name.len :0];\n}\n\npub const execvp = driver.execvp;\n\nconst driver = if (has_driver) struct {\n    const sys = @import(\"syscalls.zig\");\n\n    pub fn execvp(name: [*:0]const u8, argv: [*]const ?[*:0]const u8) i32 {\n        return sys.exec_path(name, argv);\n    }\n} else struct {\n    pub fn execvp(_: [*:0]const u8, _: [*]const ?[*:0]const u8) i32 {\n        return -1;\n    }\n};\n")
}

test "io port: comptime-format printf — comptime params/vars, inline while, +%/++/[_]u8 literal" {
    try expectLower("pub fn printf(comptime fmt []u8, args anytype) {\n    comptime var i usize = 0\n    inline while i < fmt.len {\n        const c = fmt[i]\n        const m = c +% 1\n        emit(m, args[i])\n        i += 1\n    }\n}\n\ninline fn emit(comptime spec u8, arg anytype) {\n    switch spec {\n        'x' => put(arg),\n        else => #compileError(\"bad %\" ++ &[_]u8{spec}),\n    }\n}", "pub fn printf(comptime fmt: []const u8, args: anytype) void {\n    comptime var i: usize = 0;\n    inline while (i < fmt.len) {\n        const c = fmt[i];\n        const m = c +% 1;\n        emit(m, args[i]);\n        i += 1;\n    }\n}\n\ninline fn emit(comptime spec: u8, arg: anytype) void {\n    switch (spec) {\n        'x' => put(arg),\n        else => @compileError(\"bad %\" ++ &[_]u8{spec}),\n    }\n}\n")
}

test "keys port: VT100 decoder — switch ranges, multi-pattern and labeled-block prongs, driver select" {
    try expectLower("const has_driver = builtin.cpu.arch == .aarch64 && builtin.target.os.tag == .freestanding\n\npub const Decoder = struct {\n    state State = .ground,\n\n    const State = enum { ground, esc, csi }\n\n    fn atGround(self *mut Decoder, b u8) Event {\n        return switch b {\n            0x1b => blk: {\n                self.state = .esc\n                break :blk .{ .key = .none }\n            },\n            '\\r', '\\n' => .{ .key = .enter },\n            0x20...0x7e => .{ .key = .char, .ch = b },\n            else => .{ .key = .none },\n        }\n    }\n}\n\nconst driver = if (has_driver) struct {\n    pub fn readKey() Event {\n        var b u8 = 0\n        if (b >= '0' && b <= '9') || b == 0 {\n            return .{ .key = .eof }\n        }\n        return .{ .key = .none }\n    }\n} else struct {\n    pub fn readKey() Event {\n        return .{ .key = .eof }\n    }\n}", "const has_driver = builtin.cpu.arch == .aarch64 and builtin.target.os.tag == .freestanding;\n\npub const Decoder = struct {\n    state: State = .ground,\n\n    const State = enum {\n        ground,\n        esc,\n        csi,\n    };\n\n    fn atGround(self: *Decoder, b: u8) Event {\n        return switch (b) {\n            0x1b => blk: {\n                self.state = .esc;\n                break :blk .{ .key = .none };\n            },\n            '\\r', '\\n' => .{ .key = .enter },\n            0x20...0x7e => .{ .key = .char, .ch = b },\n            else => .{ .key = .none },\n        };\n    }\n};\n\nconst driver = if (has_driver) struct {\n    pub fn readKey() Event {\n        var b: u8 = 0;\n        if ((b >= '0' and b <= '9') or b == 0) {\n            return .{ .key = .eof };\n        }\n        return .{ .key = .none };\n    }\n} else struct {\n    pub fn readKey() Event {\n        return .{ .key = .eof };\n    }\n};\n")
}

test "completion port: parenthesised value-if condition, optional-capture, range-for" {
    try expectLower("pub fn classify(count usize, best usize, typed usize) Tab {\n    if count == 0 {\n        return .empty\n    }\n    return if (best > typed) .progressed else .stuck\n}\n\npub fn split(line []u8) ?usize {\n    var slash ?usize = null\n    for i in 0..line.len {\n        if line[i] == '/' {\n            slash = i\n        }\n    }\n    if slash |s| {\n        return s\n    }\n    return null\n}", "pub fn classify(count: usize, best: usize, typed: usize) Tab {\n    if (count == 0) {\n        return .empty;\n    }\n    return if (best > typed) .progressed else .stuck;\n}\n\npub fn split(line: []const u8) ?usize {\n    var slash: ?usize = null;\n    for (0..line.len) |i| {\n        if (line[i] == '/') {\n            slash = i;\n        }\n    }\n    if (slash) |s| {\n        return s;\n    }\n    return null;\n}\n")
}

test "pager port: value+pointer receivers, void mutator, #intCast, const-default slice fields" {
    try expectLower("pub const Pager = struct {\n    text []u8,\n    lines []mut u32,\n    n usize,\n    top usize,\n    rows usize,\n\n    pub fn init(text []u8, slots []mut u32, rows usize) Pager {\n        var n usize = 0\n        if text.len > 0 && slots.len > 0 {\n            slots[0] = 0\n            n = 1\n            for i in 0..text.len {\n                if text[i] == '\\n' && i + 1 < text.len {\n                    if n >= slots.len {\n                        break\n                    }\n                    slots[n] = #intCast(i + 1)\n                    n += 1\n                }\n            }\n        }\n        return .{ .text = text, .lines = slots, .n = n, .top = 0, .rows = rows }\n    }\n\n    pub fn maxTop(self Pager) usize {\n        return if (self.n > self.rows) self.n - self.rows else 0\n    }\n\n    pub fn down(self *mut Pager, k usize) {\n        const mt = self.maxTop()\n        self.top = if (self.top + k > mt) mt else self.top + k\n    }\n}", "pub const Pager = struct {\n    text: []const u8,\n    lines: []u32,\n    n: usize,\n    top: usize,\n    rows: usize,\n\n    pub fn init(text: []const u8, slots: []u32, rows: usize) Pager {\n        var n: usize = 0;\n        if (text.len > 0 and slots.len > 0) {\n            slots[0] = 0;\n            n = 1;\n            for (0..text.len) |i| {\n                if (text[i] == '\\n' and i + 1 < text.len) {\n                    if (n >= slots.len) {\n                        break;\n                    }\n                    slots[n] = @intCast(i + 1);\n                    n += 1;\n                }\n            }\n        }\n        return .{ .text = text, .lines = slots, .n = n, .top = 0, .rows = rows };\n    }\n\n    pub fn maxTop(self: Pager) usize {\n        return if (self.n > self.rows) self.n - self.rows else 0;\n    }\n\n    pub fn down(self: *Pager, k: usize) void {\n        const mt = self.maxTop();\n        self.top = if (self.top + k > mt) mt else self.top + k;\n    }\n};\n")
}

test "reserved value keywords lower verbatim — byte-identical to zig fmt" {
    try expectLower("const yes = true\nconst no = false\nconst nothing = null\nfn halt() noreturn {\n    unreachable\n}\nvar seed u32 = undefined", "const yes = true;\n\nconst no = false;\n\nconst nothing = null;\n\nfn halt() noreturn {\n    unreachable;\n}\n\nvar seed: u32 = undefined;\n")
}

test "composite-type aliases lower to Zig type aliases — byte-identical to zig fmt" {
    try expectLower("const F = *fn(u8) u8\nconst O = ?u8\nconst S = []u8\nconst M = *mut fn() void\nfn take(g Get([]u8)) void {\n    _ = g\n}", "const F = *const fn (u8) u8;\n\nconst O = ?u8;\n\nconst S = []const u8;\n\nconst M = *fn () void;\n\nfn take(g: Get([]const u8)) void {\n    _ = g;\n}\n")
}

test "test blocks lower to Zig test blocks — byte-identical to zig fmt" {
    try expectLower("use std\n\nfn add(a i32, b i32) i32 {\n    return a + b\n}\n\ntest \"add sums two integers\" {\n    try std.testing.expectEqual(5, add(2, 3))\n}\n\ntest \"empty body is accepted\" {}", "const std = @import(\"std\");\n\nfn add(a: i32, b: i32) i32 {\n    return a + b;\n}\n\ntest \"add sums two integers\" {\n    try std.testing.expectEqual(5, add(2, 3));\n}\n\ntest \"empty body is accepted\" {}\n")
}

test "labeled loops lower to Zig label prefixes — byte-identical to zig fmt" {
    // The sched.zig shape: a labeled while broken out of from a nested loop,
    // plus a labelled continue on a for, plus the label-before-inline order.
    try expectLower("fn f(xs []u8) usize {\n    outer: while true {\n        for x, i in xs {\n            if x == 0 {\n                break :outer\n            }\n            _ = i\n        }\n    }\n    return 0\n}", "fn f(xs: []const u8) usize {\n    outer: while (true) {\n        for (xs, 0..) |x, i| {\n            if (x == 0) {\n                break :outer;\n            }\n            _ = i;\n        }\n    }\n    return 0;\n}\n")
    try expectLower("fn g(xs []u8) void {\n    scan: for x in xs {\n        if x == 0 {\n            continue :scan\n        }\n        _ = x\n    }\n}", "fn g(xs: []const u8) void {\n    scan: for (xs) |x| {\n        if (x == 0) {\n            continue :scan;\n        }\n        _ = x;\n    }\n}\n")
    try expectLower("fn h() void {\n    un: inline while true {\n        break :un\n    }\n}", "fn h() void {\n    un: inline while (true) {\n        break :un;\n    }\n}\n")
}