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:
77
pkg/debruijn/expression.go
Normal file
77
pkg/debruijn/expression.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package debruijn
|
||||
|
||||
type Expression interface {
|
||||
Accept(Visitor)
|
||||
}
|
||||
|
||||
/** ------------------------------------------------------------------------- */
|
||||
|
||||
type Abstraction struct {
|
||||
body Expression
|
||||
}
|
||||
|
||||
func (a *Abstraction) Body() Expression {
|
||||
return a.body
|
||||
}
|
||||
|
||||
func (a *Abstraction) Accept(v Visitor) {
|
||||
v.VisitAbstraction(a)
|
||||
}
|
||||
|
||||
func NewAbstraction(body Expression) *Abstraction {
|
||||
return &Abstraction{body: body}
|
||||
}
|
||||
|
||||
/** ------------------------------------------------------------------------- */
|
||||
|
||||
type Application struct {
|
||||
abstraction Expression
|
||||
argument Expression
|
||||
}
|
||||
|
||||
func (a *Application) Abstraction() Expression {
|
||||
return a.abstraction
|
||||
}
|
||||
|
||||
func (a *Application) Argument() Expression {
|
||||
return a.argument
|
||||
}
|
||||
|
||||
func (a *Application) Accept(v Visitor) {
|
||||
v.VisitApplication(a)
|
||||
}
|
||||
|
||||
func NewApplication(abstraction Expression, argument Expression) *Application {
|
||||
return &Application{abstraction: abstraction, argument: argument}
|
||||
}
|
||||
|
||||
/** ------------------------------------------------------------------------- */
|
||||
|
||||
type Variable struct {
|
||||
index int
|
||||
label string
|
||||
}
|
||||
|
||||
func (v *Variable) Index() int {
|
||||
return v.index
|
||||
}
|
||||
|
||||
func (v *Variable) Label() string {
|
||||
return v.label
|
||||
}
|
||||
|
||||
func (v *Variable) Accept(visitor Visitor) {
|
||||
visitor.VisitVariable(v)
|
||||
}
|
||||
|
||||
func NewVariable(index int, label string) *Variable {
|
||||
return &Variable{index: index, label: label}
|
||||
}
|
||||
|
||||
/** ------------------------------------------------------------------------- */
|
||||
|
||||
type Visitor interface {
|
||||
VisitAbstraction(*Abstraction)
|
||||
VisitApplication(*Application)
|
||||
VisitVariable(*Variable)
|
||||
}
|
||||
68
pkg/debruijn/iterator.go
Normal file
68
pkg/debruijn/iterator.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package debruijn
|
||||
|
||||
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{}
|
||||
}
|
||||
}
|
||||
30
pkg/debruijn/reduce.go
Normal file
30
pkg/debruijn/reduce.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package debruijn
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func ReduceAll(e *Expression, step func()) {
|
||||
it := NewIterator(e)
|
||||
|
||||
for !it.Done() {
|
||||
if fn, arg, ok := IsViable(it.Current()); !ok {
|
||||
it.Next()
|
||||
} else {
|
||||
it.Swap(Substitute(fn.body, arg))
|
||||
step()
|
||||
|
||||
if _, _, ok := IsViable(it.Parent()); ok {
|
||||
it.Back()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
pkg/debruijn/stringify.go
Normal file
38
pkg/debruijn/stringify.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package debruijn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type stringifyVisitor struct {
|
||||
builder strings.Builder
|
||||
}
|
||||
|
||||
func (v *stringifyVisitor) VisitVariable(a *Variable) {
|
||||
if a.label != "" {
|
||||
v.builder.WriteString(a.label)
|
||||
} else {
|
||||
v.builder.WriteString(fmt.Sprintf("%d", 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(')')
|
||||
}
|
||||
|
||||
func Stringify(e Expression) string {
|
||||
b := &stringifyVisitor{builder: strings.Builder{}}
|
||||
e.Accept(b)
|
||||
return b.builder.String()
|
||||
}
|
||||
68
pkg/debruijn/substitute.go
Normal file
68
pkg/debruijn/substitute.go
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user