// 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 '@' — the cast family // #intCast/#ptrCast/#bitCast/#as/#truncate/#alignCast/ // #intFromPtr/#ptrFromInt each keeps its distinct // semantics; the spelling is the only change) // 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) // if c { … } -> if (c) { … } (else / else-if supported) // while c { … } -> while (c) { … } // while c |x| {…} -> while (c) |x| { … } (optional/error payload capture) // for x in xs {…} -> for (xs) |x| { … } // for i in a..b {} -> for (a..b) |i| { … } (range; `for x, i in xs` indexes) // inline while/for -> inline while/for … (compile-time-unrolled loops) // x += e -> x += e; (= += -= *= /= %= &= |= ^= <<= >>= // pass through) // break / continue -> break; / continue; // return e -> return e; // ?T / !T -> ?T / !T (optional / inferred error-union types) // E!T -> E!T (explicit error union: a named set E) // error.Name -> error.Name (an error-value origination) // error{ A, B } -> error{ A, B } (a named error set; a single member // and the empty set stay tight: error{One} / error{}) // try e -> try e (propagate an error union) // e catch h -> e catch h (`e catch |err| h` with a capture) // if opt |x| {…} -> if (opt) |x| {…} (optional-capture if) // defer s -> defer s; (errdefer s -> errdefer s;) // 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) // .{ .x = 1 } -> .{ .x = 1 } (named struct-init field) // .red -> .red (inferred enum literal) // p.* -> p.* (single-item pointer dereference; // valid as an lvalue, so `p.* = v` stores through it) // a[lo..hi :s] -> a[lo..hi :s] (sentinel-terminated slice; space // before the `:`, as zig fmt lays it out) // const U = \\line -> const U =\n \\line ; (multiline / raw string; // byte-exact in const/binding/discard value position) // // Flash makes braces mandatory, so a single-statement `if`/`while`/`for` still // lowers with braces. That keeps the output valid, idiomatic Zig but means a // control-flow port is a human-diffable equivalent of the hand-written form // rather than byte-identical to it (the straight-line coreutils stay exact). // // 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. const std = @import("std"); const ast = @import("ast.zig"); 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: std.mem.Allocator, program: ast.Program) Error![]const u8 { var e: Emitter = .{ .arena = arena }; const 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 (items) |it| { if (it == .const_decl) { if (std.mem.eql(u8, it.const_decl.name, "argv")) e.argv_shadowed = true; if (std.mem.eql(u8, it.const_decl.name, "cstr")) e.cstr_shadowed = true; } } var i: usize = 0; var first = true; while (i < items.len) { if (!first) try e.raw("\n"); // blank line between units first = false; switch (items[i]) { .use_decl => { while (i < items.len and items[i] == .use_decl) : (i += 1) { try e.emitUseDeclAt(items[i].use_decl); try e.raw("\n"); } }, .link_decl => { try e.raw("comptime {\n"); while (i < items.len and items[i] == .link_decl) : (i += 1) { try e.print(" _ = @import(\"{s}\");\n", .{items[i].link_decl.module}); } try e.raw("}\n"); }, .const_decl => |c| { try e.emitConstDecl(c); i += 1; }, .fn_decl => |f| { try e.emitFn(f); i += 1; }, .comptime_block => |stmts| { // `comptime { … }` at file scope — the body reuses the shared // block layout (statements at depth 1, closing brace at 0). try e.raw("comptime "); try e.emitBlockBody(stmts, 0); try e.raw("\n"); i += 1; }, .test_decl => |t| { // `test "name" { … }` lowers one-to-one to a Zig test block; // the name lexeme (quotes included) re-emits verbatim. try e.print("test {s} ", .{t.name}); try e.emitBlockBody(t.body, 0); try e.raw("\n"); i += 1; }, } } return e.buf.toOwnedSlice(arena); } const Emitter = struct { arena: std.mem.Allocator, buf: std.ArrayList(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, fn raw(self: *Emitter, s: []const u8) Error!void { try self.buf.appendSlice(self.arena, s); } fn print(self: *Emitter, comptime fmt: []const u8, args: anytype) Error!void { try self.buf.print(self.arena, fmt, args); } // 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: *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: *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.print("fn {s}(", .{f.name}); for (f.params, 0..) |p, idx| { 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(")"); // 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: *Emitter, c: ast.ConstDecl) Error!void { try self.emitDoc(c.doc, 0); try self.emitConstDeclAt(c, 0); try self.raw("\n"); } // Emit `const NAME[: T] = 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: *Emitter, c: ast.ConstDecl, depth: usize) Error!void { if (c.is_pub) try self.raw("pub "); 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); } if (c.value == .multiline_str) { try self.raw(" "); try self.emitMultilineRhs(c.value.multiline_str, depth); } else { try self.raw(" = "); try self.emitValue(c.value, depth); 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: *Emitter, u: ast.UseDecl) Error!void { const vis = if (u.is_pub) "pub " else ""; const name = u.alias orelse u.module; if (u.is_file) { try self.print("{s}const {s} = @import(\"{s}{s}\");", .{ vis, name, u.module, backend_ext }); } else { try self.print("{s}const {s} = @import(\"{s}\");", .{ vis, name, u.module }); } } // 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: *Emitter, lines: []const []const u8, depth: usize) Error!void { try self.raw("=\n"); for (lines) |ln| { 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: *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: *Emitter, x: ast.Expr, depth: usize) Error!void { switch (x) { .struct_def => |sd| { // A container with no members at all is zig fmt's one-line // form: `struct {}`. if (sd.fields.len == 0 and sd.decls.len == 0) { try self.raw("struct {}"); return; } try self.raw("struct {\n"); for (sd.fields) |f| { 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 and ed.decls.len == 0) { try self.raw(" {}"); return; } try self.raw(" {\n"); for (ed.variants) |v| { 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 and ud.decls.len == 0) { try self.raw(" {}"); return; } try self.raw(" {\n"); for (ud.variants) |v| { 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: *Emitter, decls: []const ast.ContainerDecl, has_members: bool, depth: usize) Error!void { for (decls, 0..) |d, idx| { if (idx != 0 or 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); }, .use_import => |u| { // No doc (the parser forbids it); a container-level import // lowers exactly like a top-level one, just indented. 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: *Emitter, stmts: []const 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: *Emitter, stmts: []const ast.Stmt, depth: usize) Error!void { for (stmts) |s| { try self.indent(depth); try self.emitStmt(s, depth); try self.raw("\n"); } } fn indent(self: *Emitter, depth: usize) Error!void { var k: usize = 0; while (k < depth) : (k += 1) try self.raw(" "); } // 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: *Emitter, doc: []const []const u8, depth: usize) Error!void { for (doc) |line| { try self.indent(depth); try self.raw("///"); try self.raw(line); try self.raw("\n"); } } fn emitType(self: *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 and std.mem.eql(u8, n, "argv")) { try self.raw("[*]const ?[*:0]const u8"); } else if (!self.cstr_shadowed and std.mem.eql(u8, n, "cstr")) { try self.raw("[*:0]const u8"); } else { try self.raw(n); } }, .slice => |inner| { try self.raw("[]const "); try self.emitType(inner.*); }, .slice_mut => |inner| { try self.raw("[]"); try self.emitType(inner.*); }, .slice_sentinel => |sp| { // `[:s]const T` — the sentinel-terminated slice; const-pointee by // default like a plain `[]T`. The sentinel sits between `:` and `]`. try self.raw("[:"); try self.emitExpr(sp.sentinel.*); try self.raw("]const "); try self.emitType(sp.elem.*); }, .slice_sentinel_mut => |sp| { // `[:s]T` — its mutable form (no `const`), as `[]mut T` is to `[]T`. try self.raw("[:"); try self.emitExpr(sp.sentinel.*); try self.raw("]"); try self.emitType(sp.elem.*); }, .many_ptr => |inner| { // `[*]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. try self.raw("[*]const "); try self.emitType(inner.*); }, .many_ptr_mut => |inner| { // `[*]mut T` — a writable many-item pointer; no `const` in Zig. try self.raw("[*]"); try self.emitType(inner.*); }, .many_ptr_volatile => |inner| { // `[*]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. try self.raw("[*]const volatile "); try self.emitType(inner.*); }, .many_ptr_mut_volatile => |inner| { // `[*]mut volatile T` — the writable+volatile form; no `const`. try self.raw("[*]volatile "); try self.emitType(inner.*); }, .many_ptr_sentinel => |sp| { // `[*: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. try self.raw("[*:"); try self.emitExpr(sp.sentinel.*); try self.raw("]const "); try self.emitType(sp.elem.*); }, .many_ptr_sentinel_mut => |sp| { // `[*:s]mut T` — the writable sentinel form; no `const` in Zig. try self.raw("[*:"); try self.emitExpr(sp.sentinel.*); try self.raw("]"); try self.emitType(sp.elem.*); }, .ptr => |inner| { // `*T` is const-pointee by default, like `[]T` is a const slice. try self.raw("*const "); try self.emitType(inner.*); }, .ptr_mut => |inner| { try self.raw("*"); try self.emitType(inner.*); }, .ptr_volatile => |inner| { // `*volatile T` — const+volatile pointee, const by default; the // implicit `const` renders before `volatile`, as zig fmt lays it out. try self.raw("*const volatile "); try self.emitType(inner.*); }, .ptr_mut_volatile => |inner| { // `*mut volatile T` — the writable+volatile form; no `const`. try self.raw("*volatile "); try self.emitType(inner.*); }, .array => |arr| { try self.raw("["); try self.emitExpr(arr.len.*); try self.raw("]"); try self.emitType(arr.elem.*); }, .array_sentinel => |a| { // `[N:s]T` — a fixed-length sentinel-terminated array; length and // sentinel render verbatim, no spaces around the `:`, as zig fmt // lays it out. try self.raw("["); try self.emitExpr(a.len.*); try self.raw(":"); try self.emitExpr(a.sentinel.*); try self.raw("]"); try self.emitType(a.elem.*); }, .array_inferred => |elem| { // `[_]T` — the inferred-length array; the `_` lowers verbatim. try self.raw("[_]"); try self.emitType(elem.*); }, .array_inferred_sentinel => |sp| { // `[_:s]T` — the inferred-length sentinel array (`[_:0]u8{ … }`). 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.*); }, .errunion => |eu| { // `E!T` names the error set, `!T` (set == null) infers it. The // set renders before the `!`, the payload after — both verbatim. if (eu.set) |s| try self.emitType(s.*); try self.raw("!"); try self.emitType(eu.payload.*); }, .fn_type => |ft| { // `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. try self.raw("fn ("); for (ft.params, 0..) |p, idx| { if (idx != 0) try self.raw(", "); try self.emitType(p); } try self.raw(") "); if (ft.ret) |r| try self.emitType(r.*) else try self.raw("void"); }, .generic => |g| { // `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. try self.raw(g.name); try self.raw("("); for (g.args, 0..) |arg, idx| { if (idx != 0) try self.raw(", "); try self.emitExpr(arg); } try self.raw(")"); }, .tuple => |elems| { // `(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 }`. try self.raw("struct { "); for (elems, 0..) |e, idx| { if (idx != 0) try self.raw(", "); try self.emitType(e); } try self.raw(" }"); }, } } fn emitStmt(self: *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(";"); } }, .bind => |b| { // 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. const force_value_comptime = b.is_comptime and !b.is_mut; if (b.is_comptime and 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); } // `align(expr)` sits after the type, before `=` (Zig order). if (b.align_expr) |ae| { 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(";"); } }, .assign => |a| { try self.emitExprAt(a.target, depth); try self.raw(" "); try self.raw(a.op); // "=", "+=", … are spelled the same in Zig 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 (d.names, 0..) |maybe, i| { 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 (da.targets, 0..) |t, i| { 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); }, .errdefer_stmt => |inner| { try self.raw("errdefer "); try self.emitStmt(inner.*, 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 => |stmts| { try self.raw("errdefer "); try self.emitBlockBody(stmts, depth); }, .while_stmt => |w| { if (w.is_inline) try self.raw("inline "); try self.raw("while ("); try self.emitExprAt(w.cond, depth); try self.raw(")"); // `|x|` — the optional / error payload capture: `while (e) |x| {…}`. if (w.capture) |cap| { try self.raw(" |"); try self.raw(cap); try self.raw("|"); } try self.raw(" "); try self.emitBlockBody(w.body, depth); // The loop else arm — ` else { … }`, the error capture rendered // as ` else |err| { … }`. if (w.else_body) |eb| { try self.raw(" else "); if (w.else_capture) |cap| { try self.raw("|"); try self.raw(cap); try self.raw("| "); } try self.emitBlockBody(eb, depth); } }, .for_stmt => |fr| { if (fr.is_inline) try self.raw("inline "); try self.raw("for ("); try self.emitExprAt(fr.iter, depth); // A range iterator prints its `..hi` bound; a second capture // adds the implicit index range `0..` as a parallel input. 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 (fr.captures, 0..) |c, i| { if (i != 0) try self.raw(", "); try self.raw(c); } try self.raw("| "); try self.emitBlockBody(fr.body, depth); // The loop else arm — runs when the iteration completes // without `break`; capture-less. if (fr.else_body) |eb| { try self.raw(" else "); try self.emitBlockBody(eb, depth); } }, .expr => |x| { try self.emitExprAt(x, 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. if (!isBlockFormStmt(x)) 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, }; } // `if (cond) { … }`, with an `else { … }` arm or, when the else body is // exactly one nested if, an idiomatic `else if (…) { … }` chain. fn emitIf(self: *Emitter, iff: ast.If, depth: usize) Error!void { try self.raw("if ("); try self.emitExprAt(iff.cond, depth); try self.raw(")"); // An optional-capture if renders the payload binding: `if (opt) |x| { … }`. if (iff.capture) |cap| { 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 and eb[0] == .if_stmt) { try self.raw(" else "); try self.emitIf(eb[0].if_stmt, depth); } else { // ` else { … }`, the error capture rendered as ` else |err| { … }`. 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: *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: *Emitter, x: ast.Expr, depth: usize) Error!void { switch (x) { .int, .float, .string, .char, .ident, .value_word => |s| try self.raw(s), .multiline_str => |lines| { // 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. try self.raw("\n"); for (lines) |ln| { 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); }, .deref => |d| { // `p.*` — a single-item pointer dereference, spelled identically // in Zig; valid in value and lvalue (assignment target) position. try self.emitExprAt(d.*, depth); try self.raw(".*"); }, .optional_unwrap => |u| { // `opt.?` — optional unwrap (assert non-null), spelled identically // in Zig; sits in the same postfix slot as `.*`. 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("]"); }, .slice => |s| { try self.emitExprAt(s.base.*, depth); try self.raw("["); try self.emitExprAt(s.lo.*, depth); // 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 `..`. const spaced = sliceBoundSpaces(s.lo.*) or (s.hi != null and 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); } // `[lo..hi :s]` — zig fmt puts a space before the sentinel `:` // and none after; an open-ended `[lo.. :s]` keeps the same form. if (s.sentinel) |sen| { try self.raw(" :"); try self.emitExprAt(sen.*, depth); } try self.raw("]"); }, .builtin_call => |b| { // The AST holds the bare intrinsic name (the Flash '#' sigil is // stripped in the parser); Tier 0 emits Zig's '@'-prefixed form. 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); }, .struct_lit => |fields| { // 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. const spaced = !(fields.len == 0 or (fields.len == 1 and fields[0].name == null)); try self.raw(if (spaced) ".{ " else ".{"); for (fields, 0..) |f, idx| { 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); const spaced = !(tl.fields.len == 0 or (tl.fields.len == 1 and tl.fields[0].name == null)); try self.raw(if (spaced) "{ " else "{"); for (tl.fields, 0..) |f, idx| { 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| { const spaced = names.len > 1; try self.raw(if (spaced) "error{ " else "error{"); for (names, 0..) |n, idx| { 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. .switch_expr => |sw| { try self.raw("switch ("); try self.emitExprAt(sw.subject.*, depth); try self.raw(") {\n"); for (sw.prongs) |prong| { try self.indent(depth + 1); if (prong.is_else) { try self.raw("else"); } else { for (prong.patterns, 0..) |p, idx| { if (idx != 0) try self.raw(", "); try self.emitExprAt(p.lo, depth + 1); // An inclusive range `lo...hi` — no spaces, as zig fmt. if (p.hi) |hi| { try self.raw("..."); try self.emitExprAt(hi, depth + 1); } } } try self.raw(" => "); // A `=> |x|` payload capture renders before the body. if (prong.capture) |cap| { 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); } }, .cont => try self.raw("continue"), .ret => |maybe| { try self.raw("return"); if (maybe) |vals| { try self.raw(" "); if (vals.len == 1) { try self.emitExprAt(vals[0], depth); } else { // `return a, b` — the multi-return sugar: the value // list lowers to one anonymous tuple literal, // `return .{ a, b };`, zig fmt's one-line layout. try self.raw(".{ "); for (vals, 0..) |v, idx| { if (idx != 0) try self.raw(", "); try self.emitExprAt(v, depth); } try self.raw(" }"); } } }, } } fn emitArgs(self: *Emitter, args: []const ast.Expr, depth: usize) Error!void { try self.raw("("); for (args, 0..) |a, idx| { 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: *Emitter, a: ast.AsmExpr, depth: usize) Error!void { try self.raw("asm "); if (a.is_volatile) try self.raw("volatile "); try self.raw("("); const ml_template = a.template.* == .multiline_str; const multiline = ml_template or a.outputs.len > 0 or 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 (a.template.*.multiline_str) |ln| { 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. const n_sections: usize = if (a.clobbers != null) 3 else if (a.inputs.len > 0) 2 else if (a.outputs.len > 0) 1 else 0; 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: *Emitter, ops: []const ast.AsmOperand, depth: usize) Error!void { if (ops.len == 0) { try self.raw("\n"); return; } for (ops, 0..) |op, idx| { 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: *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 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: []const u8) []const u8 { if (std.mem.eql(u8, op, "&&")) return "and"; if (std.mem.eql(u8, op, "||")) return "or"; return op; } // --- tests --------------------------------------------------------------- const testing = std.testing; const Parser = @import("parser.zig").Parser; fn lowerSrc(arena: std.mem.Allocator, src: []const u8) ![]const u8 { var p = Parser.init(arena, src); const program = try p.parseProgram(); return emit(arena, program); } test "hello: bind, discard, call lower to diffable Zig" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\use flibc \\ \\link "flibc_start" \\link "flibc_mem" \\ \\export fn main(_ usize, _ argv) noreturn { \\ msg := "hello from flash\n" \\ _ = flibc.sys.write_fd(1, msg.ptr, msg.len) \\ flibc.exit() \\} ); const want = \\const flibc = @import("flibc"); \\ \\comptime { \\ _ = @import("flibc_start"); \\ _ = @import("flibc_mem"); \\} \\ \\export fn main(_: usize, _: [*]const ?[*:0]const u8) callconv(.c) noreturn { \\ const msg = "hello from flash\n"; \\ _ = flibc.sys.write_fd(1, msg.ptr, msg.len); \\ flibc.exit(); \\} \\ ; try testing.expectEqualStrings(want, got); } test "clear: cross-import, aliasless void fn, const slice param" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\use flibc \\use console_ui \\ \\link "flibc_start" \\link "flibc_mem" \\ \\fn sink(bytes []u8) { \\ _ = flibc.sys.write_fd(1, bytes.ptr, bytes.len) \\} \\ \\export fn main(_ usize, _ argv) noreturn { \\ console_ui.screen.clear(sink) \\ flibc.exit() \\} ); const want = \\const flibc = @import("flibc"); \\const console_ui = @import("console_ui"); \\ \\comptime { \\ _ = @import("flibc_start"); \\ _ = @import("flibc_mem"); \\} \\ \\fn sink(bytes: []const u8) void { \\ _ = flibc.sys.write_fd(1, bytes.ptr, bytes.len); \\} \\ \\export fn main(_: usize, _: [*]const ?[*:0]const u8) callconv(.c) noreturn { \\ console_ui.screen.clear(sink); \\ flibc.exit(); \\} \\ ; try testing.expectEqualStrings(want, got); } test "use alias and opt-in mutable slice" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\use console_ui as ui \\ \\fn paint(buf []mut u8) { \\ ui.fill(buf) \\} ); const want = \\const ui = @import("console_ui"); \\ \\fn paint(buf: []u8) void { \\ ui.fill(buf); \\} \\ ; try testing.expectEqualStrings(want, got); } test "single-item pointer types: *T is const-pointee, *mut T is mutable" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn pass(p *u32, q *mut u32, rows *[4]u8) *mut u32 { \\ return q \\} ); // `*T` lowers const-pointee (`*const T`), parallel to `[]T` being a const // slice; `*mut T` is the mutable opt-in (`*T`). The pointer prefix composes // over any element type, including a pointer-to-array (`*[4]u8`). const want = \\fn pass(p: *const u32, q: *u32, rows: *const [4]u8) *u32 { \\ return q; \\} \\ ; try testing.expectEqualStrings(want, got); } test "function types: `fn(P) R` lowers to `fn (P) R`, and `*` over it is the const pointer" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn install(cb *fn(usize) void, opt ?*fn(i32, u8) bool, raw *mut fn() void) *fn() u8 { \\ return undefined \\} ); // A `fn(P, …) R` is a function *type*; zig fmt writes the anonymous form // with a space after `fn` (unlike a named `fn name(`), and a missing return // is `void`. The pointer-ness is the surrounding `*` — `*fn(…)` reuses the // const-by-default single-item pointer (`*const fn (…)`), `*mut fn(…)` is the // mutable opt-in (`*fn (…)`), and `?` / the parameter types compose as on any // other type. Byte-identical to `zig fmt`. const want = \\fn install(cb: *const fn (usize) void, opt: ?*const fn (i32, u8) bool, raw: *fn () void) *const fn () u8 { \\ return undefined; \\} \\ ; try testing.expectEqualStrings(want, got); } test "function pointer v-table: the explicit-allocator interface shape lowers byte-for-byte" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const Allocator = struct { \\ ptr *mut anyopaque, \\ vtable *VTable, \\} \\ \\const VTable = struct { \\ alloc *fn(*mut anyopaque, usize) ?[*]mut u8, \\ free *fn(*mut anyopaque, []mut u8) void, \\} ); // The no-GC keystone: an allocator is a `{ ptr, *VTable }` pair and the // v-table is a struct of function pointers — the dynamic-dispatch substrate // expressed with no language magic, the shape Zig's `std.mem.Allocator` // uses. The `*fn(…) R` fields lower to `*const fn (…) R`; this output is // `zig ast-check`-clean and fmt-idempotent. const want = \\const Allocator = struct { \\ ptr: *anyopaque, \\ vtable: *const VTable, \\}; \\ \\const VTable = struct { \\ alloc: *const fn (*anyopaque, usize) ?[*]u8, \\ free: *const fn (*anyopaque, []u8) void, \\}; \\ ; try testing.expectEqualStrings(want, got); } test "error model: origination, named sets, and infix unions lower byte-for-byte" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const AllocError = error{ OutOfMemory, Overflow } \\const One = error{Bad} \\ \\fn dup(path cstr) AllocError!i32 { \\ return error.OutOfMemory \\} \\ \\fn run() !void { \\ return \\} \\ \\const VTable = struct { \\ alloc *fn(usize) AllocError![]u8, \\} ); // The full error model. A named set lowers `error{ A, B }` (zig fmt spaces // two-plus members; `error{Bad}` stays tight). `error.Name` is an // origination, spelled identically. The infix `E!T` names the set // (`AllocError!i32`); the prefix `!T` keeps the inferred set, valid on a // function-DECL return (`!void`). The v-table field carries an explicit-set // fn-pointer return (`AllocError![]const u8`). Byte-identical to `zig fmt`, // `zig ast-check`-clean. const want = \\const AllocError = error{ OutOfMemory, Overflow }; \\ \\const One = error{Bad}; \\ \\fn dup(path: [*:0]const u8) AllocError!i32 { \\ return error.OutOfMemory; \\} \\ \\fn run() !void { \\ return; \\} \\ \\const VTable = struct { \\ alloc: *const fn (usize) AllocError![]const u8, \\}; \\ ; try testing.expectEqualStrings(want, got); } test "sentinel pointer types: [*:0]T composes under *, [N], and ?" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn parse(args *[4]?[*:0]u8, p [*:0]u8) [*:0]u8 { \\ return p \\} ); // `[*:s]T` is a sentinel-terminated many-item pointer; the terminator expr // is kept verbatim. Like `[*]T` the element is const-pointee by default, so // the bare `[*:0]u8` lowers to `[*:0]const u8`; the prefix nests under `*` / // `[N]` / `?` (here `*const [4]?[*:0]const u8`). This output is byte-identical // to `zig fmt` and passes `zig ast-check`. const want = \\fn parse(args: *const [4]?[*:0]const u8, p: [*:0]const u8) [*:0]const u8 { \\ return p; \\} \\ ; try testing.expectEqualStrings(want, got); } test "volatile many-item pointer: [*]volatile T is const+volatile, [*]mut volatile T writable" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn copy(dst []mut u8, src []u8) { \\ const d [*]mut volatile u8 = #ptrCast(dst.ptr) \\ d[0] = src[0] \\ const s [*]volatile u8 = #ptrCast(src.ptr) \\ _ = s[0] \\} ); // A volatile pointee is const by default like every pointer family: `[*]volatile T` // is const+volatile (an MMIO read or a read-only mapping), and `[*]mut volatile T` // is the writable form (the byte-copy destination here). The `volatile` qualifier // sits after the implicit `const`, both spaced from the element type as zig fmt lays // it out. Writing through the `mut` form and only reading through the const form keeps // the output `zig ast-check`-clean; it is byte-identical to `zig fmt`. const want = \\fn copy(dst: []u8, src: []const u8) void { \\ const d: [*]volatile u8 = @ptrCast(dst.ptr); \\ d[0] = src[0]; \\ const s: [*]const volatile u8 = @ptrCast(src.ptr); \\ _ = s[0]; \\} \\ ; try testing.expectEqualStrings(want, got); } test "volatile single-item pointer: *volatile T is const+volatile, *mut volatile T writable" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn mmio(reg *mut volatile u32, status *volatile u32) u32 { \\ reg.* = 1 \\ return status.* \\} ); // The single-item volatile forms mirror the many-item ones: `*volatile T` is // const+volatile (a read-only register), `*mut volatile T` writable (the // configuration register written here). This is the canonical MMIO shape — // a store through the writable register, a load through the read-only one. // The `const` is implicit before `volatile`. Byte-identical to `zig fmt`, // `zig ast-check`-clean (the store needs the mutable pointee). const want = \\fn mmio(reg: *volatile u32, status: *const volatile u32) u32 { \\ reg.* = 1; \\ return status.*; \\} \\ ; try testing.expectEqualStrings(want, got); } test "pointer dereference: p.* reads and stores through a single-item pointer" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn store(p *mut u32, v u32) { \\ p.* = v \\ _ = p.* \\} ); // `p.*` is a single-item pointer dereference; it lowers verbatim and is a // valid lvalue, so `p.* = v` stores through the pointer while `_ = p.*` // reads it. The mutable pointee (`*mut u32` -> `*u32`) is what the store // needs. This output is byte-identical to `zig fmt` and passes `zig // ast-check` (both params used). const want = \\fn store(p: *u32, v: u32) void { \\ p.* = v; \\ _ = p.*; \\} \\ ; try testing.expectEqualStrings(want, got); } test "sentinel slice: a[lo..hi :s] lowers byte-for-byte with zig fmt's space before the colon" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn span(buf []mut u8, pos usize, n usize) [*:0]mut u8 { \\ return buf[pos..n :0].ptr \\} ); // `a[lo..hi :s]` is a sentinel-terminated slice; zig fmt puts a space before // the `:` and none after, with no spaces around `..`. The `.ptr` postfix is // the real use — a nul-terminated pointer out of a buffer. This output is // byte-identical to `zig fmt` and passes `zig ast-check`. const want = \\fn span(buf: []u8, pos: usize, n: usize) [*:0]u8 { \\ return buf[pos..n :0].ptr; \\} \\ ; try testing.expectEqualStrings(want, got); } test "sentinel array: [N:s]T lowers verbatim, no spaces around the colon" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn first(buf [4:0]u8) u8 { \\ return buf[0] \\} ); // `[N:s]T` is a fixed-length array with a trailing sentinel — `[4:0]u8` is // four bytes plus a guaranteed `0` at index 4. Length and sentinel render // verbatim with no spaces around the `:`, exactly as zig fmt lays it out // (unlike the sentinel *slice* `[lo..hi :s]`, which is spaced before the `:`). // This output is byte-identical to `zig fmt` and passes `zig ast-check`. const want = \\fn first(buf: [4:0]u8) u8 { \\ return buf[0]; \\} \\ ; try testing.expectEqualStrings(want, got); } test "inferred sentinel array: [_:s]T{ … } lowers as the argv-style null-terminated vector" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn run() { \\ const argv = [_:null]?[*:0]u8{ "sh" } \\ _ = argv \\} ); // `[_:s]T` is the inferred-length sentinel array — its length comes from the // initializer, the sentinel from the `:`. The canonical use is the // null-terminated argument vector an exec call wants: `[_:null]?[*:0]u8` // lowers the const-default many-item element to `?[*:0]const u8`. A single // positional element renders unspaced (`{"sh"}`), as zig fmt does. This // output is byte-identical to `zig fmt` and passes `zig ast-check`. const want = \\fn run() void { \\ const argv = [_:null]?[*:0]const u8{"sh"}; \\ _ = argv; \\} \\ ; try testing.expectEqualStrings(want, got); } test "generic type application: Name(args) lowers verbatim in type position" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn build(a List(u8), b Map(Key, Val)) List(Ring(8)) { \\ _ = a \\ _ = b \\ return undefined \\} ); // A generic instance named in type position — a parameter (`List(u8)`, // multi-arg `Map(Key, Val)`), a return (nested `List(Ring(8))` with a value // argument `8`) — lowers as the call zig reads it; the arguments are parsed // exactly as a value-position call's. This output is byte-identical to `zig // fmt`. A declared generic (`Box(u8)` over `fn Box(comptime T type) type`) // is `zig ast-check`-clean through the full pipeline. const want = \\fn build(a: List(u8), b: Map(Key, Val)) List(Ring(8)) { \\ _ = a; \\ _ = b; \\ return undefined; \\} \\ ; try testing.expectEqualStrings(want, got); } test "slice bound spacing: zig fmt spaces `..` only when a bound is a binary op" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // zig fmt renders `a[lo..hi]` tight when both bounds are simple (idents, // literals, member access, indexing), and spaces the `..` to `a[lo .. hi]` // when either bound is a binary operation. An open-ended `a[lo + 1 ..]` keeps // the space only before `..`. The sentinel `:s` form follows the same rule. // This output is byte-identical to `zig fmt` and passes `zig ast-check`. const got = try lowerSrc(a.allocator(), \\fn slices(buf []mut u8, lo usize, hi usize) { \\ _ = buf[lo..hi] \\ _ = buf[lo .. hi + 1] \\ _ = buf[lo + 1 .. hi] \\ _ = buf[lo + 1 ..] \\ _ = buf[lo..][0..hi] \\ _ = buf[lo .. hi + 1 :0] \\} ); const want = \\fn slices(buf: []u8, lo: usize, hi: usize) void { \\ _ = buf[lo..hi]; \\ _ = buf[lo .. hi + 1]; \\ _ = buf[lo + 1 .. hi]; \\ _ = buf[lo + 1 ..]; \\ _ = buf[lo..][0..hi]; \\ _ = buf[lo .. hi + 1 :0]; \\} \\ ; try testing.expectEqualStrings(want, got); } test "meminfo: an anonymous struct literal in a printf call" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\use flibc \\ \\link "flibc_start" \\link "flibc_mem" \\ \\export fn main(_ usize, _ argv) noreturn { \\ flibc.printf("free pages: %u\n", .{flibc.sys.dump_free()}) \\ flibc.exit() \\} ); const want = \\const flibc = @import("flibc"); \\ \\comptime { \\ _ = @import("flibc_start"); \\ _ = @import("flibc_mem"); \\} \\ \\export fn main(_: usize, _: [*]const ?[*:0]const u8) callconv(.c) noreturn { \\ flibc.printf("free pages: %u\n", .{flibc.sys.dump_free()}); \\ flibc.exit(); \\} \\ ; try testing.expectEqualStrings(want, got); } test "expressions: precedence, index/slice, unary, builtins, && -> and, grouping" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn f(xs []u8, n usize, m usize) { \\ _ = n + 1 < m \\ _ = xs[n] != 0 \\ _ = xs[0..n] \\ _ = -xs[n] + #intCast(n % 10) \\ _ = &xs \\ _ = n < m && m != 0 \\ _ = n * (m + n) \\} ); const want = \\fn f(xs: []const u8, n: usize, m: usize) void { \\ _ = n + 1 < m; \\ _ = xs[n] != 0; \\ _ = xs[0..n]; \\ _ = -xs[n] + @intCast(n % 10); \\ _ = &xs; \\ _ = n < m and m != 0; \\ _ = n * (m + n); \\} \\ ; try testing.expectEqualStrings(want, got); } test "control-flow shapes: while, for-in, if/else-if/else, compound assign, return" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn f(xs []u8, n usize) usize { \\ var total usize = 0 \\ for x in xs { \\ _ = x \\ total += 1 \\ } \\ while n > 0 { \\ n -= 1 \\ } \\ if n == 0 { \\ return total \\ } else if n == 1 { \\ return n \\ } else { \\ return 0 \\ } \\} ); const want = \\fn f(xs: []const u8, n: usize) usize { \\ var total: usize = 0; \\ for (xs) |x| { \\ _ = x; \\ total += 1; \\ } \\ while (n > 0) { \\ n -= 1; \\ } \\ if (n == 0) { \\ return total; \\ } else if (n == 1) { \\ return n; \\ } else { \\ return 0; \\ } \\} \\ ; try testing.expectEqualStrings(want, got); } test "while optional-capture lowers to `while (expr) |x| { … }`" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn drain(it Iter) { \\ while it.next() |x| { \\ _ = x \\ } \\} ); // The `| ident | {` capture shape stops the condition parse (the // atCapturePipe guard); the capture renders after the parenthesised // condition, exactly as the `if` capture does. Byte-identical to zig fmt. const want = \\fn drain(it: Iter) void { \\ while (it.next()) |x| { \\ _ = x; \\ } \\} \\ ; try testing.expectEqualStrings(want, got); } test "for surface: range iterator and indexed capture lower to Zig's `for (lo..hi)` / `for (xs, 0..)`" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn f(xs []u8, n usize) { \\ for i in 0..n { \\ _ = i \\ } \\ for i in lo..hi { \\ _ = i \\ } \\ for x, i in xs { \\ _ = x \\ _ = i \\ } \\} ); // `for i in lo..hi` → `for (lo..hi) |i|`: the `..` range is emitted with no // surrounding spaces (as zig fmt keeps a range). A second capture appends the // implicit index range `0..`, giving `for (xs, 0..) |x, i|`. Both are // byte-identical to zig fmt and pass zig ast-check. const want = \\fn f(xs: []const u8, n: usize) void { \\ for (0..n) |i| { \\ _ = i; \\ } \\ for (lo..hi) |i| { \\ _ = i; \\ } \\ for (xs, 0..) |x, i| { \\ _ = x; \\ _ = i; \\ } \\} \\ ; try testing.expectEqualStrings(want, got); } test "inline for lowers to Zig's `inline for` across element, range, indexed, and else shapes" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn f(xs []u8, n usize) { \\ inline for x in xs { \\ _ = x \\ } \\ inline for i in 0..n { \\ _ = i \\ } \\ inline for x, i in xs { \\ _ = x \\ _ = i \\ } else { \\ g() \\ } \\} ); // The `inline ` prefix rides every for shape unchanged — element, range, // indexed capture, and the loop else arm. Byte-identical to zig fmt. const want = \\fn f(xs: []const u8, n: usize) void { \\ inline for (xs) |x| { \\ _ = x; \\ } \\ inline for (0..n) |i| { \\ _ = i; \\ } \\ inline for (xs, 0..) |x, i| { \\ _ = x; \\ _ = i; \\ } else { \\ g(); \\ } \\} \\ ; try testing.expectEqualStrings(want, got); } test "for surface: a `_` capture lowers verbatim to Zig's `|_|` discard" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn f(xs []u8, n usize) { \\ for _ in 0..n { \\ tick() \\ } \\ for x, _ in xs { \\ _ = x \\ } \\} ); // The element and index discards lower to Zig's `|_|` and `|x, _|` — the // verbatim capture spelling, byte-identical to zig fmt. const want = \\fn f(xs: []const u8, n: usize) void { \\ for (0..n) |_| { \\ tick(); \\ } \\ for (xs, 0..) |x, _| { \\ _ = x; \\ } \\} \\ ; try testing.expectEqualStrings(want, got); } test "if-expression lowers to a parenthesised-condition value if" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // The expression form of `if` (distinct from the statement form): both arms // are expressions and `else` is mandatory. The condition is parenthesised in // Flash too (required for a value `if`); the parens are not doubled in the // lowered Zig, and the arms render inline. const got = try lowerSrc(a.allocator(), \\fn classify(n usize) usize { \\ x := if (n == 0) 1 else 2 \\ return if (n > x) n else x \\} ); const want = \\fn classify(n: usize) usize { \\ const x = if (n == 0) 1 else 2; \\ return if (n > x) n else x; \\} \\ ; try testing.expectEqualStrings(want, got); } test "typed struct/union literal lowers, and a header brace stays the body" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // `Type{ … }` lowers with the type prefix and the same brace-spacing as the // anonymous form. Crucially, the `{` after a control-flow header subject // (`while i < buf.len {`) still opens the body — it is not swallowed as a // typed literal — so the loop and the initializer coexist. const got = try lowerSrc(a.allocator(), \\const Action = union(enum) { \\ none, \\ echo u8, \\} \\ \\fn step(buf []u8, byte u8) Action { \\ var i usize = 0 \\ while i < buf.len { \\ i += 1 \\ } \\ return Action{ .echo = byte } \\} ); const want = \\const Action = union(enum) { \\ none, \\ echo: u8, \\}; \\ \\fn step(buf: []const u8, byte: u8) Action { \\ var i: usize = 0; \\ while (i < buf.len) { \\ i += 1; \\ } \\ return Action{ .echo = byte }; \\} \\ ; try testing.expectEqualStrings(want, got); } test "switch expression lowers prongs, value lists, ranges, and block arms" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // Covers every prong shape: a value list (`' ', '\t'`), an inclusive range // (`'0'...'9'`, no spaces around `...`), an inline `if`-expression arm, a // multi-statement `label: { … }` arm with valued breaks, and the `else` // default. The subject is parenthesised; prongs sit one per line at depth + 1. const got = try lowerSrc(a.allocator(), \\fn classify(c u8) u8 { \\ return switch c { \\ ' ', '\t' => 0, \\ '0'...'9' => 1, \\ '+' => if (c == 0) 2 else 3, \\ else => blk: { \\ if c == 0 { \\ break :blk 9 \\ } \\ break :blk 4 \\ }, \\ } \\} ); const want = \\fn classify(c: u8) u8 { \\ return switch (c) { \\ ' ', '\t' => 0, \\ '0'...'9' => 1, \\ '+' => if (c == 0) 2 else 3, \\ else => blk: { \\ if (c == 0) { \\ break :blk 9; \\ } \\ break :blk 4; \\ }, \\ }; \\} \\ ; try testing.expectEqualStrings(want, got); } test "switch prong payload captures and a void block arm lower like Zig" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // The fsh `dispatch` shape: a `=> |x|` payload capture on three prongs // (`.err => |e|`, `.single => |n|`, `.piped => |p|`), a void `.empty => {}` // arm (an unlabelled empty block), and a nested `switch e { … }` as a prong // body. The capture renders `|x| ` after `=>`; the void arm collapses to `{}`; // the bare-statement switch takes no trailing `;`. const got = try lowerSrc(a.allocator(), \\fn dispatch(line []u8) { \\ var argv [tok.MAX_ARGS]?[*:0]mut u8 = undefined \\ var buf [TOK_BUF]u8 = undefined \\ switch tok.tokenize(line, &argv, &buf) { \\ .empty => {}, \\ .err => |e| switch e { \\ .too_many_pipes => emit(2, "fsh: only one pipe supported\n"), \\ .empty_side => emit(2, "fsh: missing command around |\n"), \\ }, \\ .single => |n| runSingle(&argv, n), \\ .piped => |p| runPiped(&argv, p), \\ } \\} ); const want = \\fn dispatch(line: []const u8) void { \\ var argv: [tok.MAX_ARGS]?[*:0]u8 = undefined; \\ var buf: [TOK_BUF]u8 = undefined; \\ switch (tok.tokenize(line, &argv, &buf)) { \\ .empty => {}, \\ .err => |e| switch (e) { \\ .too_many_pipes => emit(2, "fsh: only one pipe supported\n"), \\ .empty_side => emit(2, "fsh: missing command around |\n"), \\ }, \\ .single => |n| runSingle(&argv, n), \\ .piped => |p| runPiped(&argv, p), \\ } \\} \\ ; try testing.expectEqualStrings(want, got); } test "align(N) binding qualifier and a bare-return switch prong lower like Zig" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // The fsh `repl` core: an `align(16)` qualifier on a typed binding (between // the type and `=`), and a `.eof => return` prong whose bare `return` stops // at the prong comma instead of consuming it as a value. const got = try lowerSrc(a.allocator(), \\fn repl() { \\ const comp Completion align(16) = .{ .a = 1 } \\ switch read(comp) { \\ .eof => return, \\ .line => |l| { \\ handle(l) \\ }, \\ } \\} ); const want = \\fn repl() void { \\ const comp: Completion align(16) = .{ .a = 1 }; \\ switch (read(comp)) { \\ .eof => return, \\ .line => |l| { \\ handle(l); \\ }, \\ } \\} \\ ; try testing.expectEqualStrings(want, got); } test "labeled block expression and valued break lower with correct depth" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // `label: { … }` is an expression whose value is a `break :label v` inside // it. The block body indents at depth + 1 and closes at depth even when // nested under `return`; the labelled breaks render `break :blk v`. const got = try lowerSrc(a.allocator(), \\fn pick(n usize) usize { \\ return blk: { \\ if n == 0 { \\ break :blk 1 \\ } \\ break :blk 2 \\ } \\} ); const want = \\fn pick(n: usize) usize { \\ return blk: { \\ if (n == 0) { \\ break :blk 1; \\ } \\ break :blk 2; \\ }; \\} \\ ; try testing.expectEqualStrings(want, got); } test "empty blocks collapse to `{}` across fn, while, for, and if/else" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn noop() { \\} \\ \\fn loops(n usize) { \\ var i usize = 0 \\ while i < n { \\ } \\ for x in n { \\ } \\} \\ \\fn branch(c bool) { \\ if c { \\ } else { \\ } \\} ); // An empty statement list collapses to a one-line `{}` (matching zig fmt) at // every block site — function body, `while`, `for`, and both `if` arms. // Byte-identical to `zig fmt`; the collapsed `fn noop() void {}` is also // `ast-check`-clean. const want = \\fn noop() void {} \\ \\fn loops(n: usize) void { \\ var i: usize = 0; \\ while (i < n) {} \\ for (n) |x| {} \\} \\ \\fn branch(c: bool) void { \\ if (c) {} else {} \\} \\ ; try testing.expectEqualStrings(want, got); } test "error-union surface: !T/?T, try, catch, defer, errdefer, optional-capture if" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn open(p cstr) !i32 { \\ fd := try sys.open(p) \\ errdefer _ = sys.close(fd) \\ defer _ = sys.flush() \\ return fd \\} \\ \\fn pick(xs []u8) ?u8 { \\ if find(xs) |hit| { \\ return hit \\ } \\ _ = run(xs) catch |e| report(e) \\ return none \\} ); const want = \\fn open(p: [*:0]const u8) !i32 { \\ const fd = try sys.open(p); \\ errdefer _ = sys.close(fd); \\ defer _ = sys.flush(); \\ return fd; \\} \\ \\fn pick(xs: []const u8) ?u8 { \\ if (find(xs)) |hit| { \\ return hit; \\ } \\ _ = run(xs) catch |e| report(e); \\ return none; \\} \\ ; try testing.expectEqualStrings(want, got); } test "catch block recovery: bare void block, captured block, value position" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn run() !u8 { \\ flush() catch {} \\ _ = work() catch |e| { \\ log(e) \\ return 0 \\ } \\ x := work() catch { return 0 } \\ return x \\} ); const want = \\fn run() !u8 { \\ flush() catch {}; \\ _ = work() catch |e| { \\ log(e); \\ return 0; \\ }; \\ const x = work() catch { \\ return 0; \\ }; \\ return x; \\} \\ ; try testing.expectEqualStrings(want, got); } test "dmesg port: array decl + if guard lower to diffable Zig" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\use flibc \\ \\link "flibc_start" \\link "flibc_mem" \\ \\export fn main(_ usize, _ argv) noreturn { \\ var buf [flibc.KLOG_SIZE]u8 = undefined \\ n := flibc.sys.klog_read(&buf, buf.len) \\ if n > 0 { \\ _ = flibc.sys.write_fd(1, &buf, #intCast(n)) \\ } \\ flibc.exit() \\} ); const want = \\const flibc = @import("flibc"); \\ \\comptime { \\ _ = @import("flibc_start"); \\ _ = @import("flibc_mem"); \\} \\ \\export fn main(_: usize, _: [*]const ?[*:0]const u8) callconv(.c) noreturn { \\ var buf: [flibc.KLOG_SIZE]u8 = undefined; \\ const n = flibc.sys.klog_read(&buf, buf.len); \\ if (n > 0) { \\ _ = flibc.sys.write_fd(1, &buf, @intCast(n)); \\ } \\ flibc.exit(); \\} \\ ; try testing.expectEqualStrings(want, got); } test "echo port: while + orelse break + nul-scan helper" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\use flibc \\ \\link "flibc_start" \\link "flibc_mem" \\ \\fn emit(s []u8) { \\ _ = flibc.sys.write_fd(1, s.ptr, s.len) \\} \\ \\fn emitz(s cstr) { \\ var n usize = 0 \\ while s[n] != 0 { \\ n += 1 \\ } \\ _ = flibc.sys.write_fd(1, s, n) \\} \\ \\export fn main(argc usize, argv argv) noreturn { \\ var i usize = 1 \\ while i < argc { \\ s := argv[i] orelse break \\ emitz(s) \\ if i + 1 < argc { \\ emit(" ") \\ } \\ i += 1 \\ } \\ emit("\n") \\ flibc.exit() \\} ); const want = \\const flibc = @import("flibc"); \\ \\comptime { \\ _ = @import("flibc_start"); \\ _ = @import("flibc_mem"); \\} \\ \\fn emit(s: []const u8) void { \\ _ = flibc.sys.write_fd(1, s.ptr, s.len); \\} \\ \\fn emitz(s: [*:0]const u8) void { \\ var n: usize = 0; \\ while (s[n] != 0) { \\ n += 1; \\ } \\ _ = flibc.sys.write_fd(1, s, n); \\} \\ \\export fn main(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn { \\ var i: usize = 1; \\ while (i < argc) { \\ const s = argv[i] orelse break; \\ emitz(s); \\ if (i + 1 < argc) { \\ emit(" "); \\ } \\ i += 1; \\ } \\ emit("\n"); \\ flibc.exit(); \\} \\ ; try testing.expectEqualStrings(want, got); } test "cat port: top-const, if/else, nested while, continue, import alias" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\use flibc \\use syscall_defs as defs \\ \\link "flibc_start" \\link "flibc_mem" \\ \\const BUF_LEN usize = 512 \\ \\fn drain(fd i32) { \\ var buf [BUF_LEN]u8 = undefined \\ while true { \\ n := flibc.sys.read(fd, &buf, buf.len) \\ if n <= 0 { \\ break \\ } \\ _ = flibc.sys.write_fd(1, &buf, #intCast(n)) \\ } \\} \\ \\export fn main(argc usize, argv argv) noreturn { \\ if argc <= 1 { \\ drain(0) \\ } else { \\ var i usize = 1 \\ while i < argc { \\ path := argv[i] orelse break \\ i += 1 \\ fd := flibc.sys.open(path) \\ if fd < 0 { \\ var msg []u8 = "cat: cannot open\n" \\ if fd == -defs.EACCES { \\ msg = "cat: Permission denied\n" \\ } \\ _ = flibc.sys.write_fd(2, msg.ptr, msg.len) \\ continue \\ } \\ drain(fd) \\ _ = flibc.sys.close(fd) \\ } \\ } \\ flibc.exit() \\} ); const want = \\const flibc = @import("flibc"); \\const defs = @import("syscall_defs"); \\ \\comptime { \\ _ = @import("flibc_start"); \\ _ = @import("flibc_mem"); \\} \\ \\const BUF_LEN: usize = 512; \\ \\fn drain(fd: i32) void { \\ var buf: [BUF_LEN]u8 = undefined; \\ while (true) { \\ const n = flibc.sys.read(fd, &buf, buf.len); \\ if (n <= 0) { \\ break; \\ } \\ _ = flibc.sys.write_fd(1, &buf, @intCast(n)); \\ } \\} \\ \\export fn main(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn { \\ if (argc <= 1) { \\ drain(0); \\ } else { \\ var i: usize = 1; \\ while (i < argc) { \\ const path = argv[i] orelse break; \\ i += 1; \\ const fd = flibc.sys.open(path); \\ if (fd < 0) { \\ var msg: []const u8 = "cat: cannot open\n"; \\ if (fd == -defs.EACCES) { \\ msg = "cat: Permission denied\n"; \\ } \\ _ = flibc.sys.write_fd(2, msg.ptr, msg.len); \\ continue; \\ } \\ drain(fd); \\ _ = flibc.sys.close(fd); \\ } \\ } \\ flibc.exit(); \\} \\ ; try testing.expectEqualStrings(want, got); } test "ls port: struct literal, member address-of, && condition" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\use flibc \\ \\link "flibc_start" \\link "flibc_mem" \\ \\fn emit(s []u8) { \\ _ = flibc.sys.write_fd(1, s.ptr, s.len) \\} \\ \\fn listDir(path cstr) { \\ var d flibc.Dirent = .{} \\ var i u64 = 0 \\ while flibc.sys.readdir(path, i, &d) == 0 { \\ var n usize = 0 \\ while n < d.name.len && d.name[n] != 0 { \\ n += 1 \\ } \\ _ = flibc.sys.write_fd(1, &d.name, n) \\ if d.d_type == flibc.DT_DIR { \\ emit("/") \\ } \\ emit("\n") \\ i += 1 \\ } \\} \\ \\export fn main(argc usize, argv argv) noreturn { \\ if argc <= 1 { \\ listDir(".") \\ } else { \\ var a usize = 1 \\ while a < argc { \\ path := argv[a] orelse break \\ listDir(path) \\ a += 1 \\ } \\ } \\ flibc.exit() \\} ); const want = \\const flibc = @import("flibc"); \\ \\comptime { \\ _ = @import("flibc_start"); \\ _ = @import("flibc_mem"); \\} \\ \\fn emit(s: []const u8) void { \\ _ = flibc.sys.write_fd(1, s.ptr, s.len); \\} \\ \\fn listDir(path: [*:0]const u8) void { \\ var d: flibc.Dirent = .{}; \\ var i: u64 = 0; \\ while (flibc.sys.readdir(path, i, &d) == 0) { \\ var n: usize = 0; \\ while (n < d.name.len and d.name[n] != 0) { \\ n += 1; \\ } \\ _ = flibc.sys.write_fd(1, &d.name, n); \\ if (d.d_type == flibc.DT_DIR) { \\ emit("/"); \\ } \\ emit("\n"); \\ i += 1; \\ } \\} \\ \\export fn main(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn { \\ if (argc <= 1) { \\ listDir("."); \\ } else { \\ var a: usize = 1; \\ while (a < argc) { \\ const path = argv[a] orelse break; \\ listDir(path); \\ a += 1; \\ } \\ } \\ flibc.exit(); \\} \\ ; try testing.expectEqualStrings(want, got); } test "readfile port: error-union fns, try, defer, errdefer, catch capture" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\use flibc \\ \\link "flibc_start" \\link "flibc_mem" \\ \\const BUF_LEN usize = 512 \\ \\fn dup(path cstr) !i32 { \\ fd := try flibc.sys.open(path) \\ errdefer _ = flibc.sys.close(fd) \\ _ = try flibc.sys.fstat(fd) \\ return fd \\} \\ \\fn copy(path cstr) !usize { \\ fd := try dup(path) \\ defer _ = flibc.sys.close(fd) \\ var buf [BUF_LEN]u8 = undefined \\ var total usize = 0 \\ while true { \\ n := try flibc.sys.read(fd, &buf, buf.len) \\ if n == 0 { \\ break \\ } \\ _ = flibc.sys.write_fd(1, &buf, #intCast(n)) \\ total += n \\ } \\ return total \\} \\ \\fn report(e flibc.Error) usize { \\ _ = e \\ _ = flibc.sys.write_fd(2, "readfile: I/O error\n", 20) \\ return 0 \\} \\ \\export fn main(argc usize, argv argv) noreturn { \\ var i usize = 1 \\ while i < argc { \\ path := argv[i] orelse break \\ _ = copy(path) catch |e| report(e) \\ i += 1 \\ } \\ flibc.exit() \\} ); const want = \\const flibc = @import("flibc"); \\ \\comptime { \\ _ = @import("flibc_start"); \\ _ = @import("flibc_mem"); \\} \\ \\const BUF_LEN: usize = 512; \\ \\fn dup(path: [*:0]const u8) !i32 { \\ const fd = try flibc.sys.open(path); \\ errdefer _ = flibc.sys.close(fd); \\ _ = try flibc.sys.fstat(fd); \\ return fd; \\} \\ \\fn copy(path: [*:0]const u8) !usize { \\ const fd = try dup(path); \\ defer _ = flibc.sys.close(fd); \\ var buf: [BUF_LEN]u8 = undefined; \\ var total: usize = 0; \\ while (true) { \\ const n = try flibc.sys.read(fd, &buf, buf.len); \\ if (n == 0) { \\ break; \\ } \\ _ = flibc.sys.write_fd(1, &buf, @intCast(n)); \\ total += n; \\ } \\ return total; \\} \\ \\fn report(e: flibc.Error) usize { \\ _ = e; \\ _ = flibc.sys.write_fd(2, "readfile: I/O error\n", 20); \\ return 0; \\} \\ \\export fn main(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn { \\ var i: usize = 1; \\ while (i < argc) { \\ const path = argv[i] orelse break; \\ _ = copy(path) catch |e| report(e); \\ i += 1; \\ } \\ flibc.exit(); \\} \\ ; try testing.expectEqualStrings(want, got); } test "struct/enum definitions and literals lower to canonical Zig" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const Point = struct { \\ x i32, \\ y i32, \\} \\ \\const Kind = enum(u8) { \\ file, \\ dir, \\} \\ \\const Mode = enum { \\ on, \\ off, \\} \\ \\fn make(n i32) Point { \\ return .{ .x = n, .y = 0 } \\} \\ \\fn demo(n i32) { \\ _ = .{n} \\ _ = .{ n, n } \\ var k Kind = .file \\ _ = k \\} ); const want = \\const Point = struct { \\ x: i32, \\ y: i32, \\}; \\ \\const Kind = enum(u8) { \\ file, \\ dir, \\}; \\ \\const Mode = enum { \\ on, \\ off, \\}; \\ \\fn make(n: i32) Point { \\ return .{ .x = n, .y = 0 }; \\} \\ \\fn demo(n: i32) void { \\ _ = .{n}; \\ _ = .{ n, n }; \\ var k: Kind = .file; \\ _ = k; \\} \\ ; try testing.expectEqualStrings(want, got); } test "union(enum) definitions lower to canonical Zig" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // A bare void variant stays bare; a typed variant gains the `: T`; the slice // payload `[]u8` lowers through emitType to `[]const u8`, like any other. const got = try lowerSrc(a.allocator(), \\const Result = union(enum) { \\ empty, \\ single usize, \\ line []u8, \\ piped Piped, \\} ); const want = \\const Result = union(enum) { \\ empty, \\ single: usize, \\ line: []const u8, \\ piped: Piped, \\}; \\ ; try testing.expectEqualStrings(want, got); } test "empty container definitions lower to zig fmt's one-line `{}` form" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const Empty = struct {} \\const E = enum {} \\const T = enum(u8) {} \\const U = union {} \\const V = union(enum) {} ); const want = \\const Empty = struct {}; \\ \\const E = enum {}; \\ \\const T = enum(u8) {}; \\ \\const U = union {}; \\ \\const V = union(enum) {}; \\ ; try testing.expectEqualStrings(want, got); } test "pub visibility lowers to a pub prefix" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // `pub` precedes `export`; a `pub export fn` keeps the C calling convention. // A bare (non-pub) const/fn is unchanged. const got = try lowerSrc(a.allocator(), \\pub const MAX usize = 16 \\const MIN usize = 0 \\pub fn span(n usize) usize { \\ return n + MIN \\} \\pub export fn main() { \\ _ = span(MAX) \\} ); const want = \\pub const MAX: usize = 16; \\ \\const MIN: usize = 0; \\ \\pub fn span(n: usize) usize { \\ return n + MIN; \\} \\ \\pub export fn main() callconv(.c) void { \\ _ = span(MAX); \\} \\ ; try testing.expectEqualStrings(want, got); } test "inline fn lowers to an inline prefix" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // `inline` follows `pub` and takes no callconv (that is export-only); it // applies to a top-level function and to a struct method alike. A bare fn is // unchanged. const got = try lowerSrc(a.allocator(), \\inline fn hot(n usize) usize { \\ return n + 1 \\} \\ \\const Math = struct { \\ inline fn twice(n usize) usize { \\ return n + n \\ } \\} \\ \\pub inline fn exposed(n usize) usize { \\ return hot(n) \\} ); const want = \\inline fn hot(n: usize) usize { \\ return n + 1; \\} \\ \\const Math = struct { \\ inline fn twice(n: usize) usize { \\ return n + n; \\ } \\}; \\ \\pub inline fn exposed(n: usize) usize { \\ return hot(n); \\} \\ ; try testing.expectEqualStrings(want, got); } test "enum discriminants lower to canonical Zig" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const Errno = enum(u8) { \\ ok = 0, \\ perm = 1, \\ again, \\} \\ \\const Mode = enum(u32) { \\ none = 0, \\ exec = 0x10, \\} ); // Each explicit discriminant emits as ` = `, the literal kept verbatim // (decimal and `0x` hex alike); an implicit variant stays a bare name, and // mixing the two in one enum is preserved. const want = \\const Errno = enum(u8) { \\ ok = 0, \\ perm = 1, \\ again, \\}; \\ \\const Mode = enum(u32) { \\ none = 0, \\ exec = 0x10, \\}; \\ ; try testing.expectEqualStrings(want, got); } test "bitwise and shift expressions lower verbatim" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn f(a u32, b u32) { \\ _ = a << 2 \\ _ = a >> 1 \\ _ = a & b \\ _ = a | b \\ _ = a ^ b \\} ); // Every bitwise/shift operator is spelled identically in Zig, so each // passes through unchanged (only "&&"/"||" are remapped, to and/or). const want = \\fn f(a: u32, b: u32) void { \\ _ = a << 2; \\ _ = a >> 1; \\ _ = a & b; \\ _ = a | b; \\ _ = a ^ b; \\} \\ ; try testing.expectEqualStrings(want, got); } test "bitwise and shift compound assignment lowers verbatim" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn f(flags u32) { \\ var f2 u32 = flags \\ f2 &= 0x0F \\ f2 |= 0x10 \\ f2 ^= 0xFF \\ f2 <<= 1 \\ f2 >>= 2 \\} ); // Zig spells these compound assignments identically, so the op lexeme passes // through unchanged (the "&&"/"||" remap is expression-only and does not // touch the assignment operators). const want = \\fn f(flags: u32) void { \\ var f2: u32 = flags; \\ f2 &= 0x0F; \\ f2 |= 0x10; \\ f2 ^= 0xFF; \\ f2 <<= 1; \\ f2 >>= 2; \\} \\ ; try testing.expectEqualStrings(want, got); } test "bitflag enum discriminants lower to canonical Zig" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // The motivating case for shifts: a flag enum whose discriminants are // `1 << n`. Now that the shift operators lex and parse, the discriminant // expression lowers verbatim and stays byte-exact, valid Zig. const got = try lowerSrc(a.allocator(), \\const Flags = enum(u8) { \\ none = 0, \\ read = 1 << 0, \\ write = 1 << 1, \\ exec = 1 << 2, \\} ); const want = \\const Flags = enum(u8) { \\ none = 0, \\ read = 1 << 0, \\ write = 1 << 1, \\ exec = 1 << 2, \\}; \\ ; try testing.expectEqualStrings(want, got); } test "struct methods and an associated constant lower to canonical Zig" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const Point = struct { \\ x i32, \\ y i32, \\ \\ fn sum(self Point) i32 { \\ return self.x + self.y \\ } \\ \\ const ZERO i32 = 0 \\} ); const want = \\const Point = struct { \\ x: i32, \\ y: i32, \\ \\ fn sum(self: Point) i32 { \\ return self.x + self.y; \\ } \\ \\ const ZERO: i32 = 0; \\}; \\ ; try testing.expectEqualStrings(want, got); } test "a declaration-only struct lowers without a leading blank line" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // No fields: the first method follows '{' directly (zig fmt strips a blank // line there), and consecutive methods stay one blank line apart. const got = try lowerSrc(a.allocator(), \\const Counter = struct { \\ fn zero() usize { \\ return 0 \\ } \\ \\ fn bump(n usize) usize { \\ return n + 1 \\ } \\} ); const want = \\const Counter = struct { \\ fn zero() usize { \\ return 0; \\ } \\ \\ fn bump(n: usize) usize { \\ return n + 1; \\ } \\}; \\ ; try testing.expectEqualStrings(want, got); } test "enum methods and an associated constant lower to canonical Zig" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const Color = enum(u8) { \\ red, \\ green = 5, \\ \\ const COUNT usize = 2 \\ \\ fn isRed(self Color) bool { \\ return self == .red \\ } \\} ); const want = \\const Color = enum(u8) { \\ red, \\ green = 5, \\ \\ const COUNT: usize = 2; \\ \\ fn isRed(self: Color) bool { \\ return self == .red; \\ } \\}; \\ ; try testing.expectEqualStrings(want, got); } test "union methods lower to canonical Zig" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // A tagged union with a method; a declaration-only enum gets no leading // blank line, matching the struct rule. const got = try lowerSrc(a.allocator(), \\const Tok = union(enum) { \\ eof, \\ int usize, \\ \\ fn isEof(self Tok) bool { \\ return self == .eof \\ } \\} \\ \\const Util = enum { \\ fn zero() usize { \\ return 0 \\ } \\} ); const want = \\const Tok = union(enum) { \\ eof, \\ int: usize, \\ \\ fn isEof(self: Tok) bool { \\ return self == .eof; \\ } \\}; \\ \\const Util = enum { \\ fn zero() usize { \\ return 0; \\ } \\}; \\ ; try testing.expectEqualStrings(want, got); } test "struct field default values lower after the type" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // A default value renders as `name: T = expr,` — a literal, `undefined`, or // an empty `.{}` alike; a field with no default keeps the bare `name: T,`. const got = try lowerSrc(a.allocator(), \\const Slot = struct { \\ bytes [CAP]u8 = undefined, \\ seen [2]u8 = .{}, \\ len usize = 0, \\ src []mut u8, \\} ); const want = \\const Slot = struct { \\ bytes: [CAP]u8 = undefined, \\ seen: [2]u8 = .{}, \\ len: usize = 0, \\ src: []u8, \\}; \\ ; try testing.expectEqualStrings(want, got); } test "multiline string as a top-level const lowers to a zig fmt block" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const USAGE = \\ \\Usage: demo \\ \\ \\ \\ -h help ); const want = \\const USAGE = \\ \\Usage: demo \\ \\ \\ \\ -h help \\; \\ ; try testing.expectEqualStrings(want, got); } test "top-level `var` lowers to a file-scope mutable global, `const` stays immutable" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // A file-scope `var` is a mutable global, the item-level binding with the // keyword flipped — `pub var` keeps the `pub`. A `const` global is unchanged. const got = try lowerSrc(a.allocator(), \\var counter usize = 0 \\pub var ready bool = false \\const LIMIT usize = 10 ); const want = \\var counter: usize = 0; \\ \\pub var ready: bool = false; \\ \\const LIMIT: usize = 10; \\ ; try testing.expectEqualStrings(want, got); } test "multiline string in binding and discard value position" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn show() { \\ msg := \\ \\hi \\ \\there \\ _ = msg \\} ); const want = \\fn show() void { \\ const msg = \\ \\hi \\ \\there \\ ; \\ _ = msg; \\} \\ ; try testing.expectEqualStrings(want, got); } test "sysinfo port: optional-capture if, decimal helper, for-over-bytes" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\use flibc \\use pwfile \\use console_ui \\use build_options \\ \\link "flibc_start" \\link "flibc_mem" \\ \\const PASSWD_MAX usize = build_options.passwd_max \\ \\fn u64dec(out []mut u8, v u64) usize { \\ var x u64 = v \\ if x == 0 { \\ out[0] = '0' \\ return 1 \\ } \\ var tmp [20]u8 = undefined \\ var n usize = 0 \\ while x != 0 { \\ tmp[n] = '0' + #as(u8, #intCast(x % 10)) \\ n += 1 \\ x /= 10 \\ } \\ var i usize = 0 \\ while i < n { \\ out[i] = tmp[n - 1 - i] \\ i += 1 \\ } \\ return n \\} \\ \\fn currentUser(buf []mut u8) []u8 { \\ uid_raw := flibc.sys.getuid() \\ if uid_raw < 0 { \\ return "?" \\ } \\ uid := #as(u32, #intCast(uid_raw)) \\ n := u64dec(buf, uid) \\ if pwfile.lookupByUid(buf[0..n], uid) |entry| { \\ return entry.user \\ } \\ return buf[0..n] \\} \\ \\fn freePages(out []mut u8) usize { \\ pages := flibc.sys.dump_free() \\ var n usize = u64dec(out, pages) \\ suffix := " free" \\ for c in suffix { \\ out[n] = c \\ n += 1 \\ } \\ return n \\} \\ \\export fn main(_ usize, _ argv) noreturn { \\ console_ui.banner("sysinfo") \\ var ubuf [PASSWD_MAX]u8 = undefined \\ user := currentUser(&ubuf) \\ console_ui.screen.kv("user", user) \\ var fbuf [32]u8 = undefined \\ m := freePages(&fbuf) \\ console_ui.screen.kv("memory", fbuf[0..m]) \\ flibc.exit() \\} ); const want = \\const flibc = @import("flibc"); \\const pwfile = @import("pwfile"); \\const console_ui = @import("console_ui"); \\const build_options = @import("build_options"); \\ \\comptime { \\ _ = @import("flibc_start"); \\ _ = @import("flibc_mem"); \\} \\ \\const PASSWD_MAX: usize = build_options.passwd_max; \\ \\fn u64dec(out: []u8, v: u64) usize { \\ var x: u64 = v; \\ if (x == 0) { \\ out[0] = '0'; \\ return 1; \\ } \\ var tmp: [20]u8 = undefined; \\ var n: usize = 0; \\ while (x != 0) { \\ tmp[n] = '0' + @as(u8, @intCast(x % 10)); \\ n += 1; \\ x /= 10; \\ } \\ var i: usize = 0; \\ while (i < n) { \\ out[i] = tmp[n - 1 - i]; \\ i += 1; \\ } \\ return n; \\} \\ \\fn currentUser(buf: []u8) []const u8 { \\ const uid_raw = flibc.sys.getuid(); \\ if (uid_raw < 0) { \\ return "?"; \\ } \\ const uid = @as(u32, @intCast(uid_raw)); \\ const n = u64dec(buf, uid); \\ if (pwfile.lookupByUid(buf[0..n], uid)) |entry| { \\ return entry.user; \\ } \\ return buf[0..n]; \\} \\ \\fn freePages(out: []u8) usize { \\ const pages = flibc.sys.dump_free(); \\ var n: usize = u64dec(out, pages); \\ const suffix = " free"; \\ for (suffix) |c| { \\ out[n] = c; \\ n += 1; \\ } \\ return n; \\} \\ \\export fn main(_: usize, _: [*]const ?[*:0]const u8) callconv(.c) noreturn { \\ console_ui.banner("sysinfo"); \\ var ubuf: [PASSWD_MAX]u8 = undefined; \\ const user = currentUser(&ubuf); \\ console_ui.screen.kv("user", user); \\ var fbuf: [32]u8 = undefined; \\ const m = freePages(&fbuf); \\ console_ui.screen.kv("memory", fbuf[0..m]); \\ flibc.exit(); \\} \\ ; try testing.expectEqualStrings(want, got); } test "doc comments lower verbatim before their declarations" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // A `///` block is kept byte-for-byte and re-emitted before the const, fn, // struct field/member, and enum/union variant it leads — the content after // each `///` (the leading space included) is preserved. This output is // byte-identical to `zig fmt` of itself and passes `zig ast-check`. const got = try lowerSrc(a.allocator(), \\/// The maximum number of entries. \\/// Tunable at build time. \\pub const MAX usize = 16 \\ \\/// Returns the smaller of two values. \\inline fn min(a usize, b usize) usize { \\ return a \\} \\ \\const Point = struct { \\ /// The horizontal coordinate. \\ x i32, \\ y i32, \\ \\ /// Manhattan distance from the origin. \\ pub fn norm(self Point) i32 { \\ return self.x + self.y \\ } \\ \\ /// The origin point. \\ const ZERO i32 = 0 \\} \\ \\const Kind = enum(u8) { \\ /// A regular file. \\ file, \\ dir, \\} \\ \\const Tok = union(enum) { \\ /// End of input. \\ eof, \\ int usize, \\} ); const want = \\/// The maximum number of entries. \\/// Tunable at build time. \\pub const MAX: usize = 16; \\ \\/// Returns the smaller of two values. \\inline fn min(a: usize, b: usize) usize { \\ return a; \\} \\ \\const Point = struct { \\ /// The horizontal coordinate. \\ x: i32, \\ y: i32, \\ \\ /// Manhattan distance from the origin. \\ pub fn norm(self: Point) i32 { \\ return self.x + self.y; \\ } \\ \\ /// The origin point. \\ const ZERO: i32 = 0; \\}; \\ \\const Kind = enum(u8) { \\ /// A regular file. \\ file, \\ dir, \\}; \\ \\const Tok = union(enum) { \\ /// End of input. \\ eof, \\ int: usize, \\}; \\ ; try testing.expectEqualStrings(want, got); } test "readline port: step's switch — value list, range, if-expr arm, block arms, typed literal" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // The centerpiece of the flibc readline core, ported from hand-written Zig: // a `switch` over the input byte with a value list (`'\r', '\n'`), an // inclusive range (`0x20...0x7e`), an inline `if`-expression arm, two // `blk: { … }` block arms whose value is a `break :blk …`, a typed union // literal (`Action{ .echo = byte }`), and struct field defaults. The braceless // single-statement `if` bodies of the reference become Flash's mandatory // braces (a canonical normalization); otherwise the token stream matches. The // full module lives in examples/readline.flash. const got = try lowerSrc(a.allocator(), \\pub const State = struct { \\ buf []mut u8, \\ len usize = 0, \\ pos usize = 0, \\} \\ \\pub const Action = union(enum) { \\ none, \\ echo u8, \\ backspace, \\ submit, \\ complete, \\ abandon, \\ eof, \\} \\ \\/// One-byte state transition for the plain (append-only) editor. Pure: no \\/// syscalls, no allocator. \\pub fn step(state *mut State, byte u8) Action { \\ return switch byte { \\ '\r', '\n' => .submit, \\ 0x03 => .abandon, \\ 0x04 => if (state.len == 0) Action.eof else Action.none, \\ 0x09 => .complete, \\ 0x08, 0x7f => blk: { \\ if state.len == 0 { \\ break :blk Action.none \\ } \\ state.len -= 1 \\ break :blk Action.backspace \\ }, \\ 0x20...0x7e => blk: { \\ if state.len >= state.buf.len { \\ break :blk Action.none \\ } \\ state.buf[state.len] = byte \\ state.len += 1 \\ break :blk Action{ .echo = byte } \\ }, \\ else => .none, \\ } \\} ); const want = \\pub const State = struct { \\ buf: []u8, \\ len: usize = 0, \\ pos: usize = 0, \\}; \\ \\pub const Action = union(enum) { \\ none, \\ echo: u8, \\ backspace, \\ submit, \\ complete, \\ abandon, \\ eof, \\}; \\ \\/// One-byte state transition for the plain (append-only) editor. Pure: no \\/// syscalls, no allocator. \\pub fn step(state: *State, byte: u8) Action { \\ return switch (byte) { \\ '\r', '\n' => .submit, \\ 0x03 => .abandon, \\ 0x04 => if (state.len == 0) Action.eof else Action.none, \\ 0x09 => .complete, \\ 0x08, 0x7f => blk: { \\ if (state.len == 0) { \\ break :blk Action.none; \\ } \\ state.len -= 1; \\ break :blk Action.backspace; \\ }, \\ 0x20...0x7e => blk: { \\ if (state.len >= state.buf.len) { \\ break :blk Action.none; \\ } \\ state.buf[state.len] = byte; \\ state.len += 1; \\ break :blk Action{ .echo = byte }; \\ }, \\ else => .none, \\ }; \\} \\ ; try testing.expectEqualStrings(want, got); } test "tokenize port: union(enum) result, union/enum-literal returns, sentinel slice, doc comments" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // The fsh command tokenizer ported from hand-written Zig: a tagged-union // `Result`, union literals (including a nested `.{ .piped = .{ … } }`), bare // enum-literal returns, the composite `*mut [MAX_ARGS]?[*:0]u8` signature, a // sentinel slice, an open-ended chained slice, and two compound-condition // `while` scans. `///` doc comments carry through verbatim. The output is // byte-identical to `zig fmt` of itself and passes `zig ast-check`. const got = try lowerSrc(a.allocator(), \\/// argv capacity, including the interleaved `null` separators (the pipe \\/// boundary and the trailing terminator). 16 covers a command plus a \\/// generous argument list for demoware; longer lines truncate. \\pub const MAX_ARGS usize = 16 \\ \\/// Why the two sides of a `|` cannot both be commands, or why a second \\/// `|` appeared. \\pub const Err = enum { \\ too_many_pipes, \\ empty_side, \\} \\ \\/// A single-pipe decomposition. The right command's argv begins at \\/// `argv[left_argc + 1]` (the `+ 1` skips the `null` the tokenizer wrote \\/// at the pipe boundary); both vectors are NULL-terminated in place. \\pub const Piped = struct { \\ left_argc usize, \\ right_argc usize, \\} \\ \\/// How a line decomposed. \\pub const Result = union(enum) { \\ /// Blank or whitespace-only line — fsh redraws the prompt. \\ empty, \\ /// One command; `argv[0..argc]` valid, `argv[argc] == null`. \\ single usize, \\ /// One pipe stage; see `Piped`. \\ piped Piped, \\ /// Malformed pipe usage. \\ err Err, \\} \\ \\inline fn is_space(c u8) bool { \\ return c == ' ' || c == '\t' || c == '\r' || c == '\n' \\} \\ \\/// Split `line` into `argv` (pointers into `buf`). See the module header \\/// for the decomposition rules. `argv` and `buf` are caller-owned and \\/// reused per line; the returned pointers are valid until the next call \\/// that reuses them. \\pub fn tokenize(line []u8, argv *mut [MAX_ARGS]?[*:0]mut u8, buf []mut u8) Result { \\ var argc usize = 0 \\ var buf_pos usize = 0 \\ var pipe_at ?usize = null \\ var pipes usize = 0 \\ \\ var i usize = 0 \\ while i < line.len { \\ while i < line.len && is_space(line[i]) { \\ i += 1 \\ } \\ if i >= line.len { \\ break \\ } \\ \\ if argc >= MAX_ARGS - 1 { \\ break \\ } \\ \\ if line[i] == '|' { \\ pipes += 1 \\ if pipes > 1 { \\ return .{ .err = .too_many_pipes } \\ } \\ pipe_at = argc \\ argv[argc] = null \\ argc += 1 \\ i += 1 \\ continue \\ } \\ \\ start := i \\ while i < line.len && !is_space(line[i]) && line[i] != '|' { \\ i += 1 \\ } \\ tok := line[start..i] \\ \\ if buf_pos + tok.len + 1 > buf.len { \\ break \\ } \\ #memcpy(buf[buf_pos..][0..tok.len], tok) \\ buf[buf_pos + tok.len] = 0 \\ argv[argc] = buf[buf_pos .. buf_pos + tok.len :0].ptr \\ argc += 1 \\ buf_pos += tok.len + 1 \\ } \\ \\ if argc < MAX_ARGS { \\ argv[argc] = null \\ } \\ \\ if pipe_at |p| { \\ left_argc := p \\ right_argc := argc - p - 1 \\ if left_argc == 0 || right_argc == 0 { \\ return .{ .err = .empty_side } \\ } \\ return .{ .piped = .{ .left_argc = left_argc, .right_argc = right_argc } } \\ } \\ \\ if argc == 0 { \\ return .empty \\ } \\ return .{ .single = argc } \\} ); const want = \\/// argv capacity, including the interleaved `null` separators (the pipe \\/// boundary and the trailing terminator). 16 covers a command plus a \\/// generous argument list for demoware; longer lines truncate. \\pub const MAX_ARGS: usize = 16; \\ \\/// Why the two sides of a `|` cannot both be commands, or why a second \\/// `|` appeared. \\pub const Err = enum { \\ too_many_pipes, \\ empty_side, \\}; \\ \\/// A single-pipe decomposition. The right command's argv begins at \\/// `argv[left_argc + 1]` (the `+ 1` skips the `null` the tokenizer wrote \\/// at the pipe boundary); both vectors are NULL-terminated in place. \\pub const Piped = struct { \\ left_argc: usize, \\ right_argc: usize, \\}; \\ \\/// How a line decomposed. \\pub const Result = union(enum) { \\ /// Blank or whitespace-only line — fsh redraws the prompt. \\ empty, \\ /// One command; `argv[0..argc]` valid, `argv[argc] == null`. \\ single: usize, \\ /// One pipe stage; see `Piped`. \\ piped: Piped, \\ /// Malformed pipe usage. \\ err: Err, \\}; \\ \\inline fn is_space(c: u8) bool { \\ return c == ' ' or c == '\t' or c == '\r' or c == '\n'; \\} \\ \\/// Split `line` into `argv` (pointers into `buf`). See the module header \\/// for the decomposition rules. `argv` and `buf` are caller-owned and \\/// reused per line; the returned pointers are valid until the next call \\/// that reuses them. \\pub fn tokenize(line: []const u8, argv: *[MAX_ARGS]?[*:0]u8, buf: []u8) Result { \\ var argc: usize = 0; \\ var buf_pos: usize = 0; \\ var pipe_at: ?usize = null; \\ var pipes: usize = 0; \\ var i: usize = 0; \\ while (i < line.len) { \\ while (i < line.len and is_space(line[i])) { \\ i += 1; \\ } \\ if (i >= line.len) { \\ break; \\ } \\ if (argc >= MAX_ARGS - 1) { \\ break; \\ } \\ if (line[i] == '|') { \\ pipes += 1; \\ if (pipes > 1) { \\ return .{ .err = .too_many_pipes }; \\ } \\ pipe_at = argc; \\ argv[argc] = null; \\ argc += 1; \\ i += 1; \\ continue; \\ } \\ const start = i; \\ while (i < line.len and !is_space(line[i]) and line[i] != '|') { \\ i += 1; \\ } \\ const tok = line[start..i]; \\ if (buf_pos + tok.len + 1 > buf.len) { \\ break; \\ } \\ @memcpy(buf[buf_pos..][0..tok.len], tok); \\ buf[buf_pos + tok.len] = 0; \\ argv[argc] = buf[buf_pos .. buf_pos + tok.len :0].ptr; \\ argc += 1; \\ buf_pos += tok.len + 1; \\ } \\ if (argc < MAX_ARGS) { \\ argv[argc] = null; \\ } \\ if (pipe_at) |p| { \\ const left_argc = p; \\ const right_argc = argc - p - 1; \\ if (left_argc == 0 or right_argc == 0) { \\ return .{ .err = .empty_side }; \\ } \\ return .{ .piped = .{ .left_argc = left_argc, .right_argc = right_argc } }; \\ } \\ if (argc == 0) { \\ return .empty; \\ } \\ return .{ .single = argc }; \\} \\ ; try testing.expectEqualStrings(want, got); } test "many-item pointers are const by default; `mut` opts the pointee into mutability" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // A many-item pointer is const-pointee by default in Flash, exactly like a // slice (`[]T`) and a single pointer (`*T`); `mut` opts the pointee in. So the // plain `[*]T` / `[*:0]T` gain an implicit `const` in the Zig output while the // `[*]mut T` / `[*:0]mut T` forms drop it — each round-trips verbatim. const got = try lowerSrc(a.allocator(), \\fn f(a [*]u8, b [*]mut u8, c [*:0]u8, d [*:0]mut u8) { \\ _ = a \\ _ = b \\ _ = c \\ _ = d \\} ); const want = \\fn f(a: [*]const u8, b: [*]u8, c: [*:0]const u8, d: [*:0]u8) void { \\ _ = a; \\ _ = b; \\ _ = c; \\ _ = d; \\} \\ ; try testing.expectEqualStrings(want, got); } test "mem port: C-ABI mem*/strlen providers — const many-ptr sources, sentinel-const scan" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // The flibc `mem*` providers, ported from hand-written Zig: three C-ABI // `export fn`s LLVM's loop-idiom recognizer needs to find by name. memcpy's // 8-at-a-time fast path is the centerpiece — `[*]const u8` / `[*]const u64` // copy sources, an `&&` alignment guard lowering to `and`, and // `#ptrCast`/`#alignCast`/`#intFromPtr` builtins; strlen scans a // `[*:0]const u8`. Every `export fn` gains the explicit `callconv(.c)` C-ABI // boundary marker. The full module lives in examples/mem.flash. const got = try lowerSrc(a.allocator(), \\/// memset(dst, c, n) — fill `n` bytes of `dst` with byte `c`. Byte \\/// granular; the C ABI returns `dst`. \\export fn memset(dst [*]mut u8, c i32, n_in u64) [*]mut u8 { \\ var n = n_in \\ var p = dst \\ const byte u8 = #truncate(#as(u32, #bitCast(c))) \\ while n != 0 { \\ p[0] = byte \\ p += 1 \\ n -= 1 \\ } \\ return dst \\} \\ \\/// memcpy(dst, src, bytes) — copy `bytes` bytes from `src` to `dst` \\/// (non-overlapping). Copies 8 bytes at a time when both operands are \\/// 8-aligned, then drains the tail byte-wise. The C ABI returns `dst`. \\export fn memcpy(dst *mut anyopaque, src *anyopaque, bytes u64) *mut anyopaque { \\ var d [*]mut u8 = #ptrCast(dst) \\ var s [*]u8 = #ptrCast(src) \\ var n = bytes \\ \\ if #intFromPtr(d) % 8 == 0 && #intFromPtr(s) % 8 == 0 { \\ var d64 [*]mut u64 = #ptrCast(#alignCast(d)) \\ var s64 [*]u64 = #ptrCast(#alignCast(s)) \\ while n >= 8 { \\ d64[0] = s64[0] \\ d64 += 1 \\ s64 += 1 \\ n -= 8 \\ } \\ d = #ptrCast(d64) \\ s = #ptrCast(s64) \\ } \\ \\ while n > 0 { \\ d[0] = s[0] \\ d += 1 \\ s += 1 \\ n -= 1 \\ } \\ return dst \\} \\ \\/// strlen(s) — length of the NUL-terminated string at `s`, excluding the \\/// terminator. The lone scan the idiom recognizer would otherwise route \\/// to an external `strlen`; defining it here closes the loop. \\export fn strlen(s [*:0]u8) u64 { \\ var n u64 = 0 \\ while s[n] != 0 { \\ n += 1 \\ } \\ return n \\} ); const want = \\/// memset(dst, c, n) — fill `n` bytes of `dst` with byte `c`. Byte \\/// granular; the C ABI returns `dst`. \\export fn memset(dst: [*]u8, c: i32, n_in: u64) callconv(.c) [*]u8 { \\ var n = n_in; \\ var p = dst; \\ const byte: u8 = @truncate(@as(u32, @bitCast(c))); \\ while (n != 0) { \\ p[0] = byte; \\ p += 1; \\ n -= 1; \\ } \\ return dst; \\} \\ \\/// memcpy(dst, src, bytes) — copy `bytes` bytes from `src` to `dst` \\/// (non-overlapping). Copies 8 bytes at a time when both operands are \\/// 8-aligned, then drains the tail byte-wise. The C ABI returns `dst`. \\export fn memcpy(dst: *anyopaque, src: *const anyopaque, bytes: u64) callconv(.c) *anyopaque { \\ var d: [*]u8 = @ptrCast(dst); \\ var s: [*]const u8 = @ptrCast(src); \\ var n = bytes; \\ if (@intFromPtr(d) % 8 == 0 and @intFromPtr(s) % 8 == 0) { \\ var d64: [*]u64 = @ptrCast(@alignCast(d)); \\ var s64: [*]const u64 = @ptrCast(@alignCast(s)); \\ while (n >= 8) { \\ d64[0] = s64[0]; \\ d64 += 1; \\ s64 += 1; \\ n -= 8; \\ } \\ d = @ptrCast(d64); \\ s = @ptrCast(s64); \\ } \\ while (n > 0) { \\ d[0] = s[0]; \\ d += 1; \\ s += 1; \\ n -= 1; \\ } \\ return dst; \\} \\ \\/// strlen(s) — length of the NUL-terminated string at `s`, excluding the \\/// terminator. The lone scan the idiom recognizer would otherwise route \\/// to an external `strlen`; defining it here closes the loop. \\export fn strlen(s: [*:0]const u8) callconv(.c) u64 { \\ var n: u64 = 0; \\ while (s[n] != 0) { \\ n += 1; \\ } \\ return n; \\} \\ ; try testing.expectEqualStrings(want, got); } test "start port: extern fn prototype, explicit callconv, and a comptime #export block" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // The flibc `_start` argv shim, ported from hand-written Zig — the first // port to need three grammar forms: a bodyless `extern fn` prototype // (closing with `;`, no block), an explicit `callconv(.c)` written in the // signature, and a top-level `comptime { … }` block. The `argv` spelling // lowers to the argv pointer type. The full module is examples/start.flash. const got = try lowerSrc(a.allocator(), \\extern fn main(argc usize, argv argv) callconv(.c) noreturn \\ \\fn _start_shim(argc usize, argv argv) callconv(.c) noreturn { \\ main(argc, argv) \\} \\ \\comptime { \\ #export(&_start_shim, .{ .name = "_start", .linkage = .strong }) \\} ); const want = \\extern fn main(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn; \\ \\fn _start_shim(argc: usize, argv: [*]const ?[*:0]const u8) callconv(.c) noreturn { \\ main(argc, argv); \\} \\ \\comptime { \\ @export(&_start_shim, .{ .name = "_start", .linkage = .strong }); \\} \\ ; try testing.expectEqualStrings(want, got); } test "file imports: a quoted extensionless `use \"x\"` lowers to a sibling-file @import" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // A quoted target is a sibling-file import: the source names the stem only and // lowering supplies the backend `.zig`, distinct from the bare-name module // import. Both fold into the same run of consecutive `use` declarations, one // `const … = @import(…)` per line. const got = try lowerSrc(a.allocator(), \\use flibc \\use "syscalls" as sys ); const want = \\const flibc = @import("flibc"); \\const sys = @import("syscalls.zig"); \\ ; try testing.expectEqualStrings(want, got); } test "a `use` inside a struct body lowers to an indented struct-level @import" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // The struct-level `use` lowers to the same `const … = @import(…)` as the // top-level form, just indented one level — replacing the in-struct // `const sys = #import(…)` workaround so `use` is the one import spelling. // A quoted import is extensionless; lowering adds the backend `.zig`. const got = try lowerSrc(a.allocator(), \\const driver = struct { \\ use "syscalls" as sys \\ \\ pub fn run() {} \\} ); const want = \\const driver = struct { \\ const sys = @import("syscalls.zig"); \\ \\ pub fn run() void {} \\}; \\ ; try testing.expectEqualStrings(want, got); } test "process port: flibc process glue over a sibling-file syscalls import" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // The flibc process-glue layer, ported from hand-written Zig — the first // port to use a sibling file import (`use "syscalls" as sys`). The five // wrappers are thin C-ABI passthroughs; `exit` returns `noreturn` and its // body is a bare call, and the pointer params use the `cstr` / `argv` // aliases. The full module lives in examples/process.flash. const got = try lowerSrc(a.allocator(), \\use "syscalls" as sys \\ \\/// fork() — clone the current process. Returns the child's pid in the \\/// parent and 0 in the child. -1 on failure (NR_TASKS exhausted, \\/// out-of-memory, etc.). \\pub fn fork() i32 { \\ return sys.fork() \\} \\ \\/// exit() — terminate the current process. Never returns. The kernel \\/// flips the task to TASK_ZOMBIE; the parent's wait reaps it (frees \\/// every user/kernel page tracked by `mm`). \\pub fn exit() noreturn { \\ sys.exit() \\} \\ \\/// execve(path, argv) — path-resolved exec on slot 31. `path` is a \\/// NUL-terminated UVA; `argv` points at a NULL-terminated array of \\/// `[*:0]u8`. Returns -1 on failure with the address space untouched. \\pub fn execve(path cstr, argv argv) i32 { \\ return sys.exec_path(path, argv) \\} ); const want = \\const sys = @import("syscalls.zig"); \\ \\/// fork() — clone the current process. Returns the child's pid in the \\/// parent and 0 in the child. -1 on failure (NR_TASKS exhausted, \\/// out-of-memory, etc.). \\pub fn fork() i32 { \\ return sys.fork(); \\} \\ \\/// exit() — terminate the current process. Never returns. The kernel \\/// flips the task to TASK_ZOMBIE; the parent's wait reaps it (frees \\/// every user/kernel page tracked by `mm`). \\pub fn exit() noreturn { \\ sys.exit(); \\} \\ \\/// execve(path, argv) — path-resolved exec on slot 31. `path` is a \\/// NUL-terminated UVA; `argv` points at a NULL-terminated array of \\/// `[*:0]u8`. Returns -1 on failure with the address space untouched. \\pub fn execve(path: [*:0]const u8, argv: [*]const ?[*:0]const u8) i32 { \\ return sys.exec_path(path, argv); \\} \\ ; try testing.expectEqualStrings(want, got); } test "heap port: bump allocator — unary `~` alignment mask, optional many-ptr, empty no-op body" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // flibc's heap layer, ported from hand-written Zig. The port that surfaces // the unary bitwise-NOT `~`, used in the 8-byte alignment mask // `~(ALIGN - 1)`. `malloc` returns an optional many-item pointer // (`?[*]u8`); `free` is an empty no-op body with an ignored `_` param. The // grouping parens are preserved verbatim and the two single-statement `if`s // lower with mandatory braces. The full module lives in examples/heap.flash. const got = try lowerSrc(a.allocator(), \\use "syscalls" as sys \\ \\const ALIGN u64 = 8 \\ \\/// malloc(n) — return a pointer to a freshly-allocated region of at \\/// least `n` bytes (rounded up to 8). Returns null on failure \\/// (kernel rejects out-of-bounds break, propagated as a negative sbrk \\/// return). The memory is zeroed by the kernel's get_free_page on first \\/// touch via the do_data_abort demand-alloc path. \\/// \\/// C `malloc(0)` is implementation-defined; flibc returns null. \\/// Callers must distinguish `len == 0` themselves before treating \\/// null as failure. \\pub fn malloc(n u64) ?[*]mut u8 { \\ if n == 0 { return null } \\ const aligned u64 = (n + ALIGN - 1) & ~(ALIGN - 1) \\ const prev = sys.sbrk(#intCast(aligned)) \\ if prev < 0 { return null } \\ return #ptrFromInt(#as(u64, #bitCast(prev))) \\} \\ \\/// free — no-op. The bump allocator never reclaims individual \\/// allocations; the kernel reaps the entire heap on process exit \\/// (do_wait clears every page in `mm.user_pages`). Provided so consumers \\/// can keep the alloc/free pairing readable even though the call is \\/// inert. \\pub fn free(_ ?[*]mut u8) {} ); const want = \\const sys = @import("syscalls.zig"); \\ \\const ALIGN: u64 = 8; \\ \\/// malloc(n) — return a pointer to a freshly-allocated region of at \\/// least `n` bytes (rounded up to 8). Returns null on failure \\/// (kernel rejects out-of-bounds break, propagated as a negative sbrk \\/// return). The memory is zeroed by the kernel's get_free_page on first \\/// touch via the do_data_abort demand-alloc path. \\/// \\/// C `malloc(0)` is implementation-defined; flibc returns null. \\/// Callers must distinguish `len == 0` themselves before treating \\/// null as failure. \\pub fn malloc(n: u64) ?[*]u8 { \\ if (n == 0) { \\ return null; \\ } \\ const aligned: u64 = (n + ALIGN - 1) & ~(ALIGN - 1); \\ const prev = sys.sbrk(@intCast(aligned)); \\ if (prev < 0) { \\ return null; \\ } \\ return @ptrFromInt(@as(u64, @bitCast(prev))); \\} \\ \\/// free — no-op. The bump allocator never reclaims individual \\/// allocations; the kernel reaps the entire heap on process exit \\/// (do_wait clears every page in `mm.user_pages`). Provided so consumers \\/// can keep the alloc/free pairing readable even though the call is \\/// inert. \\pub fn free(_: ?[*]u8) void {} \\ ; try testing.expectEqualStrings(want, got); } test "flibc port: `pub use` re-exports lower to `pub const … = @import`, interleaved with value re-exports" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // flibc's re-export hub, ported from hand-written Zig. The port that // surfaces `pub use`: a re-exported import (`pub use "io" as io` → // `pub const io = @import("io.zig")`), packed in a run with the bare-module // `use syscall_defs as defs`. The value re-exports (`pub const printf = // io.printf`) are ordinary `pub const`s over a member-access expression. // The full 35-declaration module lives in examples/flibc.flash; its // declaration stream is token-identical to the reference, modulo the dropped // `//` comments and Flash's uniform one-blank-per-declaration layout. const got = try lowerSrc(a.allocator(), \\pub use "syscalls" as sys \\pub use "io" as io \\use syscall_defs as defs \\pub const Dirent = defs.Dirent \\pub const printf = io.printf \\pub use "heap" as heap \\pub const malloc = heap.malloc ); const want = \\pub const sys = @import("syscalls.zig"); \\pub const io = @import("io.zig"); \\const defs = @import("syscall_defs"); \\ \\pub const Dirent = defs.Dirent; \\ \\pub const printf = io.printf; \\ \\pub const heap = @import("heap.zig"); \\ \\pub const malloc = heap.malloc; \\ ; try testing.expectEqualStrings(want, got); } test "execvp port: sentinel-slice return type and the if-expression driver select" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // flibc's bare-name program resolver, ported from hand-written Zig. The port // that surfaces the sentinel-terminated *slice* type — `?[:0]mut u8`, a // mutable `[:0]u8`, lowering to `?[:0]u8` — and the driver select: a comptime // `if`-expression whose two `struct { … }` arms pick the real aarch64 driver // or the host stub, so the off-target SVC path is never analysed. The // in-struct sibling import (`use "syscalls" as sys`) lowers to a struct-level // `const sys = @import("syscalls.zig")` — the same `use` form as at the top // level, just indented; the `cstr` / `argv` spelling aliases stand in for the // two C-string pointer types. The full module lives in examples/execvp.flash; its core is // token-identical to the reference, modulo the mandatory braces on the // single-statement `if`s. The `&&` operator lowers to `and`. const got = try lowerSrc(a.allocator(), \\use builtin \\ \\const has_driver = builtin.cpu.arch == .aarch64 && builtin.target.os.tag == .freestanding \\ \\pub fn resolve(name []u8, out []mut u8) ?[:0]mut u8 { \\ if name.len == 0 { return null } \\ out[name.len] = 0 \\ return out[0..name.len :0] \\} \\ \\pub const execvp = driver.execvp \\ \\const driver = if (has_driver) struct { \\ use "syscalls" as sys \\ \\ pub fn execvp(name cstr, argv argv) i32 { \\ return sys.exec_path(name, argv) \\ } \\} else struct { \\ pub fn execvp(_ cstr, _ argv) i32 { \\ return -1 \\ } \\} ); const want = \\const builtin = @import("builtin"); \\ \\const has_driver = builtin.cpu.arch == .aarch64 and builtin.target.os.tag == .freestanding; \\ \\pub fn resolve(name: []const u8, out: []u8) ?[:0]u8 { \\ if (name.len == 0) { \\ return null; \\ } \\ out[name.len] = 0; \\ return out[0..name.len :0]; \\} \\ \\pub const execvp = driver.execvp; \\ \\const driver = if (has_driver) struct { \\ const sys = @import("syscalls.zig"); \\ \\ pub fn execvp(name: [*:0]const u8, argv: [*]const ?[*:0]const u8) i32 { \\ return sys.exec_path(name, argv); \\ } \\} else struct { \\ pub fn execvp(_: [*:0]const u8, _: [*]const ?[*:0]const u8) i32 { \\ return -1; \\ } \\}; \\ ; try testing.expectEqualStrings(want, got); } test "io port: comptime-format printf — comptime params/vars, inline while, +%/++/[_]u8 literal" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // flibc's console I/O layer, ported from hand-written Zig. The port that // surfaces the comptime-format machinery: a `comptime fmt` parameter with // `args anytype`, `comptime var` walk counters, an `inline while` over the // format string, the wrapping add `+%`, array/string concat `++`, and an // inferred-length array literal `&[_]u8{spec}` inside `#compileError`. The // `switch` used as a bare statement takes no trailing `;` (a Zig block-form // statement). The full module lives in examples/io.flash; its core lowers // token-identical to the reference. const got = try lowerSrc(a.allocator(), \\pub fn printf(comptime fmt []u8, args anytype) { \\ comptime var i usize = 0 \\ inline while i < fmt.len { \\ const c = fmt[i] \\ const m = c +% 1 \\ emit(m, args[i]) \\ i += 1 \\ } \\} \\ \\inline fn emit(comptime spec u8, arg anytype) { \\ switch spec { \\ 'x' => put(arg), \\ else => #compileError("bad %" ++ &[_]u8{spec}), \\ } \\} ); const want = \\pub fn printf(comptime fmt: []const u8, args: anytype) void { \\ comptime var i: usize = 0; \\ inline while (i < fmt.len) { \\ const c = fmt[i]; \\ const m = c +% 1; \\ emit(m, args[i]); \\ i += 1; \\ } \\} \\ \\inline fn emit(comptime spec: u8, arg: anytype) void { \\ switch (spec) { \\ 'x' => put(arg), \\ else => @compileError("bad %" ++ &[_]u8{spec}), \\ } \\} \\ ; try testing.expectEqualStrings(want, got); } test "comptime binding: `comptime const` wraps its value, `comptime var` keeps the prefix" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // Zig rejects `comptime const` as redundant — its own diagnostic directs // the comptime-ness onto the initializer ("wrap the initialization // expression with 'comptime'"). So a comptime immutable binding lowers to // `const x = comptime e`, preserving the force-comptime intent on the value // rather than emitting the invalid `comptime const x = e`. A `comptime var` // is valid Zig and keeps its prefix unchanged. const got = try lowerSrc(a.allocator(), \\pub fn f() { \\ comptime const N = 4 \\ comptime var i usize = 0 \\ i += N \\} ); const want = \\pub fn f() void { \\ const N = comptime 4; \\ comptime var i: usize = 0; \\ i += N; \\} \\ ; try testing.expectEqualStrings(want, got); } test "keys port: VT100 decoder — switch ranges, multi-pattern and labeled-block prongs, driver select" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // flibc's console key decoder, ported from hand-written Zig. The first pure // port: it adds no new grammar. This subset exercises the combination the // reference leans on — a struct with a defaulted field and a nested `const` // enum, a method over `*mut Decoder` whose `return switch` body carries a // labeled-block prong (`blk: { … break :blk … }`), a multi-pattern prong // (`'\r', '\n' =>`), an inclusive range prong (`0x20...0x7e =>`), the // comptime gate `&&` (lowering to `and`), and the driver-select // `if (has_driver) struct {…} else struct {…}`. A brace-less single-statement // `if` gains its mandatory braces. The full module lives in // examples/keys.flash; its core lowers token-identical to the reference. const got = try lowerSrc(a.allocator(), \\const has_driver = builtin.cpu.arch == .aarch64 && builtin.target.os.tag == .freestanding \\ \\pub const Decoder = struct { \\ state State = .ground, \\ \\ const State = enum { ground, esc, csi } \\ \\ fn atGround(self *mut Decoder, b u8) Event { \\ return switch b { \\ 0x1b => blk: { \\ self.state = .esc \\ break :blk .{ .key = .none } \\ }, \\ '\r', '\n' => .{ .key = .enter }, \\ 0x20...0x7e => .{ .key = .char, .ch = b }, \\ else => .{ .key = .none }, \\ } \\ } \\} \\ \\const driver = if (has_driver) struct { \\ pub fn readKey() Event { \\ var b u8 = 0 \\ if (b >= '0' && b <= '9') || b == 0 { \\ return .{ .key = .eof } \\ } \\ return .{ .key = .none } \\ } \\} else struct { \\ pub fn readKey() Event { \\ return .{ .key = .eof } \\ } \\} ); const want = \\const has_driver = builtin.cpu.arch == .aarch64 and builtin.target.os.tag == .freestanding; \\ \\pub const Decoder = struct { \\ state: State = .ground, \\ \\ const State = enum { \\ ground, \\ esc, \\ csi, \\ }; \\ \\ fn atGround(self: *Decoder, b: u8) Event { \\ return switch (b) { \\ 0x1b => blk: { \\ self.state = .esc; \\ break :blk .{ .key = .none }; \\ }, \\ '\r', '\n' => .{ .key = .enter }, \\ 0x20...0x7e => .{ .key = .char, .ch = b }, \\ else => .{ .key = .none }, \\ }; \\ } \\}; \\ \\const driver = if (has_driver) struct { \\ pub fn readKey() Event { \\ var b: u8 = 0; \\ if ((b >= '0' and b <= '9') or b == 0) { \\ return .{ .key = .eof }; \\ } \\ return .{ .key = .none }; \\ } \\} else struct { \\ pub fn readKey() Event { \\ return .{ .key = .eof }; \\ } \\}; \\ ; try testing.expectEqualStrings(want, got); } test "completion port: parenthesised value-if condition, optional-capture, range-for" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // flibc's tab-completion core, ported from hand-written Zig. The second pure // port — but it surfaces one grammar gap: a value `if`-expression whose // then-arm is a `.enumLiteral` needs its condition parenthesised // (`if (best > typed) .progressed else .stuck`), so the `.progressed` is not // glued onto the condition's tail as a member access. The lowered Zig keeps // the single condition parens (no doubling). The rest reuses landed surface: // an optional-capture `if (slash) |s| { … }`, a `?usize` optional, a // range-`for` loop (`for (0..line.len)`), and Flash's mandatory braces on the // single-statement `if`. The full module lives in examples/completion.flash; // its core lowers token-identical to the reference. const got = try lowerSrc(a.allocator(), \\pub fn classify(count usize, best usize, typed usize) Tab { \\ if count == 0 { \\ return .empty \\ } \\ return if (best > typed) .progressed else .stuck \\} \\ \\pub fn split(line []u8) ?usize { \\ var slash ?usize = null \\ for i in 0..line.len { \\ if line[i] == '/' { \\ slash = i \\ } \\ } \\ if slash |s| { \\ return s \\ } \\ return null \\} ); const want = \\pub fn classify(count: usize, best: usize, typed: usize) Tab { \\ if (count == 0) { \\ return .empty; \\ } \\ return if (best > typed) .progressed else .stuck; \\} \\ \\pub fn split(line: []const u8) ?usize { \\ var slash: ?usize = null; \\ for (0..line.len) |i| { \\ if (line[i] == '/') { \\ slash = i; \\ } \\ } \\ if (slash) |s| { \\ return s; \\ } \\ return null; \\} \\ ; try testing.expectEqualStrings(want, got); } test "pager port: value+pointer receivers, void mutator, #intCast, const-default slice fields" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // flibc's pager core, ported from hand-written Zig. The third pure port, and // it adds no new grammar — but it is the first struct to mix a value receiver // (`self Pager` -> `self: Pager`, the read-only queries) with a pointer // receiver (`self *mut Pager` -> `self: *Pager`, the scroll mutators), and the // first with a void-returning method (no return type -> `void`). The const-default // slice convention carries the field types: an immutable `[]u8` lowers to // `[]const u8`, while a mutable `[]mut u32` lowers to a bare `[]u32`. It also // reuses `#intCast`, a value `if`-expression, and Flash's mandatory braces on // the single-statement `break`. The full module lives in examples/pager.flash; // its core lowers token-identical to the reference. const got = try lowerSrc(a.allocator(), \\pub const Pager = struct { \\ text []u8, \\ lines []mut u32, \\ n usize, \\ top usize, \\ rows usize, \\ \\ pub fn init(text []u8, slots []mut u32, rows usize) Pager { \\ var n usize = 0 \\ if text.len > 0 && slots.len > 0 { \\ slots[0] = 0 \\ n = 1 \\ for i in 0..text.len { \\ if text[i] == '\n' && i + 1 < text.len { \\ if n >= slots.len { \\ break \\ } \\ slots[n] = #intCast(i + 1) \\ n += 1 \\ } \\ } \\ } \\ return .{ .text = text, .lines = slots, .n = n, .top = 0, .rows = rows } \\ } \\ \\ pub fn maxTop(self Pager) usize { \\ return if (self.n > self.rows) self.n - self.rows else 0 \\ } \\ \\ pub fn down(self *mut Pager, k usize) { \\ const mt = self.maxTop() \\ self.top = if (self.top + k > mt) mt else self.top + k \\ } \\} ); const want = \\pub const Pager = struct { \\ text: []const u8, \\ lines: []u32, \\ n: usize, \\ top: usize, \\ rows: usize, \\ \\ pub fn init(text: []const u8, slots: []u32, rows: usize) Pager { \\ var n: usize = 0; \\ if (text.len > 0 and slots.len > 0) { \\ slots[0] = 0; \\ n = 1; \\ for (0..text.len) |i| { \\ if (text[i] == '\n' and i + 1 < text.len) { \\ if (n >= slots.len) { \\ break; \\ } \\ slots[n] = @intCast(i + 1); \\ n += 1; \\ } \\ } \\ } \\ return .{ .text = text, .lines = slots, .n = n, .top = 0, .rows = rows }; \\ } \\ \\ pub fn maxTop(self: Pager) usize { \\ return if (self.n > self.rows) self.n - self.rows else 0; \\ } \\ \\ pub fn down(self: *Pager, k: usize) void { \\ const mt = self.maxTop(); \\ self.top = if (self.top + k > mt) mt else self.top + k; \\ } \\}; \\ ; try testing.expectEqualStrings(want, got); } test "fsh port: `.?` optional unwrap — value-if fallback and pipe-stage name assert" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); // fsh's command-execution layer, ported from hand-written Zig. The leaf that // surfaces the `.?` optional-unwrap postfix: `cd` takes its argument or falls // back to "/" through a value-form `if` (`if (argc >= 2) argv[1].? else "/"`), // and a pipe stage asserts its command name is present before exec // (`left[0].?`). The argv vector keeps its `*mut [N]?[*:0]u8` shape, the // recast pipe view its `[*]const ?[*:0]const u8`, each spelled as the // const-default convention maps it. The full module lives in // examples/fsh.flash; its core lowers token-identical to the reference. const got = try lowerSrc(a.allocator(), \\use flibc \\ \\fn cd(argv *mut [16]?[*:0]mut u8, argc usize) { \\ const target [*:0]u8 = if (argc >= 2) argv[1].? else "/" \\ _ = flibc.chdir(target) \\} \\ \\fn pipeLeft(argv *mut [16]?[*:0]mut u8) { \\ const left [*]?[*:0]u8 = #ptrCast(argv) \\ _ = flibc.execvp(left[0].?, left) \\} ); const want = \\const flibc = @import("flibc"); \\ \\fn cd(argv: *[16]?[*:0]u8, argc: usize) void { \\ const target: [*:0]const u8 = if (argc >= 2) argv[1].? else "/"; \\ _ = flibc.chdir(target); \\} \\ \\fn pipeLeft(argv: *[16]?[*:0]u8) void { \\ const left: [*]const ?[*:0]const u8 = @ptrCast(argv); \\ _ = flibc.execvp(left[0].?, left); \\} \\ ; try testing.expectEqualStrings(want, got); } test "inline assembly: operands, positional sections, and the volatile modifier lower byte-for-byte" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn spin() { \\ asm volatile ("wfe") \\} \\ \\fn read_iar() u64 { \\ var iar u64 = undefined \\ asm volatile ("mrs %[iar], S3_0_C12_C12_0" \\ : [iar] "=r" (iar), \\ ) \\ return iar \\} \\ \\fn eoi(iar u64) { \\ asm volatile ("msr S3_0_C12_C12_1, %[iar]" \\ : \\ : [iar] "r" (iar), \\ ) \\} \\ \\fn exec_path(path usize, argv usize) i32 { \\ return asm volatile ("svc #0" \\ : [ret] "={x0}" (-> i32), \\ : [nr] "{x8}" (11), \\ [path] "{x0}" (path), \\ [argv] "{x1}" (argv), \\ : .{ .memory = true }) \\} ); // The bare form stays single-line; any output/input operand breaks the // expression across lines with one positional colon per section. An empty // earlier section still occupies its `:` line (`eoi`), the trailing clobber // hugs the `)`, and an output/input last section closes the `)` on its own // line at the statement's depth (`read_iar`). Verified ast-check-clean and // fmt-idempotent against the FlashOS syscall/GIC asm corpus. const want = \\fn spin() void { \\ asm volatile ("wfe"); \\} \\ \\fn read_iar() u64 { \\ var iar: u64 = undefined; \\ asm volatile ("mrs %[iar], S3_0_C12_C12_0" \\ : [iar] "=r" (iar), \\ ); \\ return iar; \\} \\ \\fn eoi(iar: u64) void { \\ asm volatile ("msr S3_0_C12_C12_1, %[iar]" \\ : \\ : [iar] "r" (iar), \\ ); \\} \\ \\fn exec_path(path: usize, argv: usize) i32 { \\ return asm volatile ("svc #0" \\ : [ret] "={x0}" (-> i32), \\ : [nr] "{x8}" (11), \\ [path] "{x0}" (path), \\ [argv] "{x1}" (argv), \\ : .{ .memory = true }); \\} \\ ; try testing.expectEqualStrings(want, got); } test "inline assembly: multiline template, clobber-only single line, non-volatile, and a `#` intrinsic input" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\export fn user_entry() { \\ asm volatile ( \\ \\bl pid1_main \\ \\mov x8, #2 \\ \\svc #0 \\ ) \\} \\ \\fn flush(addr u64) { \\ asm volatile ( \\ \\dc cvau, %[a] \\ \\dsb ish \\ \\isb \\ : \\ : [a] "r" (addr), \\ : .{ .memory = true }) \\} \\ \\fn barrier() { \\ asm volatile ("dsb sy" ::: .{ .memory = true }) \\} \\ \\fn one() i32 { \\ return asm ("mov %[r], #1" \\ : [r] "=r" (-> i32), \\ ) \\} \\ \\fn set_pri() { \\ asm volatile ("msr S3_0_C4_C6_0, %[v]" \\ : \\ : [v] "r" (#as(u64, 255)), \\ ) \\} ); // A `\\` multiline template heads its own line(s) and forces the multi-line // layout even with no operands (`user_entry`). With no output and no input // operand and a single-string template, the expression stays on one line — // bare, or clobber-only with three tight colons (`barrier`). `asm` without // `volatile` keeps the space before `(` (`one`). A `#`-intrinsic input // lowers through the ordinary expression path (`#as` → `@as`). const want = \\export fn user_entry() callconv(.c) void { \\ asm volatile ( \\ \\bl pid1_main \\ \\mov x8, #2 \\ \\svc #0 \\ ); \\} \\ \\fn flush(addr: u64) void { \\ asm volatile ( \\ \\dc cvau, %[a] \\ \\dsb ish \\ \\isb \\ : \\ : [a] "r" (addr), \\ : .{ .memory = true }); \\} \\ \\fn barrier() void { \\ asm volatile ("dsb sy" ::: .{ .memory = true }); \\} \\ \\fn one() i32 { \\ return asm ("mov %[r], #1" \\ : [r] "=r" (-> i32), \\ ); \\} \\ \\fn set_pri() void { \\ asm volatile ("msr S3_0_C4_C6_0, %[v]" \\ : \\ : [v] "r" (@as(u64, 255)), \\ ); \\} \\ ; try testing.expectEqualStrings(want, got); } test "float literals lower verbatim — byte-identical to zig fmt" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const pi = 3.14 \\const grav = 9.81e-2 \\const tiny = 1_000.5e+0 ); // Float literals pass through unchanged: zig fmt emits them verbatim, and // Zig's grammar accepts the same decimal-float forms Flash defines. The // `_` digit separator and the signed exponent are both valid Zig syntax. const want = \\const pi = 3.14; \\ \\const grav = 9.81e-2; \\ \\const tiny = 1_000.5e+0; \\ ; try testing.expectEqualStrings(want, got); } test "reserved value keywords lower verbatim — byte-identical to zig fmt" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const yes = true \\const no = false \\const nothing = null \\fn halt() noreturn { \\ unreachable \\} \\var seed u32 = undefined ); // `true`/`false`/`null`/`undefined`/`unreachable` are reserved value words in // Flash and spelled identically in Zig, so they pass straight through: the // emitted text is byte-for-byte what zig fmt produces. Reserving them changed // how they parse (a value_word leaf, not an ident), not how they lower. const want = \\const yes = true; \\ \\const no = false; \\ \\const nothing = null; \\ \\fn halt() noreturn { \\ unreachable; \\} \\ \\var seed: u32 = undefined; \\ ; try testing.expectEqualStrings(want, got); } test "the argv/cstr builtin type aliases yield to a same-named top-level declaration" { // Unshadowed, the aliases expand to the two pointer types the corpus relies // on but the surface gives no syntax for. { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const a cstr = x \\const v argv = y ); const want = \\const a: [*:0]const u8 = x; \\ \\const v: [*]const ?[*:0]const u8 = y; \\ ; try testing.expectEqualStrings(want, got); } // When the program declares a top-level `cstr` / `argv` constant, that // declaration wins: the builtin rewrite is suppressed and the name lowers // verbatim, so a user (or a future standard library) alias is no longer // silently overridden. The corpus declares neither, so no emitted byte moves. { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const cstr = u8 \\const argv = u32 \\const a cstr = x \\const v argv = y ); const want = \\const cstr = u8; \\ \\const argv = u32; \\ \\const a: cstr = x; \\ \\const v: argv = y; \\ ; try testing.expectEqualStrings(want, got); } } test "wrapping operators `-%` / `*%` lower verbatim — byte-identical to zig fmt" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const d = x -% y \\const p = x *% y \\const m = a -% b *% c ); // Zig spells the wrapping operators identically and gives them the same // precedence, so they pass straight through: `*%` binds tighter than `-%`, // so `a -% b *% c` needs no parentheses and is byte-for-byte what zig fmt // emits. (`+%` already round-tripped this way.) const want = \\const d = x -% y; \\ \\const p = x *% y; \\ \\const m = a -% b *% c; \\ ; try testing.expectEqualStrings(want, got); } test "composite-type aliases lower to Zig type aliases — byte-identical to zig fmt" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const F = *fn(u8) u8 \\const O = ?u8 \\const S = []u8 \\const M = *mut fn() void \\fn take(g Get([]u8)) void { \\ _ = g \\} ); // A `?`/`*`/`[`/`fn`-led composite type is an expression (a `type_lit`), so // a type alias needs no wrapper: the alias value lowers through emitType // exactly as the same type in annotation position — `*fn` gains the const // pointee (`*const fn (u8) u8`, with zig fmt's anonymous-fn space), `[]u8` // the const element. A composite generic argument (`Get([]u8)`) rides the // same node. This output is byte-identical to `zig fmt`. const want = \\const F = *const fn (u8) u8; \\ \\const O = ?u8; \\ \\const S = []const u8; \\ \\const M = *fn () void; \\ \\fn take(g: Get([]const u8)) void { \\ _ = g; \\} \\ ; try testing.expectEqualStrings(want, got); } test "defer/errdefer block form lowers to a Zig brace body — byte-identical to zig fmt" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn run(fd i32) !void { \\ defer { \\ close(fd) \\ close(fd + 1) \\ } \\ errdefer { \\ close(0) \\ } \\ defer close(fd) \\ return \\} ); // The block body renders like any brace body — statements indented one // level, the closing `}` without a `;` — and the single-statement form is // untouched. This output is byte-identical to `zig fmt`. const want = \\fn run(fd: i32) !void { \\ defer { \\ close(fd); \\ close(fd + 1); \\ } \\ errdefer { \\ close(0); \\ } \\ defer close(fd); \\ return; \\} \\ ; try testing.expectEqualStrings(want, got); } test "test blocks lower to Zig test blocks — byte-identical to zig fmt" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\use std \\ \\fn add(a i32, b i32) i32 { \\ return a + b \\} \\ \\test "add sums two integers" { \\ try std.testing.expectEqual(5, add(2, 3)) \\} \\ \\test "empty body is accepted" {} ); // A test block is its own unit (one blank line on each side) and lowers // one-to-one: the quoted name verbatim, the body as a brace body, an empty // body collapsed to `{}`. This output is byte-identical to `zig fmt`. const want = \\const std = @import("std"); \\ \\fn add(a: i32, b: i32) i32 { \\ return a + b; \\} \\ \\test "add sums two integers" { \\ try std.testing.expectEqual(5, add(2, 3)); \\} \\ \\test "empty body is accepted" {} \\ ; try testing.expectEqualStrings(want, got); } test "loop else arms and the if else-capture lower — byte-identical to zig fmt" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn f(xs []u8, c bool) void { \\ if next() |v| { \\ consume(v) \\ } else |err| { \\ log(err) \\ } \\ while next() |v| { \\ consume(v) \\ } else |err| { \\ log(err) \\ } \\ while c { \\ step() \\ } else { \\ done() \\ } \\ for x in xs { \\ consume(x) \\ } else { \\ done() \\ } \\ if c {} else {} \\} ); // Each else arm rides on the closing brace (`} else {`), the error capture // as `else |err|`; an empty arm collapses to `{}`. This output is // byte-identical to `zig fmt`. const want = \\fn f(xs: []const u8, c: bool) void { \\ if (next()) |v| { \\ consume(v); \\ } else |err| { \\ log(err); \\ } \\ while (next()) |v| { \\ consume(v); \\ } else |err| { \\ log(err); \\ } \\ while (c) { \\ step(); \\ } else { \\ done(); \\ } \\ for (xs) |x| { \\ consume(x); \\ } else { \\ done(); \\ } \\ if (c) {} else {} \\} \\ ; try testing.expectEqualStrings(want, got); } test "tuple types and multi-return lower to Zig tuples — byte-identical to zig fmt" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\const Pair = (u8, bool) \\fn pair() (u8, bool) { \\ return 42, true \\} \\fn lit() Pair { \\ return .{ 7, false } \\} \\fn first(t (u8, (u8, bool))) u8 { \\ return t[0] + t[1][0] \\} \\fn three() (u8, u8, u8) { \\ return 1, 2, 3 \\} ); // A tuple type lowers to Zig's inline positional struct (`struct { A, B }`, // zig fmt's one-line layout) in every position — alias value, return, // parameter, nested element. The multi-return value list folds into one // anonymous tuple literal (`return .{ 42, true };`); a written `.{ … }` // return and the postfix tuple index lower verbatim. This output is // byte-identical to `zig fmt`. const want = \\const Pair = struct { u8, bool }; \\ \\fn pair() struct { u8, bool } { \\ return .{ 42, true }; \\} \\ \\fn lit() Pair { \\ return .{ 7, false }; \\} \\ \\fn first(t: struct { u8, struct { u8, bool } }) u8 { \\ return t[0] + t[1][0]; \\} \\ \\fn three() struct { u8, u8, u8 } { \\ return .{ 1, 2, 3 }; \\} \\ ; try testing.expectEqualStrings(want, got); } test "destructuring binds and assigns lower to Zig destructures — byte-identical to zig fmt" { var a = std.heap.ArenaAllocator.init(testing.allocator); defer a.deinit(); const got = try lowerSrc(a.allocator(), \\fn pair() (u8, bool) { \\ return 42, true \\} \\fn demo() void { \\ a, b := pair() \\ _ = a \\ _ = b \\ tok, _ := pair() \\ _ = tok \\ _, ok := pair() \\ _ = ok \\ var x, y = pair() \\ x, y = pair() \\ var arr [3]u8 = .{ 0, 0, 0 } \\ arr[0], y = pair() \\ _ = x \\} ); // The binding keyword repeats per name (Zig's native destructure // spelling); a `_` skip stays `_`; the assignment list lowers verbatim. // This output is byte-identical to `zig fmt`. const want = \\fn pair() struct { u8, bool } { \\ return .{ 42, true }; \\} \\ \\fn demo() void { \\ const a, const b = pair(); \\ _ = a; \\ _ = b; \\ const tok, _ = pair(); \\ _ = tok; \\ _, const ok = pair(); \\ _ = ok; \\ var x, var y = pair(); \\ x, y = pair(); \\ var arr: [3]u8 = .{ 0, 0, 0 }; \\ arr[0], y = pair(); \\ _ = x; \\} \\ ; try testing.expectEqualStrings(want, got); }