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
11func TestCompileIdentity(t *testing.T) {
12 testCompile(t, "identity", "(x: x) a", "a")
13}
14
15func TestCompileKCombinator(t *testing.T) {
16 testCompile(t, "k_combinator", "(x: y: x) a b", "a")
17}
18
19func TestCompileChurchZero(t *testing.T) {
20 testCompile(t, "church_zero", "f: x: x", "(x0: (x1: x1))")
21}
22
23func TestCompileChurchSucc(t *testing.T) {
24 testCompile(t, "church_succ",
25 "let succ = n: f: x: f (n f x); zero = f: x: x in succ zero",
26 "(x0: (x1: (x0")
27}
28
29func TestCompileFreeVariable(t *testing.T) {
30 testCompile(t, "free_var", "x: y", "(x0: y)")
31}
32
33func TestCompileNestedApp(t *testing.T) {
34 testCompile(t, "nested_app", "((x: y: z: x) a) b c", "a")
35}
36
37func TestCompileSCombinator(t *testing.T) {
38 testCompile(t, "s_combinator",
39 "(x: y: z: (x z) (y z)) (a: a) (b: b) d",
40 "(d d)")
41}
42
43func testCompile(t *testing.T, name string, source string, expected string) {
44 t.Helper()
45
46 // Create temp directory in project root for module support
47 cwd, _ := os.Getwd()
48 projectRoot := filepath.Join(cwd, "../..")
49 tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
50 if err != nil {
51 t.Fatalf("Failed to create temp dir: %v", err)
52 }
53 defer os.RemoveAll(tmpDir)
54
55 // Write source file
56 sourceFile := filepath.Join(tmpDir, name+".lam")
57 if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil {
58 t.Fatalf("Failed to write source file: %v", err)
59 }
60
61 // Compile with absolute output path
62 outputFile := filepath.Join(tmpDir, name)
63 c := Compiler{
64 SourceFile: sourceFile,
65 OutputName: outputFile,
66 KeepTemp: false,
67 }
68
69 builtFile, err := c.Compile()
70 if err != nil {
71 t.Fatalf("Compilation failed: %v", err)
72 }
73
74 if builtFile != outputFile {
75 t.Fatalf("Output file mismatch: expected %s, got %s", outputFile, builtFile)
76 }
77
78 // Make sure binary exists
79 if _, err := os.Stat(outputFile); os.IsNotExist(err) {
80 t.Fatalf("Output binary not found: %s", outputFile)
81 }
82 defer os.Remove(outputFile)
83
84 // Run the binary
85 cmd := exec.Command(outputFile)
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 // Check result
95 result := strings.TrimSpace(string(output))
96 if !strings.HasPrefix(result, expected) {
97 t.Errorf("Expected output to start with:\n%s\nGot:\n%s", expected, result)
98 }
99}
100
101func TestCompileWithFlags(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 sourceFile := filepath.Join(tmpDir, "test.lam")
111 if err := os.WriteFile(sourceFile, []byte("x: x"), 0644); err != nil {
112 t.Fatalf("Failed to write source file: %v", err)
113 }
114
115 // Set custom output name
116 customOut := filepath.Join(tmpDir, "custom_name")
117
118 c := Compiler{
119 SourceFile: sourceFile,
120 OutputName: customOut,
121 GoFlags: []string{"-v"}, // verbose go build
122 }
123
124 outputFile, err := c.Compile()
125 if err != nil {
126 t.Fatalf("Compilation with flags failed: %v", err)
127 }
128
129 if outputFile != customOut {
130 t.Errorf("Expected output name %s, got %s", customOut, outputFile)
131 }
132
133 if _, err := os.Stat(outputFile); os.IsNotExist(err) {
134 t.Errorf("Custom output file not created: %s", outputFile)
135 }
136}
137
138func TestCompileKeepTemp(t *testing.T) {
139 cwd, _ := os.Getwd()
140 projectRoot := filepath.Join(cwd, "../..")
141 tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
142 if err != nil {
143 t.Fatalf("Failed to create temp dir: %v", err)
144 }
145 defer os.RemoveAll(tmpDir)
146
147 sourceFile := filepath.Join(tmpDir, "test.lam")
148 if err := os.WriteFile(sourceFile, []byte("x: x"), 0644); err != nil {
149 t.Fatalf("Failed to write source file: %v", err)
150 }
151
152 outputFile := filepath.Join(tmpDir, "test_keeptemp")
153 c := Compiler{
154 SourceFile: sourceFile,
155 OutputName: outputFile,
156 KeepTemp: true,
157 }
158
159 builtFile, err := c.Compile()
160 if err != nil {
161 t.Fatalf("Compilation failed: %v", err)
162 }
163 defer os.Remove(builtFile)
164
165 // Note: The temp file is created in /tmp, not next to source
166 // We just verify KeepTemp was respected (stderr message printed)
167 // Actual temp file cleanup is handled by OS
168}
169
170func TestCompileInvalidSource(t *testing.T) {
171 cwd, _ := os.Getwd()
172 projectRoot := filepath.Join(cwd, "../..")
173 tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
174 if err != nil {
175 t.Fatalf("Failed to create temp dir: %v", err)
176 }
177 defer os.RemoveAll(tmpDir)
178
179 sourceFile := filepath.Join(tmpDir, "invalid.lam")
180 if err := os.WriteFile(sourceFile, []byte("((("), 0644); err != nil {
181 t.Fatalf("Failed to write source file: %v", err)
182 }
183
184 c := Compiler{
185 SourceFile: sourceFile,
186 }
187
188 _, err = c.Compile()
189 if err == nil {
190 t.Error("Expected compilation to fail for invalid syntax")
191 }
192}