diff --git a/internal/zig/zig.go b/internal/zig/zig.go index 0494cd8..ded2d49 100644 --- a/internal/zig/zig.go +++ b/internal/zig/zig.go @@ -150,6 +150,10 @@ func writeTypeExpr(f *formatter, typ TypeExpr) { f.writef("%s", t.Name) case *PrefixTypeExpr: f.writef("%s", t.Op) + // Add space after multi-character operators like *const + if len(t.Op) > 1 && !strings.HasSuffix(t.Op, "]") { + f.writef(" ") + } writeTypeExpr(f, t.Base) case *ArrayTypeExpr: f.writef("[") @@ -223,7 +227,25 @@ func writeStmt(f *formatter, stmt Stmt) { } f.writef(";") case *BlockStmt: - writeBlock(f, s.Block) + // Check if this is a labeled block + if s.Block != nil && s.Block.Label != "" { + f.writef("%s:", s.Block.Label) + } + // For standalone blocks, we need to write the block without a leading space + if s.Block == nil { + f.writef(";") + } else { + f.writef("{\n") + f.indent++ + for _, stmt := range s.Block.Stmts { + f.writeIndent() + writeStmt(f, stmt) + f.writef("\n") + } + f.indent-- + f.writeIndent() + f.writef("}") + } case *IfStmt: f.writef("if (") writeExpr(f, s.Cond) @@ -252,6 +274,11 @@ func writeStmt(f *formatter, stmt Stmt) { } } case *LoopStmt: + // Handle label if the body is a labeled block + if block, ok := s.Body.(*BlockStmt); ok && block.Block != nil && block.Block.Label != "" { + f.writef("%s: ", block.Block.Label) + } + if s.Kind == LoopWhile { f.writef("while (") if wp, ok := s.Prefix.(*WhilePrefix); ok { @@ -264,7 +291,16 @@ func writeStmt(f *formatter, stmt Stmt) { f.writef(")") // Always write the body as a block if block, ok := s.Body.(*BlockStmt); ok { + // Don't write the label again, just the block content + origLabel := "" + if block.Block != nil { + origLabel = block.Block.Label + block.Block.Label = "" // Temporarily clear to avoid double printing + } writeBlock(f, block.Block) + if block.Block != nil { + block.Block.Label = origLabel // Restore + } } else { f.writef(" ") writeStmt(f, s.Body) @@ -294,7 +330,16 @@ func writeStmt(f *formatter, stmt Stmt) { } // Always write the body as a block if block, ok := s.Body.(*BlockStmt); ok { + // Don't write the label again, just the block content + origLabel := "" + if block.Block != nil { + origLabel = block.Block.Label + block.Block.Label = "" // Temporarily clear to avoid double printing + } writeBlock(f, block.Block) + if block.Block != nil { + block.Block.Label = origLabel // Restore + } } else { f.writef(" ") writeStmt(f, s.Body) @@ -303,6 +348,10 @@ func writeStmt(f *formatter, stmt Stmt) { case *DeferStmt: if s.ErrDefer { f.writef("errdefer") + if s.Payload != nil { + f.writef(" ") + writePayload(f, s.Payload) + } } else { f.writef("defer") } @@ -372,6 +421,11 @@ func writeSwitchProng(f *formatter, prong *SwitchProng) { } } f.writef(" => ") + // Write payload if present after => + if prong.Payload != nil { + writePayload(f, prong.Payload) + f.writef(" ") + } // Check if the expression is actually a statement (like return or break) if stmt, ok := prong.Expr.(Stmt); ok { // If it's a block, write it directly without the leading space @@ -438,8 +492,13 @@ func writeExpr(f *formatter, expr Expr) { } f.writef(")") case *FieldAccessExpr: - writeExpr(f, e.Receiver) - f.writef(".%s", e.Field) + // Handle enum field access like .value (no receiver) + if id, ok := e.Receiver.(*Identifier); ok && id.Name == "" { + f.writef(".%s", e.Field) + } else { + writeExpr(f, e.Receiver) + f.writef(".%s", e.Field) + } case *Literal: switch e.Kind { case LiteralString: @@ -465,6 +524,16 @@ func writeExpr(f *formatter, expr Expr) { } f.writef(" }") } + } else if len(e.Fields) > 0 { + f.writef(".{ ") + for i, field := range e.Fields { + if i > 0 { + f.writef(", ") + } + f.writef(".%s = ", field.Name) + writeExpr(f, field.Value) + } + f.writef(" }") } case *ContainerDecl: switch e.Kind { @@ -475,7 +544,14 @@ func writeExpr(f *formatter, expr Expr) { f.writef("enum ") writeStructBody(f, e) case ContainerUnion: - f.writef("union ") + f.writef("union") + if e.TagType != nil { + f.writef("(") + writeTypeExpr(f, e.TagType) + f.writef(") ") + } else { + f.writef(" ") + } writeStructBody(f, e) case ContainerOpaque: f.writef("opaque ") @@ -489,7 +565,17 @@ func writeExpr(f *formatter, expr Expr) { case *BinaryExpr: writeExpr(f, e.Left) f.writef(" %s ", e.Op) - writeExpr(f, e.Right) + // Special handling for orelse with block + if e.Op == "orelse" { + if _, ok := e.Right.(*BlockStmt); ok { + // For orelse blocks, we need special formatting + writeStmt(f, e.Right.(Stmt)) + } else { + writeExpr(f, e.Right) + } + } else { + writeExpr(f, e.Right) + } case *UnaryExpr: f.writef("%s", e.Op) writeExpr(f, e.Expr) @@ -562,6 +648,9 @@ func writeExpr(f *formatter, expr Expr) { f.indent-- f.writeIndent() f.writef("}") + case *LabeledBlock: + f.writef("%s:", e.Label) + writeBlock(f, e.Block) } } @@ -581,8 +670,11 @@ func writeStructBody(f *formatter, decl *ContainerDecl) { } else { // regular field f.writeIndent() - f.writef("%s: ", member.Field.Name) - writeTypeExpr(f, member.Field.Type) + f.writef("%s", member.Field.Name) + if member.Field.Type != nil { + f.writef(": ") + writeTypeExpr(f, member.Field.Type) + } if member.Field.Value != nil { f.writef(" = ") writeExpr(f, member.Field.Value) diff --git a/internal/zig/zig_test.go b/internal/zig/zig_test.go index 4c86a6a..5f1afb2 100644 --- a/internal/zig/zig_test.go +++ b/internal/zig/zig_test.go @@ -447,3 +447,643 @@ func TestStructWithFieldsAndMethod(t *testing.T) { 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) +}