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:
2026-01-12 21:27:40 -05:00
parent 335ce95c50
commit f3b9137d75
16 changed files with 679 additions and 63 deletions

View File

@@ -0,0 +1,68 @@
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
}
}