feat: add De Bruijn index reduction engine
Closes #26 - Added -i flag to select interpreter (lambda or debruijn) - Created debruijn package with Expression interface - Variable contains index and optional label - Abstraction contains only body (no parameter) - Application structure remains similar - Implemented De Bruijn reduction without variable renaming - Shift operation handles index adjustments - Substitute replaces by index instead of name - Abstracted Engine into interface with two implementations - LambdaEngine: original named variable engine - DeBruijnEngine: new index-based engine - Added conversion functions between representations - LambdaToDeBruijn: converts named to indexed - DeBruijnToLambda: converts indexed back to named - SaccharineToDeBruijn: direct saccharine to De Bruijn - Updated main to switch engines based on -i flag - All test samples pass with both engines Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
63
pkg/convert/debruijn_to_lambda.go
Normal file
63
pkg/convert/debruijn_to_lambda.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.maximhutz.com/max/lambda/pkg/debruijn"
|
||||
"git.maximhutz.com/max/lambda/pkg/lambda"
|
||||
)
|
||||
|
||||
// DeBruijnToLambda converts a De Bruijn expression back to named lambda calculus.
|
||||
func DeBruijnToLambda(expr debruijn.Expression) lambda.Expression {
|
||||
return deBruijnToLambda(expr, []string{})
|
||||
}
|
||||
|
||||
func deBruijnToLambda(expr debruijn.Expression, context []string) lambda.Expression {
|
||||
switch e := expr.(type) {
|
||||
case *debruijn.Variable:
|
||||
if e.Index() >= 0 && e.Index() < len(context) {
|
||||
return lambda.NewVariable(context[e.Index()])
|
||||
}
|
||||
if e.Label() != "" {
|
||||
return lambda.NewVariable(e.Label())
|
||||
}
|
||||
return lambda.NewVariable(fmt.Sprintf("free_%d", e.Index()))
|
||||
|
||||
case *debruijn.Abstraction:
|
||||
paramName := generateParamName(context)
|
||||
newContext := append([]string{paramName}, context...)
|
||||
body := deBruijnToLambda(e.Body(), newContext)
|
||||
return lambda.NewAbstraction(paramName, body)
|
||||
|
||||
case *debruijn.Application:
|
||||
abs := deBruijnToLambda(e.Abstraction(), context)
|
||||
arg := deBruijnToLambda(e.Argument(), context)
|
||||
return lambda.NewApplication(abs, arg)
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// generateParamName generates a fresh parameter name that doesn't conflict with context.
|
||||
func generateParamName(context []string) string {
|
||||
base := 'a'
|
||||
for i := 0; ; i++ {
|
||||
name := string(rune(base + rune(i%26)))
|
||||
if i >= 26 {
|
||||
name = fmt.Sprintf("%s%d", name, i/26)
|
||||
}
|
||||
|
||||
conflict := false
|
||||
for _, existing := range context {
|
||||
if existing == name {
|
||||
conflict = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !conflict {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user