Introduce reducer.Reducer interface and update the engine to use abstract expr.Expression and reducer.Reducer types, enabling pluggable reduction strategies. - Add pkg/reducer/reducer.go with Reducer interface. - Add pkg/lambda/reducer.go with NormalOrderReducer implementation. - Update engine to accept expr.Expression and reducer.Reducer. - Update plugins to use expr.Expression directly (no pointer dereference). - Update main and tests to pass reducer to engine. Closes #30
99 lines
2.8 KiB
Go
99 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.maximhutz.com/max/lambda/internal/config"
|
|
"git.maximhutz.com/max/lambda/internal/engine"
|
|
"git.maximhutz.com/max/lambda/pkg/convert"
|
|
"git.maximhutz.com/max/lambda/pkg/lambda"
|
|
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// Helper function to run a single sample through the lambda interpreter.
|
|
func runSample(samplePath string) (string, error) {
|
|
// Read the sample file.
|
|
input, err := os.ReadFile(samplePath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Parse code into syntax tree.
|
|
ast, err := saccharine.Parse(string(input))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Compile expression to lambda calculus.
|
|
compiled := convert.SaccharineToLambda(ast)
|
|
|
|
// Create minimal config for benchmarking.
|
|
cfg := &config.Config{
|
|
Source: config.StringSource{Data: ""},
|
|
Destination: config.StdoutDestination{},
|
|
Profile: "",
|
|
Explanation: false,
|
|
Statistics: false,
|
|
Verbose: false,
|
|
}
|
|
|
|
// Create and run the engine with normal order reducer.
|
|
reducer := lambda.NewNormalOrderReducer()
|
|
process := engine.New(cfg, compiled, reducer)
|
|
process.Run()
|
|
|
|
return process.Expression.String() + "\n", nil
|
|
}
|
|
|
|
// Test that all samples produce expected output.
|
|
func TestSamplesValidity(t *testing.T) {
|
|
// Discover all .test files in the tests directory.
|
|
testFiles, err := filepath.Glob("../../tests/*.test")
|
|
assert.NoError(t, err, "Failed to read tests directory.")
|
|
assert.NotEmpty(t, testFiles, "No '*.test' files found in directory.")
|
|
|
|
for _, testPath := range testFiles {
|
|
// Build expected file path.
|
|
expectedPath := strings.TrimSuffix(testPath, filepath.Ext(testPath)) + ".expected"
|
|
|
|
name := strings.TrimSuffix(filepath.Base(testPath), filepath.Ext(testPath))
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
// Run the sample and capture output.
|
|
actual, err := runSample(testPath)
|
|
assert.NoError(t, err, "Failed to run sample.")
|
|
|
|
// Read expected output.
|
|
expectedBytes, err := os.ReadFile(expectedPath)
|
|
assert.NoError(t, err, "Failed to read expected output.")
|
|
expected := string(expectedBytes)
|
|
|
|
// Compare outputs.
|
|
assert.Equal(t, expected, actual, "Output does not match expected.")
|
|
})
|
|
}
|
|
}
|
|
|
|
// Benchmark all samples using sub-benchmarks.
|
|
func BenchmarkSamples(b *testing.B) {
|
|
// Discover all .test files in the tests directory.
|
|
testFiles, err := filepath.Glob("../../tests/*.test")
|
|
assert.NoError(b, err, "Failed to read tests directory.")
|
|
assert.NotEmpty(b, testFiles, "No '*.test' files found in directory.")
|
|
|
|
for _, path := range testFiles {
|
|
name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
|
|
|
|
b.Run(name, func(b *testing.B) {
|
|
for b.Loop() {
|
|
_, err := runSample(path)
|
|
assert.NoError(b, err, "Failed to run sample.")
|
|
}
|
|
})
|
|
}
|
|
}
|