- 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
1090 lines
31 KiB
Go
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)
|
|
}
|