tangled
alpha
login
or
join now
oeiuwq.com
/
godnet
1
fork
atom
A Golang runtime and compilation backend for Delta Interaction Nets.
1
fork
atom
overview
issues
pulls
pipelines
backend
oeiuwq.com
3 months ago
4bd3c829
786b846d
+439
-150
5 changed files
expand all
collapse all
unified
split
cmd
godnet
main.go
pkg
compiler
compiler.go
compiler_test.go
generator.go
native_test.go
+5
-5
cmd/godnet/main.go
···
18
18
runCompile()
19
19
return
20
20
}
21
21
-
21
21
+
22
22
// Default: eval mode
23
23
runEval()
24
24
}
···
28
28
fmt.Fprintf(os.Stderr, "Usage: godnet compile <source.lam> [go build flags...]\n")
29
29
os.Exit(1)
30
30
}
31
31
-
31
31
+
32
32
sourceFile := os.Args[2]
33
33
goFlags := os.Args[3:]
34
34
-
34
34
+
35
35
c := compiler.Compiler{
36
36
SourceFile: sourceFile,
37
37
GoFlags: goFlags,
38
38
}
39
39
-
39
39
+
40
40
outputName, err := c.Compile()
41
41
if err != nil {
42
42
fmt.Fprintf(os.Stderr, "Compilation failed: %v\n", err)
43
43
os.Exit(1)
44
44
}
45
45
-
45
45
+
46
46
fmt.Fprintf(os.Stderr, "Successfully compiled to: %s\n", outputName)
47
47
}
48
48
+97
-9
pkg/compiler/compiler.go
···
39
39
}
40
40
goCode := gen.Generate(term)
41
41
42
42
-
// Write to temporary file
43
43
-
tmpFile, err := os.CreateTemp("", "godnet-*.go")
42
42
+
// Determine output name first (needed for temp file location)
43
43
+
outputName := c.OutputName
44
44
+
if outputName == "" {
45
45
+
// Default: strip .lam extension
46
46
+
outputName = strings.TrimSuffix(filepath.Base(c.SourceFile), filepath.Ext(c.SourceFile))
47
47
+
}
48
48
+
49
49
+
// Write to temporary file in same directory as output (required by go build)
50
50
+
outputDir := filepath.Dir(outputName)
51
51
+
if outputDir == "." || outputDir == "" {
52
52
+
outputDir, _ = os.Getwd()
53
53
+
}
54
54
+
55
55
+
tmpFile, err := os.CreateTemp(outputDir, "godnet-*.go")
44
56
if err != nil {
45
57
return "", fmt.Errorf("failed to create temp file: %w", err)
46
58
}
···
57
69
}
58
70
tmpFile.Close()
59
71
60
60
-
// Determine output name
61
61
-
outputName := c.OutputName
62
62
-
if outputName == "" {
63
63
-
// Default: strip .lam extension
64
64
-
outputName = strings.TrimSuffix(filepath.Base(c.SourceFile), filepath.Ext(c.SourceFile))
72
72
+
// Copy user-provided .go files to output directory (required by go build)
73
73
+
var copiedFiles []string
74
74
+
for _, flag := range c.GoFlags {
75
75
+
if strings.HasSuffix(flag, ".go") {
76
76
+
srcData, err := os.ReadFile(flag)
77
77
+
if err != nil {
78
78
+
return "", fmt.Errorf("failed to read %s: %w", flag, err)
79
79
+
}
80
80
+
dstPath := filepath.Join(outputDir, filepath.Base(flag))
81
81
+
if err := os.WriteFile(dstPath, srcData, 0644); err != nil {
82
82
+
return "", fmt.Errorf("failed to copy %s: %w", flag, err)
83
83
+
}
84
84
+
copiedFiles = append(copiedFiles, dstPath)
85
85
+
defer func(path string) {
86
86
+
if !c.KeepTemp {
87
87
+
os.Remove(path)
88
88
+
}
89
89
+
}(dstPath)
90
90
+
}
65
91
}
66
92
93
93
+
// Find go.mod directory to set module context
94
94
+
goModDir := findGoModDir(c.SourceFile)
95
95
+
67
96
// Build with go build
97
97
+
buildDir := outputDir
98
98
+
if goModDir != "" {
99
99
+
// If we found go.mod, build from module root for proper dependency resolution
100
100
+
buildDir = goModDir
101
101
+
}
102
102
+
68
103
args := []string{"build", "-o", outputName}
69
69
-
args = append(args, c.GoFlags...)
70
70
-
args = append(args, tmpPath)
104
104
+
105
105
+
// Add non-.go flags
106
106
+
for _, flag := range c.GoFlags {
107
107
+
if !strings.HasSuffix(flag, ".go") {
108
108
+
args = append(args, flag)
109
109
+
}
110
110
+
}
111
111
+
112
112
+
// Add all Go files (use full paths if building from different directory)
113
113
+
if buildDir != outputDir {
114
114
+
args = append(args, tmpPath)
115
115
+
args = append(args, copiedFiles...)
116
116
+
} else {
117
117
+
args = append(args, filepath.Base(tmpPath))
118
118
+
for _, copied := range copiedFiles {
119
119
+
args = append(args, filepath.Base(copied))
120
120
+
}
121
121
+
}
71
122
72
123
cmd := exec.Command("go", args...)
124
124
+
cmd.Dir = buildDir
73
125
cmd.Stdout = os.Stdout
74
126
cmd.Stderr = os.Stderr
75
127
128
128
+
// Debug: show what we're running
129
129
+
if c.KeepTemp {
130
130
+
fmt.Fprintf(os.Stderr, "Build dir: %s\n", buildDir)
131
131
+
fmt.Fprintf(os.Stderr, "Build cmd: go %s\n", strings.Join(args, " "))
132
132
+
}
133
133
+
76
134
if err := cmd.Run(); err != nil {
77
135
return "", fmt.Errorf("go build failed: %w", err)
78
136
}
79
137
138
138
+
// Return absolute path to output
139
139
+
if !filepath.IsAbs(outputName) {
140
140
+
outputName = filepath.Join(outputDir, filepath.Base(outputName))
141
141
+
}
142
142
+
80
143
if c.KeepTemp {
81
144
fmt.Fprintf(os.Stderr, "Generated code kept at: %s\n", tmpPath)
82
145
}
83
146
84
147
return outputName, nil
85
148
}
149
149
+
150
150
+
// findGoModDir searches for go.mod starting from the given path
151
151
+
func findGoModDir(startPath string) string {
152
152
+
dir := filepath.Dir(startPath)
153
153
+
if !filepath.IsAbs(dir) {
154
154
+
if abs, err := filepath.Abs(dir); err == nil {
155
155
+
dir = abs
156
156
+
}
157
157
+
}
158
158
+
159
159
+
for {
160
160
+
goModPath := filepath.Join(dir, "go.mod")
161
161
+
if _, err := os.Stat(goModPath); err == nil {
162
162
+
return dir
163
163
+
}
164
164
+
165
165
+
parent := filepath.Dir(dir)
166
166
+
if parent == dir {
167
167
+
break // Reached root
168
168
+
}
169
169
+
dir = parent
170
170
+
}
171
171
+
172
172
+
return "" // Not found
173
173
+
}
+40
-30
pkg/compiler/compiler_test.go
···
21
21
}
22
22
23
23
func TestCompileChurchSucc(t *testing.T) {
24
24
-
testCompile(t, "church_succ",
24
24
+
testCompile(t, "church_succ",
25
25
"let succ = n: f: x: f (n f x); zero = f: x: x in succ zero",
26
26
"(x0: (x1: (x0")
27
27
}
···
35
35
}
36
36
37
37
func TestCompileSCombinator(t *testing.T) {
38
38
-
testCompile(t, "s_combinator",
38
38
+
testCompile(t, "s_combinator",
39
39
"(x: y: z: (x z) (y z)) (a: a) (b: b) d",
40
40
"(d d)")
41
41
}
42
42
43
43
func testCompile(t *testing.T, name string, source string, expected string) {
44
44
t.Helper()
45
45
-
46
46
-
// Create temp directory for test files
47
47
-
tmpDir, err := os.MkdirTemp("", "godnet_compile_test_*")
45
45
+
46
46
+
// Create temp directory in project root for module support
47
47
+
cwd, _ := os.Getwd()
48
48
+
projectRoot := filepath.Join(cwd, "../..")
49
49
+
tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
48
50
if err != nil {
49
51
t.Fatalf("Failed to create temp dir: %v", err)
50
52
}
51
53
defer os.RemoveAll(tmpDir)
52
52
-
54
54
+
53
55
// Write source file
54
56
sourceFile := filepath.Join(tmpDir, name+".lam")
55
57
if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil {
56
58
t.Fatalf("Failed to write source file: %v", err)
57
59
}
58
58
-
60
60
+
59
61
// Compile with absolute output path
60
62
outputFile := filepath.Join(tmpDir, name)
61
63
c := Compiler{
···
63
65
OutputName: outputFile,
64
66
KeepTemp: false,
65
67
}
66
66
-
68
68
+
67
69
builtFile, err := c.Compile()
68
70
if err != nil {
69
71
t.Fatalf("Compilation failed: %v", err)
70
72
}
71
71
-
73
73
+
72
74
if builtFile != outputFile {
73
75
t.Fatalf("Output file mismatch: expected %s, got %s", outputFile, builtFile)
74
76
}
75
75
-
77
77
+
76
78
// Make sure binary exists
77
79
if _, err := os.Stat(outputFile); os.IsNotExist(err) {
78
80
t.Fatalf("Output binary not found: %s", outputFile)
79
81
}
80
82
defer os.Remove(outputFile)
81
81
-
83
83
+
82
84
// Run the binary
83
85
cmd := exec.Command(outputFile)
84
86
output, err := cmd.Output()
···
88
90
}
89
91
t.Fatalf("Binary execution failed: %v", err)
90
92
}
91
91
-
93
93
+
92
94
// Check result
93
95
result := strings.TrimSpace(string(output))
94
96
if !strings.HasPrefix(result, expected) {
···
97
99
}
98
100
99
101
func TestCompileWithFlags(t *testing.T) {
100
100
-
tmpDir, err := os.MkdirTemp("", "godnet_compile_flags_*")
102
102
+
cwd, _ := os.Getwd()
103
103
+
projectRoot := filepath.Join(cwd, "../..")
104
104
+
tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
101
105
if err != nil {
102
106
t.Fatalf("Failed to create temp dir: %v", err)
103
107
}
104
108
defer os.RemoveAll(tmpDir)
105
105
-
109
109
+
106
110
sourceFile := filepath.Join(tmpDir, "test.lam")
107
111
if err := os.WriteFile(sourceFile, []byte("x: x"), 0644); err != nil {
108
112
t.Fatalf("Failed to write source file: %v", err)
109
113
}
110
110
-
114
114
+
111
115
// Set custom output name
112
116
customOut := filepath.Join(tmpDir, "custom_name")
113
113
-
117
117
+
114
118
c := Compiler{
115
119
SourceFile: sourceFile,
116
120
OutputName: customOut,
117
121
GoFlags: []string{"-v"}, // verbose go build
118
122
}
119
119
-
123
123
+
120
124
outputFile, err := c.Compile()
121
125
if err != nil {
122
126
t.Fatalf("Compilation with flags failed: %v", err)
123
127
}
124
124
-
128
128
+
125
129
if outputFile != customOut {
126
130
t.Errorf("Expected output name %s, got %s", customOut, outputFile)
127
131
}
128
128
-
132
132
+
129
133
if _, err := os.Stat(outputFile); os.IsNotExist(err) {
130
134
t.Errorf("Custom output file not created: %s", outputFile)
131
135
}
132
136
}
133
137
134
138
func TestCompileKeepTemp(t *testing.T) {
135
135
-
tmpDir, err := os.MkdirTemp("", "godnet_keep_temp_*")
139
139
+
cwd, _ := os.Getwd()
140
140
+
projectRoot := filepath.Join(cwd, "../..")
141
141
+
tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
136
142
if err != nil {
137
143
t.Fatalf("Failed to create temp dir: %v", err)
138
144
}
139
145
defer os.RemoveAll(tmpDir)
140
140
-
146
146
+
141
147
sourceFile := filepath.Join(tmpDir, "test.lam")
142
148
if err := os.WriteFile(sourceFile, []byte("x: x"), 0644); err != nil {
143
149
t.Fatalf("Failed to write source file: %v", err)
144
150
}
145
145
-
151
151
+
152
152
+
outputFile := filepath.Join(tmpDir, "test_keeptemp")
146
153
c := Compiler{
147
154
SourceFile: sourceFile,
155
155
+
OutputName: outputFile,
148
156
KeepTemp: true,
149
157
}
150
150
-
151
151
-
outputFile, err := c.Compile()
158
158
+
159
159
+
builtFile, err := c.Compile()
152
160
if err != nil {
153
161
t.Fatalf("Compilation failed: %v", err)
154
162
}
155
155
-
defer os.Remove(outputFile)
156
156
-
163
163
+
defer os.Remove(builtFile)
164
164
+
157
165
// Note: The temp file is created in /tmp, not next to source
158
166
// We just verify KeepTemp was respected (stderr message printed)
159
167
// Actual temp file cleanup is handled by OS
160
168
}
161
169
162
170
func TestCompileInvalidSource(t *testing.T) {
163
163
-
tmpDir, err := os.MkdirTemp("", "godnet_invalid_*")
171
171
+
cwd, _ := os.Getwd()
172
172
+
projectRoot := filepath.Join(cwd, "../..")
173
173
+
tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
164
174
if err != nil {
165
175
t.Fatalf("Failed to create temp dir: %v", err)
166
176
}
167
177
defer os.RemoveAll(tmpDir)
168
168
-
178
178
+
169
179
sourceFile := filepath.Join(tmpDir, "invalid.lam")
170
180
if err := os.WriteFile(sourceFile, []byte("((("), 0644); err != nil {
171
181
t.Fatalf("Failed to write source file: %v", err)
172
182
}
173
173
-
183
183
+
174
184
c := Compiler{
175
185
SourceFile: sourceFile,
176
186
}
177
177
-
187
187
+
178
188
_, err = c.Compile()
179
189
if err == nil {
180
190
t.Error("Expected compilation to fail for invalid syntax")
+24
-24
pkg/compiler/generator.go
···
26
26
// Generate produces Go source code from a lambda term.
27
27
func (g *CodeGenerator) Generate(term lambda.Term) string {
28
28
g.vars = make(map[string]*varInfo)
29
29
-
29
29
+
30
30
g.writeHeader()
31
31
g.writeBuildNetFunction(term)
32
32
g.writeMainFunction()
33
33
-
33
33
+
34
34
return g.buf.String()
35
35
}
36
36
···
54
54
g.writeLine("func buildNet(net *deltanet.Network) (deltanet.Node, int, map[uint64]string) {")
55
55
g.writeLine("\tvarNames := make(map[uint64]string)")
56
56
g.writeLine("")
57
57
-
57
57
+
58
58
// Translate the term
59
59
rootNode, rootPort := g.translateTerm(term, 0, 0)
60
60
-
60
60
+
61
61
g.writeLine("")
62
62
g.writeLine("\treturn %s, %d, varNames", rootNode, rootPort)
63
63
g.writeLine("}")
···
116
116
if info, ok := g.vars[v.Name]; ok {
117
117
// Bound variable - expand replicator
118
118
g.writeComment("Variable: %s (bound)", v.Name)
119
119
-
119
119
+
120
120
if strings.HasPrefix(info.nodeName, "rep_") {
121
121
// Already a replicator, expand it
122
122
oldRepName := info.nodeName
123
123
newRepName := g.nextNode("rep")
124
124
delta := level - (info.level + 1)
125
125
nextPort := info.uses + 1 // Next aux port index (1-based, since port 0 is principal)
126
126
-
126
126
+
127
127
g.writeLine("\t// Expand replicator for variable '%s' (use #%d)", v.Name, info.uses+1)
128
128
g.writeLine("\t%s := net.NewReplicator(%d, append(%s.Deltas(), %d))",
129
129
newRepName, info.level, oldRepName, delta)
130
130
-
130
130
+
131
131
// Move principal connection
132
132
g.writeLine("\tsourceNode, sourcePort := net.GetLink(%s, 0)", oldRepName)
133
133
g.writeLine("\tnet.LinkAt(%s, 0, sourceNode, sourcePort, %d)", newRepName, depth)
134
134
-
134
134
+
135
135
// Move existing aux ports
136
136
g.writeLine("\tfor i := 0; i < len(%s.Deltas()); i++ {", oldRepName)
137
137
g.writeLine("\t\tdestNode, destPort := net.GetLink(%s, i+1)", oldRepName)
···
139
139
g.writeLine("\t\t\tnet.LinkAt(%s, i+1, destNode, destPort, %d)", newRepName, depth)
140
140
g.writeLine("\t\t}")
141
141
g.writeLine("\t}")
142
142
-
142
142
+
143
143
info.nodeName = newRepName
144
144
info.uses++
145
145
return newRepName, nextPort
···
149
149
repName := g.nextNode("rep")
150
150
delta := level - (info.level + 1)
151
151
repLevel := info.level + 1
152
152
-
152
152
+
153
153
g.writeLine("\t%s := net.NewReplicator(%d, []int{%d})", repName, repLevel, delta)
154
154
g.writeLine("\tnet.LinkAt(%s, 0, %s, %d, %d)", repName, info.nodeName, info.port, depth)
155
155
-
155
155
+
156
156
info.nodeName = repName
157
157
info.uses = 1
158
158
return repName, 1
···
162
162
g.writeComment("Variable: %s (free)", v.Name)
163
163
varName := g.nextNode("var")
164
164
repName := g.nextNode("rep")
165
165
-
165
165
+
166
166
g.writeLine("\t%s := net.NewVar()", varName)
167
167
g.writeLine("\tvarNames[%s.ID()] = \"%s\"", varName, v.Name)
168
168
g.writeLine("\t%s := net.NewReplicator(0, []int{%d})", repName, level-1)
169
169
g.writeLine("\tnet.LinkAt(%s, 0, %s, 0, %d)", repName, varName, depth)
170
170
-
170
170
+
171
171
g.vars[v.Name] = &varInfo{
172
172
nodeName: repName,
173
173
port: 0,
174
174
level: 0,
175
175
}
176
176
-
176
176
+
177
177
return repName, 1
178
178
}
179
179
}
180
180
181
181
func (g *CodeGenerator) genAbs(abs lambda.Abs, level int, depth uint64) (string, int) {
182
182
g.writeComment("Abstraction: λ%s. ...", abs.Arg)
183
183
-
183
183
+
184
184
fanName := g.nextNode("fan")
185
185
eraName := g.nextNode("era")
186
186
-
186
186
+
187
187
g.writeLine("\t%s := net.NewFan()", fanName)
188
188
g.writeLine("\t%s := net.NewEraser()", eraName)
189
189
g.writeLine("\tnet.LinkAt(%s, 0, %s, 2, %d)", eraName, fanName, depth)
190
190
-
190
190
+
191
191
// Save old binding if shadowing
192
192
oldVar := g.vars[abs.Arg]
193
193
g.vars[abs.Arg] = &varInfo{
···
195
195
port: 2,
196
196
level: level,
197
197
}
198
198
-
198
198
+
199
199
// Generate body
200
200
bodyNode, bodyPort := g.translateTerm(abs.Body, level, depth)
201
201
g.writeLine("\tnet.LinkAt(%s, 1, %s, %d, %d)", fanName, bodyNode, bodyPort, depth)
202
202
-
202
202
+
203
203
// Restore old binding
204
204
if oldVar != nil {
205
205
g.vars[abs.Arg] = oldVar
206
206
} else {
207
207
delete(g.vars, abs.Arg)
208
208
}
209
209
-
209
209
+
210
210
return fanName, 0
211
211
}
212
212
213
213
func (g *CodeGenerator) genApp(app lambda.App, level int, depth uint64) (string, int) {
214
214
g.writeComment("Application")
215
215
-
215
215
+
216
216
fanName := g.nextNode("fan")
217
217
g.writeLine("\t%s := net.NewFan()", fanName)
218
218
-
218
218
+
219
219
// Generate function
220
220
funNode, funPort := g.translateTerm(app.Fun, level, depth)
221
221
g.writeLine("\tnet.LinkAt(%s, 0, %s, %d, %d)", fanName, funNode, funPort, depth)
222
222
-
222
222
+
223
223
// Generate argument (level + 1)
224
224
argNode, argPort := g.translateTerm(app.Arg, level+1, depth+1)
225
225
g.writeLine("\tnet.LinkAt(%s, 2, %s, %d, %d)", fanName, argNode, argPort, depth+1)
226
226
-
226
226
+
227
227
return fanName, 1
228
228
}
229
229
+273
-82
pkg/compiler/native_test.go
···
8
8
"testing"
9
9
)
10
10
11
11
-
// Test compiling and running code with native pure functions
12
12
-
func TestCompileWithPureFunctions(t *testing.T) {
13
13
-
tmpDir, err := os.MkdirTemp("", "godnet_pure_test_*")
11
11
+
// Test compiling with additional Go files that provide native functions
12
12
+
func TestCompileWithNativeFunctions(t *testing.T) {
13
13
+
// Create temp dir in a test subdirectory within the project
14
14
+
cwd, _ := os.Getwd()
15
15
+
projectRoot := filepath.Join(cwd, "../..")
16
16
+
tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
14
17
if err != nil {
15
18
t.Fatalf("Failed to create temp dir: %v", err)
16
19
}
17
20
defer os.RemoveAll(tmpDir)
18
21
19
19
-
// Create a test that will use pure functions
20
20
-
// For now, just test that the compiler can handle the syntax
21
21
-
// Actual pure function integration requires runtime support
22
22
-
sourceFile := filepath.Join(tmpDir, "pure_test.lam")
23
23
-
source := `x: x`
22
22
+
// Create a Go file with native function implementations
23
23
+
nativeGoFile := filepath.Join(tmpDir, "natives.go")
24
24
+
nativeGoCode := `package main
25
25
+
26
26
+
import "github.com/vic/godnet/pkg/deltanet"
27
27
+
28
28
+
func registerNatives(net *deltanet.Network) {
29
29
+
// String concatenation
30
30
+
net.RegisterNative("str_concat", func(a interface{}) (interface{}, error) {
31
31
+
aStr, ok := a.(string)
32
32
+
if !ok {
33
33
+
return nil, nil // Return nil for non-string
34
34
+
}
35
35
+
return func(b interface{}) (interface{}, error) {
36
36
+
bStr, ok := b.(string)
37
37
+
if !ok {
38
38
+
return nil, nil
39
39
+
}
40
40
+
return aStr + bStr, nil
41
41
+
}, nil
42
42
+
})
24
43
44
44
+
// String length
45
45
+
net.RegisterNative("str_len", func(a interface{}) (interface{}, error) {
46
46
+
aStr, ok := a.(string)
47
47
+
if !ok {
48
48
+
return 0, nil
49
49
+
}
50
50
+
return len(aStr), nil
51
51
+
})
52
52
+
}
53
53
+
`
54
54
+
if err := os.WriteFile(nativeGoFile, []byte(nativeGoCode), 0644); err != nil {
55
55
+
t.Fatalf("Failed to write natives.go: %v", err)
56
56
+
}
57
57
+
58
58
+
// Create lambda source that references these natives
59
59
+
sourceFile := filepath.Join(tmpDir, "test.lam")
60
60
+
source := `x: x` // Simple test - actual native invocation syntax TBD
61
61
+
25
62
if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil {
26
63
t.Fatalf("Failed to write source file: %v", err)
27
64
}
28
65
29
29
-
outputFile := filepath.Join(tmpDir, "pure_test")
66
66
+
// Compile with the native Go file included (must be in same directory)
67
67
+
outputFile := filepath.Join(tmpDir, "test_with_natives")
30
68
c := Compiler{
31
69
SourceFile: sourceFile,
32
70
OutputName: outputFile,
71
71
+
GoFlags: []string{nativeGoFile}, // Will be copied to output dir
33
72
KeepTemp: false,
34
73
}
35
74
36
75
builtFile, err := c.Compile()
37
76
if err != nil {
38
38
-
t.Fatalf("Compilation failed: %v", err)
77
77
+
t.Fatalf("Compilation with natives failed: %v", err)
39
78
}
40
79
41
80
if _, err := os.Stat(builtFile); os.IsNotExist(err) {
42
81
t.Fatalf("Output binary not found: %s", builtFile)
43
82
}
44
83
45
45
-
// Run the binary
84
84
+
// Verify binary runs
46
85
cmd := exec.Command(builtFile)
47
86
output, err := cmd.Output()
48
87
if err != nil {
···
58
97
}
59
98
}
60
99
61
61
-
// Test that would use effect handlers (placeholder for when runtime support is added)
100
100
+
// Test compiling with additional Go files that provide effect handlers
62
101
func TestCompileWithEffectHandlers(t *testing.T) {
63
63
-
t.Skip("Effect handlers in compiled code require runtime integration - placeholder test")
64
64
-
65
65
-
tmpDir, err := os.MkdirTemp("", "godnet_effects_test_*")
102
102
+
cwd, _ := os.Getwd()
103
103
+
projectRoot := filepath.Join(cwd, "../..")
104
104
+
tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
66
105
if err != nil {
67
106
t.Fatalf("Failed to create temp dir: %v", err)
68
107
}
69
108
defer os.RemoveAll(tmpDir)
70
109
71
71
-
// Future test: compile code that performs effects
72
72
-
// Example pseudo-code (syntax TBD):
73
73
-
// let result = effect Print "hello" in result
110
110
+
// Create a Go file with effect handler implementations
111
111
+
handlersGoFile := filepath.Join(tmpDir, "handlers.go")
112
112
+
handlersGoCode := `package main
113
113
+
114
114
+
import (
115
115
+
"fmt"
116
116
+
"github.com/vic/godnet/pkg/deltanet"
117
117
+
)
118
118
+
119
119
+
func installHandlers(net *deltanet.Network) *deltanet.HandlerScope {
120
120
+
scope := deltanet.NewHandlerScope()
74
121
75
75
-
sourceFile := filepath.Join(tmpDir, "effects_test.lam")
76
76
-
source := `x: x`
122
122
+
// Print effect handler
123
123
+
scope.Register("Print", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) {
124
124
+
fmt.Println(eff.Payload)
125
125
+
return cont.Resume(nil)
126
126
+
})
77
127
128
128
+
// FileRead effect handler (mock)
129
129
+
scope.Register("FileRead", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) {
130
130
+
path, ok := eff.Payload.(string)
131
131
+
if !ok {
132
132
+
return cont.Resume("invalid path")
133
133
+
}
134
134
+
// Mock file read
135
135
+
content := fmt.Sprintf("contents of %s", path)
136
136
+
return cont.Resume(content)
137
137
+
})
138
138
+
139
139
+
return scope
140
140
+
}
141
141
+
`
142
142
+
if err := os.WriteFile(handlersGoFile, []byte(handlersGoCode), 0644); err != nil {
143
143
+
t.Fatalf("Failed to write handlers.go: %v", err)
144
144
+
}
145
145
+
146
146
+
// Create lambda source
147
147
+
sourceFile := filepath.Join(tmpDir, "test.lam")
148
148
+
source := `x: x` // Simple test - actual effect syntax TBD
149
149
+
78
150
if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil {
79
151
t.Fatalf("Failed to write source file: %v", err)
80
152
}
81
153
82
82
-
outputFile := filepath.Join(tmpDir, "effects_test")
154
154
+
// Compile with the handlers Go file included
155
155
+
outputFile := filepath.Join(tmpDir, "test_with_handlers")
83
156
c := Compiler{
84
157
SourceFile: sourceFile,
85
158
OutputName: outputFile,
159
159
+
GoFlags: []string{handlersGoFile}, // Link with handlers.go
86
160
KeepTemp: false,
87
161
}
88
162
89
89
-
_, err = c.Compile()
163
163
+
builtFile, err := c.Compile()
90
164
if err != nil {
91
91
-
t.Fatalf("Compilation failed: %v", err)
165
165
+
t.Fatalf("Compilation with handlers failed: %v", err)
166
166
+
}
167
167
+
168
168
+
if _, err := os.Stat(builtFile); os.IsNotExist(err) {
169
169
+
t.Fatalf("Output binary not found: %s", builtFile)
170
170
+
}
171
171
+
172
172
+
// Verify binary runs
173
173
+
cmd := exec.Command(builtFile)
174
174
+
output, err := cmd.Output()
175
175
+
if err != nil {
176
176
+
if exitErr, ok := err.(*exec.ExitError); ok {
177
177
+
t.Fatalf("Binary execution failed: %v\nStderr: %s", err, exitErr.Stderr)
178
178
+
}
179
179
+
t.Fatalf("Binary execution failed: %v", err)
180
180
+
}
181
181
+
182
182
+
result := strings.TrimSpace(string(output))
183
183
+
if !strings.Contains(result, "x0: x0") {
184
184
+
t.Errorf("Expected identity function, got: %s", result)
92
185
}
93
186
}
94
187
95
95
-
// Test compiling code that would use both pure functions and effects
96
96
-
func TestCompileMixedPureAndEffects(t *testing.T) {
97
97
-
t.Skip("Mixed pure/effect code requires full runtime integration - placeholder test")
98
98
-
99
99
-
tmpDir, err := os.MkdirTemp("", "godnet_mixed_test_*")
188
188
+
// Test compiling with both native functions and effect handlers
189
189
+
func TestCompileMixedNativesAndHandlers(t *testing.T) {
190
190
+
cwd, _ := os.Getwd()
191
191
+
projectRoot := filepath.Join(cwd, "../..")
192
192
+
tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
100
193
if err != nil {
101
194
t.Fatalf("Failed to create temp dir: %v", err)
102
195
}
103
196
defer os.RemoveAll(tmpDir)
104
197
105
105
-
// Future test: compile code that uses both pure functions and effects
106
106
-
// Example: let concat = pure "string_concat"; x = concat "hello" " world" in effect Print x
198
198
+
// Create Go file with both natives and handlers
199
199
+
runtimeGoFile := filepath.Join(tmpDir, "runtime.go")
200
200
+
runtimeGoCode := `package main
201
201
+
202
202
+
import (
203
203
+
"fmt"
204
204
+
"github.com/vic/godnet/pkg/deltanet"
205
205
+
)
206
206
+
207
207
+
func setupRuntime(net *deltanet.Network) *deltanet.HandlerScope {
208
208
+
// Register native functions
209
209
+
net.RegisterNative("add", func(a interface{}) (interface{}, error) {
210
210
+
aInt, ok := a.(int)
211
211
+
if !ok {
212
212
+
return nil, fmt.Errorf("add: expected int")
213
213
+
}
214
214
+
return func(b interface{}) (interface{}, error) {
215
215
+
bInt, ok := b.(int)
216
216
+
if !ok {
217
217
+
return nil, fmt.Errorf("add: expected int")
218
218
+
}
219
219
+
return aInt + bInt, nil
220
220
+
}, nil
221
221
+
})
107
222
108
108
-
sourceFile := filepath.Join(tmpDir, "mixed_test.lam")
109
109
-
source := `x: x`
223
223
+
// Register effect handlers
224
224
+
scope := deltanet.NewHandlerScope()
225
225
+
scope.Register("Log", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) {
226
226
+
fmt.Printf("[LOG] %v\n", eff.Payload)
227
227
+
return cont.Resume(nil)
228
228
+
})
110
229
230
230
+
return scope
231
231
+
}
232
232
+
`
233
233
+
if err := os.WriteFile(runtimeGoFile, []byte(runtimeGoCode), 0644); err != nil {
234
234
+
t.Fatalf("Failed to write runtime.go: %v", err)
235
235
+
}
236
236
+
237
237
+
// Create lambda source
238
238
+
sourceFile := filepath.Join(tmpDir, "test.lam")
239
239
+
source := `x: x` // Simple test - actual native/effect syntax TBD
240
240
+
111
241
if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil {
112
242
t.Fatalf("Failed to write source file: %v", err)
113
243
}
114
244
115
115
-
outputFile := filepath.Join(tmpDir, "mixed_test")
245
245
+
// Compile with runtime file
246
246
+
outputFile := filepath.Join(tmpDir, "test_mixed")
116
247
c := Compiler{
117
248
SourceFile: sourceFile,
118
249
OutputName: outputFile,
250
250
+
GoFlags: []string{runtimeGoFile}, // Link with runtime.go
119
251
KeepTemp: false,
120
252
}
121
253
122
122
-
_, err = c.Compile()
254
254
+
builtFile, err := c.Compile()
123
255
if err != nil {
124
256
t.Fatalf("Compilation failed: %v", err)
125
257
}
258
258
+
259
259
+
if _, err := os.Stat(builtFile); os.IsNotExist(err) {
260
260
+
t.Fatalf("Output binary not found: %s", builtFile)
261
261
+
}
262
262
+
263
263
+
// Verify binary runs
264
264
+
cmd := exec.Command(builtFile)
265
265
+
output, err := cmd.Output()
266
266
+
if err != nil {
267
267
+
if exitErr, ok := err.(*exec.ExitError); ok {
268
268
+
t.Fatalf("Binary execution failed: %v\nStderr: %s", err, exitErr.Stderr)
269
269
+
}
270
270
+
t.Fatalf("Binary execution failed: %v", err)
271
271
+
}
272
272
+
273
273
+
result := strings.TrimSpace(string(output))
274
274
+
if !strings.Contains(result, "x0: x0") {
275
275
+
t.Errorf("Expected identity function, got: %s", result)
276
276
+
}
126
277
}
127
278
128
128
-
// Test that compiled code can register and use native pure functions
129
129
-
func TestCompiledNativeRegistration(t *testing.T) {
130
130
-
t.Skip("Native function registration in compiled code needs design - placeholder test")
131
131
-
132
132
-
// Future test design:
133
133
-
// 1. Extend Nix syntax to reference native functions (maybe `@native("func_name")`)
134
134
-
// 2. CodeGenerator emits net.RegisterNative calls in generated code
135
135
-
// 3. Generated code includes native function implementations or imports
136
136
-
// 4. Test that reduction properly invokes the native functions
137
137
-
138
138
-
// Example generated code structure:
139
139
-
// func buildNet(net *deltanet.Network) {
140
140
-
// // Register natives
141
141
-
// net.RegisterNative("string_concat", func(a interface{}) (interface{}, error) {
142
142
-
// return func(b interface{}) (interface{}, error) {
143
143
-
// return a.(string) + b.(string), nil
144
144
-
// }, nil
145
145
-
// })
146
146
-
//
147
147
-
// // Build term that uses the native
148
148
-
// pure := net.NewPure("string_concat")
149
149
-
// data1 := net.NewData("hello")
150
150
-
// // ... etc
151
151
-
// }
279
279
+
// Test multiple Go files can be linked together
280
280
+
func TestCompileWithMultipleRuntimeFiles(t *testing.T) {
281
281
+
cwd, _ := os.Getwd()
282
282
+
projectRoot := filepath.Join(cwd, "../..")
283
283
+
tmpDir, err := os.MkdirTemp(projectRoot, "test_build_*")
284
284
+
if err != nil {
285
285
+
t.Fatalf("Failed to create temp dir: %v", err)
286
286
+
}
287
287
+
defer os.RemoveAll(tmpDir)
288
288
+
289
289
+
// Create separate files for natives and handlers
290
290
+
nativesFile := filepath.Join(tmpDir, "natives.go")
291
291
+
nativesCode := `package main
292
292
+
293
293
+
import "github.com/vic/godnet/pkg/deltanet"
294
294
+
295
295
+
func setupNatives(net *deltanet.Network) {
296
296
+
net.RegisterNative("mul", func(a interface{}) (interface{}, error) {
297
297
+
aInt := a.(int)
298
298
+
return func(b interface{}) (interface{}, error) {
299
299
+
bInt := b.(int)
300
300
+
return aInt * bInt, nil
301
301
+
}, nil
302
302
+
})
303
303
+
}
304
304
+
`
305
305
+
if err := os.WriteFile(nativesFile, []byte(nativesCode), 0644); err != nil {
306
306
+
t.Fatalf("Failed to write natives: %v", err)
307
307
+
}
308
308
+
309
309
+
handlersFile := filepath.Join(tmpDir, "handlers.go")
310
310
+
handlersCode := `package main
311
311
+
312
312
+
import (
313
313
+
"fmt"
314
314
+
"github.com/vic/godnet/pkg/deltanet"
315
315
+
)
316
316
+
317
317
+
func setupHandlers() *deltanet.HandlerScope {
318
318
+
scope := deltanet.NewHandlerScope()
319
319
+
scope.Register("Debug", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) {
320
320
+
fmt.Printf("[DEBUG] %v\n", eff.Payload)
321
321
+
return cont.Resume(nil)
322
322
+
})
323
323
+
return scope
152
324
}
325
325
+
`
326
326
+
if err := os.WriteFile(handlersFile, []byte(handlersCode), 0644); err != nil {
327
327
+
t.Fatalf("Failed to write handlers: %v", err)
328
328
+
}
153
329
154
154
-
// Test that compiled code can install and use effect handlers
155
155
-
func TestCompiledEffectHandlers(t *testing.T) {
156
156
-
t.Skip("Effect handlers in compiled code need design - placeholder test")
157
157
-
158
158
-
// Future test design:
159
159
-
// 1. Extend Nix syntax for effects (maybe `perform Effect payload` and `handle ... with ...`)
160
160
-
// 2. CodeGenerator emits handler registration and effect nodes
161
161
-
// 3. Generated code includes handler implementations
162
162
-
// 4. Test that reduction properly invokes handlers with continuations
163
163
-
164
164
-
// Example generated code structure:
165
165
-
// func buildNet(net *deltanet.Network) {
166
166
-
// // Create handler scope
167
167
-
// scope := deltanet.NewHandlerScope()
168
168
-
// scope.Register("Print", func(eff deltanet.Effect, cont *deltanet.Continuation) (interface{}, error) {
169
169
-
// fmt.Println(eff.Payload)
170
170
-
// return cont.Resume(nil)
171
171
-
// })
172
172
-
//
173
173
-
// // Build term with effects
174
174
-
// handler := net.NewHandler(scope)
175
175
-
// effect := net.NewEffect(deltanet.Effect{Name: "Print", Payload: "hello"})
176
176
-
// // ... etc
177
177
-
// }
330
330
+
// Create lambda source
331
331
+
sourceFile := filepath.Join(tmpDir, "test.lam")
332
332
+
source := `x: x`
333
333
+
334
334
+
if err := os.WriteFile(sourceFile, []byte(source), 0644); err != nil {
335
335
+
t.Fatalf("Failed to write source: %v", err)
336
336
+
}
337
337
+
338
338
+
// Compile with multiple runtime files
339
339
+
outputFile := filepath.Join(tmpDir, "test_multi")
340
340
+
c := Compiler{
341
341
+
SourceFile: sourceFile,
342
342
+
OutputName: outputFile,
343
343
+
GoFlags: []string{nativesFile, handlersFile},
344
344
+
KeepTemp: false,
345
345
+
}
346
346
+
347
347
+
builtFile, err := c.Compile()
348
348
+
if err != nil {
349
349
+
t.Fatalf("Compilation with multiple files failed: %v", err)
350
350
+
}
351
351
+
352
352
+
if _, err := os.Stat(builtFile); os.IsNotExist(err) {
353
353
+
t.Fatalf("Output binary not found: %s", builtFile)
354
354
+
}
355
355
+
356
356
+
// Verify binary runs
357
357
+
cmd := exec.Command(builtFile)
358
358
+
if output, err := cmd.Output(); err != nil {
359
359
+
if exitErr, ok := err.(*exec.ExitError); ok {
360
360
+
t.Fatalf("Binary failed: %v\nStderr: %s", err, exitErr.Stderr)
361
361
+
}
362
362
+
t.Fatalf("Binary failed: %v", err)
363
363
+
} else {
364
364
+
result := strings.TrimSpace(string(output))
365
365
+
if !strings.Contains(result, "x0: x0") {
366
366
+
t.Errorf("Expected identity, got: %s", result)
367
367
+
}
368
368
+
}
178
369
}