Add comprehensive test system for Go-to-Zig compiler

- Implement end-to-end test runner for compilation and behavior tests
- Add test cases for basic print functionality
- Refactor translator to use proper AST generation
- Remove redundant programs directory in favor of tests
This commit is contained in:
2025-06-05 22:35:18 -05:00
parent d5f346cf8b
commit bea04d6810
10 changed files with 370 additions and 37 deletions

View File

@@ -8,6 +8,8 @@ import (
"go/token"
"os"
"strings"
"git.frop.prof/luke/go-zig-compiler/internal/zig"
)
var (
@@ -36,57 +38,122 @@ func main() {
panic(err)
}
output, err := generate(f)
zigRoot, err := translateToZig(f)
if err != nil {
panic(err)
}
outputFile, err := os.Create(*outputFilepath)
if err != nil {
panic(err)
}
_, err = outputFile.WriteString(output)
defer outputFile.Close()
err = zig.Write(outputFile, zigRoot)
if err != nil {
panic(err)
}
fmt.Printf("%v:\n", *outputFilepath)
fmt.Println("--------------------")
fmt.Println(output)
}
func generate(f *ast.File) (string, error) {
sb := new(strings.Builder)
def := f.Decls[0].(*ast.FuncDecl)
if def.Name.Name != "main" {
return "", fmt.Errorf("must have main")
func translateToZig(f *ast.File) (*zig.Root, error) {
// Create the root AST node
root := &zig.Root{
ContainerMembers: []*zig.ContainerMember{},
}
sb.WriteString(`const std = @import("std");`)
sb.WriteString("\npub fn main() void {\n")
// Add the std import
root.ContainerMembers = append(root.ContainerMembers, &zig.ContainerMember{
Decl: zig.DeclareGlobalVar("std",
zig.Call(zig.Id("@import"), zig.StringLit("std")),
zig.GlobalVarConst,
),
})
stmt := def.Body.List[0].(*ast.ExprStmt)
call := stmt.X.(*ast.CallExpr)
fn := call.Fun.(*ast.Ident)
if fn.Name == "print" {
sb.WriteString(fmt.Sprintf(`std.debug.print(`))
args := call.Args
for _, arg := range args {
if s, ok := arg.(*ast.BasicLit); ok {
sb.WriteString(fmt.Sprintf("%s", s.Value))
} else {
panic("WTF")
// Find and translate the main function
for _, decl := range f.Decls {
if fn, ok := decl.(*ast.FuncDecl); ok && fn.Name.Name == "main" {
mainFunc, err := translateMainFunction(fn)
if err != nil {
return nil, err
}
root.ContainerMembers = append(root.ContainerMembers, &zig.ContainerMember{
Decl: mainFunc,
})
}
sb.WriteString(", .{});\n")
} else {
return "", fmt.Errorf("expected printf")
}
sb.WriteString("}\n")
return sb.String(), nil
return root, nil
}
func translateMainFunction(fn *ast.FuncDecl) (*zig.FnDecl, error) {
// Create the main function
stmts := []zig.Stmt{}
// Translate each statement in the function body
for _, stmt := range fn.Body.List {
zigStmt, err := translateStatement(stmt)
if err != nil {
return nil, err
}
if zigStmt != nil {
stmts = append(stmts, zigStmt)
}
}
return zig.DeclareFn(
"main",
zig.Id("void"),
zig.NewBlock(stmts...),
nil,
zig.FnExport,
), nil
}
func translateStatement(stmt ast.Stmt) (zig.Stmt, error) {
switch s := stmt.(type) {
case *ast.ExprStmt:
// Handle expression statements (like function calls)
expr, err := translateExpression(s.X)
if err != nil {
return nil, err
}
return zig.NewExprStmt(expr), nil
default:
return nil, fmt.Errorf("unsupported statement type: %T", stmt)
}
}
func translateExpression(expr ast.Expr) (zig.Expr, error) {
switch e := expr.(type) {
case *ast.CallExpr:
// Handle function calls
if ident, ok := e.Fun.(*ast.Ident); ok && ident.Name == "print" {
// Translate print() to std.debug.print()
args := []zig.Expr{}
// First argument is the format string
if len(e.Args) > 0 {
if lit, ok := e.Args[0].(*ast.BasicLit); ok && lit.Kind == token.STRING {
// Remove quotes and use the string value
args = append(args, zig.StringLit(strings.Trim(lit.Value, `"`)))
} else {
return nil, fmt.Errorf("print() requires a string literal argument")
}
}
// Second argument is always .{} for now
args = append(args, zig.InitList())
return zig.Call(
zig.FieldAccess(
zig.FieldAccess(zig.Id("std"), "debug"),
"print",
),
args...,
), nil
}
return nil, fmt.Errorf("unsupported function call: %v", e.Fun)
default:
return nil, fmt.Errorf("unsupported expression type: %T", expr)
}
}