Compare commits
5 Commits
feat/debru
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f2c8d9f7d2 | |||
| 9c7fb8ceba | |||
| e85cf7ceff | |||
| c2aa77cb92 | |||
| 52d40adcc6 |
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: "Bug Report"
|
name: "Bug Report"
|
||||||
about: "Report a bug or unexpected behavior in the lambda interpreter."
|
about: "Report a bug or unexpected behavior in the lambda runtime."
|
||||||
title: "fix: "
|
title: "fix: "
|
||||||
ref: "main"
|
ref: "main"
|
||||||
assignees: []
|
assignees: []
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: "Feature Request"
|
name: "Feature Request"
|
||||||
about: "Suggest a new feature or enhancement for the lambda interpreter."
|
about: "Suggest a new feature or enhancement for the lambda runtime."
|
||||||
title: "feat: "
|
title: "feat: "
|
||||||
ref: "main"
|
ref: "main"
|
||||||
assignees: []
|
assignees: []
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -48,7 +48,7 @@ The "source code" for a work means the preferred form of the work for making mod
|
|||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
|
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
|
||||||
|
|
||||||
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
|
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code runtime used to run it.
|
||||||
|
|
||||||
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those
|
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those
|
||||||
subprograms and other parts of the work.
|
subprograms and other parts of the work.
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -8,7 +8,7 @@ TEST=simple
|
|||||||
help:
|
help:
|
||||||
echo "Available targets:"
|
echo "Available targets:"
|
||||||
echo " build - Build the lambda executable"
|
echo " build - Build the lambda executable"
|
||||||
echo " run - Build and run the lambda interpreter (use TEST=<name> to specify sample)"
|
echo " run - Build and run the lambda runtime (use TEST=<name> to specify sample)"
|
||||||
echo " profile - Build and run with CPU profiling enabled"
|
echo " profile - Build and run with CPU profiling enabled"
|
||||||
echo " explain - Build and run with explanation mode and profiling"
|
echo " explain - Build and run with explanation mode and profiling"
|
||||||
echo " graph - Generate and open CPU profile visualization"
|
echo " graph - Generate and open CPU profile visualization"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# lambda
|
# lambda
|
||||||
|
|
||||||
Making a lambda calculus interpreter in Go.
|
Making a lambda calculus runtime in Go.
|
||||||
|
|
||||||
## Things to talk about
|
## Things to talk about
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import (
|
|||||||
"git.maximhutz.com/max/lambda/internal/config"
|
"git.maximhutz.com/max/lambda/internal/config"
|
||||||
"git.maximhutz.com/max/lambda/internal/plugins"
|
"git.maximhutz.com/max/lambda/internal/plugins"
|
||||||
"git.maximhutz.com/max/lambda/pkg/convert"
|
"git.maximhutz.com/max/lambda/pkg/convert"
|
||||||
"git.maximhutz.com/max/lambda/pkg/debruijn"
|
"git.maximhutz.com/max/lambda/pkg/normalorder"
|
||||||
"git.maximhutz.com/max/lambda/pkg/lambda"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,51 +33,35 @@ func main() {
|
|||||||
compiled := convert.SaccharineToLambda(ast)
|
compiled := convert.SaccharineToLambda(ast)
|
||||||
logger.Info("compiled λ expression", "tree", compiled.String())
|
logger.Info("compiled λ expression", "tree", compiled.String())
|
||||||
|
|
||||||
// Create reducer based on the selected interpreter.
|
// Create reducer with the compiled expression.
|
||||||
var red reducer.Reducer
|
runtime := normalorder.NewRuntime(compiled)
|
||||||
switch options.Interpreter {
|
|
||||||
case config.DeBruijnInterpreter:
|
|
||||||
dbExpr := convert.LambdaToDeBruijn(compiled)
|
|
||||||
logger.Info("converted to De Bruijn", "tree", dbExpr.String())
|
|
||||||
red = debruijn.NewNormalOrderReducer(&dbExpr)
|
|
||||||
default:
|
|
||||||
red = lambda.NewNormalOrderReducer(&compiled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user selected to track CPU performance, attach a profiler.
|
// If the user selected to track CPU performance, attach a profiler.
|
||||||
if options.Profile != "" {
|
if options.Profile != "" {
|
||||||
plugins.NewPerformance(options.Profile, red)
|
plugins.NewPerformance(options.Profile, runtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user selected to produce a step-by-step explanation, attach an
|
// If the user selected to produce a step-by-step explanation, attach an
|
||||||
// observer.
|
// observer.
|
||||||
if options.Explanation {
|
if options.Explanation {
|
||||||
plugins.NewExplanation(red)
|
plugins.NewExplanation(runtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user opted to track statistics, attach a tracker.
|
// If the user opted to track statistics, attach a tracker.
|
||||||
if options.Statistics {
|
if options.Statistics {
|
||||||
plugins.NewStatistics(red)
|
plugins.NewStatistics(runtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user selected for verbose debug logs, attach a reduction tracker.
|
// If the user selected for verbose debug logs, attach a reduction tracker.
|
||||||
if options.Verbose {
|
if options.Verbose {
|
||||||
plugins.NewLogs(logger, red)
|
plugins.NewLogs(logger, runtime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run reduction.
|
// Run reduction.
|
||||||
red.Reduce()
|
runtime.Run()
|
||||||
|
|
||||||
// Return the final reduced result.
|
// Return the final reduced result.
|
||||||
// For De Bruijn, convert back to lambda for consistent output.
|
result := runtime.Expression().String()
|
||||||
var result string
|
|
||||||
if options.Interpreter == config.DeBruijnInterpreter {
|
|
||||||
dbExpr := red.Expression().(debruijn.Expression)
|
|
||||||
lambdaExpr := convert.DeBruijnToLambda(dbExpr)
|
|
||||||
result = lambdaExpr.String()
|
|
||||||
} else {
|
|
||||||
result = red.Expression().String()
|
|
||||||
}
|
|
||||||
err = options.Destination.Write(result)
|
err = options.Destination.Write(result)
|
||||||
cli.HandleError(err)
|
cli.HandleError(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/convert"
|
"git.maximhutz.com/max/lambda/pkg/convert"
|
||||||
"git.maximhutz.com/max/lambda/pkg/debruijn"
|
"git.maximhutz.com/max/lambda/pkg/normalorder"
|
||||||
"git.maximhutz.com/max/lambda/pkg/lambda"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper function to run a single sample through the lambda interpreter.
|
// Helper function to run a single sample through the lambda runtime.
|
||||||
func runSample(samplePath string) (string, error) {
|
func runSample(samplePath string) (string, error) {
|
||||||
// Read the sample file.
|
// Read the sample file.
|
||||||
input, err := os.ReadFile(samplePath)
|
input, err := os.ReadFile(samplePath)
|
||||||
@@ -31,42 +30,13 @@ func runSample(samplePath string) (string, error) {
|
|||||||
compiled := convert.SaccharineToLambda(ast)
|
compiled := convert.SaccharineToLambda(ast)
|
||||||
|
|
||||||
// Create and run the reducer.
|
// Create and run the reducer.
|
||||||
reducer := lambda.NewNormalOrderReducer(&compiled)
|
reducer := normalorder.NewRuntime(compiled)
|
||||||
reducer.Reduce()
|
reducer.Run()
|
||||||
|
|
||||||
return reducer.Expression().String() + "\n", nil
|
return reducer.Expression().String() + "\n", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to run a single sample through the De Bruijn interpreter.
|
// Test that all samples produce expected output.
|
||||||
func runSampleDeBruijn(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)
|
|
||||||
|
|
||||||
// Convert to De Bruijn and run reducer.
|
|
||||||
dbExpr := convert.LambdaToDeBruijn(compiled)
|
|
||||||
reducer := debruijn.NewNormalOrderReducer(&dbExpr)
|
|
||||||
reducer.Reduce()
|
|
||||||
|
|
||||||
// Convert back to lambda for output.
|
|
||||||
result := reducer.Expression().(debruijn.Expression)
|
|
||||||
lambdaResult := convert.DeBruijnToLambda(result)
|
|
||||||
|
|
||||||
return lambdaResult.String() + "\n", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that all samples produce expected output with lambda interpreter.
|
|
||||||
func TestSamplesValidity(t *testing.T) {
|
func TestSamplesValidity(t *testing.T) {
|
||||||
// Discover all .test files in the tests directory.
|
// Discover all .test files in the tests directory.
|
||||||
testFiles, err := filepath.Glob("../../tests/*.test")
|
testFiles, err := filepath.Glob("../../tests/*.test")
|
||||||
@@ -95,35 +65,6 @@ func TestSamplesValidity(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test that all samples produce expected output with De Bruijn interpreter.
|
|
||||||
func TestSamplesValidityDeBruijn(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 := runSampleDeBruijn(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.
|
// Benchmark all samples using sub-benchmarks.
|
||||||
func BenchmarkSamples(b *testing.B) {
|
func BenchmarkSamples(b *testing.B) {
|
||||||
// Discover all .test files in the tests directory.
|
// Discover all .test files in the tests directory.
|
||||||
@@ -142,22 +83,3 @@ func BenchmarkSamples(b *testing.B) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmark all samples using De Bruijn interpreter.
|
|
||||||
func BenchmarkSamplesDeBruijn(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 := runSampleDeBruijn(path)
|
|
||||||
assert.NoError(b, err, "Failed to run sample.")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
// Package "config" parses ad handles the user settings given to the program.
|
// Package "config" parses ad handles the user settings given to the program.
|
||||||
package config
|
package config
|
||||||
|
|
||||||
// Interpreter specifies the reduction engine to use.
|
|
||||||
type Interpreter string
|
|
||||||
|
|
||||||
const (
|
|
||||||
LambdaInterpreter Interpreter = "lambda"
|
|
||||||
DeBruijnInterpreter Interpreter = "debruijn"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Configuration settings for the program.
|
// Configuration settings for the program.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Source Source // The source code given to the program.
|
Source Source // The source code given to the program.
|
||||||
@@ -17,5 +9,4 @@ type Config struct {
|
|||||||
Explanation bool // Whether or not to print an explanation of the reduction.
|
Explanation bool // Whether or not to print an explanation of the reduction.
|
||||||
Profile string // If not nil, print a CPU profile during execution.
|
Profile string // If not nil, print a CPU profile during execution.
|
||||||
Statistics bool // Whether or not to print statistics.
|
Statistics bool // Whether or not to print statistics.
|
||||||
Interpreter Interpreter // The interpreter engine to use.
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,20 +14,8 @@ func FromArgs() (*Config, error) {
|
|||||||
profile := flag.String("p", "", "CPU profiling. If an output file is defined, the program will profile its execution and dump its results into it.")
|
profile := flag.String("p", "", "CPU profiling. If an output file is defined, the program will profile its execution and dump its results into it.")
|
||||||
file := flag.String("f", "", "File. If set, read source from the specified file.")
|
file := flag.String("f", "", "File. If set, read source from the specified file.")
|
||||||
output := flag.String("o", "", "Output. If set, write result to the specified file. Use '-' for stdout (default).")
|
output := flag.String("o", "", "Output. If set, write result to the specified file. Use '-' for stdout (default).")
|
||||||
interpreter := flag.String("i", "lambda", "Interpreter. The reduction engine to use: 'lambda' or 'debruijn'.")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Validate interpreter flag.
|
|
||||||
var interpType Interpreter
|
|
||||||
switch *interpreter {
|
|
||||||
case "lambda":
|
|
||||||
interpType = LambdaInterpreter
|
|
||||||
case "debruijn":
|
|
||||||
interpType = DeBruijnInterpreter
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid interpreter: %s (must be 'lambda' or 'debruijn')", *interpreter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse source type.
|
// Parse source type.
|
||||||
var source Source
|
var source Source
|
||||||
if *file != "" {
|
if *file != "" {
|
||||||
@@ -64,6 +52,5 @@ func FromArgs() (*Config, error) {
|
|||||||
Explanation: *explanation,
|
Explanation: *explanation,
|
||||||
Profile: *profile,
|
Profile: *profile,
|
||||||
Statistics: *statistics,
|
Statistics: *statistics,
|
||||||
Interpreter: interpType,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,17 @@ package plugins
|
|||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
"git.maximhutz.com/max/lambda/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Logs struct {
|
type Logs struct {
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
reducer reducer.Reducer
|
reducer runtime.Runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogs(logger *slog.Logger, r reducer.Reducer) *Logs {
|
func NewLogs(logger *slog.Logger, r runtime.Runtime) *Logs {
|
||||||
plugin := &Logs{logger, r}
|
plugin := &Logs{logger, r}
|
||||||
r.On(reducer.StepEvent, plugin.Step)
|
r.On(runtime.StepEvent, plugin.Step)
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ package plugins
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
"git.maximhutz.com/max/lambda/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Track the reductions made by a reduction process.
|
// Track the reductions made by a reduction process.
|
||||||
type Explanation struct {
|
type Explanation struct {
|
||||||
reducer reducer.Reducer
|
reducer runtime.Runtime
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attaches a new explanation tracker to a reducer.
|
// Attaches a new explanation tracker to a reducer.
|
||||||
func NewExplanation(r reducer.Reducer) *Explanation {
|
func NewExplanation(r runtime.Runtime) *Explanation {
|
||||||
plugin := &Explanation{reducer: r}
|
plugin := &Explanation{reducer: r}
|
||||||
r.On(reducer.StartEvent, plugin.Start)
|
r.On(runtime.StartEvent, plugin.Start)
|
||||||
r.On(reducer.StepEvent, plugin.Step)
|
r.On(runtime.StepEvent, plugin.Step)
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
"git.maximhutz.com/max/lambda/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Observes a reduction process, and publishes a CPU performance profile on
|
// Observes a reduction process, and publishes a CPU performance profile on
|
||||||
@@ -19,10 +19,10 @@ type Performance struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a performance tracker that outputs a profile to "file".
|
// Create a performance tracker that outputs a profile to "file".
|
||||||
func NewPerformance(file string, process reducer.Reducer) *Performance {
|
func NewPerformance(file string, process runtime.Runtime) *Performance {
|
||||||
plugin := &Performance{File: file}
|
plugin := &Performance{File: file}
|
||||||
process.On(reducer.StartEvent, plugin.Start)
|
process.On(runtime.StartEvent, plugin.Start)
|
||||||
process.On(reducer.StopEvent, plugin.Stop)
|
process.On(runtime.StopEvent, plugin.Stop)
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/internal/statistics"
|
"git.maximhutz.com/max/lambda/internal/statistics"
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
"git.maximhutz.com/max/lambda/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// An observer, to track reduction performance.
|
// An observer, to track reduction performance.
|
||||||
@@ -16,11 +16,11 @@ type Statistics struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new reduction performance Statistics.
|
// Create a new reduction performance Statistics.
|
||||||
func NewStatistics(r reducer.Reducer) *Statistics {
|
func NewStatistics(r runtime.Runtime) *Statistics {
|
||||||
plugin := &Statistics{}
|
plugin := &Statistics{}
|
||||||
r.On(reducer.StartEvent, plugin.Start)
|
r.On(runtime.StartEvent, plugin.Start)
|
||||||
r.On(reducer.StepEvent, plugin.Step)
|
r.On(runtime.StepEvent, plugin.Step)
|
||||||
r.On(reducer.StopEvent, plugin.Stop)
|
r.On(runtime.StopEvent, plugin.Stop)
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
package convert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/debruijn"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/lambda"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/set"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeBruijnToLambda converts a De Bruijn indexed expression back to named lambda calculus.
|
|
||||||
func DeBruijnToLambda(expr debruijn.Expression) lambda.Expression {
|
|
||||||
return deBruijnToLambdaWithContext(expr, []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func deBruijnToLambdaWithContext(expr debruijn.Expression, context []string) lambda.Expression {
|
|
||||||
switch e := expr.(type) {
|
|
||||||
case *debruijn.Variable:
|
|
||||||
index := e.Index()
|
|
||||||
if index < len(context) {
|
|
||||||
// Bound variable: look up name in context.
|
|
||||||
name := context[len(context)-1-index]
|
|
||||||
return lambda.NewVariable(name)
|
|
||||||
}
|
|
||||||
// Free variable: use the label if available.
|
|
||||||
if e.Label() != "" {
|
|
||||||
return lambda.NewVariable(e.Label())
|
|
||||||
}
|
|
||||||
// Generate a name for free variables without labels.
|
|
||||||
return lambda.NewVariable(fmt.Sprintf("free%d", index))
|
|
||||||
|
|
||||||
case *debruijn.Abstraction:
|
|
||||||
// Generate a fresh parameter name.
|
|
||||||
used := collectUsedNames(e.Body(), context)
|
|
||||||
paramName := generateFreshName(used)
|
|
||||||
newContext := append(context, paramName)
|
|
||||||
body := deBruijnToLambdaWithContext(e.Body(), newContext)
|
|
||||||
return lambda.NewAbstraction(paramName, body)
|
|
||||||
|
|
||||||
case *debruijn.Application:
|
|
||||||
abs := deBruijnToLambdaWithContext(e.Abstraction(), context)
|
|
||||||
arg := deBruijnToLambdaWithContext(e.Argument(), context)
|
|
||||||
return lambda.NewApplication(abs, arg)
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic("unknown expression type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// collectUsedNames gathers all variable labels used in an expression.
|
|
||||||
func collectUsedNames(expr debruijn.Expression, context []string) *set.Set[string] {
|
|
||||||
used := set.New[string]()
|
|
||||||
for _, name := range context {
|
|
||||||
used.Add(name)
|
|
||||||
}
|
|
||||||
collectUsedNamesHelper(expr, used)
|
|
||||||
return used
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectUsedNamesHelper(expr debruijn.Expression, used *set.Set[string]) {
|
|
||||||
switch e := expr.(type) {
|
|
||||||
case *debruijn.Variable:
|
|
||||||
if e.Label() != "" {
|
|
||||||
used.Add(e.Label())
|
|
||||||
}
|
|
||||||
case *debruijn.Abstraction:
|
|
||||||
collectUsedNamesHelper(e.Body(), used)
|
|
||||||
case *debruijn.Application:
|
|
||||||
collectUsedNamesHelper(e.Abstraction(), used)
|
|
||||||
collectUsedNamesHelper(e.Argument(), used)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateFreshName creates a fresh variable name not in the used set.
|
|
||||||
func generateFreshName(used *set.Set[string]) string {
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
name := fmt.Sprintf("_%d", i)
|
|
||||||
if !used.Has(name) {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package convert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/debruijn"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/lambda"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LambdaToDeBruijn converts a lambda calculus expression to De Bruijn indexed form.
|
|
||||||
// The context parameter tracks bound variables from outer abstractions.
|
|
||||||
func LambdaToDeBruijn(expr lambda.Expression) debruijn.Expression {
|
|
||||||
return lambdaToDeBruijnWithContext(expr, []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func lambdaToDeBruijnWithContext(expr lambda.Expression, context []string) debruijn.Expression {
|
|
||||||
switch e := expr.(type) {
|
|
||||||
case *lambda.Variable:
|
|
||||||
name := e.Value()
|
|
||||||
// Search for the variable in the context (innermost to outermost).
|
|
||||||
for i := len(context) - 1; i >= 0; i-- {
|
|
||||||
if context[i] == name {
|
|
||||||
index := len(context) - 1 - i
|
|
||||||
return debruijn.NewVariable(index, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Free variable: use a negative index to mark it.
|
|
||||||
// We encode free variables with index = len(context) + position.
|
|
||||||
// For simplicity, we use a large index that won't conflict.
|
|
||||||
return debruijn.NewVariable(len(context), name)
|
|
||||||
|
|
||||||
case *lambda.Abstraction:
|
|
||||||
// Add the parameter to the context.
|
|
||||||
newContext := append(context, e.Parameter())
|
|
||||||
body := lambdaToDeBruijnWithContext(e.Body(), newContext)
|
|
||||||
return debruijn.NewAbstraction(body)
|
|
||||||
|
|
||||||
case *lambda.Application:
|
|
||||||
abs := lambdaToDeBruijnWithContext(e.Abstraction(), context)
|
|
||||||
arg := lambdaToDeBruijnWithContext(e.Argument(), context)
|
|
||||||
return debruijn.NewApplication(abs, arg)
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic("unknown expression type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,7 @@ func convertAbstraction(n *saccharine.Abstraction) lambda.Expression {
|
|||||||
// If the function has no parameters, it is a thunk. Lambda calculus still
|
// If the function has no parameters, it is a thunk. Lambda calculus still
|
||||||
// requires _some_ parameter exists, so generate one.
|
// requires _some_ parameter exists, so generate one.
|
||||||
if len(parameters) == 0 {
|
if len(parameters) == 0 {
|
||||||
freeVars := lambda.GetFreeVariables(result)
|
freeVars := result.GetFree()
|
||||||
freshName := lambda.GenerateFreshName(freeVars)
|
freshName := lambda.GenerateFreshName(freeVars)
|
||||||
parameters = append(parameters, freshName)
|
parameters = append(parameters, freshName)
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ func reduceLet(s *saccharine.LetStatement, e lambda.Expression) lambda.Expressio
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reduceDeclare(s *saccharine.DeclareStatement, e lambda.Expression) lambda.Expression {
|
func reduceDeclare(s *saccharine.DeclareStatement, e lambda.Expression) lambda.Expression {
|
||||||
freshVar := lambda.GenerateFreshName(lambda.GetFreeVariables(e))
|
freshVar := lambda.GenerateFreshName(e.GetFree())
|
||||||
|
|
||||||
return lambda.NewApplication(
|
return lambda.NewApplication(
|
||||||
lambda.NewAbstraction(freshVar, e),
|
lambda.NewAbstraction(freshVar, e),
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
// Package debruijn provides De Bruijn indexed lambda calculus expressions.
|
|
||||||
// De Bruijn indices eliminate the need for variable names by using numeric
|
|
||||||
// indices to refer to bound variables, avoiding capture issues during substitution.
|
|
||||||
package debruijn
|
|
||||||
|
|
||||||
import "git.maximhutz.com/max/lambda/pkg/expr"
|
|
||||||
|
|
||||||
// Expression is the interface for all De Bruijn indexed expression types.
|
|
||||||
// It embeds the general expr.Expression interface for cross-mode compatibility.
|
|
||||||
type Expression interface {
|
|
||||||
expr.Expression
|
|
||||||
Accept(Visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
// Abstraction represents a lambda abstraction without a named parameter.
|
|
||||||
// In De Bruijn notation, the parameter is implicit and referenced by index 0
|
|
||||||
// within the body.
|
|
||||||
type Abstraction struct {
|
|
||||||
body Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
// Body returns the body of the abstraction.
|
|
||||||
func (a *Abstraction) Body() Expression {
|
|
||||||
return a.body
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept implements the Visitor pattern.
|
|
||||||
func (a *Abstraction) Accept(v Visitor) {
|
|
||||||
v.VisitAbstraction(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the De Bruijn notation string representation.
|
|
||||||
func (a *Abstraction) String() string {
|
|
||||||
return Stringify(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAbstraction creates a new De Bruijn abstraction with the given body.
|
|
||||||
func NewAbstraction(body Expression) *Abstraction {
|
|
||||||
return &Abstraction{body: body}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
// Application represents the application of one expression to another.
|
|
||||||
type Application struct {
|
|
||||||
abstraction Expression
|
|
||||||
argument Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
// Abstraction returns the function expression being applied.
|
|
||||||
func (a *Application) Abstraction() Expression {
|
|
||||||
return a.abstraction
|
|
||||||
}
|
|
||||||
|
|
||||||
// Argument returns the argument expression.
|
|
||||||
func (a *Application) Argument() Expression {
|
|
||||||
return a.argument
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept implements the Visitor pattern.
|
|
||||||
func (a *Application) Accept(v Visitor) {
|
|
||||||
v.VisitApplication(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the De Bruijn notation string representation.
|
|
||||||
func (a *Application) String() string {
|
|
||||||
return Stringify(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApplication creates a new application expression.
|
|
||||||
func NewApplication(abstraction Expression, argument Expression) *Application {
|
|
||||||
return &Application{abstraction: abstraction, argument: argument}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
// Variable represents a De Bruijn indexed variable.
|
|
||||||
// The index indicates how many binders to skip to find the binding abstraction.
|
|
||||||
// The label is an optional hint for display purposes.
|
|
||||||
type Variable struct {
|
|
||||||
index int
|
|
||||||
label string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index returns the De Bruijn index.
|
|
||||||
func (v *Variable) Index() int {
|
|
||||||
return v.index
|
|
||||||
}
|
|
||||||
|
|
||||||
// Label returns the optional variable label.
|
|
||||||
func (v *Variable) Label() string {
|
|
||||||
return v.label
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept implements the Visitor pattern.
|
|
||||||
func (v *Variable) Accept(visitor Visitor) {
|
|
||||||
visitor.VisitVariable(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the De Bruijn notation string representation.
|
|
||||||
func (v *Variable) String() string {
|
|
||||||
return Stringify(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewVariable creates a new De Bruijn variable with the given index and label.
|
|
||||||
func NewVariable(index int, label string) *Variable {
|
|
||||||
return &Variable{index: index, label: label}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
// Visitor interface for traversing De Bruijn expressions.
|
|
||||||
type Visitor interface {
|
|
||||||
VisitAbstraction(*Abstraction)
|
|
||||||
VisitApplication(*Application)
|
|
||||||
VisitVariable(*Variable)
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package debruijn
|
|
||||||
|
|
||||||
// Iterator provides depth-first traversal of De Bruijn expressions.
|
|
||||||
type Iterator struct {
|
|
||||||
trace []*Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIterator creates a new iterator starting at the given expression.
|
|
||||||
func NewIterator(expr *Expression) *Iterator {
|
|
||||||
return &Iterator{[]*Expression{expr}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done returns true when the iterator has finished traversal.
|
|
||||||
func (i *Iterator) Done() bool {
|
|
||||||
return len(i.trace) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current returns a pointer to the current expression.
|
|
||||||
func (i *Iterator) Current() *Expression {
|
|
||||||
if i.Done() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.trace[len(i.trace)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parent returns a pointer to the parent expression.
|
|
||||||
func (i *Iterator) Parent() *Expression {
|
|
||||||
if len(i.trace) < 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.trace[len(i.trace)-2]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap replaces the current expression with the given expression.
|
|
||||||
func (i *Iterator) Swap(with Expression) {
|
|
||||||
current := i.Current()
|
|
||||||
if current != nil {
|
|
||||||
*current = with
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Back moves the iterator back to the parent expression.
|
|
||||||
func (i *Iterator) Back() bool {
|
|
||||||
if i.Done() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
i.trace = i.trace[:len(i.trace)-1]
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next advances the iterator to the next expression in leftmost-outermost order.
|
|
||||||
func (i *Iterator) Next() {
|
|
||||||
switch typed := (*i.Current()).(type) {
|
|
||||||
case *Abstraction:
|
|
||||||
i.trace = append(i.trace, &typed.body)
|
|
||||||
case *Application:
|
|
||||||
i.trace = append(i.trace, &typed.abstraction)
|
|
||||||
case *Variable:
|
|
||||||
for len(i.trace) > 1 {
|
|
||||||
if app, ok := (*i.Parent()).(*Application); ok {
|
|
||||||
if app.abstraction == *i.Current() {
|
|
||||||
i.Back()
|
|
||||||
i.trace = append(i.trace, &app.argument)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i.Back()
|
|
||||||
}
|
|
||||||
|
|
||||||
i.trace = []*Expression{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package debruijn
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/emitter"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/expr"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NormalOrderReducer implements normal order (leftmost-outermost) reduction
|
|
||||||
// for De Bruijn indexed lambda calculus expressions.
|
|
||||||
type NormalOrderReducer struct {
|
|
||||||
emitter.BaseEmitter[reducer.Event]
|
|
||||||
expression *Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNormalOrderReducer creates a new normal order reducer.
|
|
||||||
func NewNormalOrderReducer(expression *Expression) *NormalOrderReducer {
|
|
||||||
return &NormalOrderReducer{
|
|
||||||
BaseEmitter: *emitter.New[reducer.Event](),
|
|
||||||
expression: expression,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expression returns the current expression state.
|
|
||||||
func (r *NormalOrderReducer) Expression() expr.Expression {
|
|
||||||
return *r.expression
|
|
||||||
}
|
|
||||||
|
|
||||||
// isViable checks if an expression is a redex (reducible expression).
|
|
||||||
// A redex is an application of an abstraction to an argument.
|
|
||||||
func isViable(e *Expression) (*Abstraction, Expression, bool) {
|
|
||||||
if e == nil {
|
|
||||||
return nil, nil, false
|
|
||||||
} else if app, appOk := (*e).(*Application); !appOk {
|
|
||||||
return nil, nil, false
|
|
||||||
} else if fn, fnOk := app.abstraction.(*Abstraction); !fnOk {
|
|
||||||
return nil, nil, false
|
|
||||||
} else {
|
|
||||||
return fn, app.argument, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reduce performs normal order reduction on a De Bruijn expression.
|
|
||||||
func (r *NormalOrderReducer) Reduce() {
|
|
||||||
r.Emit(reducer.StartEvent)
|
|
||||||
it := NewIterator(r.expression)
|
|
||||||
|
|
||||||
for !it.Done() {
|
|
||||||
if fn, arg, ok := isViable(it.Current()); !ok {
|
|
||||||
it.Next()
|
|
||||||
} else {
|
|
||||||
// Substitute arg for variable 0 in the body.
|
|
||||||
substituted := Substitute(fn.body, 0, Shift(arg, 1, 0))
|
|
||||||
// Shift down to account for the removed abstraction.
|
|
||||||
it.Swap(Shift(substituted, -1, 0))
|
|
||||||
|
|
||||||
r.Emit(reducer.StepEvent)
|
|
||||||
|
|
||||||
if _, _, ok := isViable(it.Parent()); ok {
|
|
||||||
it.Back()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Emit(reducer.StopEvent)
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package debruijn
|
|
||||||
|
|
||||||
// Shift increments all free variable indices in an expression by the given amount.
|
|
||||||
// A variable is free if its index is >= the cutoff (depth of nested abstractions).
|
|
||||||
// This is necessary when substituting an expression into a different binding context.
|
|
||||||
func Shift(expr Expression, amount int, cutoff int) Expression {
|
|
||||||
switch e := expr.(type) {
|
|
||||||
case *Variable:
|
|
||||||
if e.index >= cutoff {
|
|
||||||
return NewVariable(e.index+amount, e.label)
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
|
|
||||||
case *Abstraction:
|
|
||||||
newBody := Shift(e.body, amount, cutoff+1)
|
|
||||||
if newBody == e.body {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
return NewAbstraction(newBody)
|
|
||||||
|
|
||||||
case *Application:
|
|
||||||
newAbs := Shift(e.abstraction, amount, cutoff)
|
|
||||||
newArg := Shift(e.argument, amount, cutoff)
|
|
||||||
if newAbs == e.abstraction && newArg == e.argument {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
return NewApplication(newAbs, newArg)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package debruijn
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stringifyVisitor struct {
|
|
||||||
builder strings.Builder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *stringifyVisitor) VisitVariable(a *Variable) {
|
|
||||||
v.builder.WriteString(strconv.Itoa(a.index))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *stringifyVisitor) VisitAbstraction(f *Abstraction) {
|
|
||||||
v.builder.WriteRune('\\')
|
|
||||||
v.builder.WriteRune('.')
|
|
||||||
f.body.Accept(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *stringifyVisitor) VisitApplication(c *Application) {
|
|
||||||
v.builder.WriteRune('(')
|
|
||||||
c.abstraction.Accept(v)
|
|
||||||
v.builder.WriteRune(' ')
|
|
||||||
c.argument.Accept(v)
|
|
||||||
v.builder.WriteRune(')')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stringify converts a De Bruijn expression to its string representation.
|
|
||||||
func Stringify(e Expression) string {
|
|
||||||
b := &stringifyVisitor{builder: strings.Builder{}}
|
|
||||||
e.Accept(b)
|
|
||||||
return b.builder.String()
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package debruijn
|
|
||||||
|
|
||||||
// Substitute replaces the variable at the given index with the replacement expression.
|
|
||||||
// The replacement is shifted appropriately as we descend into nested abstractions.
|
|
||||||
func Substitute(expr Expression, index int, replacement Expression) Expression {
|
|
||||||
switch e := expr.(type) {
|
|
||||||
case *Variable:
|
|
||||||
if e.index == index {
|
|
||||||
return replacement
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
|
|
||||||
case *Abstraction:
|
|
||||||
// When entering an abstraction, increment the target index and shift the
|
|
||||||
// replacement to account for the new binding context.
|
|
||||||
shiftedReplacement := Shift(replacement, 1, 0)
|
|
||||||
newBody := Substitute(e.body, index+1, shiftedReplacement)
|
|
||||||
if newBody == e.body {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
return NewAbstraction(newBody)
|
|
||||||
|
|
||||||
case *Application:
|
|
||||||
newAbs := Substitute(e.abstraction, index, replacement)
|
|
||||||
newArg := Substitute(e.argument, index, replacement)
|
|
||||||
if newAbs == e.abstraction && newArg == e.argument {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
return NewApplication(newAbs, newArg)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// Package "deltanet" is a reduction strategy using ∆-nets.
|
|
||||||
package deltanet
|
|
||||||
|
|
||||||
type Graph struct {
|
|
||||||
Nodes []Node
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package deltanet
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
// A connection between exactly two nodes in a graph.
|
|
||||||
type Edge struct {
|
|
||||||
A, B Node
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns all nodes the edge is connected to.
|
|
||||||
func (e Edge) GetConnections() []Node { return []Node{e.A, e.B} }
|
|
||||||
|
|
||||||
// Determines if a node is connected via this edge.
|
|
||||||
func (e Edge) IsConnected(n Node) bool { return e.A == n || e.B == n }
|
|
||||||
|
|
||||||
// Swaps an edges connected with one node, for another.
|
|
||||||
func (e *Edge) Swap(from Node, to Node) {
|
|
||||||
if e.A == from {
|
|
||||||
e.A = to
|
|
||||||
}
|
|
||||||
if e.B == from {
|
|
||||||
e.B = to
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the edge is connected to each node via their pricniple ports.
|
|
||||||
func (e Edge) IsPrincipleEdge() bool {
|
|
||||||
return e.A.GetMainPort() == e && e.B.GetMainPort() == e
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
type Node interface {
|
|
||||||
// Returns the principle port that the node is attached to.
|
|
||||||
GetMainPort() Edge
|
|
||||||
|
|
||||||
// Returns all auxiliary ports that the node has. These ports are guaranteed
|
|
||||||
// to be ordered clockwise, as they would appear graphically.
|
|
||||||
GetAuxPorts() []Edge
|
|
||||||
|
|
||||||
// Returns the label of the node. May be blank.
|
|
||||||
GetLabel() string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
type EraserNode struct {
|
|
||||||
Main Edge
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n EraserNode) GetLabel() string { return "Ⓧ" }
|
|
||||||
func (n EraserNode) GetMainPort() Edge { return n.Main }
|
|
||||||
func (n EraserNode) GetAuxPorts() []Edge { return []Edge{} }
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
type ReplicatorNode struct {
|
|
||||||
Main Edge
|
|
||||||
Level uint
|
|
||||||
Aux []Edge
|
|
||||||
Deltas []int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n ReplicatorNode) GetLabel() string { return "" }
|
|
||||||
func (n ReplicatorNode) GetMainPort() Edge { return n.Main }
|
|
||||||
func (n ReplicatorNode) GetAuxPorts() []Edge { return n.Aux }
|
|
||||||
|
|
||||||
// Returns the level of the replicator node.
|
|
||||||
func (n ReplicatorNode) GetLevel() uint { return n.Level }
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
type FanNode struct {
|
|
||||||
Label string
|
|
||||||
Main Edge
|
|
||||||
Left, Right Edge
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n FanNode) GetLabel() string { return n.Label }
|
|
||||||
func (n FanNode) GetMainPort() Edge { return n.Main }
|
|
||||||
func (n FanNode) GetAuxPorts() []Edge { return []Edge{n.Left, n.Right} }
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
type TerminalNode struct {
|
|
||||||
Label string
|
|
||||||
Main Edge
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n TerminalNode) GetLabel() string { return n.Label }
|
|
||||||
func (n TerminalNode) GetMainPort() Edge { return n.Main }
|
|
||||||
func (n TerminalNode) GetAuxPorts() []Edge { return []Edge{} }
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
@@ -9,7 +9,7 @@ type Emitter[E comparable] interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BaseEmitter[E comparable] struct {
|
type BaseEmitter[E comparable] struct {
|
||||||
listeners map[E]*set.Set[Listener[E]]
|
listeners map[E]set.Set[Listener[E]]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *BaseEmitter[E]) On(kind E, fn func()) Listener[E] {
|
func (e *BaseEmitter[E]) On(kind E, fn func()) Listener[E] {
|
||||||
@@ -41,6 +41,6 @@ func (e *BaseEmitter[E]) Emit(event E) {
|
|||||||
|
|
||||||
func New[E comparable]() *BaseEmitter[E] {
|
func New[E comparable]() *BaseEmitter[E] {
|
||||||
return &BaseEmitter[E]{
|
return &BaseEmitter[E]{
|
||||||
listeners: map[E]*set.Set[Listener[E]]{},
|
listeners: map[E]set.Set[Listener[E]]{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
// Package expr provides the abstract Expression interface for all evaluatable
|
// Package expr provides the abstract Expression interface for all evaluatable
|
||||||
// expression types in the lambda interpreter.
|
// expression types in the lambda runtime.
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
// Expression is the base interface for all evaluatable expression types.
|
// Expression is the base interface for all evaluatable expression types.
|
||||||
// Different evaluation modes (lambda calculus, SKI combinators, typed lambda
|
// Different evaluation modes (lambda calculus, SKI combinators, typed lambda
|
||||||
// calculus, etc.) implement this interface with their own concrete types.
|
// calculus, etc.) implement this interface with their own concrete types.
|
||||||
type Expression interface {
|
type Expression interface {
|
||||||
// String returns a human-readable representation of the expression.
|
// The expression should have a human-readable representation.
|
||||||
String() string
|
fmt.Stringer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
package lambda
|
package lambda
|
||||||
|
|
||||||
import "git.maximhutz.com/max/lambda/pkg/expr"
|
import (
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/expr"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/set"
|
||||||
|
)
|
||||||
|
|
||||||
// Expression is the interface for all lambda calculus expression types.
|
// Expression is the interface for all lambda calculus expression types.
|
||||||
// It embeds the general expr.Expression interface for cross-mode compatibility.
|
// It embeds the general expr.Expression interface for cross-mode compatibility.
|
||||||
type Expression interface {
|
type Expression interface {
|
||||||
expr.Expression
|
expr.Expression
|
||||||
Accept(Visitor)
|
|
||||||
|
// Substitute replaces all free occurrences of the target variable with the
|
||||||
|
// replacement expression. Alpha-renaming is performed automatically to
|
||||||
|
// avoid variable capture.
|
||||||
|
Substitute(target string, replacement Expression) Expression
|
||||||
|
|
||||||
|
// GetFree returns the set of all free variable names in the expression.
|
||||||
|
// This function does not mutate the input expression.
|
||||||
|
// The returned set is newly allocated and can be modified by the caller.
|
||||||
|
GetFree() set.Set[string]
|
||||||
|
|
||||||
|
// Rename replaces all occurrences of the target variable name with the new name.
|
||||||
|
Rename(target string, newName string) Expression
|
||||||
|
|
||||||
|
// IsFree returns true if the variable name n occurs free in the expression.
|
||||||
|
// This function does not mutate the input expression.
|
||||||
|
IsFree(n string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
/** ------------------------------------------------------------------------- */
|
||||||
@@ -16,24 +35,22 @@ type Abstraction struct {
|
|||||||
body Expression
|
body Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Abstraction) Parameter() string {
|
var _ Expression = Abstraction{}
|
||||||
|
|
||||||
|
func (a Abstraction) Parameter() string {
|
||||||
return a.parameter
|
return a.parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Abstraction) Body() Expression {
|
func (a Abstraction) Body() Expression {
|
||||||
return a.body
|
return a.body
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Abstraction) Accept(v Visitor) {
|
func (a Abstraction) String() string {
|
||||||
v.VisitAbstraction(a)
|
return "\\" + a.parameter + "." + a.body.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Abstraction) String() string {
|
func NewAbstraction(parameter string, body Expression) Abstraction {
|
||||||
return Stringify(a)
|
return Abstraction{parameter, body}
|
||||||
}
|
|
||||||
|
|
||||||
func NewAbstraction(parameter string, body Expression) *Abstraction {
|
|
||||||
return &Abstraction{parameter: parameter, body: body}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
/** ------------------------------------------------------------------------- */
|
||||||
@@ -43,52 +60,40 @@ type Application struct {
|
|||||||
argument Expression
|
argument Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Application) Abstraction() Expression {
|
var _ Expression = Application{}
|
||||||
|
|
||||||
|
func (a Application) Abstraction() Expression {
|
||||||
return a.abstraction
|
return a.abstraction
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Application) Argument() Expression {
|
func (a Application) Argument() Expression {
|
||||||
return a.argument
|
return a.argument
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Application) Accept(v Visitor) {
|
func (a Application) String() string {
|
||||||
v.VisitApplication(a)
|
return "(" + a.abstraction.String() + " " + a.argument.String() + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Application) String() string {
|
func NewApplication(abstraction Expression, argument Expression) Application {
|
||||||
return Stringify(a)
|
return Application{abstraction, argument}
|
||||||
}
|
|
||||||
|
|
||||||
func NewApplication(abstraction Expression, argument Expression) *Application {
|
|
||||||
return &Application{abstraction: abstraction, argument: argument}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
/** ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
value string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) Value() string {
|
var _ Expression = Variable{}
|
||||||
return v.value
|
|
||||||
|
func (v Variable) Name() string {
|
||||||
|
return v.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) Accept(visitor Visitor) {
|
func (v Variable) String() string {
|
||||||
visitor.VisitVariable(v)
|
return v.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Variable) String() string {
|
func NewVariable(name string) Variable {
|
||||||
return Stringify(v)
|
return Variable{name}
|
||||||
}
|
|
||||||
|
|
||||||
func NewVariable(name string) *Variable {
|
|
||||||
return &Variable{value: name}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
type Visitor interface {
|
|
||||||
VisitAbstraction(*Abstraction)
|
|
||||||
VisitApplication(*Application)
|
|
||||||
VisitVariable(*Variable)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import (
|
|||||||
"git.maximhutz.com/max/lambda/pkg/set"
|
"git.maximhutz.com/max/lambda/pkg/set"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateFreshName(used *set.Set[string]) string {
|
// GenerateFreshName generates a variable name that is not in the used set.
|
||||||
|
// This function does not mutate the used set.
|
||||||
|
func GenerateFreshName(used set.Set[string]) string {
|
||||||
for i := uint64(0); ; i++ {
|
for i := uint64(0); ; i++ {
|
||||||
attempt := "_" + string(strconv.AppendUint(nil, i, 10))
|
attempt := "_" + string(strconv.AppendUint(nil, i, 10))
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,18 @@ package lambda
|
|||||||
|
|
||||||
import "git.maximhutz.com/max/lambda/pkg/set"
|
import "git.maximhutz.com/max/lambda/pkg/set"
|
||||||
|
|
||||||
func GetFreeVariables(e Expression) *set.Set[string] {
|
func (e Variable) GetFree() set.Set[string] {
|
||||||
switch e := e.(type) {
|
return set.New(e.Name())
|
||||||
case *Variable:
|
}
|
||||||
return set.New(e.value)
|
|
||||||
case *Abstraction:
|
func (e Abstraction) GetFree() set.Set[string] {
|
||||||
vars := GetFreeVariables(e.body)
|
vars := e.Body().GetFree()
|
||||||
vars.Remove(e.parameter)
|
vars.Remove(e.Parameter())
|
||||||
return vars
|
return vars
|
||||||
case *Application:
|
}
|
||||||
vars := GetFreeVariables(e.abstraction)
|
|
||||||
vars.Merge(GetFreeVariables(e.argument))
|
func (e Application) GetFree() set.Set[string] {
|
||||||
return vars
|
vars := e.Abstraction().GetFree()
|
||||||
default:
|
vars.Merge(e.Argument().GetFree())
|
||||||
return nil
|
return vars
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
package lambda
|
package lambda
|
||||||
|
|
||||||
func IsFreeVariable(n string, e Expression) bool {
|
func (e Variable) IsFree(n string) bool {
|
||||||
switch e := e.(type) {
|
return e.Name() == n
|
||||||
case *Variable:
|
}
|
||||||
return e.value == n
|
|
||||||
case *Abstraction:
|
func (e Abstraction) IsFree(n string) bool {
|
||||||
return e.parameter != n && IsFreeVariable(n, e.body)
|
return e.Parameter() != n && e.Body().IsFree(n)
|
||||||
case *Application:
|
}
|
||||||
return IsFreeVariable(n, e.abstraction) || IsFreeVariable(n, e.argument)
|
func (e Application) IsFree(n string) bool {
|
||||||
default:
|
return e.Abstraction().IsFree(n) || e.Argument().IsFree(n)
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package lambda
|
|
||||||
|
|
||||||
type Iterator struct {
|
|
||||||
trace []*Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIterator(expr *Expression) *Iterator {
|
|
||||||
return &Iterator{[]*Expression{expr}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Done() bool {
|
|
||||||
return len(i.trace) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Current() *Expression {
|
|
||||||
if i.Done() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.trace[len(i.trace)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Parent() *Expression {
|
|
||||||
if len(i.trace) < 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.trace[len(i.trace)-2]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Swap(with Expression) {
|
|
||||||
current := i.Current()
|
|
||||||
if current != nil {
|
|
||||||
*current = with
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Back() bool {
|
|
||||||
if i.Done() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
i.trace = i.trace[:len(i.trace)-1]
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iterator) Next() {
|
|
||||||
switch typed := (*i.Current()).(type) {
|
|
||||||
case *Abstraction:
|
|
||||||
i.trace = append(i.trace, &typed.body)
|
|
||||||
case *Application:
|
|
||||||
i.trace = append(i.trace, &typed.abstraction)
|
|
||||||
case *Variable:
|
|
||||||
for len(i.trace) > 1 {
|
|
||||||
if app, ok := (*i.Parent()).(*Application); ok {
|
|
||||||
if app.abstraction == *i.Current() {
|
|
||||||
i.Back()
|
|
||||||
i.trace = append(i.trace, &app.argument)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i.Back()
|
|
||||||
}
|
|
||||||
|
|
||||||
i.trace = []*Expression{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package lambda
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/emitter"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/expr"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/reducer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NormalOrderReducer implements normal order (leftmost-outermost) reduction
|
|
||||||
// for lambda calculus expressions.
|
|
||||||
type NormalOrderReducer struct {
|
|
||||||
emitter.BaseEmitter[reducer.Event]
|
|
||||||
expression *Expression
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNormalOrderReducer creates a new normal order reducer.
|
|
||||||
func NewNormalOrderReducer(expression *Expression) *NormalOrderReducer {
|
|
||||||
return &NormalOrderReducer{
|
|
||||||
BaseEmitter: *emitter.New[reducer.Event](),
|
|
||||||
expression: expression,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expression returns the current expression state.
|
|
||||||
func (r *NormalOrderReducer) Expression() expr.Expression {
|
|
||||||
return *r.expression
|
|
||||||
}
|
|
||||||
|
|
||||||
func isViable(e *Expression) (*Abstraction, Expression, bool) {
|
|
||||||
if e == nil {
|
|
||||||
return nil, nil, false
|
|
||||||
} else if app, appOk := (*e).(*Application); !appOk {
|
|
||||||
return nil, nil, false
|
|
||||||
} else if fn, fnOk := app.abstraction.(*Abstraction); !fnOk {
|
|
||||||
return nil, nil, false
|
|
||||||
} else {
|
|
||||||
return fn, app.argument, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reduce performs normal order reduction on a lambda expression.
|
|
||||||
// The expression must be a lambda.Expression; other types are returned unchanged.
|
|
||||||
func (r *NormalOrderReducer) Reduce() {
|
|
||||||
r.Emit(reducer.StartEvent)
|
|
||||||
it := NewIterator(r.expression)
|
|
||||||
|
|
||||||
for !it.Done() {
|
|
||||||
if fn, arg, ok := isViable(it.Current()); !ok {
|
|
||||||
it.Next()
|
|
||||||
} else {
|
|
||||||
it.Swap(Substitute(fn.body, fn.parameter, arg))
|
|
||||||
r.Emit(reducer.StepEvent)
|
|
||||||
|
|
||||||
if _, _, ok := isViable(it.Parent()); ok {
|
|
||||||
it.Back()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Emit(reducer.StopEvent)
|
|
||||||
}
|
|
||||||
@@ -1,38 +1,28 @@
|
|||||||
package lambda
|
package lambda
|
||||||
|
|
||||||
func Rename(expr Expression, target string, newName string) Expression {
|
// Rename replaces all occurrences of the target variable name with the new name.
|
||||||
switch e := expr.(type) {
|
func (e Variable) Rename(target string, newName string) Expression {
|
||||||
case *Variable:
|
if e.Name() == target {
|
||||||
if e.value == target {
|
|
||||||
return NewVariable(newName)
|
return NewVariable(newName)
|
||||||
}
|
}
|
||||||
return e
|
|
||||||
|
|
||||||
case *Abstraction:
|
return e
|
||||||
newParam := e.parameter
|
}
|
||||||
if e.parameter == target {
|
|
||||||
|
func (e Abstraction) Rename(target string, newName string) Expression {
|
||||||
|
newParam := e.Parameter()
|
||||||
|
if e.Parameter() == target {
|
||||||
newParam = newName
|
newParam = newName
|
||||||
}
|
}
|
||||||
|
|
||||||
newBody := Rename(e.body, target, newName)
|
newBody := e.Body().Rename(target, newName)
|
||||||
|
|
||||||
if newParam == e.parameter && newBody == e.body {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewAbstraction(newParam, newBody)
|
return NewAbstraction(newParam, newBody)
|
||||||
|
}
|
||||||
|
|
||||||
case *Application:
|
func (e Application) Rename(target string, newName string) Expression {
|
||||||
newAbs := Rename(e.abstraction, target, newName)
|
newAbs := e.Abstraction().Rename(target, newName)
|
||||||
newArg := Rename(e.argument, target, newName)
|
newArg := e.Argument().Rename(target, newName)
|
||||||
|
|
||||||
if newAbs == e.abstraction && newArg == e.argument {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewApplication(newAbs, newArg)
|
return NewApplication(newAbs, newArg)
|
||||||
|
|
||||||
default:
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package lambda
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
type stringifyVisitor struct {
|
|
||||||
builder strings.Builder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *stringifyVisitor) VisitVariable(a *Variable) {
|
|
||||||
v.builder.WriteString(a.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *stringifyVisitor) VisitAbstraction(f *Abstraction) {
|
|
||||||
v.builder.WriteRune('\\')
|
|
||||||
v.builder.WriteString(f.parameter)
|
|
||||||
v.builder.WriteRune('.')
|
|
||||||
f.body.Accept(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *stringifyVisitor) VisitApplication(c *Application) {
|
|
||||||
v.builder.WriteRune('(')
|
|
||||||
c.abstraction.Accept(v)
|
|
||||||
v.builder.WriteRune(' ')
|
|
||||||
c.argument.Accept(v)
|
|
||||||
v.builder.WriteRune(')')
|
|
||||||
}
|
|
||||||
|
|
||||||
func Stringify(e Expression) string {
|
|
||||||
b := &stringifyVisitor{builder: strings.Builder{}}
|
|
||||||
e.Accept(b)
|
|
||||||
return b.builder.String()
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,35 @@
|
|||||||
package lambda
|
package lambda
|
||||||
|
|
||||||
func Substitute(expr Expression, target string, replacement Expression) Expression {
|
func (e Variable) Substitute(target string, replacement Expression) Expression {
|
||||||
switch e := expr.(type) {
|
if e.Name() == target {
|
||||||
case *Variable:
|
|
||||||
if e.value == target {
|
|
||||||
return replacement
|
return replacement
|
||||||
}
|
}
|
||||||
return e
|
|
||||||
|
|
||||||
case *Abstraction:
|
return e
|
||||||
if e.parameter == target {
|
}
|
||||||
|
|
||||||
|
func (e Abstraction) Substitute(target string, replacement Expression) Expression {
|
||||||
|
if e.Parameter() == target {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
body := e.body
|
body := e.Body()
|
||||||
param := e.parameter
|
param := e.Parameter()
|
||||||
if IsFreeVariable(param, replacement) {
|
if replacement.IsFree(param) {
|
||||||
freeVars := GetFreeVariables(replacement)
|
freeVars := replacement.GetFree()
|
||||||
freeVars.Merge(GetFreeVariables(body))
|
freeVars.Merge(body.GetFree())
|
||||||
freshVar := GenerateFreshName(freeVars)
|
freshVar := GenerateFreshName(freeVars)
|
||||||
body = Rename(body, param, freshVar)
|
body = body.Rename(param, freshVar)
|
||||||
param = freshVar
|
param = freshVar
|
||||||
}
|
}
|
||||||
|
|
||||||
newBody := Substitute(body, target, replacement)
|
newBody := body.Substitute(target, replacement)
|
||||||
if newBody == body && param == e.parameter {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewAbstraction(param, newBody)
|
return NewAbstraction(param, newBody)
|
||||||
|
}
|
||||||
case *Application:
|
|
||||||
newAbs := Substitute(e.abstraction, target, replacement)
|
func (e Application) Substitute(target string, replacement Expression) Expression {
|
||||||
newArg := Substitute(e.argument, target, replacement)
|
abs := e.Abstraction().Substitute(target, replacement)
|
||||||
|
arg := e.Argument().Substitute(target, replacement)
|
||||||
if newAbs == e.abstraction && newArg == e.argument {
|
|
||||||
return e
|
return NewApplication(abs, arg)
|
||||||
}
|
|
||||||
|
|
||||||
return NewApplication(newAbs, newArg)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
34
pkg/normalorder/reduce_once.go
Normal file
34
pkg/normalorder/reduce_once.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package normalorder
|
||||||
|
|
||||||
|
import "git.maximhutz.com/max/lambda/pkg/lambda"
|
||||||
|
|
||||||
|
func ReduceOnce(e lambda.Expression) (lambda.Expression, bool) {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case lambda.Abstraction:
|
||||||
|
body, reduced := ReduceOnce(e.Body())
|
||||||
|
if reduced {
|
||||||
|
return lambda.NewAbstraction(e.Parameter(), body), true
|
||||||
|
}
|
||||||
|
return e, false
|
||||||
|
|
||||||
|
case lambda.Application:
|
||||||
|
if fn, fnOk := e.Abstraction().(lambda.Abstraction); fnOk {
|
||||||
|
return fn.Body().Substitute(fn.Parameter(), e.Argument()), true
|
||||||
|
}
|
||||||
|
|
||||||
|
abs, reduced := ReduceOnce(e.Abstraction())
|
||||||
|
if reduced {
|
||||||
|
return lambda.NewApplication(abs, e.Argument()), true
|
||||||
|
}
|
||||||
|
|
||||||
|
arg, reduced := ReduceOnce(e.Argument())
|
||||||
|
if reduced {
|
||||||
|
return lambda.NewApplication(e.Abstraction(), arg), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, false
|
||||||
|
|
||||||
|
default:
|
||||||
|
return e, false
|
||||||
|
}
|
||||||
|
}
|
||||||
46
pkg/normalorder/runtime.go
Normal file
46
pkg/normalorder/runtime.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package normalorder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/emitter"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/expr"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/lambda"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NormalOrderReducer implements normal order (leftmost-outermost) reduction
|
||||||
|
// for lambda calculus expressions.
|
||||||
|
type Runtime struct {
|
||||||
|
emitter.BaseEmitter[runtime.Event]
|
||||||
|
expression lambda.Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNormalOrderReducer creates a new normal order reducer.
|
||||||
|
func NewRuntime(expression lambda.Expression) *Runtime {
|
||||||
|
return &Runtime{
|
||||||
|
BaseEmitter: *emitter.New[runtime.Event](),
|
||||||
|
expression: expression,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expression returns the current expression state.
|
||||||
|
func (r *Runtime) Expression() expr.Expression {
|
||||||
|
return r.expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runtime) Step() bool {
|
||||||
|
result, done := ReduceOnce(r.expression)
|
||||||
|
r.expression = result
|
||||||
|
return !done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce performs normal order reduction on a lambda expression.
|
||||||
|
// The expression must be a lambda.Expression; other types are returned unchanged.
|
||||||
|
func (r *Runtime) Run() {
|
||||||
|
r.Emit(runtime.StartEvent)
|
||||||
|
|
||||||
|
for !r.Step() {
|
||||||
|
r.Emit(runtime.StepEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Emit(runtime.StopEvent)
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package reducer
|
|
||||||
|
|
||||||
// Event represents lifecycle events during reduction.
|
|
||||||
type Event int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StartEvent is emitted before reduction begins.
|
|
||||||
StartEvent Event = iota
|
|
||||||
// StepEvent is emitted after each reduction step.
|
|
||||||
StepEvent
|
|
||||||
// StopEvent is emitted after reduction completes.
|
|
||||||
StopEvent
|
|
||||||
)
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// Package reducer provides the abstract Reducer interface for all expression
|
|
||||||
// reduction strategies.
|
|
||||||
package reducer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/emitter"
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/expr"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reducer defines the interface for expression reduction strategies.
|
|
||||||
// Different evaluation modes (normal order, applicative order, SKI combinators,
|
|
||||||
// etc.) implement this interface with their own reduction logic.
|
|
||||||
//
|
|
||||||
// Reducers also implement the Emitter interface to allow plugins to observe
|
|
||||||
// reduction lifecycle events (Start, Step, Stop).
|
|
||||||
type Reducer interface {
|
|
||||||
emitter.Emitter[Event]
|
|
||||||
|
|
||||||
// Reduce performs all reduction steps on the expression.
|
|
||||||
// Emits StartEvent before reduction, StepEvent after each step, and
|
|
||||||
// StopEvent after completion.
|
|
||||||
// Returns the final reduced expression.
|
|
||||||
Reduce()
|
|
||||||
|
|
||||||
// Expression returns the current expression state.
|
|
||||||
Expression() expr.Expression
|
|
||||||
}
|
|
||||||
13
pkg/runtime/events.go
Normal file
13
pkg/runtime/events.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
// Event represents lifecycle events during interpretation.
|
||||||
|
type Event int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StartEvent is emitted before interpretation begins.
|
||||||
|
StartEvent Event = iota
|
||||||
|
// StepEvent is emitted after each interpretation step.
|
||||||
|
StepEvent
|
||||||
|
// StopEvent is emitted after interpretation completes.
|
||||||
|
StopEvent
|
||||||
|
)
|
||||||
27
pkg/runtime/runtime.go
Normal file
27
pkg/runtime/runtime.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Package runtime provides the abstract Reducer interface for all expression
|
||||||
|
// reduction strategies.
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/emitter"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/expr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Runtime defines the interface for expression reduction strategies.
|
||||||
|
// Different evaluation modes (normal order, applicative order, SKI combinators,
|
||||||
|
// etc.) implement this interface with their own reduction logic.
|
||||||
|
//
|
||||||
|
// Runtimes also implement the Emitter interface to allow plugins to observe
|
||||||
|
// reduction lifecycle events (Start, Step, Stop).
|
||||||
|
type Runtime interface {
|
||||||
|
emitter.Emitter[Event]
|
||||||
|
|
||||||
|
// Run a single step. Returns whether the runtime is complete or not.
|
||||||
|
Step() bool
|
||||||
|
|
||||||
|
// Run until completion.
|
||||||
|
Run()
|
||||||
|
|
||||||
|
// Copy the state of the runtime.
|
||||||
|
Expression() expr.Expression
|
||||||
|
}
|
||||||
@@ -4,9 +4,9 @@ import "iter"
|
|||||||
|
|
||||||
type Set[T comparable] map[T]bool
|
type Set[T comparable] map[T]bool
|
||||||
|
|
||||||
func (s *Set[T]) Add(items ...T) {
|
func (s Set[T]) Add(items ...T) {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
(*s)[item] = true
|
s[item] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,14 +14,14 @@ func (s Set[T]) Has(item T) bool {
|
|||||||
return s[item]
|
return s[item]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Set[T]) Remove(items ...T) {
|
func (s Set[T]) Remove(items ...T) {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
delete(*s, item)
|
delete(s, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Set[T]) Merge(o *Set[T]) {
|
func (s Set[T]) Merge(o Set[T]) {
|
||||||
for item := range *o {
|
for item := range o {
|
||||||
s.Add(item)
|
s.Add(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,8 +46,8 @@ func (s Set[T]) Items() iter.Seq[T] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func New[T comparable](items ...T) *Set[T] {
|
func New[T comparable](items ...T) Set[T] {
|
||||||
result := &Set[T]{}
|
result := Set[T]{}
|
||||||
|
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
result.Add(item)
|
result.Add(item)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,8 +0,0 @@
|
|||||||
0 := \f.\x.x
|
|
||||||
inc n := \f x.(f (n f x))
|
|
||||||
exp n m := (m n)
|
|
||||||
print n := (n F X)
|
|
||||||
|
|
||||||
N := (inc (inc (inc (inc (inc (inc 0))))))
|
|
||||||
|
|
||||||
(print (exp N N))
|
|
||||||
Reference in New Issue
Block a user