diff --git a/.gitignore b/.gitignore index 0b209db..c36ac72 100644 --- a/.gitignore +++ b/.gitignore @@ -3,16 +3,13 @@ # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # # Binaries for programs and plugins -lambda +/lambda *.exe *.exe~ *.dll *.so *.dylib -# Test binary, built with `go test -c` -*.test - # Output of the go coverage tool, specifically when used with LiteIDE *.out diff --git a/Makefile b/Makefile index 52b2222..cc7e5e5 100644 --- a/Makefile +++ b/Makefile @@ -21,13 +21,13 @@ build: chmod +x ${BINARY_NAME} run: build - ./${BINARY_NAME} -f ./samples/$(TEST).txt -o program.out + ./${BINARY_NAME} -s -f ./tests/$(TEST).test -o program.out profile: build - ./${BINARY_NAME} -p profile/cpu.prof -f ./samples/$(TEST).txt -o program.out + ./${BINARY_NAME} -p profile/cpu.prof -f ./tests/$(TEST).test -o program.out explain: build - ./${BINARY_NAME} -x -p profile/cpu.prof -f ./samples/$(TEST).txt -o program.out + ./${BINARY_NAME} -x -p profile/cpu.prof -f ./tests/$(TEST).test -o program.out > explain.out graph: go tool pprof -raw -output=profile/cpu.raw profile/cpu.prof diff --git a/cmd/lambda/benchmark_test.go b/cmd/lambda/benchmark_test.go index ed5d871..72fd5b6 100644 --- a/cmd/lambda/benchmark_test.go +++ b/cmd/lambda/benchmark_test.go @@ -51,11 +51,11 @@ func runSample(samplePath string) error { // Benchmark all samples using sub-benchmarks. func BenchmarkSamples(b *testing.B) { samples := map[string]string{ - "Church": "../../samples/church.txt", - "Fast": "../../samples/fast.txt", - "Saccharine": "../../samples/saccharine.txt", - "Simple": "../../samples/simple.txt", - "Thunk": "../../samples/thunk.txt", + "Church": "../../tests/church.test", + "Fast": "../../tests/fast.test", + "Saccharine": "../../tests/saccharine.test", + "Simple": "../../tests/simple.test", + "Thunk": "../../tests/thunk.test", } for name, path := range samples { diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 9fb242e..0add767 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -24,9 +24,9 @@ func New(config *config.Config, expression *lambda.Expression) *Engine { func (e Engine) Run() { e.Emit("start") - for lambda.ReduceOnce(e.Expression) { + lambda.ReduceAll(e.Expression, func() { e.Emit("step") - } + }) e.Emit("end") } diff --git a/pkg/fifo/fifo.go b/pkg/fifo/fifo.go deleted file mode 100644 index 6e0657e..0000000 --- a/pkg/fifo/fifo.go +++ /dev/null @@ -1,35 +0,0 @@ -package fifo - -import "fmt" - -type FIFO[T any] []T - -func New[T any](items ...T) *FIFO[T] { - f := FIFO[T](items) - return &f -} - -func (f *FIFO[T]) Push(item T) { - *f = append(*f, item) -} - -func (f *FIFO[T]) Empty() bool { - return len(*f) == 0 -} - -func (f *FIFO[T]) MustPop() T { - var item T - - *f, item = (*f)[:len(*f)-1], (*f)[len(*f)-1] - return item -} - -func (f *FIFO[T]) Pop() (T, error) { - var item T - - if f.Empty() { - return item, fmt.Errorf("stack is exhausted") - } - - return f.MustPop(), nil -} diff --git a/pkg/lambda/iterator.go b/pkg/lambda/iterator.go new file mode 100644 index 0000000..76b711a --- /dev/null +++ b/pkg/lambda/iterator.go @@ -0,0 +1,59 @@ +package lambda + +type Iterator struct { + trace []*Expression +} + +func NewIterator(expr *Expression) *Iterator { + return &Iterator{ + trace: []*Expression{expr}, + } +} + +func (i *Iterator) Current() *Expression { + if len(i.trace) < 1 { + 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) 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{} + } +} + +func (i *Iterator) Back() bool { + if len(i.trace) == 0 { + return false + } + + i.trace = i.trace[:len(i.trace)-1] + return true +} diff --git a/pkg/lambda/reduce.go b/pkg/lambda/reduce.go index 55d366a..8e56bd0 100644 --- a/pkg/lambda/reduce.go +++ b/pkg/lambda/reduce.go @@ -1,9 +1,11 @@ package lambda -import "git.maximhutz.com/max/lambda/pkg/fifo" +import ( + "git.maximhutz.com/max/lambda/pkg/lifo" +) func ReduceOnce(e *Expression) bool { - stack := fifo.New(e) + stack := lifo.New(e) for !stack.Empty() { top := stack.MustPop() @@ -13,8 +15,7 @@ func ReduceOnce(e *Expression) bool { stack.Push(&typed.body) case *Application: if fn, fnOk := typed.abstraction.(*Abstraction); fnOk { - reduced := Substitute(fn.body, fn.parameter, typed.argument) - *top = reduced + *top = Substitute(fn.body, fn.parameter, typed.argument) return true } @@ -25,3 +26,35 @@ func ReduceOnce(e *Expression) bool { return false } + +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.Current() != nil { + current := it.Current() + + if fn, arg, ok := IsViable(current); !ok { + it.Next() + } else { + *current = Substitute(fn.body, fn.parameter, arg) + step() + + if _, _, ok := IsViable(it.Parent()); ok { + it.Back() + } else { + } + } + } +} diff --git a/pkg/lifo/lifo.go b/pkg/lifo/lifo.go new file mode 100644 index 0000000..6cc51d1 --- /dev/null +++ b/pkg/lifo/lifo.go @@ -0,0 +1,35 @@ +package lifo + +import "fmt" + +type LIFO[T any] []T + +func New[T any](items ...T) *LIFO[T] { + l := LIFO[T](items) + return &l +} + +func (l *LIFO[T]) Push(item T) { + *l = append(*l, item) +} + +func (l *LIFO[T]) Empty() bool { + return len(*l) == 0 +} + +func (l *LIFO[T]) MustPop() T { + var item T + + *l, item = (*l)[:len(*l)-1], (*l)[len(*l)-1] + return item +} + +func (l *LIFO[T]) Pop() (T, error) { + var item T + + if l.Empty() { + return item, fmt.Errorf("stack is exhausted") + } + + return l.MustPop(), nil +} diff --git a/samples/church.txt b/tests/church.test similarity index 100% rename from samples/church.txt rename to tests/church.test diff --git a/samples/fast.txt b/tests/fast.test similarity index 100% rename from samples/fast.txt rename to tests/fast.test diff --git a/samples/saccharine.txt b/tests/saccharine.test similarity index 100% rename from samples/saccharine.txt rename to tests/saccharine.test diff --git a/samples/simple.txt b/tests/simple.test similarity index 100% rename from samples/simple.txt rename to tests/simple.test diff --git a/samples/thunk.txt b/tests/thunk.test similarity index 100% rename from samples/thunk.txt rename to tests/thunk.test