Starting Zig code generator with one test

This commit is contained in:
Luke Wilson 2025-05-21 23:00:35 -05:00
parent bdf7ea85d3
commit 0bf8c3ef6b
5 changed files with 217 additions and 1 deletions

View File

@ -1,2 +1,2 @@
run:
go run internal/main.go -o hello.zig programs/hello.go
go run internal/main.go -o hello.zig programs/hello.go && zig run hello.zig

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.frop.prof/luke/go-zig-compiler
go 1.24.3

50
internal/zig/ast.go Normal file
View File

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

108
internal/zig/zig.go Normal file
View File

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

55
internal/zig/zig_test.go Normal file
View File

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