diff --git a/Makefile b/Makefile index 47f5f42..505b684 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,2 @@ run: - go run internal/main.go -o hello.zig programs/hello.go \ No newline at end of file + go run internal/main.go -o hello.zig programs/hello.go && zig run hello.zig \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e0b13c7 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.frop.prof/luke/go-zig-compiler + +go 1.24.3 diff --git a/internal/zig/ast.go b/internal/zig/ast.go new file mode 100644 index 0000000..af9aea0 --- /dev/null +++ b/internal/zig/ast.go @@ -0,0 +1,50 @@ +package zig + +// https://github.com/ziglang/zig-spec/blob/master/grammar/grammar.peg + +type Root struct { + ContainerDocComment string // //! Doc Comment + ContainerMembers []*ContainerMember +} + +type ContainerMember struct { + // FIXME + Decls []Decl +} + +type Decl interface{} + +type FnDecl struct { + Name string + Params []*ParamDecl + CallConv string + ReturnType TypeExpr + Body *Block // nil means semicolon +} + +type ParamDecl struct { + DocComment string // ??? It's what it says + Name string // Can be empty + Type TypeExpr // anytype when empty +} + +type Block struct { + Label string + Stmts []Stmt +} + +type Stmt interface{} + +type ReturnStmt struct{} + +type Expr interface{} + +// This will need to become a real type expr someday +type TypeExpr string + +func (t TypeExpr) String() string { + if string(t) == "" { + return "anytype" + } + return string(t) +} diff --git a/internal/zig/zig.go b/internal/zig/zig.go new file mode 100644 index 0000000..fef1d1b --- /dev/null +++ b/internal/zig/zig.go @@ -0,0 +1,108 @@ +package zig + +import ( + "fmt" + "io" +) + +type formatter struct { + w io.Writer +} + +func (f *formatter) WriteString(s string) (n int, err error) { + return f.w.Write([]byte(s)) +} + +func (f *formatter) Writef(format string, a ...any) (err error) { + _, err = f.w.Write(fmt.Appendf(nil, format, a...)) + return err +} + +func Write(w io.Writer, root *Root) error { + f := &formatter{ + w: w, + } + + if root.ContainerDocComment != "" { + err := f.Writef("//! %s\n\n", root.ContainerDocComment) + if err != nil { + return err + } + } + for _, member := range root.ContainerMembers { + for _, decl := range member.Decls { + if err := writeDecl(f, decl); err != nil { + return err + } + } + } + return nil +} + +func writeDecl(f *formatter, decl Decl) (err error) { + switch typ := decl.(type) { + case *FnDecl: + if err = f.Writef("fn %s(", typ.Name); err != nil { + return err + } + if err = writeParams(f, typ.Params); err != nil { + return err + } + if err = f.Writef(") %s", typ.ReturnType); err != nil { + return err + } + if err = writeBlock(f, typ.Body); err != nil { + return err + } + } + return nil +} + +func writeParams(f *formatter, params []*ParamDecl) (err error) { + for _, param := range params { + if param.Name != "" { + if err = f.Writef("%s: ", param.Name); err != nil { + return err + } + } + if err = f.Writef("%s", param.Type); err != nil { + return err + } + } + return nil +} + +func writeBlock(f *formatter, block *Block) (err error) { + if block == nil { + if _, err = f.WriteString(";"); err != nil { + return err + } + return nil + } + if err = f.Writef(" {\n"); err != nil { + return err + } + for _, stmt := range block.Stmts { + if err = writeStmt(f, stmt); err != nil { + return err + } + // Should this be the job of the formatter? + if _, err = f.WriteString("\n"); err != nil { + return err + } + } + if err = f.Writef("}\n"); err != nil { + return err + } + return nil +} + +func writeStmt(f *formatter, stmt Stmt) (err error) { + switch stmt.(type) { + case *ReturnStmt: + if _, err = f.WriteString("return;"); err != nil { + return err + } + } + return nil +} diff --git a/internal/zig/zig_test.go b/internal/zig/zig_test.go new file mode 100644 index 0000000..fa2943b --- /dev/null +++ b/internal/zig/zig_test.go @@ -0,0 +1,55 @@ +package zig_test + +import ( + "cmp" + "fmt" + "strings" + "testing" + + "git.frop.prof/luke/go-zig-compiler/internal/zig" +) + +func Expect[T cmp.Ordered](expected, actual T) error { + if expected != actual { + return fmt.Errorf("\nExpected: %v\nActual: %v", expected, actual) + } + return nil +} + +func TestHelloWorld(t *testing.T) { + expected := `//! Hello, world! + +fn main() void { +return; +} +` + + root := &zig.Root{ + ContainerDocComment: "Hello, world!", + ContainerMembers: []*zig.ContainerMember{ + { + Decls: []zig.Decl{ + &zig.FnDecl{ + Name: "main", + ReturnType: "void", + Body: &zig.Block{ + Stmts: []zig.Stmt{ + &zig.ReturnStmt{}, + }, + }, + }, + }, + }, + }, + } + 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) + } +}