Luke Wilson d5f346cf8b 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
2025-06-05 22:00:41 -05:00

1090 lines
31 KiB
Go

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