- 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
160 lines
3.4 KiB
Go
160 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"strings"
|
|
|
|
"git.frop.prof/luke/go-zig-compiler/internal/zig"
|
|
)
|
|
|
|
var (
|
|
outputFilepath *string = flag.String("o", "out.zig", "Zig output filename")
|
|
)
|
|
|
|
func init() {
|
|
flag.Parse()
|
|
}
|
|
|
|
func main() {
|
|
inputFilepaths := flag.Args()
|
|
|
|
if len(inputFilepaths) > 1 {
|
|
panic("Only support one file!")
|
|
} else if len(inputFilepaths) < 1 {
|
|
panic("Must supply one input file")
|
|
}
|
|
|
|
inputFilepath := inputFilepaths[0]
|
|
|
|
fset := token.NewFileSet()
|
|
|
|
f, err := parser.ParseFile(fset, inputFilepath, nil, parser.AllErrors)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
zigRoot, err := translateToZig(f)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
outputFile, err := os.Create(*outputFilepath)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer outputFile.Close()
|
|
|
|
err = zig.Write(outputFile, zigRoot)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func translateToZig(f *ast.File) (*zig.Root, error) {
|
|
// Create the root AST node
|
|
root := &zig.Root{
|
|
ContainerMembers: []*zig.ContainerMember{},
|
|
}
|
|
|
|
// 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,
|
|
),
|
|
})
|
|
|
|
// 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,
|
|
})
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|