feat: add De Bruijn indexed reduction engine

Add a new interpreter option (-i debruijn) that uses De Bruijn indices
for variable representation, eliminating the need for variable renaming
during substitution.

- Add -i flag to select interpreter (lambda or debruijn)
- Create debruijn package with Expression types (Variable with index,
  Abstraction without parameter, Application)
- Implement shift and substitute operations for De Bruijn indices
- Add conversion functions between lambda and De Bruijn representations
- Update CLI to support switching between interpreters
- Add De Bruijn tests to verify all samples pass

Closes #26
This commit is contained in:
2026-01-16 19:36:05 -05:00
parent 1974ad582f
commit 528956b033
12 changed files with 621 additions and 9 deletions

72
pkg/debruijn/reducer.go Normal file
View File

@@ -0,0 +1,72 @@
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
}
}
// betaReduce performs a single beta reduction step.
// Given (\. body) arg, it substitutes arg for index 0 in body,
// then shifts the result down to account for the removed abstraction.
func betaReduce(fn *Abstraction, arg Expression) Expression {
// 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.
return Shift(substituted, -1, 0)
}
// 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 {
it.Swap(betaReduce(fn, arg))
r.Emit(reducer.StepEvent)
if _, _, ok := isViable(it.Parent()); ok {
it.Back()
}
}
}
r.Emit(reducer.StopEvent)
}