Add comprehensive test suite for Zig AST

- Add TestAllExpressionTypes covering all expression nodes
- Add TestAllStatementTypes covering all statement nodes
- Add TestAllTypeExpressions covering all type expressions
- Add TestControlFlowPatterns demonstrating complex control flow
- Fix formatter for field initializers, type spacing, and labels
- Fix handling of orelse blocks, switch payloads, and errdefer
- Remove duplicate test functions from previous edits
- Tests serve as documentation/examples for AST construction
This commit is contained in:
Luke Wilson 2025-06-05 22:00:41 -05:00
parent 2af696078d
commit d5f346cf8b
2 changed files with 739 additions and 7 deletions

View File

@ -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)

View File

@ -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)
}