feat: reducer, but doesn`t work

This commit is contained in:
2025-12-25 00:30:15 -05:00
parent 2c3ce9baf7
commit d5999e8e1c
15 changed files with 228 additions and 150 deletions

View File

@@ -4,5 +4,5 @@ it:
@ go build -o ${BINARY_NAME} ./cmd/lambda @ go build -o ${BINARY_NAME} ./cmd/lambda
@ chmod +x ${BINARY_NAME} @ chmod +x ${BINARY_NAME}
ex1: it ex: it
@ ./lambda.exe "(\n.\f.\x.(f ((n f) x)) \f.\x.x)" @ ./lambda.exe -v "(\add.(add (add \f.\x.x)) \n.\f.\x.(f ((n f) x)))"

View File

@@ -28,11 +28,11 @@ func main() {
expression, err := parser.GetTree(tokens) expression, err := parser.GetTree(tokens)
cli.HandleError(err) cli.HandleError(err)
logger.Info("Parsed syntax tree.", "tree", expression) logger.Info("Parsed syntax tree.", "tree", lambda.Stringify(expression))
evaluated := lambda.Evaluate(expression) lambda.Normalize(&expression)
cli.HandleError(err) cli.HandleError(err)
logger.Info("Evaluated expression.", "tree", evaluated) logger.Info("Evaluated expression.", "tree", expression)
fmt.Println(lambda.ToString(evaluated)) fmt.Println(lambda.Stringify(expression))
} }

View File

@@ -1,70 +0,0 @@
package lambda
// func replaceUnbound(name string, replacement Expression, e Expression) Expression {
// switch e := e.(type) {
// case Atom:
// if e.Value == name {
// return replacement
// } else {
// return e
// }
// case Function:
// if e.Parameter == name {
// return e
// } else {
// return Function{
// Parameter: e.Parameter,
// Body: replaceUnbound(name, replacement, e.Body),
// }
// }
// case Call:
// return Call{
// Function: replaceUnbound(name, replacement, e.Function),
// Argument: replaceUnbound(name, replacement, e.Argument),
// }
// }
// }
// func evaluateAtom(a Atom) Expression {
// return a
// }
// func evaluateFunction(f Function) Expression {
// return Function{
// Parameter: f.Parameter,
// Body: Evaluate(f.Body),
// }
// }
// func evaluateCall(c Call) Expression {
// fn := c.Function
// as_fn, as_fn_ok := fn.(Function)
// if !as_fn_ok {
// fn = Evaluate(fn)
// if as_fn, as_fn_ok = fn.(Function); !as_fn_ok {
// return Call{
// Function: fn,
// Argument: Evaluate(c.Argument),
// }
// }
// }
// return replaceUnboundAtoms(as_fn.Parameter, c.Argument, as_fn.Body)
// }
func Evaluate(e Expression) Expression {
// switch e := e.(type) {
// case Atom:
// return e
// case Call:
// case Function:
// return Function{
// Parameter: e.Parameter,
// Body: Evaluate(e.Body),
// }
// }
return e
}

View File

@@ -1,65 +1,57 @@
package lambda package lambda
type Expression interface { type Expression interface {
Accept(ExpressionVisitor) Accept(Visitor)
} }
/** ------------------------------------------------------------------------- */ /** ------------------------------------------------------------------------- */
type Function struct { type Abstraction struct {
Parameter string Parameter string
Body Expression Body Expression
} }
func NewFunction(parameter string, body Expression) *Function { func NewAbstraction(parameter string, body Expression) *Abstraction {
return &Function{ return &Abstraction{Parameter: parameter, Body: body}
Parameter: parameter,
Body: body,
}
} }
func (f *Function) Accept(v ExpressionVisitor) { func (this *Abstraction) Accept(v Visitor) {
v.VisitFunction(f) v.VisitAbstraction(this)
} }
/** ------------------------------------------------------------------------- */ /** ------------------------------------------------------------------------- */
type Call struct { type Application struct {
Function Expression Abstraction Expression
Argument Expression Argument Expression
} }
func NewCall(function Expression, argument Expression) *Call { func NewApplication(function Expression, argument Expression) *Application {
return &Call{ return &Application{Abstraction: function, Argument: argument}
Function: function,
Argument: argument,
}
} }
func (c *Call) Accept(v ExpressionVisitor) { func (this *Application) Accept(v Visitor) {
v.VisitCall(c) v.VisitApplication(this)
} }
/** ------------------------------------------------------------------------- */ /** ------------------------------------------------------------------------- */
type Atom struct { type Variable struct {
Value string Value string
} }
func NewAtom(name string) *Atom { func NewVariable(name string) *Variable {
return &Atom{ return &Variable{Value: name}
Value: name,
}
} }
func (a *Atom) Accept(v ExpressionVisitor) { func (this *Variable) Accept(v Visitor) {
v.VisitAtom(a) v.VisitVariable(this)
} }
/** ------------------------------------------------------------------------- */ /** ------------------------------------------------------------------------- */
type ExpressionVisitor interface { type Visitor interface {
VisitFunction(*Function) VisitAbstraction(*Abstraction)
VisitCall(*Call) VisitApplication(*Application)
VisitAtom(*Atom) VisitVariable(*Variable)
} }

View File

@@ -0,0 +1,19 @@
package lambda
import (
"strconv"
"git.maximhutz.com/max/lambda/pkg/set"
)
func GenerateFreshName(used set.Set[string]) string {
var i uint64 = 0
for {
attempt := "_" + string(strconv.AppendUint(nil, i, 10))
i++
if !used.Has(attempt) {
return attempt
}
}
}

View File

@@ -0,0 +1,20 @@
package lambda
import "git.maximhutz.com/max/lambda/pkg/set"
func GetFreeVariables(e Expression) set.Set[string] {
switch e := e.(type) {
case *Variable:
return set.New(e.Value)
case *Abstraction:
vars := GetFreeVariables(e.Body)
vars.Remove(e.Parameter)
return vars
case *Application:
vars := GetFreeVariables(e.Abstraction)
vars.Union(GetFreeVariables(e.Argument))
return vars
default:
return nil
}
}

View File

@@ -1,36 +0,0 @@
package lambda
import "strings"
type StringifyVisitor struct {
builder strings.Builder
}
func (v *StringifyVisitor) VisitAtom(a *Atom) {
v.builder.WriteString(a.Value)
}
func (v *StringifyVisitor) VisitFunction(f *Function) {
v.builder.WriteRune('\\')
v.builder.WriteString(f.Parameter)
v.builder.WriteRune('.')
f.Body.Accept(v)
}
func (v *StringifyVisitor) VisitCall(c *Call) {
v.builder.WriteRune('(')
c.Function.Accept(v)
v.builder.WriteRune(' ')
c.Argument.Accept(v)
v.builder.WriteRune(')')
}
func ToString(e Expression) string {
b := StringifyVisitor{
builder: strings.Builder{},
}
e.Accept(&b)
return b.builder.String()
}

28
pkg/lambda/reduce.go Normal file
View File

@@ -0,0 +1,28 @@
package lambda
func ReduceOnce(e *Expression) bool {
switch typed := (*e).(type) {
case *Abstraction:
return ReduceOnce(&typed.Body)
case *Application:
fn, fn_ok := typed.Abstraction.(*Abstraction)
if fn_ok {
Substitute(&fn.Body, fn.Parameter, typed.Argument)
*e = fn.Body
return true
}
good := ReduceOnce(&typed.Abstraction)
if good {
return true
}
return ReduceOnce(&typed.Argument)
default:
return false
}
}
func Normalize(e *Expression) {
for ReduceOnce(e) {
}
}

19
pkg/lambda/rename.go Normal file
View File

@@ -0,0 +1,19 @@
package lambda
func Rename(e Expression, target string, substitute string) {
switch e := e.(type) {
case *Variable:
if e.Value == target {
e.Value = substitute
}
case *Abstraction:
if e.Parameter == target {
e.Parameter = substitute
}
Rename(e.Body, target, substitute)
case *Application:
Rename(e.Abstraction, target, substitute)
Rename(e.Argument, target, substitute)
}
}

32
pkg/lambda/stringify.go Normal file
View File

@@ -0,0 +1,32 @@
package lambda
import "strings"
type stringifyVisitor struct {
builder strings.Builder
}
func (this *stringifyVisitor) VisitVariable(a *Variable) {
this.builder.WriteString(a.Value)
}
func (this *stringifyVisitor) VisitAbstraction(f *Abstraction) {
this.builder.WriteRune('\\')
this.builder.WriteString(f.Parameter)
this.builder.WriteRune('.')
f.Body.Accept(this)
}
func (this *stringifyVisitor) VisitApplication(c *Application) {
this.builder.WriteRune('(')
c.Abstraction.Accept(this)
this.builder.WriteRune(' ')
c.Argument.Accept(this)
this.builder.WriteRune(')')
}
func Stringify(e Expression) string {
b := &stringifyVisitor{}
e.Accept(b)
return b.builder.String()
}

29
pkg/lambda/substitute.go Normal file
View File

@@ -0,0 +1,29 @@
package lambda
func Substitute(e *Expression, target string, replacement Expression) {
switch typed := (*e).(type) {
case *Variable:
if typed.Value == target {
*e = replacement
}
case *Abstraction:
if typed.Parameter == target {
return
}
replacement_free_vars := GetFreeVariables(replacement)
if !replacement_free_vars.Has(typed.Parameter) {
Substitute(&typed.Body, target, replacement)
return
}
used := GetFreeVariables(typed.Body)
used.Union(replacement_free_vars)
fresh_var := GenerateFreshName(used)
Rename(typed.Body, typed.Parameter, fresh_var)
Substitute(&typed.Body, target, replacement)
case *Application:
Substitute(&typed.Abstraction, target, replacement)
Substitute(&typed.Argument, target, replacement)
}
}

View File

@@ -15,15 +15,15 @@ func ParseExpression(i *iterator.Iterator[tokenizer.Token]) (lambda.Expression,
} }
switch token.Type { switch token.Type {
case tokenizer.TokenAtom: case tokenizer.TokenVariable:
return lambda.NewAtom(token.Value), nil return lambda.NewVariable(token.Value), nil
case tokenizer.TokenDot: case tokenizer.TokenDot:
return nil, fmt.Errorf("Token '.' found without a corresponding slash (column %d).", token.Index) return nil, fmt.Errorf("Token '.' found without a corresponding slash (column %d).", token.Index)
case tokenizer.TokenSlash: case tokenizer.TokenSlash:
atom, atom_err := i.Next() atom, atom_err := i.Next()
if atom_err != nil { if atom_err != nil {
return nil, fmt.Errorf("Could not find parameter of function: %w", atom_err) return nil, fmt.Errorf("Could not find parameter of function: %w", atom_err)
} else if atom.Type != tokenizer.TokenAtom { } else if atom.Type != tokenizer.TokenVariable {
return nil, fmt.Errorf("Expected function parameter, got '%v' (column %d).", atom.Value, atom.Index) return nil, fmt.Errorf("Expected function parameter, got '%v' (column %d).", atom.Value, atom.Index)
} }
@@ -39,7 +39,7 @@ func ParseExpression(i *iterator.Iterator[tokenizer.Token]) (lambda.Expression,
return nil, fmt.Errorf("Could not parse function body: %w", body_err) return nil, fmt.Errorf("Could not parse function body: %w", body_err)
} }
return lambda.NewFunction(atom.Value, body), nil return lambda.NewAbstraction(atom.Value, body), nil
case tokenizer.TokenOpenParen: case tokenizer.TokenOpenParen:
fn, fn_err := ParseExpression(i) fn, fn_err := ParseExpression(i)
if fn_err != nil { if fn_err != nil {
@@ -58,7 +58,7 @@ func ParseExpression(i *iterator.Iterator[tokenizer.Token]) (lambda.Expression,
return nil, fmt.Errorf("Expected call terminating parenthesis, got '%v' (column %v).", close.Value, close.Index) return nil, fmt.Errorf("Expected call terminating parenthesis, got '%v' (column %v).", close.Value, close.Index)
} }
return lambda.NewCall(fn, arg), nil return lambda.NewApplication(fn, arg), nil
case tokenizer.TokenCloseParen: case tokenizer.TokenCloseParen:
return nil, fmt.Errorf("Token ')' found without a corresponding openning parenthesis (column %d).", token.Index) return nil, fmt.Errorf("Token ')' found without a corresponding openning parenthesis (column %d).", token.Index)
default: default:

45
pkg/set/set.go Normal file
View File

@@ -0,0 +1,45 @@
package set
type Set[T comparable] map[T]bool
func (this *Set[T]) Add(items ...T) {
for _, item := range items {
(*this)[item] = true
}
}
func (this Set[T]) Has(item T) bool {
return this[item] == true
}
func (this *Set[T]) Remove(items ...T) {
for _, item := range items {
delete(*this, item)
}
}
func (this *Set[T]) Union(o Set[T]) {
for item := range o {
this.Add(item)
}
}
func (this Set[T]) ToList() []T {
list := []T{}
for item := range this {
list = append(list, item)
}
return list
}
func New[T comparable](items ...T) Set[T] {
result := Set[T]{}
for _, item := range items {
result.Add(item)
}
return result
}

View File

@@ -5,13 +5,13 @@ type TokenType int
const ( const (
TokenOpenParen TokenType = iota TokenOpenParen TokenType = iota
TokenCloseParen TokenCloseParen
TokenAtom TokenVariable
TokenSlash TokenSlash
TokenDot TokenDot
) )
type Token struct { type Token struct {
Index int Index int
Type TokenType Type TokenType
Value string Value string
} }

View File

@@ -62,7 +62,7 @@ func getToken(i *iterator.Iterator[rune]) (*Token, error) {
if err != nil || unicode.IsSpace(pop) || unicode.IsPunct(pop) { if err != nil || unicode.IsSpace(pop) || unicode.IsPunct(pop) {
return &Token{ return &Token{
Index: index, Index: index,
Type: TokenAtom, Type: TokenVariable,
Value: atom, Value: atom,
}, nil }, nil
} }