From 44f3cfca5cb2e4a792c6e57d672ce77f65819214 Mon Sep 17 00:00:00 2001 From: Luke Wilson Date: Sat, 24 May 2025 16:38:46 -0500 Subject: [PATCH] Generate hello world Zig code --- internal/zig/ast.go | 43 ++++++++++--------- internal/zig/zig.go | 89 ++++++++++++++++++++++++++++++++++------ internal/zig/zig_test.go | 32 ++++++++++++++- 3 files changed, 129 insertions(+), 35 deletions(-) diff --git a/internal/zig/ast.go b/internal/zig/ast.go index f1c2265..4edfd0e 100644 --- a/internal/zig/ast.go +++ b/internal/zig/ast.go @@ -106,6 +106,26 @@ type Stmt interface { isStmt() } +// ExprStmt represents an expression statement (e.g. a function call as a statement). +type ExprStmt struct { + Expr Expr +} + +func (*ExprStmt) isStmt() {} + +// VarDeclStmt represents a variable or const declaration at statement level, supporting destructuring and multi-var declarations. +type VarDeclStmt struct { + Const bool + Pattern VarPattern // Destructuring or multiple variable names + Type TypeExpr // Optional + Value Expr // Optional initializer + ByteAlign Expr // Optional + AddrSpace Expr // Optional + LinkSection Expr // Optional +} + +func (*VarDeclStmt) isStmt() {} + // ReturnStmt represents a 'return' statement. type ReturnStmt struct { Value Expr // Optional @@ -321,6 +341,9 @@ type CallExpr struct { } // FieldAccessExpr represents a field/member access as a suffix operation (e.g. foo.bar). +// +// Note: in order to call a function on an object, use a CallExpr with a Fun of a FieldAccessExpr. +// See TestHelloWorld for example. type FieldAccessExpr struct { Receiver Expr // The object being accessed Field string // The field name @@ -475,23 +498,3 @@ type SwitchElseProng struct { type VarPattern struct { Names []string // Variable names (single or multiple for destructuring) } - -// VarDeclStmt represents a variable or const declaration at statement level, supporting destructuring and multi-var declarations. -type VarDeclStmt struct { - Const bool - Pattern VarPattern // Destructuring or multiple variable names - Type TypeExpr // Optional - Value Expr // Optional initializer - ByteAlign Expr // Optional - AddrSpace Expr // Optional - LinkSection Expr // Optional -} - -func (*VarDeclStmt) isStmt() {} - -// DotCallExpr represents a method call expression where a method is called on a receiver. -// For example, in the expression `foo.bar()`, `foo` is the Receiver and `bar()` is the Call. -type DotCallExpr struct { - Receiver Expr // The expression being called (e.g. foo) - Call *CallExpr -} diff --git a/internal/zig/zig.go b/internal/zig/zig.go index 22cd4ac..88b8f47 100644 --- a/internal/zig/zig.go +++ b/internal/zig/zig.go @@ -12,15 +12,18 @@ type formatter struct { indent int // indentation level } -const indentStr = " " // 4 spaces per indent +// indentStr defines the string used for each indentation level (4 spaces). +const indentStr = " " +// Writef writes formatted text to the underlying writer and updates line/col counters. +// It also handles indentation after newlines when appropriate. func (f *formatter) Writef(format string, a ...any) { s := fmt.Sprintf(format, a...) for i, r := range s { if r == '\n' { f.line++ f.col = 1 - // If next character is not a newline and not end, and not a closing brace, write indentation + // After a newline, write indentation for the next line unless it's a closing brace or another newline. if i+1 < len(s) && s[i+1] != '\n' && s[i+1] != '}' { f.writeIndent() } @@ -37,6 +40,8 @@ func (f *formatter) Writef(format string, a ...any) { } } +// writeIndent writes the current indentation level to the output. +// Call this at the start of a new line before writing statements or closing braces. func (f *formatter) writeIndent() { for i := 0; i < f.indent; i++ { if _, err := f.w.Write([]byte(indentStr)); err != nil { @@ -46,6 +51,7 @@ func (f *formatter) writeIndent() { } } +// Write is the entry point for formatting a Zig AST. func Write(w io.Writer, root *Root) (err error) { defer func() { if r := recover(); r != nil { @@ -70,17 +76,27 @@ func Write(w io.Writer, root *Root) (err error) { return nil } +// writeDecl emits a top-level declaration. func writeDecl(f *formatter, decl Decl) { - switch fn := decl.(type) { + switch d := decl.(type) { case *FnDecl: - f.Writef("fn %s(", fn.Name) - writeParams(f, fn.Params) + f.Writef("\nfn %s(", d.Name) + writeParams(f, d.Params) f.Writef(") ") - writeTypeExpr(f, fn.ReturnType) - writeBlock(f, fn.Body) + writeTypeExpr(f, d.ReturnType) + writeBlock(f, d.Body) + case *GlobalVarDecl: + if d.Const { + f.Writef("const %s = ", d.Name) + } else { + f.Writef("var %s = ", d.Name) + } + writeExpr(f, d.Value) + f.Writef(";\n") } } +// writeParams emits function parameters, separated by commas. func writeParams(f *formatter, params []*ParamDecl) { for i, param := range params { if i > 0 { @@ -93,6 +109,7 @@ func writeParams(f *formatter, params []*ParamDecl) { } } +// writeTypeExpr emits a type expression. func writeTypeExpr(f *formatter, typ TypeExpr) { switch t := typ.(type) { case *Identifier: @@ -104,29 +121,75 @@ func writeTypeExpr(f *formatter, typ TypeExpr) { } } +// writeBlock emits a block, handling indentation for statements and the closing brace. func writeBlock(f *formatter, block *Block) { if block == nil { f.Writef(";") return } f.Writef(" {\n") - f.indent++ + f.indent++ // Increase indentation for block contents. for i, stmt := range block.Stmts { - f.writeIndent() + f.writeIndent() // Indent each statement. writeStmt(f, stmt) if i < len(block.Stmts)-1 { f.Writef("\n") } } - f.indent-- + f.indent-- // Decrease indentation before closing brace. f.Writef("\n") - f.writeIndent() + f.writeIndent() // Indent the closing brace. f.Writef("}\n") } +// writeStmt emits a statement. Indentation is handled by the caller (writeBlock). func writeStmt(f *formatter, stmt Stmt) { - switch stmt.(type) { + switch s := stmt.(type) { case *ReturnStmt: - f.Writef("return;") + f.Writef("return") + if s.Value != nil { + f.Writef(" ") + writeExpr(f, s.Value) + } + f.Writef(";") + case *ExprStmt: + writeExpr(f, s.Expr) + f.Writef(";") + } +} + +// writeExpr emits an expression. +func writeExpr(f *formatter, expr Expr) { + switch e := expr.(type) { + case *Identifier: + f.Writef("%s", e.Name) + case *CallExpr: + writeExpr(f, e.Fun) + f.Writef("(") + for i, arg := range e.Args { + if i > 0 { + f.Writef(", ") + } + writeExpr(f, arg) + } + f.Writef(")") + case *FieldAccessExpr: + writeExpr(f, e.Receiver) + f.Writef(".%s", e.Field) + case *Literal: + switch e.Kind { + case "string": + f.Writef("%q", e.Value) + default: + f.Writef("%v", e.Value) + } + case *InitListExpr: + if e.Empty { + f.Writef(".{}") + } else { + f.Writef(".{") + // TODO + f.Writef("}") + } } } diff --git a/internal/zig/zig_test.go b/internal/zig/zig_test.go index e582c47..d4fe6c4 100644 --- a/internal/zig/zig_test.go +++ b/internal/zig/zig_test.go @@ -19,21 +19,49 @@ func Expect[T cmp.Ordered](expected, actual T) error { func TestHelloWorld(t *testing.T) { expected := `//! Hello, world! +const std = @import("std"); + fn main() void { - return; + std.debug.print("Hello, world!\n", .{}); } ` root := &zig.Root{ ContainerDocComment: "Hello, world!", ContainerMembers: []*zig.ContainerMember{ + { + Decl: &zig.GlobalVarDecl{ + Const: true, + Name: "std", + Value: &zig.CallExpr{ + Fun: &zig.Identifier{Name: "@import"}, + Args: []zig.Expr{ + &zig.Literal{Kind: "string", Value: "std"}, + }, + }, + }, + }, { Decl: &zig.FnDecl{ Name: "main", ReturnType: &zig.Identifier{Name: "void"}, Body: &zig.Block{ Stmts: []zig.Stmt{ - &zig.ReturnStmt{}, + &zig.ExprStmt{ + Expr: &zig.CallExpr{ + Fun: &zig.FieldAccessExpr{ + Receiver: &zig.FieldAccessExpr{ + Receiver: &zig.Identifier{Name: "std"}, + Field: "debug", + }, + Field: "print", + }, + Args: []zig.Expr{ + &zig.Literal{Kind: "string", Value: "Hello, world!\n"}, + &zig.InitListExpr{Empty: true}, + }, + }, + }, }, }, },