A Golang runtime and compilation backend for Delta Interaction Nets.
1package compiler
2
3import (
4 "os"
5 "os/exec"
6 "path/filepath"
7 "strings"
8 "testing"
9)
10
11// Test compiling with additional Go files that provide native functions
12func TestCompileWithNativeFunctions(t *testing.T) {
13 // Create temp dir in a test subdirectory within the project
14 cwd, _ := os.Getwd()
15 projectRoot := filepath.Join(cwd, "../..")
16 tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
17 if err != nil {
18 t.Fatalf("Failed to create temp dir: %v", err)
19 }
20 defer os.RemoveAll(tmpDir)
21
22 // Create a Go file with native function implementations
23 nativeGoFile := filepath.Join(tmpDir, "natives.go")
24 nativeGoCode := `package main
25
26import "github.com/vic/godnet/pkg/deltanet"
27
28func registerNatives(net *deltanet.Network) {
29 // String concatenation
30 net.RegisterNative("str_concat", func(a interface{}) (interface{}, error) {
31 aStr, ok := a.(string)
32 if !ok {
33 return nil, nil // Return nil for non-string
34 }
35 return func(b interface{}) (interface{}, error) {
36 bStr, ok := b.(string)
37 if !ok {
38 return nil, nil
39 }
40 return aStr + bStr, nil
41 }, nil
42 })
43
44 // String length
45 net.RegisterNative("str_len", func(a interface{}) (interface{}, error) {
46 aStr, ok := a.(string)
47 if !ok {
48 return 0, nil
49 }
50 return len(aStr), nil
51 })
52}
53`
54 if err := os.WriteFile(nativeGoFile, []byte(nativeGoCode), 0644); err != nil {
55 t.Fatalf("Failed to write natives.go: %v", err)
56 }
57
58 // Create lambda source that references these natives
59 sourceFile := filepath.Join(tmpDir, "test.lam")
60 source := `x: x` // Simple test - actual native invocation syntax TBD
61
62 if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil {
63 t.Fatalf("Failed to write source file: %v", err)
64 }
65
66 // Compile with the native Go file included (must be in same directory)
67 outputFile := filepath.Join(tmpDir, "test_with_natives")
68 c := Compiler{
69 SourceFile: sourceFile,
70 OutputName: outputFile,
71 GoFlags: []string{nativeGoFile}, // Will be copied to output dir
72 KeepTemp: false,
73 }
74
75 builtFile, err := c.Compile()
76 if err != nil {
77 t.Fatalf("Compilation with natives failed: %v", err)
78 }
79
80 if _, err := os.Stat(builtFile); os.IsNotExist(err) {
81 t.Fatalf("Output binary not found: %s", builtFile)
82 }
83
84 // Verify binary runs
85 cmd := exec.Command(builtFile)
86 output, err := cmd.Output()
87 if err != nil {
88 if exitErr, ok := err.(*exec.ExitError); ok {
89 t.Fatalf("Binary execution failed: %v\nStderr: %s", err, exitErr.Stderr)
90 }
91 t.Fatalf("Binary execution failed: %v", err)
92 }
93
94 result := strings.TrimSpace(string(output))
95 if !strings.Contains(result, "x0: x0") {
96 t.Errorf("Expected identity function, got: %s", result)
97 }
98}
99
100// Test compiling with additional Go files that provide effect handlers
101func TestCompileWithEffectHandlers(t *testing.T) {
102 cwd, _ := os.Getwd()
103 projectRoot := filepath.Join(cwd, "../..")
104 tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
105 if err != nil {
106 t.Fatalf("Failed to create temp dir: %v", err)
107 }
108 defer os.RemoveAll(tmpDir)
109
110 // Create a Go file with effect handler implementations
111 handlersGoFile := filepath.Join(tmpDir, "handlers.go")
112 handlersGoCode := `package main
113
114import (
115 "fmt"
116 "github.com/vic/godnet/pkg/deltanet"
117)
118
119func installHandlers(net *deltanet.Network) *deltanet.HandlerScope {
120 scope := deltanet.NewHandlerScope()
121
122 // Print effect handler
123 scope.Register("Print", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) {
124 fmt.Println(eff.Payload)
125 return cont.Resume(nil)
126 })
127
128 // FileRead effect handler (mock)
129 scope.Register("FileRead", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) {
130 path, ok := eff.Payload.(string)
131 if !ok {
132 return cont.Resume("invalid path")
133 }
134 // Mock file read
135 content := fmt.Sprintf("contents of %s", path)
136 return cont.Resume(content)
137 })
138
139 return scope
140}
141`
142 if err := os.WriteFile(handlersGoFile, []byte(handlersGoCode), 0644); err != nil {
143 t.Fatalf("Failed to write handlers.go: %v", err)
144 }
145
146 // Create lambda source
147 sourceFile := filepath.Join(tmpDir, "test.lam")
148 source := `x: x` // Simple test - actual effect syntax TBD
149
150 if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil {
151 t.Fatalf("Failed to write source file: %v", err)
152 }
153
154 // Compile with the handlers Go file included
155 outputFile := filepath.Join(tmpDir, "test_with_handlers")
156 c := Compiler{
157 SourceFile: sourceFile,
158 OutputName: outputFile,
159 GoFlags: []string{handlersGoFile}, // Link with handlers.go
160 KeepTemp: false,
161 }
162
163 builtFile, err := c.Compile()
164 if err != nil {
165 t.Fatalf("Compilation with handlers failed: %v", err)
166 }
167
168 if _, err := os.Stat(builtFile); os.IsNotExist(err) {
169 t.Fatalf("Output binary not found: %s", builtFile)
170 }
171
172 // Verify binary runs
173 cmd := exec.Command(builtFile)
174 output, err := cmd.Output()
175 if err != nil {
176 if exitErr, ok := err.(*exec.ExitError); ok {
177 t.Fatalf("Binary execution failed: %v\nStderr: %s", err, exitErr.Stderr)
178 }
179 t.Fatalf("Binary execution failed: %v", err)
180 }
181
182 result := strings.TrimSpace(string(output))
183 if !strings.Contains(result, "x0: x0") {
184 t.Errorf("Expected identity function, got: %s", result)
185 }
186}
187
188// Test compiling with both native functions and effect handlers
189func TestCompileMixedNativesAndHandlers(t *testing.T) {
190 cwd, _ := os.Getwd()
191 projectRoot := filepath.Join(cwd, "../..")
192 tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
193 if err != nil {
194 t.Fatalf("Failed to create temp dir: %v", err)
195 }
196 defer os.RemoveAll(tmpDir)
197
198 // Create Go file with both natives and handlers
199 runtimeGoFile := filepath.Join(tmpDir, "runtime.go")
200 runtimeGoCode := `package main
201
202import (
203 "fmt"
204 "github.com/vic/godnet/pkg/deltanet"
205)
206
207func setupRuntime(net *deltanet.Network) *deltanet.HandlerScope {
208 // Register native functions
209 net.RegisterNative("add", func(a interface{}) (interface{}, error) {
210 aInt, ok := a.(int)
211 if !ok {
212 return nil, fmt.Errorf("add: expected int")
213 }
214 return func(b interface{}) (interface{}, error) {
215 bInt, ok := b.(int)
216 if !ok {
217 return nil, fmt.Errorf("add: expected int")
218 }
219 return aInt + bInt, nil
220 }, nil
221 })
222
223 // Register effect handlers
224 scope := deltanet.NewHandlerScope()
225 scope.Register("Log", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) {
226 fmt.Printf("[LOG] %v\n", eff.Payload)
227 return cont.Resume(nil)
228 })
229
230 return scope
231}
232`
233 if err := os.WriteFile(runtimeGoFile, []byte(runtimeGoCode), 0644); err != nil {
234 t.Fatalf("Failed to write runtime.go: %v", err)
235 }
236
237 // Create lambda source
238 sourceFile := filepath.Join(tmpDir, "test.lam")
239 source := `x: x` // Simple test - actual native/effect syntax TBD
240
241 if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil {
242 t.Fatalf("Failed to write source file: %v", err)
243 }
244
245 // Compile with runtime file
246 outputFile := filepath.Join(tmpDir, "test_mixed")
247 c := Compiler{
248 SourceFile: sourceFile,
249 OutputName: outputFile,
250 GoFlags: []string{runtimeGoFile}, // Link with runtime.go
251 KeepTemp: false,
252 }
253
254 builtFile, err := c.Compile()
255 if err != nil {
256 t.Fatalf("Compilation failed: %v", err)
257 }
258
259 if _, err := os.Stat(builtFile); os.IsNotExist(err) {
260 t.Fatalf("Output binary not found: %s", builtFile)
261 }
262
263 // Verify binary runs
264 cmd := exec.Command(builtFile)
265 output, err := cmd.Output()
266 if err != nil {
267 if exitErr, ok := err.(*exec.ExitError); ok {
268 t.Fatalf("Binary execution failed: %v\nStderr: %s", err, exitErr.Stderr)
269 }
270 t.Fatalf("Binary execution failed: %v", err)
271 }
272
273 result := strings.TrimSpace(string(output))
274 if !strings.Contains(result, "x0: x0") {
275 t.Errorf("Expected identity function, got: %s", result)
276 }
277}
278
279// Test multiple Go files can be linked together
280func TestCompileWithMultipleRuntimeFiles(t *testing.T) {
281 cwd, _ := os.Getwd()
282 projectRoot := filepath.Join(cwd, "../..")
283 tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
284 if err != nil {
285 t.Fatalf("Failed to create temp dir: %v", err)
286 }
287 defer os.RemoveAll(tmpDir)
288
289 // Create separate files for natives and handlers
290 nativesFile := filepath.Join(tmpDir, "natives.go")
291 nativesCode := `package main
292
293import "github.com/vic/godnet/pkg/deltanet"
294
295func setupNatives(net *deltanet.Network) {
296 net.RegisterNative("mul", func(a interface{}) (interface{}, error) {
297 aInt := a.(int)
298 return func(b interface{}) (interface{}, error) {
299 bInt := b.(int)
300 return aInt * bInt, nil
301 }, nil
302 })
303}
304`
305 if err := os.WriteFile(nativesFile, []byte(nativesCode), 0644); err != nil {
306 t.Fatalf("Failed to write natives: %v", err)
307 }
308
309 handlersFile := filepath.Join(tmpDir, "handlers.go")
310 handlersCode := `package main
311
312import (
313 "fmt"
314 "github.com/vic/godnet/pkg/deltanet"
315)
316
317func setupHandlers() *deltanet.HandlerScope {
318 scope := deltanet.NewHandlerScope()
319 scope.Register("Debug", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) {
320 fmt.Printf("[DEBUG] %v\n", eff.Payload)
321 return cont.Resume(nil)
322 })
323 return scope
324}
325`
326 if err := os.WriteFile(handlersFile, []byte(handlersCode), 0644); err != nil {
327 t.Fatalf("Failed to write handlers: %v", err)
328 }
329
330 // Create lambda source
331 sourceFile := filepath.Join(tmpDir, "test.lam")
332 source := `x: x`
333
334 if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil {
335 t.Fatalf("Failed to write source: %v", err)
336 }
337
338 // Compile with multiple runtime files
339 outputFile := filepath.Join(tmpDir, "test_multi")
340 c := Compiler{
341 SourceFile: sourceFile,
342 OutputName: outputFile,
343 GoFlags: []string{nativesFile, handlersFile},
344 KeepTemp: false,
345 }
346
347 builtFile, err := c.Compile()
348 if err != nil {
349 t.Fatalf("Compilation with multiple files failed: %v", err)
350 }
351
352 if _, err := os.Stat(builtFile); os.IsNotExist(err) {
353 t.Fatalf("Output binary not found: %s", builtFile)
354 }
355
356 // Verify binary runs
357 cmd := exec.Command(builtFile)
358 if output, err := cmd.Output(); err != nil {
359 if exitErr, ok := err.(*exec.ExitError); ok {
360 t.Fatalf("Binary failed: %v\nStderr: %s", err, exitErr.Stderr)
361 }
362 t.Fatalf("Binary failed: %v", err)
363 } else {
364 result := strings.TrimSpace(string(output))
365 if !strings.Contains(result, "x0: x0") {
366 t.Errorf("Expected identity, got: %s", result)
367 }
368 }
369}