A Golang runtime and compilation backend for Delta Interaction Nets.
at main 173 lines 4.2 kB view raw
1package compiler 2 3import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "path/filepath" 8 "strings" 9 10 "github.com/vic/godnet/pkg/lambda" 11) 12 13// Compiler translates lambda terms to Go code and invokes go build. 14type Compiler struct { 15 SourceFile string 16 OutputName string 17 GoFlags []string // Passed directly to go build 18 KeepTemp bool // For debugging 19} 20 21// Compile translates the lambda source to Go code and builds it. 22// Returns the output binary path on success. 23func (c *Compiler) Compile() (string, error) { 24 // Read and parse source 25 source, err := os.ReadFile(c.SourceFile) 26 if err != nil { 27 return "", fmt.Errorf("failed to read source: %w", err) 28 } 29 30 term, err := lambda.Parse(string(source)) 31 if err != nil { 32 return "", fmt.Errorf("parse error: %w", err) 33 } 34 35 // Generate Go code 36 gen := CodeGenerator{ 37 SourceFile: c.SourceFile, 38 SourceText: string(source), 39 } 40 goCode := gen.Generate(term) 41 42 // Determine output name first (needed for temp file location) 43 outputName := c.OutputName 44 if outputName == "" { 45 // Default: strip .lam extension 46 outputName = strings.TrimSuffix(filepath.Base(c.SourceFile), filepath.Ext(c.SourceFile)) 47 } 48 49 // Write to temporary file in same directory as output (required by go build) 50 outputDir := filepath.Dir(outputName) 51 if outputDir == "." || outputDir == "" { 52 outputDir, _ = os.Getwd() 53 } 54 55 tmpFile, err := os.CreateTemp(outputDir, "godnet-*.go") 56 if err != nil { 57 return "", fmt.Errorf("failed to create temp file: %w", err) 58 } 59 tmpPath := tmpFile.Name() 60 defer func() { 61 tmpFile.Close() 62 if !c.KeepTemp { 63 os.Remove(tmpPath) 64 } 65 }() 66 67 if _, err := tmpFile.WriteString(goCode); err != nil { 68 return "", fmt.Errorf("failed to write generated code: %w", err) 69 } 70 tmpFile.Close() 71 72 // Copy user-provided .go files to output directory (required by go build) 73 var copiedFiles []string 74 for _, flag := range c.GoFlags { 75 if strings.HasSuffix(flag, ".go") { 76 srcData, err := os.ReadFile(flag) 77 if err != nil { 78 return "", fmt.Errorf("failed to read %s: %w", flag, err) 79 } 80 dstPath := filepath.Join(outputDir, filepath.Base(flag)) 81 if err := os.WriteFile(dstPath, srcData, 0644); err != nil { 82 return "", fmt.Errorf("failed to copy %s: %w", flag, err) 83 } 84 copiedFiles = append(copiedFiles, dstPath) 85 defer func(path string) { 86 if !c.KeepTemp { 87 os.Remove(path) 88 } 89 }(dstPath) 90 } 91 } 92 93 // Find go.mod directory to set module context 94 goModDir := findGoModDir(c.SourceFile) 95 96 // Build with go build 97 buildDir := outputDir 98 if goModDir != "" { 99 // If we found go.mod, build from module root for proper dependency resolution 100 buildDir = goModDir 101 } 102 103 args := []string{"build", "-o", outputName} 104 105 // Add non-.go flags 106 for _, flag := range c.GoFlags { 107 if !strings.HasSuffix(flag, ".go") { 108 args = append(args, flag) 109 } 110 } 111 112 // Add all Go files (use full paths if building from different directory) 113 if buildDir != outputDir { 114 args = append(args, tmpPath) 115 args = append(args, copiedFiles...) 116 } else { 117 args = append(args, filepath.Base(tmpPath)) 118 for _, copied := range copiedFiles { 119 args = append(args, filepath.Base(copied)) 120 } 121 } 122 123 cmd := exec.Command("go", args...) 124 cmd.Dir = buildDir 125 cmd.Stdout = os.Stdout 126 cmd.Stderr = os.Stderr 127 128 // Debug: show what we're running 129 if c.KeepTemp { 130 fmt.Fprintf(os.Stderr, "Build dir: %s\n", buildDir) 131 fmt.Fprintf(os.Stderr, "Build cmd: go %s\n", strings.Join(args, " ")) 132 } 133 134 if err := cmd.Run(); err != nil { 135 return "", fmt.Errorf("go build failed: %w", err) 136 } 137 138 // Return absolute path to output 139 if !filepath.IsAbs(outputName) { 140 outputName = filepath.Join(outputDir, filepath.Base(outputName)) 141 } 142 143 if c.KeepTemp { 144 fmt.Fprintf(os.Stderr, "Generated code kept at: %s\n", tmpPath) 145 } 146 147 return outputName, nil 148} 149 150// findGoModDir searches for go.mod starting from the given path 151func findGoModDir(startPath string) string { 152 dir := filepath.Dir(startPath) 153 if !filepath.IsAbs(dir) { 154 if abs, err := filepath.Abs(dir); err == nil { 155 dir = abs 156 } 157 } 158 159 for { 160 goModPath := filepath.Join(dir, "go.mod") 161 if _, err := os.Stat(goModPath); err == nil { 162 return dir 163 } 164 165 parent := filepath.Dir(dir) 166 if parent == dir { 167 break // Reached root 168 } 169 dir = parent 170 } 171 172 return "" // Not found 173}