package zig_test import ( "fmt" "strings" "testing" "git.frop.prof/luke/go-zig-compiler/internal/zig" ) func Expect(expected, actual string) error { if expected != actual { message := fmt.Sprintf("\nExpected:\n%v\nActual:\n%v", expected, actual) message += fmt.Sprintf("\n%q", actual) // Find the first difference (handle rune/byte mismatch) minLen := len(expected) if len(actual) < minLen { minLen = len(actual) } for i := 0; i < minLen; i++ { if expected[i] != actual[i] { message += fmt.Sprintf("\nDifference at index %d: expected '%c', got '%c'", i, expected[i], actual[i]) break } } if len(expected) != len(actual) { message += fmt.Sprintf("\nLength mismatch: expected %d bytes, got %d bytes", len(expected), len(actual)) } return fmt.Errorf("%s", message) } return nil } // runZigASTTest is a helper to check if the Zig AST renders as expected. func runZigASTTest(t *testing.T, expected string, root *zig.Root) { t.Helper() sb := new(strings.Builder) err := zig.Write(sb, root) if err != nil { t.FailNow() } actual := sb.String() if err = Expect(expected, actual); err != nil { t.Error(err) } } func TestHelloWorld(t *testing.T) { expected := `//! Hello, world! const std = @import("std"); fn main() void { std.debug.print("Hello, world!\n", .{}); } ` root := &zig.Root{ ContainerDocComment: "Hello, world!", ContainerMembers: []*zig.ContainerMember{ { Decl: zig.DeclareGlobalVar("std", zig.Call( zig.Id("@import"), zig.StringLit("std"), ), zig.GlobalVarConst), }, { Decl: zig.DeclareFn( "main", zig.Id("void"), zig.NewBlock( zig.NewExprStmt( zig.Call( zig.FieldAccess( zig.FieldAccess(zig.Id("std"), "debug"), "print", ), zig.StringLit(`Hello, world!\n`), zig.InitList(), ), ), ), nil, // params 0, // flags ), }, }, } runZigASTTest(t, expected, root) } func TestCompoundFeatures(t *testing.T) { expected := `const ProcessError = error{ InvalidInput, OutOfBounds, }; fn processData(values: [5]?i32) ProcessError!i32 { var result: i32 = 0; defer std.debug.print("Cleanup\n", .{}); for (values, 0..) |opt_val, idx| { if (opt_val) |val| { switch (val) { -10...-1 => result += -val, 0 => continue, 1...10 => { if (idx > 3) break; result += val; }, else => return ProcessError.InvalidInput, } } else { if (idx == 0) return ProcessError.InvalidInput; break; } } if (values[0] == null) { unreachable; } return result; } test "processData" { const data = .{ 5, -3, 0, null, 10 }; const result = try processData(data); try std.testing.expectEqual(@as(i32, 8), result); } ` root := &zig.Root{ ContainerMembers: []*zig.ContainerMember{ { Decl: zig.DeclareGlobalVar("ProcessError", zig.ErrorSet("InvalidInput", "OutOfBounds"), zig.GlobalVarConst, ), }, { Decl: zig.DeclareFn( "processData", zig.ErrorUnionType(zig.Id("ProcessError"), zig.Id("i32")), zig.NewBlock( // var result: i32 = 0; zig.DeclareVarStmt(false, []string{"result"}, zig.Id("i32"), zig.IntLit("0")), // defer std.debug.print("Cleanup\n", .{}); zig.Defer( zig.NewExprStmt( zig.Call( zig.FieldAccess( zig.FieldAccess(zig.Id("std"), "debug"), "print", ), zig.StringLit(`Cleanup\n`), zig.InitList(), ), ), ), // for (values, 0..) |opt_val, idx| { ... } zig.ForLoop( []zig.ForArg{ zig.ForArgExpr(zig.Id("values")), {Expr: zig.IntLit("0"), From: zig.IntLit("")}, }, zig.PayloadNames([]string{"opt_val", "idx"}, []bool{false, false}), zig.NewBlockStmt( // if (opt_val) |val| { ... } else { ... } zig.IfWithPayload( zig.Id("opt_val"), zig.PayloadNames([]string{"val"}, []bool{false}), zig.NewBlockStmt( // switch (val) { ... } zig.Switch( zig.Id("val"), // -10...-1 => result += -val, zig.Prong( []*zig.SwitchCase{ zig.Case( zig.Unary("-", zig.IntLit("10")), zig.Unary("-", zig.IntLit("1")), ), }, nil, zig.NewExprStmt( zig.Binary("+=", zig.Id("result"), zig.Unary("-", zig.Id("val")), ), ), ), // 0 => continue, zig.Prong( []*zig.SwitchCase{ zig.Case(zig.IntLit("0"), nil), }, nil, zig.Continue(""), ), // 1...10 => { ... } zig.Prong( []*zig.SwitchCase{ zig.Case(zig.IntLit("1"), zig.IntLit("10")), }, nil, zig.NewBlockStmt( zig.If( zig.Binary(">", zig.Id("idx"), zig.IntLit("3")), zig.Break("", nil), nil, ), zig.NewExprStmt( zig.Binary("+=", zig.Id("result"), zig.Id("val")), ), ), ), // else => return ProcessError.InvalidInput, zig.Prong( []*zig.SwitchCase{zig.ElseCase()}, nil, zig.Return( zig.FieldAccess(zig.Id("ProcessError"), "InvalidInput"), ), ), ), ), // else block zig.NewBlockStmt( zig.If( zig.Binary("==", zig.Id("idx"), zig.IntLit("0")), zig.Return(zig.FieldAccess(zig.Id("ProcessError"), "InvalidInput")), nil, ), zig.Break("", nil), ), ), ), nil, ), // if (values[0] == null) { unreachable; } zig.If( zig.Binary("==", zig.Index(zig.Id("values"), zig.IntLit("0")), zig.Id("null"), ), zig.NewBlockStmt( zig.NewExprStmt(zig.Unreachable()), ), nil, ), // return result; zig.Return(zig.Id("result")), ), []*zig.ParamDecl{ zig.Param("values", zig.ArrayType(zig.IntLit("5"), zig.OptionalType(zig.Id("i32")))), }, 0, ), }, { Decl: zig.Test("processData", zig.NewBlock( // const data = [_]?i32{ 5, -3, 0, null, 10 }; zig.DeclareVarStmt(true, []string{"data"}, nil, &zig.InitListExpr{ Values: []zig.Expr{ zig.IntLit("5"), zig.Unary("-", zig.IntLit("3")), zig.IntLit("0"), zig.Id("null"), zig.IntLit("10"), }, }, ), // const result = try processData(data); zig.DeclareVarStmt(true, []string{"result"}, nil, zig.Try(zig.Call(zig.Id("processData"), zig.Id("data"))), ), // try std.testing.expectEqual(@as(i32, 8), result); zig.NewExprStmt( zig.Try( zig.Call( zig.FieldAccess( zig.FieldAccess(zig.Id("std"), "testing"), "expectEqual", ), zig.Call( zig.Id("@as"), zig.Id("i32"), zig.IntLit("8"), ), zig.Id("result"), ), ), ), ), ), }, }, } runZigASTTest(t, expected, root) } func TestEvenOdd(t *testing.T) { expected := `//! Abc const std = @import("std"); pub fn main() !void { const stdout = std.io.getStdOut().writer(); var i: i32 = 1; while (i <= 5) : (i += 1) { if (i % 2 == 0) { try stdout.writeAll("even: {d}\n", .{i}); } else { try stdout.writeAll("odd: {d}\n", .{i}); } } } ` root := &zig.Root{ ContainerDocComment: "Abc", ContainerMembers: []*zig.ContainerMember{ { Decl: zig.DeclareGlobalVar("std", zig.Call( zig.Id("@import"), zig.StringLit("std"), ), zig.GlobalVarConst, ), }, { Decl: zig.DeclareFn( "main", zig.Id("!void"), zig.NewBlock( // const stdout = std.io.getStdOut().writer(); zig.DeclareVarStmt(true, []string{"stdout"}, nil, zig.Call( zig.FieldAccess( zig.Call( zig.FieldAccess( zig.FieldAccess(zig.Id("std"), "io"), "getStdOut", ), ), "writer", ), ), ), // var i: i32 = 1; zig.DeclareVarStmt(false, []string{"i"}, zig.Id("i32"), zig.IntLit("1")), // while (i <= 5) : (i += 1) { ... } zig.WhileLoop( zig.Binary("<=", zig.Id("i"), zig.IntLit("5")), zig.Binary("+=", zig.Id("i"), zig.IntLit("1")), nil, zig.NewBlockStmt( &zig.IfStmt{ Cond: zig.Binary("==", zig.Binary("%", zig.Id("i"), zig.IntLit("2")), zig.IntLit("0"), ), Then: zig.NewBlockStmt( zig.NewExprStmt( zig.Try( zig.Call( zig.FieldAccess(zig.Id("stdout"), "writeAll"), zig.StringLit(`even: {d}\n`), zig.InitList(zig.Id("i")), ), ), ), ), Else: zig.NewBlockStmt( zig.NewExprStmt( zig.Try( zig.Call( zig.FieldAccess(zig.Id("stdout"), "writeAll"), zig.StringLit(`odd: {d}\n`), zig.InitList(zig.Id("i")), ), ), ), ), }, ), nil, ), ), nil, zig.FnExport, ), }, }, } runZigASTTest(t, expected, root) } func TestStructWithFieldsAndMethod(t *testing.T) { expected := `const MyStruct = struct { const Self = @This(); data: ?*u8, count: u32, pub fn reset(self: Self) void { self.count = 0; } }; ` root := &zig.Root{ ContainerMembers: []*zig.ContainerMember{ { Decl: zig.DeclareGlobalVar("MyStruct", zig.StructDecl( zig.Field("Self", nil, nil, zig.Call(zig.Id("@This"))), zig.Field("data", zig.OptionalType(zig.PointerType(zig.Id("u8"))), nil, nil), zig.Field("count", zig.Id("u32"), nil, nil), zig.Method(zig.DeclareFn( "reset", zig.Id("void"), zig.NewBlock( zig.NewExprStmt( zig.Binary("=", zig.FieldAccess(zig.Id("self"), "count"), zig.IntLit("0"), ), ), ), []*zig.ParamDecl{ zig.Param("self", zig.Id("Self")), }, zig.FnExport, )), ), zig.GlobalVarConst, ), }, }, } runZigASTTest(t, expected, root) } func TestAllExpressionTypes(t *testing.T) { expected := `fn testExpressions() void { const int_lit = 42; const float_lit = 3.14; const string_lit = "hello"; const char_lit = 'A'; const add = 1 + 2; const sub = 5 - 3; const mul = 4 * 5; const div = 10 / 2; const mod = 7 % 3; const bit_and = 0xFF & 0x0F; const bit_or = 0x10 | 0x01; const bit_xor = 0xAA ^ 0x55; const shift_left = 1 << 3; const shift_right = 16 >> 2; const eq = 5 == 5; const ne = 3 != 4; const lt = 2 < 5; const gt = 8 > 3; const le = 4 <= 4; const ge = 6 >= 5; const log_and = true and false; const log_or = true or false; const neg = -5; const bit_not = ~0xFF; const log_not = !true; const addr_of = &int_lit; const grouped = (2 + 3) * 4; const field = obj.field; const nested = obj.inner.value; const elem = array[0]; const slice_elem = slice[i + 1]; const no_args = func(); const one_arg = print("text"); const multi_args = add(1, 2, 3); const empty_init = .{}; const array_init = .{ 1, 2, 3 }; const struct_init = .{ .x = 10, .y = 20 }; const try_expr = try fallible(); const comptime_expr = comptime computeValue(); const nosuspend_expr = nosuspend asyncFunc(); const unreachable_expr = unreachable; const async_expr = async startTask(); const await_expr = await future; const resume_expr = resume frame; const optional_unwrap = opt_value.?; const optional_deref = ptr_value.*; } ` root := &zig.Root{ ContainerMembers: []*zig.ContainerMember{ { Decl: zig.DeclareFn( "testExpressions", zig.Id("void"), zig.NewBlock( // Literals zig.DeclareVarStmt(true, []string{"int_lit"}, nil, zig.IntLit("42")), zig.DeclareVarStmt(true, []string{"float_lit"}, nil, zig.FloatLit("3.14")), zig.DeclareVarStmt(true, []string{"string_lit"}, nil, zig.StringLit("hello")), zig.DeclareVarStmt(true, []string{"char_lit"}, nil, zig.CharLit("'A'")), // Binary expressions - arithmetic zig.DeclareVarStmt(true, []string{"add"}, nil, zig.Binary("+", zig.IntLit("1"), zig.IntLit("2"))), zig.DeclareVarStmt(true, []string{"sub"}, nil, zig.Binary("-", zig.IntLit("5"), zig.IntLit("3"))), zig.DeclareVarStmt(true, []string{"mul"}, nil, zig.Binary("*", zig.IntLit("4"), zig.IntLit("5"))), zig.DeclareVarStmt(true, []string{"div"}, nil, zig.Binary("/", zig.IntLit("10"), zig.IntLit("2"))), zig.DeclareVarStmt(true, []string{"mod"}, nil, zig.Binary("%", zig.IntLit("7"), zig.IntLit("3"))), // Binary expressions - bitwise zig.DeclareVarStmt(true, []string{"bit_and"}, nil, zig.Binary("&", zig.IntLit("0xFF"), zig.IntLit("0x0F"))), zig.DeclareVarStmt(true, []string{"bit_or"}, nil, zig.Binary("|", zig.IntLit("0x10"), zig.IntLit("0x01"))), zig.DeclareVarStmt(true, []string{"bit_xor"}, nil, zig.Binary("^", zig.IntLit("0xAA"), zig.IntLit("0x55"))), zig.DeclareVarStmt(true, []string{"shift_left"}, nil, zig.Binary("<<", zig.IntLit("1"), zig.IntLit("3"))), zig.DeclareVarStmt(true, []string{"shift_right"}, nil, zig.Binary(">>", zig.IntLit("16"), zig.IntLit("2"))), // Binary expressions - comparison zig.DeclareVarStmt(true, []string{"eq"}, nil, zig.Binary("==", zig.IntLit("5"), zig.IntLit("5"))), zig.DeclareVarStmt(true, []string{"ne"}, nil, zig.Binary("!=", zig.IntLit("3"), zig.IntLit("4"))), zig.DeclareVarStmt(true, []string{"lt"}, nil, zig.Binary("<", zig.IntLit("2"), zig.IntLit("5"))), zig.DeclareVarStmt(true, []string{"gt"}, nil, zig.Binary(">", zig.IntLit("8"), zig.IntLit("3"))), zig.DeclareVarStmt(true, []string{"le"}, nil, zig.Binary("<=", zig.IntLit("4"), zig.IntLit("4"))), zig.DeclareVarStmt(true, []string{"ge"}, nil, zig.Binary(">=", zig.IntLit("6"), zig.IntLit("5"))), // Binary expressions - logical zig.DeclareVarStmt(true, []string{"log_and"}, nil, zig.Binary("and", zig.Id("true"), zig.Id("false"))), zig.DeclareVarStmt(true, []string{"log_or"}, nil, zig.Binary("or", zig.Id("true"), zig.Id("false"))), // Unary expressions zig.DeclareVarStmt(true, []string{"neg"}, nil, zig.Unary("-", zig.IntLit("5"))), zig.DeclareVarStmt(true, []string{"bit_not"}, nil, zig.Unary("~", zig.IntLit("0xFF"))), zig.DeclareVarStmt(true, []string{"log_not"}, nil, zig.Unary("!", zig.Id("true"))), zig.DeclareVarStmt(true, []string{"addr_of"}, nil, zig.Unary("&", zig.Id("int_lit"))), // Grouped expression zig.DeclareVarStmt(true, []string{"grouped"}, nil, zig.Binary("*", zig.Grouped(zig.Binary("+", zig.IntLit("2"), zig.IntLit("3"))), zig.IntLit("4"), ), ), // Field access zig.DeclareVarStmt(true, []string{"field"}, nil, zig.FieldAccess(zig.Id("obj"), "field")), zig.DeclareVarStmt(true, []string{"nested"}, nil, zig.FieldAccess(zig.FieldAccess(zig.Id("obj"), "inner"), "value"), ), // Array/slice indexing zig.DeclareVarStmt(true, []string{"elem"}, nil, zig.Index(zig.Id("array"), zig.IntLit("0"))), zig.DeclareVarStmt(true, []string{"slice_elem"}, nil, zig.Index(zig.Id("slice"), zig.Binary("+", zig.Id("i"), zig.IntLit("1"))), ), // Function calls zig.DeclareVarStmt(true, []string{"no_args"}, nil, zig.Call(zig.Id("func"))), zig.DeclareVarStmt(true, []string{"one_arg"}, nil, zig.Call(zig.Id("print"), zig.StringLit("text"))), zig.DeclareVarStmt(true, []string{"multi_args"}, nil, zig.Call(zig.Id("add"), zig.IntLit("1"), zig.IntLit("2"), zig.IntLit("3")), ), // Initializer lists zig.DeclareVarStmt(true, []string{"empty_init"}, nil, zig.InitList()), zig.DeclareVarStmt(true, []string{"array_init"}, nil, zig.InitList(zig.IntLit("1"), zig.IntLit("2"), zig.IntLit("3"))), zig.DeclareVarStmt(true, []string{"struct_init"}, nil, zig.InitListFields( zig.FieldInitPair("x", zig.IntLit("10")), zig.FieldInitPair("y", zig.IntLit("20")), ), ), // Special expressions zig.DeclareVarStmt(true, []string{"try_expr"}, nil, zig.Try(zig.Call(zig.Id("fallible")))), zig.DeclareVarStmt(true, []string{"comptime_expr"}, nil, zig.Comptime(zig.Call(zig.Id("computeValue")))), zig.DeclareVarStmt(true, []string{"nosuspend_expr"}, nil, zig.Nosuspend(zig.Call(zig.Id("asyncFunc")))), zig.DeclareVarStmt(true, []string{"unreachable_expr"}, nil, zig.Unreachable()), // Async expressions zig.DeclareVarStmt(true, []string{"async_expr"}, nil, zig.Async(zig.Call(zig.Id("startTask")))), zig.DeclareVarStmt(true, []string{"await_expr"}, nil, zig.Await(zig.Id("future"))), zig.DeclareVarStmt(true, []string{"resume_expr"}, nil, zig.Resume(zig.Id("frame"))), // Optional handling zig.DeclareVarStmt(true, []string{"optional_unwrap"}, nil, zig.DotQuestion(zig.Id("opt_value"))), zig.DeclareVarStmt(true, []string{"optional_deref"}, nil, zig.DotAsterisk(zig.Id("ptr_value"))), ), nil, 0, ), }, }, } runZigASTTest(t, expected, root) } func TestAllStatementTypes(t *testing.T) { expected := `fn testStatements() void { const x = 10; var y: i32 = 20; var z = x + y; computeValue(); { const inner = 5; doWork(); } if (x > 0) { positive(); } else { negative(); } defer cleanup(); errdefer |err| handleError(err); while (y > 0) : (y -= 1) { processItem(); } for (items, 0..) |item, idx| { processIndexed(item, idx); } switch (x) { 0...9 => handleSmall(), 10 => return, else => { handleLarge(); break; }, } loop: while (true) { if (done()) break :loop; continue :loop; } return; } ` root := &zig.Root{ ContainerMembers: []*zig.ContainerMember{ { Decl: zig.DeclareFn( "testStatements", zig.Id("void"), zig.NewBlock( // Variable declarations zig.DeclareVarStmt(true, []string{"x"}, nil, zig.IntLit("10")), zig.DeclareVarStmt(false, []string{"y"}, zig.Id("i32"), zig.IntLit("20")), zig.DeclareVarStmt(false, []string{"z"}, nil, zig.Binary("+", zig.Id("x"), zig.Id("y"))), // Expression statement zig.NewExprStmt(zig.Call(zig.Id("computeValue"))), // Block statement zig.NewBlockStmt( zig.DeclareVarStmt(true, []string{"inner"}, nil, zig.IntLit("5")), zig.NewExprStmt(zig.Call(zig.Id("doWork"))), ), // If statement zig.If( zig.Binary(">", zig.Id("x"), zig.IntLit("0")), zig.NewBlockStmt( zig.NewExprStmt(zig.Call(zig.Id("positive"))), ), zig.NewBlockStmt( zig.NewExprStmt(zig.Call(zig.Id("negative"))), ), ), // Defer statement zig.Defer(zig.NewExprStmt(zig.Call(zig.Id("cleanup")))), // Errdefer with payload zig.ErrDefer( zig.PayloadNames([]string{"err"}, []bool{false}), zig.NewExprStmt(zig.Call(zig.Id("handleError"), zig.Id("err"))), ), // While loop with continue expression zig.WhileLoop( zig.Binary(">", zig.Id("y"), zig.IntLit("0")), zig.Binary("-=", zig.Id("y"), zig.IntLit("1")), nil, zig.NewBlockStmt( zig.NewExprStmt(zig.Call(zig.Id("processItem"))), ), nil, ), // For loop with payload zig.ForLoop( []zig.ForArg{ zig.ForArgExpr(zig.Id("items")), {Expr: zig.IntLit("0"), From: zig.IntLit("")}, }, zig.PayloadNames([]string{"item", "idx"}, []bool{false, false}), zig.NewBlockStmt( zig.NewExprStmt(zig.Call(zig.Id("processIndexed"), zig.Id("item"), zig.Id("idx"))), ), nil, ), // Switch statement zig.Switch( zig.Id("x"), zig.Prong( []*zig.SwitchCase{ zig.Case(zig.IntLit("0"), zig.IntLit("9")), }, nil, zig.NewExprStmt(zig.Call(zig.Id("handleSmall"))), ), zig.Prong( []*zig.SwitchCase{ zig.Case(zig.IntLit("10"), nil), }, nil, zig.Return(nil), ), zig.Prong( []*zig.SwitchCase{zig.ElseCase()}, nil, zig.NewBlockStmt( zig.NewExprStmt(zig.Call(zig.Id("handleLarge"))), zig.Break("", nil), ), ), ), // Labeled while loop with break and continue &zig.LoopStmt{ Kind: zig.LoopWhile, Prefix: &zig.WhilePrefix{ Cond: zig.Id("true"), }, Body: &zig.BlockStmt{ Block: &zig.Block{ Label: "loop", Stmts: []zig.Stmt{ zig.If( zig.Call(zig.Id("done")), zig.Break("loop", nil), nil, ), zig.Continue("loop"), }, }, }, }, // Return statement zig.Return(nil), ), nil, 0, ), }, }, } runZigASTTest(t, expected, root) } func TestAllTypeExpressions(t *testing.T) { expected := `fn testTypes() void { const a: i32 = 42; const b: u8 = 255; const c: f64 = 3.14; const d: bool = true; const e: void = .{}; const ptr: *i32 = &a; const const_ptr: *const i32 = &a; const multi_ptr: [*]i32 = &array; const slice: []i32 = getSlice(array); const array_type: [5]i32 = .{ 1, 2, 3, 4, 5 }; const opt: ?i32 = 42; const err_union: MyError!i32 = 42; const any: anytype = 42; const struct_type: MyStruct = .{}; const enum_type: MyEnum = .value; const union_type: MyUnion = .{ .field = 10 }; } const MyError = error{ OutOfMemory, InvalidInput, }; const MyStruct = struct { x: i32, y: f64, }; const MyEnum = enum { value, other, }; const MyUnion = union(enum) { field: i32, other: f64, }; ` root := &zig.Root{ ContainerMembers: []*zig.ContainerMember{ { Decl: zig.DeclareFn( "testTypes", zig.Id("void"), zig.NewBlock( // Basic types zig.DeclareVarStmt(true, []string{"a"}, zig.Id("i32"), zig.IntLit("42")), zig.DeclareVarStmt(true, []string{"b"}, zig.Id("u8"), zig.IntLit("255")), zig.DeclareVarStmt(true, []string{"c"}, zig.Id("f64"), zig.FloatLit("3.14")), zig.DeclareVarStmt(true, []string{"d"}, zig.Id("bool"), zig.Id("true")), zig.DeclareVarStmt(true, []string{"e"}, zig.Id("void"), zig.InitList()), // Pointer types zig.DeclareVarStmt(true, []string{"ptr"}, zig.PointerType(zig.Id("i32")), zig.Unary("&", zig.Id("a"))), zig.DeclareVarStmt(true, []string{"const_ptr"}, &zig.PrefixTypeExpr{Op: "*const", Base: zig.Id("i32")}, zig.Unary("&", zig.Id("a")), ), zig.DeclareVarStmt(true, []string{"multi_ptr"}, &zig.PrefixTypeExpr{Op: "[*]", Base: zig.Id("i32")}, zig.Unary("&", zig.Id("array")), ), // Slice and array types zig.DeclareVarStmt(true, []string{"slice"}, zig.SliceType(zig.Id("i32")), // TODO: array[0..] syntax not properly supported yet zig.Call(zig.Id("getSlice"), zig.Id("array")), ), zig.DeclareVarStmt(true, []string{"array_type"}, zig.ArrayType(zig.IntLit("5"), zig.Id("i32")), zig.InitList(zig.IntLit("1"), zig.IntLit("2"), zig.IntLit("3"), zig.IntLit("4"), zig.IntLit("5")), ), // Optional and error union types zig.DeclareVarStmt(true, []string{"opt"}, zig.OptionalType(zig.Id("i32")), zig.IntLit("42")), zig.DeclareVarStmt(true, []string{"err_union"}, zig.ErrorUnionType(zig.Id("MyError"), zig.Id("i32")), zig.IntLit("42")), // Special types zig.DeclareVarStmt(true, []string{"any"}, zig.Id("anytype"), zig.IntLit("42")), zig.DeclareVarStmt(true, []string{"struct_type"}, zig.Id("MyStruct"), zig.InitList()), zig.DeclareVarStmt(true, []string{"enum_type"}, zig.Id("MyEnum"), zig.FieldAccess(&zig.Identifier{Name: ""}, "value")), zig.DeclareVarStmt(true, []string{"union_type"}, zig.Id("MyUnion"), zig.InitListFields(zig.FieldInitPair("field", zig.IntLit("10"))), ), ), nil, 0, ), }, { Decl: zig.DeclareGlobalVar("MyError", zig.ErrorSet("OutOfMemory", "InvalidInput"), zig.GlobalVarConst, ), }, { Decl: zig.DeclareGlobalVar("MyStruct", zig.StructDecl( zig.Field("x", zig.Id("i32"), nil, nil), zig.Field("y", zig.Id("f64"), nil, nil), ), zig.GlobalVarConst, ), }, { Decl: zig.DeclareGlobalVar("MyEnum", zig.EnumDecl(nil, zig.Field("value", nil, nil, nil), zig.Field("other", nil, nil, nil), ), zig.GlobalVarConst, ), }, { Decl: zig.DeclareGlobalVar("MyUnion", &zig.ContainerDecl{ Kind: zig.ContainerUnion, TagType: zig.Id("enum"), Fields: []*zig.ContainerMember{ zig.Field("field", zig.Id("i32"), nil, nil), zig.Field("other", zig.Id("f64"), nil, nil), }, }, zig.GlobalVarConst, ), }, }, } runZigASTTest(t, expected, root) } func TestControlFlowPatterns(t *testing.T) { expected := `fn testControlFlow() !void { var items = .{ 1, 2, null, 4 }; var sum: i32 = 0; outer: for (items, 0..) |maybe_item, i| { const item = maybe_item orelse { if (i == 0) return error.InvalidInput; continue :outer; }; inner: while (item > 0) { defer sum += 1; switch (item) { 1 => break :inner, 2...10 => |val| { if (val == 5) break :outer; continue :inner; }, else => return error.OutOfRange, } } if (item < 0) { errdefer |err| std.log.err("Error: {}", .{err}); try processNegative(item); } } const result = blk: { if (sum > 100) break :blk sum; break :blk 0; }; return if (result > 0) .{} else error.NoResult; } ` root := &zig.Root{ ContainerMembers: []*zig.ContainerMember{ { Decl: zig.DeclareFn( "testControlFlow", zig.ErrorUnionType(nil, zig.Id("void")), zig.NewBlock( // var items = [_]?i32{ 1, 2, null, 4 }; zig.DeclareVarStmt(false, []string{"items"}, nil, &zig.InitListExpr{ Values: []zig.Expr{ zig.IntLit("1"), zig.IntLit("2"), zig.Id("null"), zig.IntLit("4"), }, }, ), // var sum: i32 = 0; zig.DeclareVarStmt(false, []string{"sum"}, zig.Id("i32"), zig.IntLit("0")), // outer: for loop with orelse handling &zig.LoopStmt{ Kind: zig.LoopFor, Prefix: &zig.ForPrefix{ Args: []zig.ForArg{ zig.ForArgExpr(zig.Id("items")), {Expr: zig.IntLit("0"), From: zig.IntLit("")}, }, Payload: zig.PayloadNames([]string{"maybe_item", "i"}, []bool{false, false}), }, Body: &zig.BlockStmt{ Block: &zig.Block{ Label: "outer", Stmts: []zig.Stmt{ // const item = maybe_item orelse { ... }; zig.DeclareVarStmt(true, []string{"item"}, nil, zig.Binary("orelse", zig.Id("maybe_item"), zig.NewBlockStmt( zig.If( zig.Binary("==", zig.Id("i"), zig.IntLit("0")), zig.Return(zig.FieldAccess(&zig.Identifier{Name: "error"}, "InvalidInput")), nil, ), zig.Continue("outer"), ), ), ), // inner: while loop with defer &zig.LoopStmt{ Kind: zig.LoopWhile, Prefix: &zig.WhilePrefix{ Cond: zig.Binary(">", zig.Id("item"), zig.IntLit("0")), }, Body: &zig.BlockStmt{ Block: &zig.Block{ Label: "inner", Stmts: []zig.Stmt{ // defer sum += 1; zig.Defer(zig.NewExprStmt(zig.Binary("+=", zig.Id("sum"), zig.IntLit("1")))), // switch with labeled breaks zig.Switch( zig.Id("item"), zig.Prong( []*zig.SwitchCase{zig.Case(zig.IntLit("1"), nil)}, nil, zig.Break("inner", nil), ), zig.Prong( []*zig.SwitchCase{zig.Case(zig.IntLit("2"), zig.IntLit("10"))}, zig.PayloadNames([]string{"val"}, []bool{false}), zig.NewBlockStmt( zig.If( zig.Binary("==", zig.Id("val"), zig.IntLit("5")), zig.Break("outer", nil), nil, ), zig.Continue("inner"), ), ), zig.Prong( []*zig.SwitchCase{zig.ElseCase()}, nil, zig.Return(zig.FieldAccess(&zig.Identifier{Name: "error"}, "OutOfRange")), ), ), }, }, }, }, // if with errdefer zig.If( zig.Binary("<", zig.Id("item"), zig.IntLit("0")), zig.NewBlockStmt( zig.ErrDefer( zig.PayloadNames([]string{"err"}, []bool{false}), zig.NewExprStmt( zig.Call( zig.FieldAccess(zig.FieldAccess(zig.Id("std"), "log"), "err"), zig.StringLit("Error: {}"), zig.InitList(zig.Id("err")), ), ), ), zig.NewExprStmt(zig.Try(zig.Call(zig.Id("processNegative"), zig.Id("item")))), ), nil, ), }, }, }, }, // const result = blk: { ... }; zig.DeclareVarStmt(true, []string{"result"}, nil, &zig.LabeledBlock{ Label: "blk", Block: zig.NewBlock( zig.If( zig.Binary(">", zig.Id("sum"), zig.IntLit("100")), zig.Break("blk", zig.Id("sum")), nil, ), zig.Break("blk", zig.IntLit("0")), ), }, ), // return if (result > 0) {} else error.NoResult; zig.Return( zig.IfExpression( zig.Binary(">", zig.Id("result"), zig.IntLit("0")), zig.InitList(), zig.FieldAccess(&zig.Identifier{Name: "error"}, "NoResult"), ), ), ), nil, 0, ), }, }, } runZigASTTest(t, expected, root) }