Compare commits
9 Commits
0bf8c3ef6b
...
main
Author | SHA1 | Date | |
---|---|---|---|
bea04d6810 | |||
d5f346cf8b | |||
2af696078d | |||
258b3c8e9b | |||
50b38254ab | |||
44f3cfca5c | |||
d002309e93 | |||
dba4617520 | |||
d94403d126 |
15
Makefile
15
Makefile
@@ -1,2 +1,15 @@
|
||||
run:
|
||||
go run internal/main.go -o hello.zig programs/hello.go && zig run hello.zig
|
||||
go run internal/main.go -o hello.zig programs/hello.go && zig run hello.zig
|
||||
|
||||
test: test-unit test-integration
|
||||
|
||||
test-unit:
|
||||
go test ./internal/zig
|
||||
|
||||
test-integration:
|
||||
go run ./cmd/testrunner
|
||||
|
||||
test-quick:
|
||||
go run ./cmd/testrunner
|
||||
|
||||
.PHONY: run test test-unit test-integration test-quick
|
||||
|
228
cmd/testrunner/main.go
Normal file
228
cmd/testrunner/main.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TestResult struct {
|
||||
Name string
|
||||
Passed bool
|
||||
Output string
|
||||
Error string
|
||||
Elapsed time.Duration
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
GoFile string
|
||||
ExpectedFile string
|
||||
ErrorFile string
|
||||
StdinFile string
|
||||
ArgsFile string
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Find all test cases
|
||||
testCases, err := findTestCases("tests")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error finding tests: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(testCases) == 0 {
|
||||
fmt.Println("No tests found")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
fmt.Printf("Running %d tests...\n\n", len(testCases))
|
||||
|
||||
// Run tests in parallel
|
||||
results := runTests(testCases)
|
||||
|
||||
// Print results
|
||||
passed := 0
|
||||
failed := 0
|
||||
for _, result := range results {
|
||||
if result.Passed {
|
||||
fmt.Printf("✓ %s (%v)\n", result.Name, result.Elapsed)
|
||||
passed++
|
||||
} else {
|
||||
fmt.Printf("✗ %s (%v)\n", result.Name, result.Elapsed)
|
||||
fmt.Printf(" Error: %s\n", result.Error)
|
||||
if result.Output != "" {
|
||||
fmt.Printf(" Output:\n%s\n", indent(result.Output, " "))
|
||||
}
|
||||
failed++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n%d passed, %d failed\n", passed, failed)
|
||||
|
||||
if failed > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func findTestCases(dir string) ([]TestCase, error) {
|
||||
var testCases []TestCase
|
||||
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasSuffix(path, ".go") {
|
||||
base := strings.TrimSuffix(path, ".go")
|
||||
testCase := TestCase{
|
||||
GoFile: path,
|
||||
ExpectedFile: base + ".expected",
|
||||
ErrorFile: base + ".error",
|
||||
StdinFile: base + ".stdin",
|
||||
ArgsFile: base + ".args",
|
||||
}
|
||||
|
||||
// Check if either .expected or .error exists
|
||||
if fileExists(testCase.ExpectedFile) || fileExists(testCase.ErrorFile) {
|
||||
testCases = append(testCases, testCase)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return testCases, err
|
||||
}
|
||||
|
||||
func runTests(testCases []TestCase) []TestResult {
|
||||
results := make([]TestResult, len(testCases))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i, tc := range testCases {
|
||||
wg.Add(1)
|
||||
go func(idx int, testCase TestCase) {
|
||||
defer wg.Done()
|
||||
results[idx] = runTest(testCase)
|
||||
}(i, tc)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return results
|
||||
}
|
||||
|
||||
func runTest(tc TestCase) TestResult {
|
||||
start := time.Now()
|
||||
testName := filepath.Base(strings.TrimSuffix(tc.GoFile, ".go"))
|
||||
|
||||
result := TestResult{
|
||||
Name: testName,
|
||||
Elapsed: time.Since(start),
|
||||
}
|
||||
|
||||
// Create temporary directory for test outputs
|
||||
tempDir, err := os.MkdirTemp("", "go-zig-test-*")
|
||||
if err != nil {
|
||||
result.Error = fmt.Sprintf("Failed to create temp dir: %v", err)
|
||||
return result
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
zigFile := filepath.Join(tempDir, testName+".zig")
|
||||
|
||||
// Compile Go to Zig
|
||||
compileCmd := exec.Command("go", "run", "./internal/main.go", "-o", zigFile, tc.GoFile)
|
||||
var compileOut bytes.Buffer
|
||||
compileCmd.Stderr = &compileOut
|
||||
compileCmd.Stdout = &compileOut
|
||||
|
||||
if err := compileCmd.Run(); err != nil {
|
||||
// Check if this is an expected compilation error
|
||||
if fileExists(tc.ErrorFile) {
|
||||
expectedError, _ := os.ReadFile(tc.ErrorFile)
|
||||
actualError := strings.TrimSpace(compileOut.String())
|
||||
expectedErrorStr := strings.TrimSpace(string(expectedError))
|
||||
|
||||
if actualError == expectedErrorStr {
|
||||
result.Passed = true
|
||||
} else {
|
||||
result.Error = fmt.Sprintf("Expected error:\n%s\n\nActual error:\n%s", expectedErrorStr, actualError)
|
||||
}
|
||||
} else {
|
||||
result.Error = fmt.Sprintf("Compilation failed: %v\nOutput: %s", err, compileOut.String())
|
||||
}
|
||||
result.Elapsed = time.Since(start)
|
||||
return result
|
||||
}
|
||||
|
||||
// If we expected a compilation error but didn't get one
|
||||
if fileExists(tc.ErrorFile) {
|
||||
result.Error = "Expected compilation to fail, but it succeeded"
|
||||
result.Elapsed = time.Since(start)
|
||||
return result
|
||||
}
|
||||
|
||||
// Run the Zig program
|
||||
runCmd := exec.Command("zig", "run", zigFile)
|
||||
|
||||
// Set up stdin if provided
|
||||
if fileExists(tc.StdinFile) {
|
||||
stdinData, _ := os.ReadFile(tc.StdinFile)
|
||||
runCmd.Stdin = bytes.NewReader(stdinData)
|
||||
}
|
||||
|
||||
// Set up args if provided
|
||||
if fileExists(tc.ArgsFile) {
|
||||
argsData, _ := os.ReadFile(tc.ArgsFile)
|
||||
args := strings.Fields(string(argsData))
|
||||
runCmd.Args = append(runCmd.Args, args...)
|
||||
}
|
||||
|
||||
// Capture both stdout and stderr (Zig's debug.print goes to stderr)
|
||||
output, err := runCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
result.Error = fmt.Sprintf("Execution failed: %v\nOutput: %s", err, string(output))
|
||||
result.Output = string(output)
|
||||
result.Elapsed = time.Since(start)
|
||||
return result
|
||||
}
|
||||
|
||||
// Compare output with expected
|
||||
if fileExists(tc.ExpectedFile) {
|
||||
expectedOutput, _ := os.ReadFile(tc.ExpectedFile)
|
||||
actualOutput := string(output)
|
||||
|
||||
if actualOutput == string(expectedOutput) {
|
||||
result.Passed = true
|
||||
} else {
|
||||
result.Error = fmt.Sprintf("Output mismatch.\nExpected:\n%s\nActual:\n%s",
|
||||
string(expectedOutput), actualOutput)
|
||||
result.Output = actualOutput
|
||||
}
|
||||
} else {
|
||||
// No expected file, just check it runs without error
|
||||
result.Passed = true
|
||||
}
|
||||
|
||||
result.Elapsed = time.Since(start)
|
||||
return result
|
||||
}
|
||||
|
||||
func fileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func indent(s string, prefix string) string {
|
||||
lines := strings.Split(s, "\n")
|
||||
for i, line := range lines {
|
||||
if line != "" {
|
||||
lines[i] = prefix + line
|
||||
}
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
5
hello.zig
Normal file
5
hello.zig
Normal file
@@ -0,0 +1,5 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn main() void {
|
||||
std.debug.print("Hello, world\n", .{});
|
||||
}
|
135
internal/main.go
135
internal/main.go
@@ -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)
|
||||
}
|
||||
}
|
||||
|
1
internal/zig/README.md
Normal file
1
internal/zig/README.md
Normal file
@@ -0,0 +1 @@
|
||||
A Zig code generator based on the [Zig language specification](https://github.com/ziglang/zig-spec) PEG grammar. The grammar is included in the source so that it can easily be diffed for changes in the future.
|
@@ -1,50 +1,543 @@
|
||||
// Abstract Syntax Tree (AST) definitions for Zig, closely following the grammar in zig-grammar.peg
|
||||
package zig
|
||||
|
||||
// https://github.com/ziglang/zig-spec/blob/master/grammar/grammar.peg
|
||||
|
||||
// Root is the top-level node of a Zig source file.
|
||||
// It represents the entire compilation unit.
|
||||
type Root struct {
|
||||
ContainerDocComment string // //! Doc Comment
|
||||
ContainerMembers []*ContainerMember
|
||||
ContainerDocComment DocComment // Module-level doc comment using //!
|
||||
ContainerMembers []*ContainerMember // Top-level declarations and fields
|
||||
}
|
||||
|
||||
type ContainerMember struct {
|
||||
// FIXME
|
||||
Decls []Decl
|
||||
DocComment DocComment // /// Doc Comment (optional)
|
||||
Comptime bool // 'comptime' field (optional)
|
||||
Field *ContainerField
|
||||
Decl Decl // Can be nil if this is a field
|
||||
}
|
||||
|
||||
type Decl interface{}
|
||||
// ContainerField represents a field in a struct/enum/union container.
|
||||
type ContainerField struct {
|
||||
Name string // May be empty for anonymous fields
|
||||
Type TypeExpr
|
||||
ByteAlign *Expr // Optional
|
||||
Value Expr // Optional initializer
|
||||
}
|
||||
|
||||
// Decl is any top-level declaration (function, variable, usingnamespace, etc).
|
||||
type Decl interface {
|
||||
isDecl()
|
||||
}
|
||||
|
||||
// FnFlags is a bitfield for function declaration options.
|
||||
type FnFlags uint8
|
||||
|
||||
const (
|
||||
FnExport FnFlags = 1 << iota
|
||||
FnExtern
|
||||
FnInline
|
||||
FnNoInline
|
||||
FnThreadLocal
|
||||
)
|
||||
|
||||
// FnDecl represents a function declaration.
|
||||
type FnDecl struct {
|
||||
Name string
|
||||
Params []*ParamDecl
|
||||
CallConv string
|
||||
ReturnType TypeExpr
|
||||
Body *Block // nil means semicolon
|
||||
Flags FnFlags
|
||||
ExternName string // Optional string for extern
|
||||
Name string // May be empty (anonymous)
|
||||
Params []*ParamDecl
|
||||
ByteAlign *Expr
|
||||
AddrSpace *Expr
|
||||
LinkSection *Expr
|
||||
CallConv *Expr
|
||||
ReturnType TypeExpr
|
||||
Body *Block // nil means semicolon
|
||||
}
|
||||
|
||||
func (*FnDecl) isDecl() {}
|
||||
|
||||
// GlobalVarFlags is a bitfield for global variable declaration options.
|
||||
type GlobalVarFlags uint8
|
||||
|
||||
const (
|
||||
GlobalVarConst GlobalVarFlags = 1 << iota
|
||||
GlobalVarExport
|
||||
GlobalVarExtern
|
||||
GlobalVarThreadLocal
|
||||
)
|
||||
|
||||
// GlobalVarDecl represents a top-level (global) variable or constant declaration.
|
||||
// These are only allowed at the container/module scope and use a restricted syntax:
|
||||
// no destructuring or multi-var declarations, just a single name and optional type/initializer.
|
||||
type GlobalVarDecl struct {
|
||||
Flags GlobalVarFlags
|
||||
ExternName string // Optional string for extern
|
||||
Name string
|
||||
Type TypeExpr // Optional
|
||||
ByteAlign *Expr
|
||||
AddrSpace *Expr
|
||||
LinkSection *Expr
|
||||
Value Expr // Optional initializer
|
||||
}
|
||||
|
||||
func (*GlobalVarDecl) isDecl() {}
|
||||
|
||||
// UsingNamespaceDecl represents a 'usingnamespace' declaration.
|
||||
type UsingNamespaceDecl struct {
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
func (*UsingNamespaceDecl) isDecl() {}
|
||||
|
||||
// ComptimeDecl represents a 'comptime' block at the container level.
|
||||
type ComptimeDecl struct {
|
||||
Block *Block
|
||||
}
|
||||
|
||||
func (*ComptimeDecl) isDecl() {}
|
||||
|
||||
// TestDecl represents a 'test' declaration.
|
||||
type TestDecl struct {
|
||||
Name string // Optional
|
||||
Block *Block
|
||||
}
|
||||
|
||||
func (*TestDecl) isDecl() {}
|
||||
|
||||
// ParamDecl represents a function parameter.
|
||||
type ParamDecl struct {
|
||||
DocComment string // ??? It's what it says
|
||||
Name string // Can be empty
|
||||
Type TypeExpr // anytype when empty
|
||||
DocComment DocComment // /// Doc Comment (optional)
|
||||
NoAlias bool
|
||||
Comptime bool
|
||||
Name string // May be empty
|
||||
Type TypeExpr // 'anytype' if empty
|
||||
}
|
||||
|
||||
// ContainerKind represents the kind of container (struct, enum, union, opaque).
|
||||
type ContainerKind int
|
||||
|
||||
const (
|
||||
ContainerStruct ContainerKind = iota
|
||||
ContainerEnum
|
||||
ContainerUnion
|
||||
ContainerOpaque
|
||||
)
|
||||
|
||||
// ContainerDecl represents a struct, enum, union, or opaque declaration.
|
||||
type ContainerDecl struct {
|
||||
Extern bool
|
||||
Packed bool
|
||||
Kind ContainerKind
|
||||
TagType TypeExpr // Optional (for enum/union)
|
||||
Fields []*ContainerMember
|
||||
DocComment DocComment
|
||||
}
|
||||
|
||||
func (*ContainerDecl) isDecl() {}
|
||||
|
||||
// ErrorSetDecl represents an error set declaration.
|
||||
type ErrorSetDecl struct {
|
||||
Names []string
|
||||
}
|
||||
|
||||
func (*ErrorSetDecl) isDecl() {}
|
||||
|
||||
// Block represents a block of statements.
|
||||
type Block struct {
|
||||
Label string
|
||||
Label string // Optional
|
||||
Stmts []Stmt
|
||||
}
|
||||
|
||||
type Stmt interface{}
|
||||
// Stmt is any statement.
|
||||
type Stmt interface {
|
||||
isStmt()
|
||||
}
|
||||
|
||||
type ReturnStmt struct{}
|
||||
// ExprStmt represents an expression statement (e.g. a function call as a statement).
|
||||
type ExprStmt struct {
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
func (*ExprStmt) isStmt() {}
|
||||
|
||||
// VarDeclStmt represents a local variable or constant declaration statement inside a function or block.
|
||||
// These support destructuring and multi-var declarations, and are only valid at statement/block scope.
|
||||
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
|
||||
}
|
||||
|
||||
func (*ReturnStmt) isStmt() {}
|
||||
|
||||
// IfStmt represents an if statement (with optional else branch and payload).
|
||||
type IfStmt struct {
|
||||
Cond Expr // Condition expression
|
||||
Then Stmt // Then branch
|
||||
Else Stmt // Optional else branch
|
||||
Payload *Payload // Optional payload (for |x|, |*x|, |*x, y|, etc.)
|
||||
}
|
||||
|
||||
func (*IfStmt) isStmt() {}
|
||||
|
||||
// IfExpr represents an if expression (with optional else branch and payload).
|
||||
type IfExpr struct {
|
||||
Cond Expr // Condition expression
|
||||
Then Expr // Then branch
|
||||
Else Expr // Optional else branch
|
||||
Payload *Payload // Optional payload (for |x|, |*x|, |*x, y|, etc.)
|
||||
}
|
||||
|
||||
// DeferStmt represents a 'defer' or 'errdefer' statement.
|
||||
type DeferStmt struct {
|
||||
ErrDefer bool // True for 'errdefer', false for 'defer'
|
||||
Payload *Payload // Optional payload (for |x|, |*x|, etc.)
|
||||
Stmt Stmt // Statement to defer
|
||||
}
|
||||
|
||||
func (*DeferStmt) isStmt() {}
|
||||
|
||||
// SuspendStmt represents a 'suspend' or 'nosuspend' statement.
|
||||
type SuspendStmt struct {
|
||||
NoSuspend bool
|
||||
Stmt Stmt
|
||||
}
|
||||
|
||||
func (*SuspendStmt) isStmt() {}
|
||||
|
||||
// BlockStmt allows a block to be used as a statement.
|
||||
type BlockStmt struct {
|
||||
Block *Block
|
||||
}
|
||||
|
||||
func (*BlockStmt) isStmt() {}
|
||||
|
||||
// BreakStmt represents a 'break' statement.
|
||||
type BreakStmt struct {
|
||||
Label string // Optional
|
||||
Value Expr // Optional
|
||||
}
|
||||
|
||||
func (*BreakStmt) isStmt() {}
|
||||
|
||||
// ContinueStmt represents a 'continue' statement.
|
||||
type ContinueStmt struct {
|
||||
Label string // Optional
|
||||
}
|
||||
|
||||
func (*ContinueStmt) isStmt() {}
|
||||
|
||||
// LoopKind represents the kind of loop (for or while).
|
||||
type LoopKind int
|
||||
|
||||
const (
|
||||
LoopFor LoopKind = iota
|
||||
LoopWhile
|
||||
)
|
||||
|
||||
// LoopStmt represents a for/while loop statement.
|
||||
type LoopStmt struct {
|
||||
Inline bool // True if 'inline' is present
|
||||
Kind LoopKind // For or While
|
||||
Prefix LoopPrefix // ForPrefix or WhilePrefix
|
||||
Body Stmt // Loop body
|
||||
Else Stmt // Optional else branch
|
||||
Payload *Payload // Optional payload (for |x|, |*x|, |*x, y|, etc.)
|
||||
}
|
||||
|
||||
func (*LoopStmt) isStmt() {}
|
||||
|
||||
// LoopPrefix is the prefix of a for/while loop.
|
||||
type LoopPrefix interface{}
|
||||
|
||||
// ForPrefix represents the prefix of a for loop.
|
||||
type ForPrefix struct {
|
||||
Args []ForArg // For loop arguments
|
||||
Payload *Payload // Payload (|*x, y|, etc.)
|
||||
}
|
||||
|
||||
// ForArg represents an argument in a for loop.
|
||||
type ForArg struct {
|
||||
Expr Expr // Argument expression
|
||||
From Expr // Optional (for .. or ..<)
|
||||
}
|
||||
|
||||
// WhilePrefix represents the prefix of a while loop.
|
||||
type WhilePrefix struct {
|
||||
Cond Expr // Condition expression
|
||||
Payload *Payload // Optional payload (for |x|, |*x|, etc.)
|
||||
Continue Expr // Optional (while continue expression)
|
||||
}
|
||||
|
||||
// SwitchStmt represents a switch statement.
|
||||
type SwitchStmt struct {
|
||||
Cond Expr
|
||||
Prongs []*SwitchProng
|
||||
}
|
||||
|
||||
func (*SwitchStmt) isStmt() {}
|
||||
|
||||
// SwitchExpr represents a switch expression.
|
||||
type SwitchExpr struct {
|
||||
Cond Expr
|
||||
Prongs []*SwitchProng
|
||||
}
|
||||
|
||||
// SwitchProng represents a prong in a switch.
|
||||
type SwitchProng struct {
|
||||
Inline bool // True if 'inline' is present
|
||||
Cases []*SwitchCase // List of cases for this prong
|
||||
Payload *Payload // Optional payload (for |*x, y|, etc.)
|
||||
Expr Expr // Result expression for this prong
|
||||
}
|
||||
|
||||
// SwitchCase represents a case in a switch.
|
||||
type SwitchCase struct {
|
||||
Expr Expr
|
||||
To Expr // Optional (for ..)
|
||||
IsElse bool
|
||||
}
|
||||
|
||||
// AsmExpr represents an inline assembly expression.
|
||||
type AsmExpr struct {
|
||||
Volatile bool
|
||||
Template Expr
|
||||
Outputs []*AsmOutputItem
|
||||
Inputs []*AsmInputItem
|
||||
Clobbers []string
|
||||
}
|
||||
|
||||
// AsmOutputItem represents an output operand in asm.
|
||||
type AsmOutputItem struct {
|
||||
Name string
|
||||
Constraint string
|
||||
Type TypeExpr
|
||||
}
|
||||
|
||||
// AsmInputItem represents an input operand in asm.
|
||||
type AsmInputItem struct {
|
||||
Name string
|
||||
Constraint string
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// InitListExpr represents an initializer list.
|
||||
// Exactly one of Fields, Values, or Empty must be set (non-nil/non-empty or true).
|
||||
type InitListExpr struct {
|
||||
Fields []*FieldInit // Field initializers (for {.foo = 1, .bar = 2}), mutually exclusive with Values/Empty
|
||||
Values []Expr // Positional initializers (for {1, 2, 3}), mutually exclusive with Fields/Empty
|
||||
Empty bool // True if '{}', mutually exclusive with Fields/Values
|
||||
}
|
||||
|
||||
// FieldInit represents a field initializer in an init list.
|
||||
type FieldInit struct {
|
||||
Name string
|
||||
Value Expr
|
||||
}
|
||||
|
||||
// Identifier represents an identifier expression (variable, field, etc).
|
||||
type Identifier struct {
|
||||
Name string // The identifier name
|
||||
}
|
||||
|
||||
// LiteralKind represents the kind of literal.
|
||||
type LiteralKind int
|
||||
|
||||
const (
|
||||
LiteralInt LiteralKind = iota
|
||||
LiteralFloat
|
||||
LiteralString
|
||||
LiteralChar
|
||||
)
|
||||
|
||||
// Literal represents a literal value (int, float, string, char).
|
||||
type Literal struct {
|
||||
Kind LiteralKind
|
||||
Value string // The literal value as a string
|
||||
}
|
||||
|
||||
// BinaryExpr represents a binary operation (e.g. +, -, *, /, etc).
|
||||
type BinaryExpr struct {
|
||||
Op string // Operator, e.g. "+", "-", "*", etc.
|
||||
Left Expr // Left operand
|
||||
Right Expr // Right operand
|
||||
}
|
||||
|
||||
// UnaryExpr represents a unary operation (e.g. !, -, ~, etc).
|
||||
type UnaryExpr struct {
|
||||
Op string // Operator, e.g. "-", "!", "~"
|
||||
Expr Expr // Operand
|
||||
}
|
||||
|
||||
// GroupedExpr represents a parenthesized expression.
|
||||
type GroupedExpr struct {
|
||||
Expr Expr // The grouped expression
|
||||
}
|
||||
|
||||
// CallExpr represents a function call.
|
||||
type CallExpr struct {
|
||||
Fun Expr // Function being called
|
||||
Args []Expr // Arguments to the function
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// IndexExpr represents an indexing operation as a suffix (e.g. arr[0]).
|
||||
type IndexExpr struct {
|
||||
Receiver Expr // The object being indexed
|
||||
Index Expr // The index expression
|
||||
}
|
||||
|
||||
// ResumeExpr represents a 'resume' expression.
|
||||
type ResumeExpr struct {
|
||||
Expr Expr // The expression to resume
|
||||
}
|
||||
|
||||
// ComptimeExpr represents a 'comptime' expression.
|
||||
type ComptimeExpr struct {
|
||||
Expr Expr // The expression to evaluate at comptime
|
||||
}
|
||||
|
||||
// NosuspendExpr represents a 'nosuspend' expression.
|
||||
type NosuspendExpr struct {
|
||||
Expr Expr // The expression to evaluate with nosuspend
|
||||
}
|
||||
|
||||
// Expr is any expression.
|
||||
// This is an empty interface to allow maximum flexibility.
|
||||
// Consider using type switches when working with expressions.
|
||||
type Expr interface{}
|
||||
|
||||
// This will need to become a real type expr someday
|
||||
type TypeExpr string
|
||||
// TypeExpr is any type expression.
|
||||
// This is an empty interface to allow maximum flexibility.
|
||||
// Consider using type switches when working with type expressions.
|
||||
type TypeExpr interface{}
|
||||
|
||||
func (t TypeExpr) String() string {
|
||||
if string(t) == "" {
|
||||
return "anytype"
|
||||
}
|
||||
return string(t)
|
||||
// PrefixTypeExpr represents a type with a string prefix. Examples include optionals and pointers.
|
||||
type PrefixTypeExpr struct {
|
||||
Op string
|
||||
Base TypeExpr
|
||||
}
|
||||
|
||||
// ArrayTypeExpr represents an array type ([N]T).
|
||||
type ArrayTypeExpr struct {
|
||||
Size Expr
|
||||
Elem TypeExpr
|
||||
}
|
||||
|
||||
// SliceTypeExpr represents a slice type ([]T).
|
||||
type SliceTypeExpr struct {
|
||||
Elem TypeExpr
|
||||
}
|
||||
|
||||
// ErrorUnionTypeExpr represents an error union type (E!T).
|
||||
type ErrorUnionTypeExpr struct {
|
||||
ErrSet TypeExpr
|
||||
Type TypeExpr
|
||||
}
|
||||
|
||||
// DocComment represents a doc comment (/// or //! lines).
|
||||
// Newlines in the string automatically add more comments in the output.
|
||||
type DocComment string
|
||||
|
||||
// Payload represents a control flow payload (|x|, |*x|, |*x, y|, etc).
|
||||
// Each entry in Names corresponds to a variable name; the same index in Pointers is true if that name is a pointer (|*x|).
|
||||
type Payload struct {
|
||||
Names []string // Names in the payload, in order
|
||||
Pointers []bool // True if the corresponding name is a pointer (|*x|, |*x, y|, |*x, *y|, etc.)
|
||||
}
|
||||
|
||||
// LabeledBlock represents a labeled block or loop (label: {...}).
|
||||
type LabeledBlock struct {
|
||||
Label string // The label name
|
||||
Block *Block // The labeled block
|
||||
}
|
||||
|
||||
// LabeledTypeExpr represents a labeled type block (label: type).
|
||||
type LabeledTypeExpr struct {
|
||||
Label string // The label name
|
||||
Type TypeExpr // The labeled type
|
||||
}
|
||||
|
||||
// IfTypeExpr represents an if expression at the type level.
|
||||
type IfTypeExpr struct {
|
||||
Cond Expr
|
||||
Then TypeExpr
|
||||
Else TypeExpr // Optional
|
||||
Payload *Payload // Optional
|
||||
}
|
||||
|
||||
// ForTypeExpr represents a for expression at the type level.
|
||||
type ForTypeExpr struct {
|
||||
Prefix *ForPrefix
|
||||
Body TypeExpr
|
||||
Else TypeExpr // Optional
|
||||
Payload *Payload // Optional
|
||||
}
|
||||
|
||||
// WhileTypeExpr represents a while expression at the type level.
|
||||
type WhileTypeExpr struct {
|
||||
Prefix *WhilePrefix
|
||||
Body TypeExpr
|
||||
Else TypeExpr // Optional
|
||||
Payload *Payload // Optional
|
||||
}
|
||||
|
||||
// DotAsteriskExpr represents a .*
|
||||
type DotAsteriskExpr struct {
|
||||
Receiver Expr // The expression being dereferenced
|
||||
}
|
||||
|
||||
// DotQuestionExpr represents a .?
|
||||
type DotQuestionExpr struct {
|
||||
Receiver Expr // The expression being checked for optional
|
||||
}
|
||||
|
||||
// AsyncExpr represents an 'async' expression.
|
||||
type AsyncExpr struct {
|
||||
Expr Expr // The expression to be awaited asynchronously
|
||||
}
|
||||
|
||||
// TryExpr represents a 'try' expression.
|
||||
type TryExpr struct {
|
||||
Expr Expr // The expression to try
|
||||
}
|
||||
|
||||
// AwaitExpr represents an 'await' expression.
|
||||
type AwaitExpr struct {
|
||||
Expr Expr // The expression to await
|
||||
}
|
||||
|
||||
// UnreachableExpr represents the 'unreachable' keyword.
|
||||
type UnreachableExpr struct{}
|
||||
|
||||
// Note: The following types were removed as they were duplicates:
|
||||
// - EmptyInitListExpr (use InitListExpr with Empty=true)
|
||||
// - PositionalInitListExpr (use InitListExpr with Values)
|
||||
// - FieldInitListExpr (use InitListExpr with Fields)
|
||||
// - SwitchProngPayload (use Payload)
|
||||
// - SwitchProngCase (use SwitchCase)
|
||||
// - SwitchProngFull (use SwitchProng)
|
||||
// - SwitchElseProng (use SwitchProng with SwitchCase.IsElse=true)
|
||||
|
||||
// VarPattern represents a variable pattern for destructuring or multiple variable declarations.
|
||||
type VarPattern struct {
|
||||
Names []string // Variable names (single or multiple for destructuring)
|
||||
}
|
||||
|
370
internal/zig/asthelpers.go
Normal file
370
internal/zig/asthelpers.go
Normal file
@@ -0,0 +1,370 @@
|
||||
package zig
|
||||
|
||||
// Id creates an identifier expression.
|
||||
func Id(name string) *Identifier {
|
||||
return &Identifier{Name: name}
|
||||
}
|
||||
|
||||
// IntLit creates an integer literal.
|
||||
func IntLit(value string) *Literal {
|
||||
return &Literal{Kind: LiteralInt, Value: value}
|
||||
}
|
||||
|
||||
// FloatLit creates a float literal.
|
||||
func FloatLit(value string) *Literal {
|
||||
return &Literal{Kind: LiteralFloat, Value: value}
|
||||
}
|
||||
|
||||
// StringLit creates a string literal.
|
||||
func StringLit(value string) *Literal {
|
||||
return &Literal{Kind: LiteralString, Value: value}
|
||||
}
|
||||
|
||||
// CharLit creates a character literal.
|
||||
func CharLit(value string) *Literal {
|
||||
return &Literal{Kind: LiteralChar, Value: value}
|
||||
}
|
||||
|
||||
// FieldAccess creates a field access expression.
|
||||
func FieldAccess(recv Expr, field string) *FieldAccessExpr {
|
||||
return &FieldAccessExpr{Receiver: recv, Field: field}
|
||||
}
|
||||
|
||||
// Call creates a function call expression.
|
||||
func Call(fun Expr, args ...Expr) *CallExpr {
|
||||
return &CallExpr{Fun: fun, Args: args}
|
||||
}
|
||||
|
||||
// InitList creates an initializer list expression.
|
||||
func InitList(exprs ...Expr) *InitListExpr {
|
||||
if len(exprs) == 0 {
|
||||
return &InitListExpr{Empty: true}
|
||||
}
|
||||
return &InitListExpr{Values: exprs}
|
||||
}
|
||||
|
||||
// DeclareFn creates a function declaration with flags.
|
||||
func DeclareFn(name string, retType TypeExpr, body *Block, params []*ParamDecl, flags FnFlags) *FnDecl {
|
||||
return &FnDecl{
|
||||
Name: name,
|
||||
ReturnType: retType,
|
||||
Body: body,
|
||||
Flags: flags,
|
||||
Params: params,
|
||||
}
|
||||
}
|
||||
|
||||
// NewExprStmt creates an expression statement.
|
||||
func NewExprStmt(expr Expr) *ExprStmt {
|
||||
return &ExprStmt{Expr: expr}
|
||||
}
|
||||
|
||||
// DeclareVarStmt creates a variable or const declaration statement.
|
||||
func DeclareVarStmt(constant bool, names []string, typ TypeExpr, value Expr) *VarDeclStmt {
|
||||
return &VarDeclStmt{
|
||||
Const: constant,
|
||||
Pattern: VarPattern{Names: names},
|
||||
Type: typ,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// DeclareGlobalVar creates a global variable declaration with flags.
|
||||
func DeclareGlobalVar(name string, value Expr, flags GlobalVarFlags) *GlobalVarDecl {
|
||||
return &GlobalVarDecl{
|
||||
Flags: flags,
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// Return creates a return statement.
|
||||
func Return(value Expr) *ReturnStmt {
|
||||
return &ReturnStmt{Value: value}
|
||||
}
|
||||
|
||||
// If creates an if statement.
|
||||
func If(cond Expr, then, els Stmt) *IfStmt {
|
||||
return &IfStmt{Cond: cond, Then: then, Else: els}
|
||||
}
|
||||
|
||||
// IfWithPayload creates an if statement with a payload.
|
||||
func IfWithPayload(cond Expr, payload *Payload, then, els Stmt) *IfStmt {
|
||||
return &IfStmt{Cond: cond, Payload: payload, Then: then, Else: els}
|
||||
}
|
||||
|
||||
// NewBlock creates a block of statements.
|
||||
func NewBlock(stmts ...Stmt) *Block {
|
||||
return &Block{Stmts: stmts}
|
||||
}
|
||||
|
||||
// NewBlock creates a block statement containing a block of statements.
|
||||
func NewBlockStmt(stmts ...Stmt) *BlockStmt {
|
||||
return &BlockStmt{
|
||||
Block: &Block{Stmts: stmts},
|
||||
}
|
||||
}
|
||||
|
||||
// Try creates a try expression.
|
||||
func Try(expr Expr) *TryExpr {
|
||||
return &TryExpr{Expr: expr}
|
||||
}
|
||||
|
||||
// Binary creates a binary expression.
|
||||
func Binary(op string, left, right Expr) *BinaryExpr {
|
||||
return &BinaryExpr{Op: op, Left: left, Right: right}
|
||||
}
|
||||
|
||||
// Param creates a function parameter declaration.
|
||||
func Param(name string, typ TypeExpr) *ParamDecl {
|
||||
return &ParamDecl{Name: name, Type: typ}
|
||||
}
|
||||
|
||||
// StructDecl creates a struct declaration with the given fields/members.
|
||||
func StructDecl(fields ...*ContainerMember) *ContainerDecl {
|
||||
return &ContainerDecl{
|
||||
Kind: ContainerStruct,
|
||||
Fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
// Field creates a struct field (optionally with initializer).
|
||||
func Field(name string, typ TypeExpr, byteAlign *Expr, value Expr) *ContainerMember {
|
||||
return &ContainerMember{
|
||||
Field: &ContainerField{
|
||||
Name: name,
|
||||
Type: typ,
|
||||
ByteAlign: byteAlign,
|
||||
Value: value,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Method creates a method (function declaration) as a struct member.
|
||||
func Method(fn *FnDecl) *ContainerMember {
|
||||
return &ContainerMember{Decl: fn}
|
||||
}
|
||||
|
||||
// OptionalType creates an optional type (?T).
|
||||
func OptionalType(base TypeExpr) *PrefixTypeExpr {
|
||||
return &PrefixTypeExpr{Op: "?", Base: base}
|
||||
}
|
||||
|
||||
// PointerType creates a pointer type (*T).
|
||||
func PointerType(base TypeExpr) *PrefixTypeExpr {
|
||||
return &PrefixTypeExpr{Op: "*", Base: base}
|
||||
}
|
||||
|
||||
// ArrayType creates an array type ([N]T).
|
||||
func ArrayType(size Expr, elem TypeExpr) *ArrayTypeExpr {
|
||||
return &ArrayTypeExpr{Size: size, Elem: elem}
|
||||
}
|
||||
|
||||
// ErrorSet creates an error set declaration.
|
||||
func ErrorSet(names ...string) *ErrorSetDecl {
|
||||
return &ErrorSetDecl{Names: names}
|
||||
}
|
||||
|
||||
// ErrorUnionType creates an error union type (E!T).
|
||||
func ErrorUnionType(errSet, typ TypeExpr) *ErrorUnionTypeExpr {
|
||||
return &ErrorUnionTypeExpr{ErrSet: errSet, Type: typ}
|
||||
}
|
||||
|
||||
// DeferStmt creates a defer statement.
|
||||
func Defer(stmt Stmt) *DeferStmt {
|
||||
return &DeferStmt{Stmt: stmt}
|
||||
}
|
||||
|
||||
// ForLoop creates a for loop statement.
|
||||
func ForLoop(args []ForArg, payload *Payload, body Stmt, els Stmt) *LoopStmt {
|
||||
return &LoopStmt{
|
||||
Kind: LoopFor,
|
||||
Prefix: &ForPrefix{Args: args, Payload: payload},
|
||||
Body: body,
|
||||
Else: els,
|
||||
}
|
||||
}
|
||||
|
||||
// WhileLoop creates a while loop statement.
|
||||
func WhileLoop(cond Expr, continueExpr Expr, payload *Payload, body Stmt, els Stmt) *LoopStmt {
|
||||
return &LoopStmt{
|
||||
Kind: LoopWhile,
|
||||
Prefix: &WhilePrefix{
|
||||
Cond: cond,
|
||||
Continue: continueExpr,
|
||||
Payload: payload,
|
||||
},
|
||||
Body: body,
|
||||
Else: els,
|
||||
}
|
||||
}
|
||||
|
||||
// ForArg creates a for loop argument.
|
||||
func ForArgExpr(expr Expr) ForArg {
|
||||
return ForArg{Expr: expr}
|
||||
}
|
||||
|
||||
// Payload creates a payload for control flow.
|
||||
func PayloadNames(names []string, pointers []bool) *Payload {
|
||||
return &Payload{Names: names, Pointers: pointers}
|
||||
}
|
||||
|
||||
// SwitchStmt creates a switch statement.
|
||||
func Switch(cond Expr, prongs ...*SwitchProng) *SwitchStmt {
|
||||
return &SwitchStmt{Cond: cond, Prongs: prongs}
|
||||
}
|
||||
|
||||
// SwitchProng creates a switch prong.
|
||||
func Prong(cases []*SwitchCase, payload *Payload, expr Expr) *SwitchProng {
|
||||
return &SwitchProng{Cases: cases, Payload: payload, Expr: expr}
|
||||
}
|
||||
|
||||
// SwitchCase creates a switch case.
|
||||
func Case(expr Expr, to Expr) *SwitchCase {
|
||||
return &SwitchCase{Expr: expr, To: to}
|
||||
}
|
||||
|
||||
// ElseCase creates an else case for a switch.
|
||||
func ElseCase() *SwitchCase {
|
||||
return &SwitchCase{IsElse: true}
|
||||
}
|
||||
|
||||
// BreakStmt creates a break statement.
|
||||
func Break(label string, value Expr) *BreakStmt {
|
||||
return &BreakStmt{Label: label, Value: value}
|
||||
}
|
||||
|
||||
// ContinueStmt creates a continue statement.
|
||||
func Continue(label string) *ContinueStmt {
|
||||
return &ContinueStmt{Label: label}
|
||||
}
|
||||
|
||||
// UnaryExpr creates a unary expression.
|
||||
func Unary(op string, expr Expr) *UnaryExpr {
|
||||
return &UnaryExpr{Op: op, Expr: expr}
|
||||
}
|
||||
|
||||
// IndexExpr creates an index expression.
|
||||
func Index(receiver, index Expr) *IndexExpr {
|
||||
return &IndexExpr{Receiver: receiver, Index: index}
|
||||
}
|
||||
|
||||
// UnreachableExpr creates an unreachable expression.
|
||||
func Unreachable() *UnreachableExpr {
|
||||
return &UnreachableExpr{}
|
||||
}
|
||||
|
||||
// TestDecl creates a test declaration.
|
||||
func Test(name string, block *Block) *TestDecl {
|
||||
return &TestDecl{Name: name, Block: block}
|
||||
}
|
||||
|
||||
// GroupedExpr creates a grouped (parenthesized) expression.
|
||||
func Grouped(expr Expr) *GroupedExpr {
|
||||
return &GroupedExpr{Expr: expr}
|
||||
}
|
||||
|
||||
// ComptimeExpr creates a comptime expression.
|
||||
func Comptime(expr Expr) *ComptimeExpr {
|
||||
return &ComptimeExpr{Expr: expr}
|
||||
}
|
||||
|
||||
// NosuspendExpr creates a nosuspend expression.
|
||||
func Nosuspend(expr Expr) *NosuspendExpr {
|
||||
return &NosuspendExpr{Expr: expr}
|
||||
}
|
||||
|
||||
// SliceType creates a slice type ([]T).
|
||||
func SliceType(elem TypeExpr) *SliceTypeExpr {
|
||||
return &SliceTypeExpr{Elem: elem}
|
||||
}
|
||||
|
||||
// IfExpression creates an if expression.
|
||||
func IfExpression(cond Expr, then, els Expr) *IfExpr {
|
||||
return &IfExpr{Cond: cond, Then: then, Else: els}
|
||||
}
|
||||
|
||||
// IfExpressionWithPayload creates an if expression with a payload.
|
||||
func IfExpressionWithPayload(cond Expr, payload *Payload, then, els Expr) *IfExpr {
|
||||
return &IfExpr{Cond: cond, Payload: payload, Then: then, Else: els}
|
||||
}
|
||||
|
||||
// SwitchExpression creates a switch expression.
|
||||
func SwitchExpression(cond Expr, prongs ...*SwitchProng) *SwitchExpr {
|
||||
return &SwitchExpr{Cond: cond, Prongs: prongs}
|
||||
}
|
||||
|
||||
// EnumDecl creates an enum declaration.
|
||||
func EnumDecl(tagType TypeExpr, fields ...*ContainerMember) *ContainerDecl {
|
||||
return &ContainerDecl{
|
||||
Kind: ContainerEnum,
|
||||
TagType: tagType,
|
||||
Fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
// UnionDecl creates a union declaration.
|
||||
func UnionDecl(tagType TypeExpr, fields ...*ContainerMember) *ContainerDecl {
|
||||
return &ContainerDecl{
|
||||
Kind: ContainerUnion,
|
||||
TagType: tagType,
|
||||
Fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
// OpaqueDecl creates an opaque declaration.
|
||||
func OpaqueDecl() *ContainerDecl {
|
||||
return &ContainerDecl{
|
||||
Kind: ContainerOpaque,
|
||||
}
|
||||
}
|
||||
|
||||
// AsyncExpr creates an async expression.
|
||||
func Async(expr Expr) *AsyncExpr {
|
||||
return &AsyncExpr{Expr: expr}
|
||||
}
|
||||
|
||||
// AwaitExpr creates an await expression.
|
||||
func Await(expr Expr) *AwaitExpr {
|
||||
return &AwaitExpr{Expr: expr}
|
||||
}
|
||||
|
||||
// ResumeExpr creates a resume expression.
|
||||
func Resume(expr Expr) *ResumeExpr {
|
||||
return &ResumeExpr{Expr: expr}
|
||||
}
|
||||
|
||||
// DotAsterisk creates a .* expression (pointer dereference).
|
||||
func DotAsterisk(receiver Expr) *DotAsteriskExpr {
|
||||
return &DotAsteriskExpr{Receiver: receiver}
|
||||
}
|
||||
|
||||
// DotQuestion creates a .? expression (optional unwrap).
|
||||
func DotQuestion(receiver Expr) *DotQuestionExpr {
|
||||
return &DotQuestionExpr{Receiver: receiver}
|
||||
}
|
||||
|
||||
// ErrDefer creates an errdefer statement.
|
||||
func ErrDefer(payload *Payload, stmt Stmt) *DeferStmt {
|
||||
return &DeferStmt{ErrDefer: true, Payload: payload, Stmt: stmt}
|
||||
}
|
||||
|
||||
// UsingNamespace creates a usingnamespace declaration.
|
||||
func UsingNamespace(expr Expr) *UsingNamespaceDecl {
|
||||
return &UsingNamespaceDecl{Expr: expr}
|
||||
}
|
||||
|
||||
// ComptimeBlock creates a comptime block declaration.
|
||||
func ComptimeBlock(block *Block) *ComptimeDecl {
|
||||
return &ComptimeDecl{Block: block}
|
||||
}
|
||||
|
||||
// InitListFields creates an init list with field initializers.
|
||||
func InitListFields(fields ...*FieldInit) *InitListExpr {
|
||||
return &InitListExpr{Fields: fields}
|
||||
}
|
||||
|
||||
// FieldInitPair creates a field initializer.
|
||||
func FieldInitPair(name string, value Expr) *FieldInit {
|
||||
return &FieldInit{Name: name, Value: value}
|
||||
}
|
582
internal/zig/zig-grammar.peg
Normal file
582
internal/zig/zig-grammar.peg
Normal file
@@ -0,0 +1,582 @@
|
||||
Root <- skip container_doc_comment? ContainerMembers eof
|
||||
|
||||
# *** Top level ***
|
||||
ContainerMembers <- ContainerDeclarations (ContainerField COMMA)* (ContainerField / ContainerDeclarations)
|
||||
|
||||
ContainerDeclarations <- (TestDecl / ComptimeDecl / doc_comment? KEYWORD_pub? Decl)*
|
||||
|
||||
TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block
|
||||
|
||||
ComptimeDecl <- KEYWORD_comptime Block
|
||||
|
||||
Decl
|
||||
<- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / KEYWORD_inline / KEYWORD_noinline)? FnProto (SEMICOLON / Block)
|
||||
/ (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? GlobalVarDecl
|
||||
/ KEYWORD_usingnamespace Expr SEMICOLON
|
||||
|
||||
FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr
|
||||
|
||||
VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection?
|
||||
|
||||
GlobalVarDecl <- VarDeclProto (EQUAL Expr)? SEMICOLON
|
||||
|
||||
ContainerField <- doc_comment? KEYWORD_comptime? !KEYWORD_fn (IDENTIFIER COLON)? TypeExpr ByteAlign? (EQUAL Expr)?
|
||||
|
||||
# *** Block Level ***
|
||||
Statement
|
||||
<- KEYWORD_comptime ComptimeStatement
|
||||
/ KEYWORD_nosuspend BlockExprStatement
|
||||
/ KEYWORD_suspend BlockExprStatement
|
||||
/ KEYWORD_defer BlockExprStatement
|
||||
/ KEYWORD_errdefer Payload? BlockExprStatement
|
||||
/ IfStatement
|
||||
/ LabeledStatement
|
||||
/ SwitchExpr
|
||||
/ VarDeclExprStatement
|
||||
|
||||
ComptimeStatement
|
||||
<- BlockExpr
|
||||
/ VarDeclExprStatement
|
||||
|
||||
IfStatement
|
||||
<- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
|
||||
/ IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
|
||||
|
||||
LabeledStatement <- BlockLabel? (Block / LoopStatement)
|
||||
|
||||
LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement)
|
||||
|
||||
ForStatement
|
||||
<- ForPrefix BlockExpr ( KEYWORD_else Statement )?
|
||||
/ ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement )
|
||||
|
||||
WhileStatement
|
||||
<- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )?
|
||||
/ WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )
|
||||
|
||||
BlockExprStatement
|
||||
<- BlockExpr
|
||||
/ AssignExpr SEMICOLON
|
||||
|
||||
BlockExpr <- BlockLabel? Block
|
||||
|
||||
# An expression, assignment, or any destructure, as a statement.
|
||||
VarDeclExprStatement
|
||||
<- VarDeclProto (COMMA (VarDeclProto / Expr))* EQUAL Expr SEMICOLON
|
||||
/ Expr (AssignOp Expr / (COMMA (VarDeclProto / Expr))+ EQUAL Expr)? SEMICOLON
|
||||
|
||||
# *** Expression Level ***
|
||||
|
||||
# An assignment or a destructure whose LHS are all lvalue expressions.
|
||||
AssignExpr <- Expr (AssignOp Expr / (COMMA Expr)+ EQUAL Expr)?
|
||||
|
||||
SingleAssignExpr <- Expr (AssignOp Expr)?
|
||||
|
||||
Expr <- BoolOrExpr
|
||||
|
||||
BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)*
|
||||
|
||||
BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)*
|
||||
|
||||
CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)?
|
||||
|
||||
BitwiseExpr <- BitShiftExpr (BitwiseOp BitShiftExpr)*
|
||||
|
||||
BitShiftExpr <- AdditionExpr (BitShiftOp AdditionExpr)*
|
||||
|
||||
AdditionExpr <- MultiplyExpr (AdditionOp MultiplyExpr)*
|
||||
|
||||
MultiplyExpr <- PrefixExpr (MultiplyOp PrefixExpr)*
|
||||
|
||||
PrefixExpr <- PrefixOp* PrimaryExpr
|
||||
|
||||
PrimaryExpr
|
||||
<- AsmExpr
|
||||
/ IfExpr
|
||||
/ KEYWORD_break BreakLabel? Expr?
|
||||
/ KEYWORD_comptime Expr
|
||||
/ KEYWORD_nosuspend Expr
|
||||
/ KEYWORD_continue BreakLabel?
|
||||
/ KEYWORD_resume Expr
|
||||
/ KEYWORD_return Expr?
|
||||
/ BlockLabel? LoopExpr
|
||||
/ Block
|
||||
/ CurlySuffixExpr
|
||||
|
||||
IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?
|
||||
|
||||
Block <- LBRACE Statement* RBRACE
|
||||
|
||||
LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)
|
||||
|
||||
ForExpr <- ForPrefix Expr (KEYWORD_else Expr)?
|
||||
|
||||
WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?
|
||||
|
||||
CurlySuffixExpr <- TypeExpr InitList?
|
||||
|
||||
InitList
|
||||
<- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
|
||||
/ LBRACE Expr (COMMA Expr)* COMMA? RBRACE
|
||||
/ LBRACE RBRACE
|
||||
|
||||
TypeExpr <- PrefixTypeOp* ErrorUnionExpr
|
||||
|
||||
ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)?
|
||||
|
||||
SuffixExpr
|
||||
<- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments
|
||||
/ PrimaryTypeExpr (SuffixOp / FnCallArguments)*
|
||||
|
||||
PrimaryTypeExpr
|
||||
<- BUILTINIDENTIFIER FnCallArguments
|
||||
/ CHAR_LITERAL
|
||||
/ ContainerDecl
|
||||
/ DOT IDENTIFIER
|
||||
/ DOT InitList
|
||||
/ ErrorSetDecl
|
||||
/ FLOAT
|
||||
/ FnProto
|
||||
/ GroupedExpr
|
||||
/ LabeledTypeExpr
|
||||
/ IDENTIFIER
|
||||
/ IfTypeExpr
|
||||
/ INTEGER
|
||||
/ KEYWORD_comptime TypeExpr
|
||||
/ KEYWORD_error DOT IDENTIFIER
|
||||
/ KEYWORD_anyframe
|
||||
/ KEYWORD_unreachable
|
||||
/ STRINGLITERAL
|
||||
/ SwitchExpr
|
||||
|
||||
ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto
|
||||
|
||||
ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
|
||||
|
||||
GroupedExpr <- LPAREN Expr RPAREN
|
||||
|
||||
IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
|
||||
|
||||
LabeledTypeExpr
|
||||
<- BlockLabel Block
|
||||
/ BlockLabel? LoopTypeExpr
|
||||
|
||||
LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
|
||||
|
||||
ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)?
|
||||
|
||||
WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
|
||||
|
||||
SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
|
||||
|
||||
# *** Assembly ***
|
||||
AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN Expr AsmOutput? RPAREN
|
||||
|
||||
AsmOutput <- COLON AsmOutputList AsmInput?
|
||||
|
||||
AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN
|
||||
|
||||
AsmInput <- COLON AsmInputList AsmClobbers?
|
||||
|
||||
AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN
|
||||
|
||||
AsmClobbers <- COLON StringList
|
||||
|
||||
# *** Helper grammar ***
|
||||
BreakLabel <- COLON IDENTIFIER
|
||||
|
||||
BlockLabel <- IDENTIFIER COLON
|
||||
|
||||
FieldInit <- DOT IDENTIFIER EQUAL Expr
|
||||
|
||||
WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN
|
||||
|
||||
LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN
|
||||
|
||||
AddrSpace <- KEYWORD_addrspace LPAREN Expr RPAREN
|
||||
|
||||
# Fn specific
|
||||
CallConv <- KEYWORD_callconv LPAREN Expr RPAREN
|
||||
|
||||
ParamDecl
|
||||
<- doc_comment? (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
|
||||
/ DOT3
|
||||
|
||||
ParamType
|
||||
<- KEYWORD_anytype
|
||||
/ TypeExpr
|
||||
|
||||
# Control flow prefixes
|
||||
IfPrefix <- KEYWORD_if LPAREN Expr RPAREN PtrPayload?
|
||||
|
||||
WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
|
||||
|
||||
ForPrefix <- KEYWORD_for LPAREN ForArgumentsList RPAREN PtrListPayload
|
||||
|
||||
# Payloads
|
||||
Payload <- PIPE IDENTIFIER PIPE
|
||||
|
||||
PtrPayload <- PIPE ASTERISK? IDENTIFIER PIPE
|
||||
|
||||
PtrIndexPayload <- PIPE ASTERISK? IDENTIFIER (COMMA IDENTIFIER)? PIPE
|
||||
|
||||
PtrListPayload <- PIPE ASTERISK? IDENTIFIER (COMMA ASTERISK? IDENTIFIER)* COMMA? PIPE
|
||||
|
||||
# Switch specific
|
||||
SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrIndexPayload? SingleAssignExpr
|
||||
|
||||
SwitchCase
|
||||
<- SwitchItem (COMMA SwitchItem)* COMMA?
|
||||
/ KEYWORD_else
|
||||
|
||||
SwitchItem <- Expr (DOT3 Expr)?
|
||||
|
||||
# For specific
|
||||
ForArgumentsList <- ForItem (COMMA ForItem)* COMMA?
|
||||
|
||||
ForItem <- Expr (DOT2 Expr?)?
|
||||
|
||||
# Operators
|
||||
AssignOp
|
||||
<- ASTERISKEQUAL
|
||||
/ ASTERISKPIPEEQUAL
|
||||
/ SLASHEQUAL
|
||||
/ PERCENTEQUAL
|
||||
/ PLUSEQUAL
|
||||
/ PLUSPIPEEQUAL
|
||||
/ MINUSEQUAL
|
||||
/ MINUSPIPEEQUAL
|
||||
/ LARROW2EQUAL
|
||||
/ LARROW2PIPEEQUAL
|
||||
/ RARROW2EQUAL
|
||||
/ AMPERSANDEQUAL
|
||||
/ CARETEQUAL
|
||||
/ PIPEEQUAL
|
||||
/ ASTERISKPERCENTEQUAL
|
||||
/ PLUSPERCENTEQUAL
|
||||
/ MINUSPERCENTEQUAL
|
||||
/ EQUAL
|
||||
|
||||
CompareOp
|
||||
<- EQUALEQUAL
|
||||
/ EXCLAMATIONMARKEQUAL
|
||||
/ LARROW
|
||||
/ RARROW
|
||||
/ LARROWEQUAL
|
||||
/ RARROWEQUAL
|
||||
|
||||
BitwiseOp
|
||||
<- AMPERSAND
|
||||
/ CARET
|
||||
/ PIPE
|
||||
/ KEYWORD_orelse
|
||||
/ KEYWORD_catch Payload?
|
||||
|
||||
BitShiftOp
|
||||
<- LARROW2
|
||||
/ RARROW2
|
||||
/ LARROW2PIPE
|
||||
|
||||
AdditionOp
|
||||
<- PLUS
|
||||
/ MINUS
|
||||
/ PLUS2
|
||||
/ PLUSPERCENT
|
||||
/ MINUSPERCENT
|
||||
/ PLUSPIPE
|
||||
/ MINUSPIPE
|
||||
|
||||
MultiplyOp
|
||||
<- PIPE2
|
||||
/ ASTERISK
|
||||
/ SLASH
|
||||
/ PERCENT
|
||||
/ ASTERISK2
|
||||
/ ASTERISKPERCENT
|
||||
/ ASTERISKPIPE
|
||||
|
||||
PrefixOp
|
||||
<- EXCLAMATIONMARK
|
||||
/ MINUS
|
||||
/ TILDE
|
||||
/ MINUSPERCENT
|
||||
/ AMPERSAND
|
||||
/ KEYWORD_try
|
||||
/ KEYWORD_await
|
||||
|
||||
PrefixTypeOp
|
||||
<- QUESTIONMARK
|
||||
/ KEYWORD_anyframe MINUSRARROW
|
||||
/ SliceTypeStart (ByteAlign / AddrSpace / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
|
||||
/ PtrTypeStart (AddrSpace / KEYWORD_align LPAREN Expr (COLON Expr COLON Expr)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
|
||||
/ ArrayTypeStart
|
||||
|
||||
SuffixOp
|
||||
<- LBRACKET Expr (DOT2 (Expr? (COLON Expr)?)?)? RBRACKET
|
||||
/ DOT IDENTIFIER
|
||||
/ DOTASTERISK
|
||||
/ DOTQUESTIONMARK
|
||||
|
||||
FnCallArguments <- LPAREN ExprList RPAREN
|
||||
|
||||
# Ptr specific
|
||||
SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET
|
||||
|
||||
PtrTypeStart
|
||||
<- ASTERISK
|
||||
/ ASTERISK2
|
||||
/ LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET
|
||||
|
||||
ArrayTypeStart <- LBRACKET Expr (COLON Expr)? RBRACKET
|
||||
|
||||
# ContainerDecl specific
|
||||
ContainerDeclAuto <- ContainerDeclType LBRACE container_doc_comment? ContainerMembers RBRACE
|
||||
|
||||
ContainerDeclType
|
||||
<- KEYWORD_struct (LPAREN Expr RPAREN)?
|
||||
/ KEYWORD_opaque
|
||||
/ KEYWORD_enum (LPAREN Expr RPAREN)?
|
||||
/ KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)?
|
||||
|
||||
# Alignment
|
||||
ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
|
||||
|
||||
# Lists
|
||||
IdentifierList <- (doc_comment? IDENTIFIER COMMA)* (doc_comment? IDENTIFIER)?
|
||||
|
||||
SwitchProngList <- (SwitchProng COMMA)* SwitchProng?
|
||||
|
||||
AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem?
|
||||
|
||||
AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
|
||||
|
||||
StringList <- (STRINGLITERAL COMMA)* STRINGLITERAL?
|
||||
|
||||
ParamDeclList <- (ParamDecl COMMA)* ParamDecl?
|
||||
|
||||
ExprList <- (Expr COMMA)* Expr?
|
||||
|
||||
# *** Tokens ***
|
||||
eof <- !.
|
||||
bin <- [01]
|
||||
bin_ <- '_'? bin
|
||||
oct <- [0-7]
|
||||
oct_ <- '_'? oct
|
||||
hex <- [0-9a-fA-F]
|
||||
hex_ <- '_'? hex
|
||||
dec <- [0-9]
|
||||
dec_ <- '_'? dec
|
||||
|
||||
bin_int <- bin bin_*
|
||||
oct_int <- oct oct_*
|
||||
dec_int <- dec dec_*
|
||||
hex_int <- hex hex_*
|
||||
|
||||
ox80_oxBF <- [\200-\277]
|
||||
oxF4 <- '\364'
|
||||
ox80_ox8F <- [\200-\217]
|
||||
oxF1_oxF3 <- [\361-\363]
|
||||
oxF0 <- '\360'
|
||||
ox90_0xBF <- [\220-\277]
|
||||
oxEE_oxEF <- [\356-\357]
|
||||
oxED <- '\355'
|
||||
ox80_ox9F <- [\200-\237]
|
||||
oxE1_oxEC <- [\341-\354]
|
||||
oxE0 <- '\340'
|
||||
oxA0_oxBF <- [\240-\277]
|
||||
oxC2_oxDF <- [\302-\337]
|
||||
|
||||
# From https://lemire.me/blog/2018/05/09/how-quickly-can-you-check-that-a-string-is-valid-unicode-utf-8/
|
||||
# First Byte Second Byte Third Byte Fourth Byte
|
||||
# [0x00,0x7F]
|
||||
# [0xC2,0xDF] [0x80,0xBF]
|
||||
# 0xE0 [0xA0,0xBF] [0x80,0xBF]
|
||||
# [0xE1,0xEC] [0x80,0xBF] [0x80,0xBF]
|
||||
# 0xED [0x80,0x9F] [0x80,0xBF]
|
||||
# [0xEE,0xEF] [0x80,0xBF] [0x80,0xBF]
|
||||
# 0xF0 [0x90,0xBF] [0x80,0xBF] [0x80,0xBF]
|
||||
# [0xF1,0xF3] [0x80,0xBF] [0x80,0xBF] [0x80,0xBF]
|
||||
# 0xF4 [0x80,0x8F] [0x80,0xBF] [0x80,0xBF]
|
||||
|
||||
mb_utf8_literal <-
|
||||
oxF4 ox80_ox8F ox80_oxBF ox80_oxBF
|
||||
/ oxF1_oxF3 ox80_oxBF ox80_oxBF ox80_oxBF
|
||||
/ oxF0 ox90_0xBF ox80_oxBF ox80_oxBF
|
||||
/ oxEE_oxEF ox80_oxBF ox80_oxBF
|
||||
/ oxED ox80_ox9F ox80_oxBF
|
||||
/ oxE1_oxEC ox80_oxBF ox80_oxBF
|
||||
/ oxE0 oxA0_oxBF ox80_oxBF
|
||||
/ oxC2_oxDF ox80_oxBF
|
||||
|
||||
# NOTE: `\135` is `]`. We separate to avoid this: [\000-\011\013-\046\050-\133]-\177]
|
||||
# ^ ^XXXXXX
|
||||
ascii_char_not_nl_slash_squote <- [\000-\011\013-\046\050-\133\136-\177] / ']'
|
||||
|
||||
char_escape
|
||||
<- "\\x" hex hex
|
||||
/ "\\u{" hex+ "}"
|
||||
/ "\\" [nr\\t'"]
|
||||
char_char
|
||||
<- mb_utf8_literal
|
||||
/ char_escape
|
||||
/ ascii_char_not_nl_slash_squote
|
||||
|
||||
string_char
|
||||
<- char_escape
|
||||
/ [^\\"\n]
|
||||
|
||||
container_doc_comment <- ('//!' [^\n]* [ \n]* skip)+
|
||||
doc_comment <- ('///' [^\n]* [ \n]* skip)+
|
||||
line_comment <- '//' ![!/][^\n]* / '////' [^\n]*
|
||||
line_string <- ('\\\\' [^\n]* [ \n]*)+
|
||||
skip <- ([ \n] / line_comment)*
|
||||
|
||||
CHAR_LITERAL <- ['] char_char ['] skip
|
||||
FLOAT
|
||||
<- '0x' hex_int '.' hex_int ([pP] [-+]? dec_int)? skip
|
||||
/ dec_int '.' dec_int ([eE] [-+]? dec_int)? skip
|
||||
/ '0x' hex_int [pP] [-+]? dec_int skip
|
||||
/ dec_int [eE] [-+]? dec_int skip
|
||||
INTEGER
|
||||
<- '0b' bin_int skip
|
||||
/ '0o' oct_int skip
|
||||
/ '0x' hex_int skip
|
||||
/ dec_int skip
|
||||
STRINGLITERALSINGLE <- ["] string_char* ["] skip
|
||||
STRINGLITERAL
|
||||
<- STRINGLITERALSINGLE
|
||||
/ (line_string skip)+
|
||||
IDENTIFIER
|
||||
<- !keyword [A-Za-z_] [A-Za-z0-9_]* skip
|
||||
/ '@' STRINGLITERALSINGLE
|
||||
BUILTINIDENTIFIER <- '@'[A-Za-z_][A-Za-z0-9_]* skip
|
||||
|
||||
|
||||
AMPERSAND <- '&' ![=] skip
|
||||
AMPERSANDEQUAL <- '&=' skip
|
||||
ASTERISK <- '*' ![*%=|] skip
|
||||
ASTERISK2 <- '**' skip
|
||||
ASTERISKEQUAL <- '*=' skip
|
||||
ASTERISKPERCENT <- '*%' ![=] skip
|
||||
ASTERISKPERCENTEQUAL <- '*%=' skip
|
||||
ASTERISKPIPE <- '*|' ![=] skip
|
||||
ASTERISKPIPEEQUAL <- '*|=' skip
|
||||
CARET <- '^' ![=] skip
|
||||
CARETEQUAL <- '^=' skip
|
||||
COLON <- ':' skip
|
||||
COMMA <- ',' skip
|
||||
DOT <- '.' ![*.?] skip
|
||||
DOT2 <- '..' ![.] skip
|
||||
DOT3 <- '...' skip
|
||||
DOTASTERISK <- '.*' skip
|
||||
DOTQUESTIONMARK <- '.?' skip
|
||||
EQUAL <- '=' ![>=] skip
|
||||
EQUALEQUAL <- '==' skip
|
||||
EQUALRARROW <- '=>' skip
|
||||
EXCLAMATIONMARK <- '!' ![=] skip
|
||||
EXCLAMATIONMARKEQUAL <- '!=' skip
|
||||
LARROW <- '<' ![<=] skip
|
||||
LARROW2 <- '<<' ![=|] skip
|
||||
LARROW2EQUAL <- '<<=' skip
|
||||
LARROW2PIPE <- '<<|' ![=] skip
|
||||
LARROW2PIPEEQUAL <- '<<|=' skip
|
||||
LARROWEQUAL <- '<=' skip
|
||||
LBRACE <- '{' skip
|
||||
LBRACKET <- '[' skip
|
||||
LPAREN <- '(' skip
|
||||
MINUS <- '-' ![%=>|] skip
|
||||
MINUSEQUAL <- '-=' skip
|
||||
MINUSPERCENT <- '-%' ![=] skip
|
||||
MINUSPERCENTEQUAL <- '-%=' skip
|
||||
MINUSPIPE <- '-|' ![=] skip
|
||||
MINUSPIPEEQUAL <- '-|=' skip
|
||||
MINUSRARROW <- '->' skip
|
||||
PERCENT <- '%' ![=] skip
|
||||
PERCENTEQUAL <- '%=' skip
|
||||
PIPE <- '|' ![|=] skip
|
||||
PIPE2 <- '||' skip
|
||||
PIPEEQUAL <- '|=' skip
|
||||
PLUS <- '+' ![%+=|] skip
|
||||
PLUS2 <- '++' skip
|
||||
PLUSEQUAL <- '+=' skip
|
||||
PLUSPERCENT <- '+%' ![=] skip
|
||||
PLUSPERCENTEQUAL <- '+%=' skip
|
||||
PLUSPIPE <- '+|' ![=] skip
|
||||
PLUSPIPEEQUAL <- '+|=' skip
|
||||
LETTERC <- 'c' skip
|
||||
QUESTIONMARK <- '?' skip
|
||||
RARROW <- '>' ![>=] skip
|
||||
RARROW2 <- '>>' ![=] skip
|
||||
RARROW2EQUAL <- '>>=' skip
|
||||
RARROWEQUAL <- '>=' skip
|
||||
RBRACE <- '}' skip
|
||||
RBRACKET <- ']' skip
|
||||
RPAREN <- ')' skip
|
||||
SEMICOLON <- ';' skip
|
||||
SLASH <- '/' ![=] skip
|
||||
SLASHEQUAL <- '/=' skip
|
||||
TILDE <- '~' skip
|
||||
|
||||
end_of_word <- ![a-zA-Z0-9_] skip
|
||||
KEYWORD_addrspace <- 'addrspace' end_of_word
|
||||
KEYWORD_align <- 'align' end_of_word
|
||||
KEYWORD_allowzero <- 'allowzero' end_of_word
|
||||
KEYWORD_and <- 'and' end_of_word
|
||||
KEYWORD_anyframe <- 'anyframe' end_of_word
|
||||
KEYWORD_anytype <- 'anytype' end_of_word
|
||||
KEYWORD_asm <- 'asm' end_of_word
|
||||
KEYWORD_async <- 'async' end_of_word
|
||||
KEYWORD_await <- 'await' end_of_word
|
||||
KEYWORD_break <- 'break' end_of_word
|
||||
KEYWORD_callconv <- 'callconv' end_of_word
|
||||
KEYWORD_catch <- 'catch' end_of_word
|
||||
KEYWORD_comptime <- 'comptime' end_of_word
|
||||
KEYWORD_const <- 'const' end_of_word
|
||||
KEYWORD_continue <- 'continue' end_of_word
|
||||
KEYWORD_defer <- 'defer' end_of_word
|
||||
KEYWORD_else <- 'else' end_of_word
|
||||
KEYWORD_enum <- 'enum' end_of_word
|
||||
KEYWORD_errdefer <- 'errdefer' end_of_word
|
||||
KEYWORD_error <- 'error' end_of_word
|
||||
KEYWORD_export <- 'export' end_of_word
|
||||
KEYWORD_extern <- 'extern' end_of_word
|
||||
KEYWORD_fn <- 'fn' end_of_word
|
||||
KEYWORD_for <- 'for' end_of_word
|
||||
KEYWORD_if <- 'if' end_of_word
|
||||
KEYWORD_inline <- 'inline' end_of_word
|
||||
KEYWORD_noalias <- 'noalias' end_of_word
|
||||
KEYWORD_nosuspend <- 'nosuspend' end_of_word
|
||||
KEYWORD_noinline <- 'noinline' end_of_word
|
||||
KEYWORD_opaque <- 'opaque' end_of_word
|
||||
KEYWORD_or <- 'or' end_of_word
|
||||
KEYWORD_orelse <- 'orelse' end_of_word
|
||||
KEYWORD_packed <- 'packed' end_of_word
|
||||
KEYWORD_pub <- 'pub' end_of_word
|
||||
KEYWORD_resume <- 'resume' end_of_word
|
||||
KEYWORD_return <- 'return' end_of_word
|
||||
KEYWORD_linksection <- 'linksection' end_of_word
|
||||
KEYWORD_struct <- 'struct' end_of_word
|
||||
KEYWORD_suspend <- 'suspend' end_of_word
|
||||
KEYWORD_switch <- 'switch' end_of_word
|
||||
KEYWORD_test <- 'test' end_of_word
|
||||
KEYWORD_threadlocal <- 'threadlocal' end_of_word
|
||||
KEYWORD_try <- 'try' end_of_word
|
||||
KEYWORD_union <- 'union' end_of_word
|
||||
KEYWORD_unreachable <- 'unreachable' end_of_word
|
||||
KEYWORD_usingnamespace <- 'usingnamespace' end_of_word
|
||||
KEYWORD_var <- 'var' end_of_word
|
||||
KEYWORD_volatile <- 'volatile' end_of_word
|
||||
KEYWORD_while <- 'while' end_of_word
|
||||
|
||||
keyword <- KEYWORD_addrspace / KEYWORD_align / KEYWORD_allowzero / KEYWORD_and
|
||||
/ KEYWORD_anyframe / KEYWORD_anytype / KEYWORD_asm / KEYWORD_async
|
||||
/ KEYWORD_await / KEYWORD_break / KEYWORD_callconv / KEYWORD_catch
|
||||
/ KEYWORD_comptime / KEYWORD_const / KEYWORD_continue / KEYWORD_defer
|
||||
/ KEYWORD_else / KEYWORD_enum / KEYWORD_errdefer / KEYWORD_error / KEYWORD_export
|
||||
/ KEYWORD_extern / KEYWORD_fn / KEYWORD_for / KEYWORD_if
|
||||
/ KEYWORD_inline / KEYWORD_noalias / KEYWORD_nosuspend / KEYWORD_noinline
|
||||
/ KEYWORD_opaque / KEYWORD_or / KEYWORD_orelse / KEYWORD_packed
|
||||
/ KEYWORD_pub / KEYWORD_resume / KEYWORD_return / KEYWORD_linksection
|
||||
/ KEYWORD_struct / KEYWORD_suspend / KEYWORD_switch / KEYWORD_test
|
||||
/ KEYWORD_threadlocal / KEYWORD_try / KEYWORD_union / KEYWORD_unreachable
|
||||
/ KEYWORD_usingnamespace / KEYWORD_var / KEYWORD_volatile / KEYWORD_while
|
@@ -3,106 +3,692 @@ package zig
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type formatter struct {
|
||||
w io.Writer
|
||||
w io.Writer
|
||||
line int // 1-based
|
||||
col int // 1-based, reset to 1 after newline
|
||||
indent int // indentation level
|
||||
}
|
||||
|
||||
func (f *formatter) WriteString(s string) (n int, err error) {
|
||||
return f.w.Write([]byte(s))
|
||||
// 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
|
||||
// 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()
|
||||
}
|
||||
} else {
|
||||
if f.col == 0 {
|
||||
f.col = 1
|
||||
} else {
|
||||
f.col++
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := f.w.Write([]byte(s)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *formatter) Writef(format string, a ...any) (err error) {
|
||||
_, err = f.w.Write(fmt.Appendf(nil, format, a...))
|
||||
// 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 {
|
||||
panic(err)
|
||||
}
|
||||
f.col += len(indentStr)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if e, ok := r.(error); ok {
|
||||
err = e
|
||||
} else {
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
sb := &strings.Builder{}
|
||||
f := &formatter{w: sb, line: 1, col: 1, indent: 0}
|
||||
|
||||
if root.ContainerDocComment != "" {
|
||||
f.writef("//! %s\n\n", root.ContainerDocComment)
|
||||
}
|
||||
for i, member := range root.ContainerMembers {
|
||||
if member.Decl != nil {
|
||||
// Only emit a leading newline before a function/global after the first declaration
|
||||
if i > 0 {
|
||||
f.writef("\n")
|
||||
}
|
||||
writeDecl(f, member.Decl)
|
||||
}
|
||||
}
|
||||
out := sb.String()
|
||||
if len(out) == 0 || out[len(out)-1] != '\n' {
|
||||
out += "\n"
|
||||
}
|
||||
_, err = w.Write([]byte(out))
|
||||
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) {
|
||||
// writeDecl emits a top-level declaration.
|
||||
func writeDecl(f *formatter, decl Decl) {
|
||||
switch d := decl.(type) {
|
||||
case *FnDecl:
|
||||
if err = f.Writef("fn %s(", typ.Name); err != nil {
|
||||
return err
|
||||
if d.Flags&FnExport != 0 {
|
||||
f.writef("pub ")
|
||||
}
|
||||
if err = writeParams(f, typ.Params); err != nil {
|
||||
return err
|
||||
f.writef("fn %s(", d.Name)
|
||||
writeParams(f, d.Params)
|
||||
f.writef(") ")
|
||||
writeTypeExpr(f, d.ReturnType)
|
||||
writeBlock(f, d.Body)
|
||||
f.writef("\n")
|
||||
case *GlobalVarDecl:
|
||||
if d.Flags&GlobalVarConst != 0 {
|
||||
f.writef("const %s = ", d.Name)
|
||||
} else {
|
||||
f.writef("var %s = ", d.Name)
|
||||
}
|
||||
if err = f.Writef(") %s", typ.ReturnType); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = writeBlock(f, typ.Body); err != nil {
|
||||
return err
|
||||
writeExpr(f, d.Value)
|
||||
f.writef(";\n")
|
||||
case *ContainerDecl:
|
||||
f.writef("struct ")
|
||||
writeStructBody(f, d)
|
||||
case *ErrorSetDecl:
|
||||
writeExpr(f, d)
|
||||
case *TestDecl:
|
||||
f.writef("test ")
|
||||
if d.Name != "" {
|
||||
f.writef(`"%s"`, d.Name)
|
||||
}
|
||||
writeBlock(f, d.Block)
|
||||
f.writef("\n")
|
||||
case *UsingNamespaceDecl:
|
||||
f.writef("usingnamespace ")
|
||||
writeExpr(f, d.Expr)
|
||||
f.writef(";\n")
|
||||
case *ComptimeDecl:
|
||||
f.writef("comptime")
|
||||
writeBlock(f, d.Block)
|
||||
f.writef("\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeParams(f *formatter, params []*ParamDecl) (err error) {
|
||||
for _, param := range params {
|
||||
// writeParams emits function parameters, separated by commas.
|
||||
func writeParams(f *formatter, params []*ParamDecl) {
|
||||
for i, param := range params {
|
||||
if i > 0 {
|
||||
f.writef(", ")
|
||||
}
|
||||
if param.Name != "" {
|
||||
if err = f.Writef("%s: ", param.Name); err != nil {
|
||||
return err
|
||||
f.writef("%s: ", param.Name)
|
||||
}
|
||||
writeTypeExpr(f, param.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// writeTypeExpr emits a type expression.
|
||||
func writeTypeExpr(f *formatter, typ TypeExpr) {
|
||||
switch t := typ.(type) {
|
||||
case *Identifier:
|
||||
f.writef("%s", t.Name)
|
||||
case *PrefixTypeExpr:
|
||||
f.writef("%s", t.Op)
|
||||
// Add space after multi-character operators like *const
|
||||
if len(t.Op) > 1 && !strings.HasSuffix(t.Op, "]") {
|
||||
f.writef(" ")
|
||||
}
|
||||
writeTypeExpr(f, t.Base)
|
||||
case *ArrayTypeExpr:
|
||||
f.writef("[")
|
||||
writeExpr(f, t.Size)
|
||||
f.writef("]")
|
||||
writeTypeExpr(f, t.Elem)
|
||||
case *SliceTypeExpr:
|
||||
f.writef("[]")
|
||||
writeTypeExpr(f, t.Elem)
|
||||
case *ErrorUnionTypeExpr:
|
||||
writeTypeExpr(f, t.ErrSet)
|
||||
f.writef("!")
|
||||
writeTypeExpr(f, t.Type)
|
||||
case nil:
|
||||
// nothing
|
||||
default:
|
||||
f.writef("%v", t)
|
||||
}
|
||||
}
|
||||
|
||||
// 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++
|
||||
for _, stmt := range block.Stmts {
|
||||
f.writeIndent()
|
||||
writeStmt(f, stmt)
|
||||
f.writef("\n")
|
||||
}
|
||||
f.indent--
|
||||
f.writeIndent()
|
||||
f.writef("}")
|
||||
}
|
||||
|
||||
// writeStmt emits a statement. Indentation is handled by the caller (writeBlock).
|
||||
func writeStmt(f *formatter, stmt Stmt) {
|
||||
switch s := stmt.(type) {
|
||||
case *ReturnStmt:
|
||||
f.writef("return")
|
||||
if s.Value != nil {
|
||||
f.writef(" ")
|
||||
writeExpr(f, s.Value)
|
||||
}
|
||||
f.writef(";")
|
||||
case *ExprStmt:
|
||||
writeExpr(f, s.Expr)
|
||||
f.writef(";")
|
||||
case *VarDeclStmt:
|
||||
if s.Const {
|
||||
f.writef("const ")
|
||||
} else {
|
||||
f.writef("var ")
|
||||
}
|
||||
for i, name := range s.Pattern.Names {
|
||||
if i > 0 {
|
||||
f.writef(", ")
|
||||
}
|
||||
f.writef("%s", name)
|
||||
}
|
||||
if s.Type != nil {
|
||||
f.writef(": ")
|
||||
writeTypeExpr(f, s.Type)
|
||||
}
|
||||
if s.Value != nil {
|
||||
f.writef(" = ")
|
||||
writeExpr(f, s.Value)
|
||||
}
|
||||
f.writef(";")
|
||||
case *BlockStmt:
|
||||
// Check if this is a labeled block
|
||||
if s.Block != nil && s.Block.Label != "" {
|
||||
f.writef("%s:", s.Block.Label)
|
||||
}
|
||||
// For standalone blocks, we need to write the block without a leading space
|
||||
if s.Block == nil {
|
||||
f.writef(";")
|
||||
} else {
|
||||
f.writef("{\n")
|
||||
f.indent++
|
||||
for _, stmt := range s.Block.Stmts {
|
||||
f.writeIndent()
|
||||
writeStmt(f, stmt)
|
||||
f.writef("\n")
|
||||
}
|
||||
f.indent--
|
||||
f.writeIndent()
|
||||
f.writef("}")
|
||||
}
|
||||
case *IfStmt:
|
||||
f.writef("if (")
|
||||
writeExpr(f, s.Cond)
|
||||
f.writef(")")
|
||||
|
||||
// Handle payload if present
|
||||
if s.Payload != nil {
|
||||
f.writef(" ")
|
||||
writePayload(f, s.Payload)
|
||||
}
|
||||
|
||||
// Always write the then branch as a block
|
||||
if block, ok := s.Then.(*BlockStmt); ok {
|
||||
writeBlock(f, block.Block)
|
||||
} else {
|
||||
f.writef(" ")
|
||||
writeStmt(f, s.Then)
|
||||
}
|
||||
if s.Else != nil {
|
||||
f.writef(" else")
|
||||
if block, ok := s.Else.(*BlockStmt); ok {
|
||||
writeBlock(f, block.Block)
|
||||
} else {
|
||||
f.writef(" ")
|
||||
writeStmt(f, s.Else)
|
||||
}
|
||||
}
|
||||
if err = f.Writef("%s", param.Type); err != nil {
|
||||
return err
|
||||
case *LoopStmt:
|
||||
// Handle label if the body is a labeled block
|
||||
if block, ok := s.Body.(*BlockStmt); ok && block.Block != nil && block.Block.Label != "" {
|
||||
f.writef("%s: ", block.Block.Label)
|
||||
}
|
||||
|
||||
if s.Kind == LoopWhile {
|
||||
f.writef("while (")
|
||||
if wp, ok := s.Prefix.(*WhilePrefix); ok {
|
||||
writeExpr(f, wp.Cond)
|
||||
if wp.Continue != nil {
|
||||
f.writef(") : (")
|
||||
writeExpr(f, wp.Continue)
|
||||
}
|
||||
}
|
||||
f.writef(")")
|
||||
// Always write the body as a block
|
||||
if block, ok := s.Body.(*BlockStmt); ok {
|
||||
// Don't write the label again, just the block content
|
||||
origLabel := ""
|
||||
if block.Block != nil {
|
||||
origLabel = block.Block.Label
|
||||
block.Block.Label = "" // Temporarily clear to avoid double printing
|
||||
}
|
||||
writeBlock(f, block.Block)
|
||||
if block.Block != nil {
|
||||
block.Block.Label = origLabel // Restore
|
||||
}
|
||||
} else {
|
||||
f.writef(" ")
|
||||
writeStmt(f, s.Body)
|
||||
}
|
||||
} else if s.Kind == LoopFor {
|
||||
f.writef("for (")
|
||||
if fp, ok := s.Prefix.(*ForPrefix); ok {
|
||||
for i, arg := range fp.Args {
|
||||
if i > 0 {
|
||||
f.writef(", ")
|
||||
}
|
||||
writeExpr(f, arg.Expr)
|
||||
if arg.From != nil {
|
||||
if lit, ok := arg.From.(*Literal); ok && lit.Value == "" {
|
||||
f.writef("..")
|
||||
} else {
|
||||
f.writef("...")
|
||||
writeExpr(f, arg.From)
|
||||
}
|
||||
}
|
||||
}
|
||||
f.writef(")")
|
||||
if fp.Payload != nil {
|
||||
f.writef(" ")
|
||||
writePayload(f, fp.Payload)
|
||||
}
|
||||
}
|
||||
// Always write the body as a block
|
||||
if block, ok := s.Body.(*BlockStmt); ok {
|
||||
// Don't write the label again, just the block content
|
||||
origLabel := ""
|
||||
if block.Block != nil {
|
||||
origLabel = block.Block.Label
|
||||
block.Block.Label = "" // Temporarily clear to avoid double printing
|
||||
}
|
||||
writeBlock(f, block.Block)
|
||||
if block.Block != nil {
|
||||
block.Block.Label = origLabel // Restore
|
||||
}
|
||||
} else {
|
||||
f.writef(" ")
|
||||
writeStmt(f, s.Body)
|
||||
}
|
||||
}
|
||||
case *DeferStmt:
|
||||
if s.ErrDefer {
|
||||
f.writef("errdefer")
|
||||
if s.Payload != nil {
|
||||
f.writef(" ")
|
||||
writePayload(f, s.Payload)
|
||||
}
|
||||
} else {
|
||||
f.writef("defer")
|
||||
}
|
||||
f.writef(" ")
|
||||
writeStmt(f, s.Stmt)
|
||||
case *BreakStmt:
|
||||
f.writef("break")
|
||||
if s.Label != "" {
|
||||
f.writef(" :%s", s.Label)
|
||||
}
|
||||
if s.Value != nil {
|
||||
f.writef(" ")
|
||||
writeExpr(f, s.Value)
|
||||
}
|
||||
f.writef(";")
|
||||
case *ContinueStmt:
|
||||
f.writef("continue")
|
||||
if s.Label != "" {
|
||||
f.writef(" :%s", s.Label)
|
||||
}
|
||||
f.writef(";")
|
||||
case *SwitchStmt:
|
||||
f.writef("switch (")
|
||||
writeExpr(f, s.Cond)
|
||||
f.writef(") {\n")
|
||||
f.indent++
|
||||
for _, prong := range s.Prongs {
|
||||
f.writeIndent()
|
||||
writeSwitchProng(f, prong)
|
||||
f.writef("\n")
|
||||
}
|
||||
f.indent--
|
||||
f.writeIndent()
|
||||
f.writef("}")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeBlock(f *formatter, block *Block) (err error) {
|
||||
if block == nil {
|
||||
if _, err = f.WriteString(";"); err != nil {
|
||||
return err
|
||||
// writePayload emits a payload (|x|, |*x|, |*x, y|, etc).
|
||||
func writePayload(f *formatter, payload *Payload) {
|
||||
f.writef("|")
|
||||
for i, name := range payload.Names {
|
||||
if i > 0 {
|
||||
f.writef(", ")
|
||||
}
|
||||
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 payload.Pointers[i] {
|
||||
f.writef("*")
|
||||
}
|
||||
f.writef("%s", name)
|
||||
}
|
||||
if err = f.Writef("}\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
f.writef("|")
|
||||
}
|
||||
|
||||
func writeStmt(f *formatter, stmt Stmt) (err error) {
|
||||
switch stmt.(type) {
|
||||
case *ReturnStmt:
|
||||
if _, err = f.WriteString("return;"); err != nil {
|
||||
return err
|
||||
// writeSwitchProng emits a switch prong.
|
||||
func writeSwitchProng(f *formatter, prong *SwitchProng) {
|
||||
for i, c := range prong.Cases {
|
||||
if i > 0 {
|
||||
f.writef(", ")
|
||||
}
|
||||
if c.IsElse {
|
||||
f.writef("else")
|
||||
} else {
|
||||
writeExpr(f, c.Expr)
|
||||
if c.To != nil {
|
||||
f.writef("...")
|
||||
writeExpr(f, c.To)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
f.writef(" => ")
|
||||
// Write payload if present after =>
|
||||
if prong.Payload != nil {
|
||||
writePayload(f, prong.Payload)
|
||||
f.writef(" ")
|
||||
}
|
||||
// Check if the expression is actually a statement (like return or break)
|
||||
if stmt, ok := prong.Expr.(Stmt); ok {
|
||||
// If it's a block, write it directly without the leading space
|
||||
if blockStmt, isBlock := stmt.(*BlockStmt); isBlock {
|
||||
f.writef("{\n")
|
||||
f.indent++
|
||||
for _, s := range blockStmt.Block.Stmts {
|
||||
f.writeIndent()
|
||||
writeStmt(f, s)
|
||||
f.writef("\n")
|
||||
}
|
||||
f.indent--
|
||||
f.writeIndent()
|
||||
f.writef("},")
|
||||
} else {
|
||||
// For single statements, write without the semicolon
|
||||
switch s := stmt.(type) {
|
||||
case *ReturnStmt:
|
||||
f.writef("return")
|
||||
if s.Value != nil {
|
||||
f.writef(" ")
|
||||
writeExpr(f, s.Value)
|
||||
}
|
||||
case *BreakStmt:
|
||||
f.writef("break")
|
||||
if s.Label != "" {
|
||||
f.writef(" :%s", s.Label)
|
||||
}
|
||||
if s.Value != nil {
|
||||
f.writef(" ")
|
||||
writeExpr(f, s.Value)
|
||||
}
|
||||
case *ContinueStmt:
|
||||
f.writef("continue")
|
||||
if s.Label != "" {
|
||||
f.writef(" :%s", s.Label)
|
||||
}
|
||||
case *ExprStmt:
|
||||
writeExpr(f, s.Expr)
|
||||
default:
|
||||
writeStmt(f, stmt)
|
||||
}
|
||||
f.writef(",")
|
||||
}
|
||||
} else {
|
||||
writeExpr(f, prong.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:
|
||||
// Handle enum field access like .value (no receiver)
|
||||
if id, ok := e.Receiver.(*Identifier); ok && id.Name == "" {
|
||||
f.writef(".%s", e.Field)
|
||||
} else {
|
||||
writeExpr(f, e.Receiver)
|
||||
f.writef(".%s", e.Field)
|
||||
}
|
||||
case *Literal:
|
||||
switch e.Kind {
|
||||
case LiteralString:
|
||||
f.writef(`"%v"`, e.Value)
|
||||
default:
|
||||
f.writef("%v", e.Value)
|
||||
}
|
||||
case *InitListExpr:
|
||||
if e.Empty {
|
||||
f.writef(".{}")
|
||||
} else if len(e.Values) > 0 {
|
||||
if len(e.Values) == 1 {
|
||||
f.writef(".{")
|
||||
writeExpr(f, e.Values[0])
|
||||
f.writef("}")
|
||||
} else {
|
||||
f.writef(".{ ")
|
||||
for i, v := range e.Values {
|
||||
if i > 0 {
|
||||
f.writef(", ")
|
||||
}
|
||||
writeExpr(f, v)
|
||||
}
|
||||
f.writef(" }")
|
||||
}
|
||||
} else if len(e.Fields) > 0 {
|
||||
f.writef(".{ ")
|
||||
for i, field := range e.Fields {
|
||||
if i > 0 {
|
||||
f.writef(", ")
|
||||
}
|
||||
f.writef(".%s = ", field.Name)
|
||||
writeExpr(f, field.Value)
|
||||
}
|
||||
f.writef(" }")
|
||||
}
|
||||
case *ContainerDecl:
|
||||
switch e.Kind {
|
||||
case ContainerStruct:
|
||||
f.writef("struct ")
|
||||
writeStructBody(f, e)
|
||||
case ContainerEnum:
|
||||
f.writef("enum ")
|
||||
writeStructBody(f, e)
|
||||
case ContainerUnion:
|
||||
f.writef("union")
|
||||
if e.TagType != nil {
|
||||
f.writef("(")
|
||||
writeTypeExpr(f, e.TagType)
|
||||
f.writef(") ")
|
||||
} else {
|
||||
f.writef(" ")
|
||||
}
|
||||
writeStructBody(f, e)
|
||||
case ContainerOpaque:
|
||||
f.writef("opaque ")
|
||||
writeStructBody(f, e)
|
||||
default:
|
||||
panic("unknown container kind")
|
||||
}
|
||||
case *TryExpr:
|
||||
f.writef("try ")
|
||||
writeExpr(f, e.Expr)
|
||||
case *BinaryExpr:
|
||||
writeExpr(f, e.Left)
|
||||
f.writef(" %s ", e.Op)
|
||||
// Special handling for orelse with block
|
||||
if e.Op == "orelse" {
|
||||
if _, ok := e.Right.(*BlockStmt); ok {
|
||||
// For orelse blocks, we need special formatting
|
||||
writeStmt(f, e.Right.(Stmt))
|
||||
} else {
|
||||
writeExpr(f, e.Right)
|
||||
}
|
||||
} else {
|
||||
writeExpr(f, e.Right)
|
||||
}
|
||||
case *UnaryExpr:
|
||||
f.writef("%s", e.Op)
|
||||
writeExpr(f, e.Expr)
|
||||
case *IndexExpr:
|
||||
writeExpr(f, e.Receiver)
|
||||
f.writef("[")
|
||||
writeExpr(f, e.Index)
|
||||
f.writef("]")
|
||||
case *UnreachableExpr:
|
||||
f.writef("unreachable")
|
||||
case *ErrorSetDecl:
|
||||
f.writef("error{\n")
|
||||
f.indent++
|
||||
for _, name := range e.Names {
|
||||
f.writeIndent()
|
||||
f.writef("%s,\n", name)
|
||||
}
|
||||
f.indent--
|
||||
f.writeIndent()
|
||||
f.writef("}")
|
||||
case *GroupedExpr:
|
||||
f.writef("(")
|
||||
writeExpr(f, e.Expr)
|
||||
f.writef(")")
|
||||
case *ComptimeExpr:
|
||||
f.writef("comptime ")
|
||||
writeExpr(f, e.Expr)
|
||||
case *NosuspendExpr:
|
||||
f.writef("nosuspend ")
|
||||
writeExpr(f, e.Expr)
|
||||
case *AsyncExpr:
|
||||
f.writef("async ")
|
||||
writeExpr(f, e.Expr)
|
||||
case *AwaitExpr:
|
||||
f.writef("await ")
|
||||
writeExpr(f, e.Expr)
|
||||
case *ResumeExpr:
|
||||
f.writef("resume ")
|
||||
writeExpr(f, e.Expr)
|
||||
case *DotAsteriskExpr:
|
||||
writeExpr(f, e.Receiver)
|
||||
f.writef(".*")
|
||||
case *DotQuestionExpr:
|
||||
writeExpr(f, e.Receiver)
|
||||
f.writef(".?")
|
||||
case *IfExpr:
|
||||
f.writef("if (")
|
||||
writeExpr(f, e.Cond)
|
||||
f.writef(")")
|
||||
if e.Payload != nil {
|
||||
f.writef(" ")
|
||||
writePayload(f, e.Payload)
|
||||
}
|
||||
f.writef(" ")
|
||||
writeExpr(f, e.Then)
|
||||
if e.Else != nil {
|
||||
f.writef(" else ")
|
||||
writeExpr(f, e.Else)
|
||||
}
|
||||
case *SwitchExpr:
|
||||
f.writef("switch (")
|
||||
writeExpr(f, e.Cond)
|
||||
f.writef(") {\n")
|
||||
f.indent++
|
||||
for _, prong := range e.Prongs {
|
||||
f.writeIndent()
|
||||
writeSwitchProng(f, prong)
|
||||
f.writef("\n")
|
||||
}
|
||||
f.indent--
|
||||
f.writeIndent()
|
||||
f.writef("}")
|
||||
case *LabeledBlock:
|
||||
f.writef("%s:", e.Label)
|
||||
writeBlock(f, e.Block)
|
||||
}
|
||||
}
|
||||
|
||||
// writeStructBody emits the body of a struct/union/enum/opaque declaration.
|
||||
func writeStructBody(f *formatter, decl *ContainerDecl) {
|
||||
f.writef("{\n")
|
||||
f.indent++
|
||||
for _, member := range decl.Fields {
|
||||
if member.Field != nil {
|
||||
// Field or const
|
||||
if member.Field.Type == nil && member.Field.Value != nil {
|
||||
// const field
|
||||
f.writeIndent()
|
||||
f.writef("const %s = ", member.Field.Name)
|
||||
writeExpr(f, member.Field.Value)
|
||||
f.writef(";\n")
|
||||
} else {
|
||||
// regular field
|
||||
f.writeIndent()
|
||||
f.writef("%s", member.Field.Name)
|
||||
if member.Field.Type != nil {
|
||||
f.writef(": ")
|
||||
writeTypeExpr(f, member.Field.Type)
|
||||
}
|
||||
if member.Field.Value != nil {
|
||||
f.writef(" = ")
|
||||
writeExpr(f, member.Field.Value)
|
||||
}
|
||||
f.writef(",\n")
|
||||
}
|
||||
} else if member.Decl != nil {
|
||||
// Method or nested decl
|
||||
f.writef("\n")
|
||||
f.writeIndent()
|
||||
writeDecl(f, member.Decl)
|
||||
}
|
||||
}
|
||||
f.indent--
|
||||
f.writeIndent()
|
||||
f.writef("}")
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
1
tests/basic/hello.expected
Normal file
1
tests/basic/hello.expected
Normal file
@@ -0,0 +1 @@
|
||||
Hello, world!
|
@@ -1,5 +1,5 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
print("Hello, world\n")
|
||||
}
|
||||
print("Hello, world!\n")
|
||||
}
|
1
tests/basic/multiple_prints.expected
Normal file
1
tests/basic/multiple_prints.expected
Normal file
@@ -0,0 +1 @@
|
||||
First Second
|
8
tests/basic/multiple_prints.go
Normal file
8
tests/basic/multiple_prints.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
print("First")
|
||||
print(" ")
|
||||
print("Second")
|
||||
print("\n")
|
||||
}
|
5
tests/basic/print_escape.expected
Normal file
5
tests/basic/print_escape.expected
Normal file
@@ -0,0 +1,5 @@
|
||||
Hello World!
|
||||
Line 1
|
||||
Line 2
|
||||
Quote: "test"
|
||||
Backslash: \
|
8
tests/basic/print_escape.go
Normal file
8
tests/basic/print_escape.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
print("Hello\tWorld!\n")
|
||||
print("Line 1\nLine 2\n")
|
||||
print("Quote: \"test\"\n")
|
||||
print("Backslash: \\\n")
|
||||
}
|
Reference in New Issue
Block a user