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>
69 lines
1.7 KiB
Go
69 lines
1.7 KiB
Go
package debruijn
|
|
|
|
// Shift increments all free variable indices by delta when crossing depth abstractions.
|
|
func Shift(expr Expression, delta int, depth int) Expression {
|
|
switch e := expr.(type) {
|
|
case *Variable:
|
|
if e.index >= depth {
|
|
return NewVariable(e.index+delta, e.label)
|
|
}
|
|
return e
|
|
|
|
case *Abstraction:
|
|
newBody := Shift(e.body, delta, depth+1)
|
|
if newBody == e.body {
|
|
return e
|
|
}
|
|
return NewAbstraction(newBody)
|
|
|
|
case *Application:
|
|
newAbs := Shift(e.abstraction, delta, depth)
|
|
newArg := Shift(e.argument, delta, depth)
|
|
if newAbs == e.abstraction && newArg == e.argument {
|
|
return e
|
|
}
|
|
return NewApplication(newAbs, newArg)
|
|
|
|
default:
|
|
return expr
|
|
}
|
|
}
|
|
|
|
// Substitute replaces variable at index 0 with replacement in expr.
|
|
// This assumes expr is the body of an abstraction being applied.
|
|
func Substitute(expr Expression, replacement Expression) Expression {
|
|
return substitute(expr, 0, replacement)
|
|
}
|
|
|
|
// substitute replaces variable at targetIndex with replacement, adjusting indices as needed.
|
|
func substitute(expr Expression, targetIndex int, replacement Expression) Expression {
|
|
switch e := expr.(type) {
|
|
case *Variable:
|
|
if e.index == targetIndex {
|
|
return Shift(replacement, targetIndex, 0)
|
|
}
|
|
if e.index > targetIndex {
|
|
return NewVariable(e.index-1, e.label)
|
|
}
|
|
return e
|
|
|
|
case *Abstraction:
|
|
newBody := substitute(e.body, targetIndex+1, replacement)
|
|
if newBody == e.body {
|
|
return e
|
|
}
|
|
return NewAbstraction(newBody)
|
|
|
|
case *Application:
|
|
newAbs := substitute(e.abstraction, targetIndex, replacement)
|
|
newArg := substitute(e.argument, targetIndex, replacement)
|
|
if newAbs == e.abstraction && newArg == e.argument {
|
|
return e
|
|
}
|
|
return NewApplication(newAbs, newArg)
|
|
|
|
default:
|
|
return expr
|
|
}
|
|
}
|