Improve testing infrastructure with dynamic discovery and validation (#20)
## Summary This PR enhances the testing infrastructure with dynamic test discovery, automated validation, and improved error handling. ## Changes ### Testing Infrastructure - Added `TestSamplesValidity` integration test that validates all test files against their expected output. - Implemented dynamic test discovery using `filepath.Glob` to automatically find all `.test` files. - Renamed `benchmark_test.go` to `lambda_test.go` for better naming consistency. - Consolidated helper functions into a single `runSample` function. - Replaced all error handling with `assert` for consistent and clear test output. - Required all `.test` files to have corresponding `.expected` files. ### Iterator Improvements - Added `Swap` method to iterator for better reduction algorithm. - Improved reduction algorithm with LIFO-based iterator implementation. ### Build System - Added `make test` target to run tests without benchmarks. - Updated Makefile help text to include the new test target. ### Test Cases - Added new test cases with expected outputs: `church_5^5`, `church_6^6`, `fast_list_2^30`, `list_2^30`. - Added validation files for all test cases. ## Test plan - Run tests with expected output validation. - Run benchmarks to ensure performance is maintained. - Verify make targets work correctly. Reviewed-on: #20 Co-authored-by: M.V. Hutz <git@maximhutz.me> Co-committed-by: M.V. Hutz <git@maximhutz.me>
This commit was merged in pull request #20.
This commit is contained in:
@@ -5,13 +5,15 @@ type Iterator struct {
|
||||
}
|
||||
|
||||
func NewIterator(expr *Expression) *Iterator {
|
||||
return &Iterator{
|
||||
trace: []*Expression{expr},
|
||||
}
|
||||
return &Iterator{[]*Expression{expr}}
|
||||
}
|
||||
|
||||
func (i *Iterator) Done() bool {
|
||||
return len(i.trace) == 0
|
||||
}
|
||||
|
||||
func (i *Iterator) Current() *Expression {
|
||||
if len(i.trace) < 1 {
|
||||
if i.Done() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,6 +28,22 @@ func (i *Iterator) Parent() *Expression {
|
||||
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:
|
||||
@@ -48,12 +66,3 @@ func (i *Iterator) Next() {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,32 +1,5 @@
|
||||
package lambda
|
||||
|
||||
import (
|
||||
"git.maximhutz.com/max/lambda/pkg/lifo"
|
||||
)
|
||||
|
||||
func ReduceOnce(e *Expression) bool {
|
||||
stack := lifo.New(e)
|
||||
|
||||
for !stack.Empty() {
|
||||
top := stack.MustPop()
|
||||
|
||||
switch typed := (*top).(type) {
|
||||
case *Abstraction:
|
||||
stack.Push(&typed.body)
|
||||
case *Application:
|
||||
if fn, fnOk := typed.abstraction.(*Abstraction); fnOk {
|
||||
*top = Substitute(fn.body, fn.parameter, typed.argument)
|
||||
return true
|
||||
}
|
||||
|
||||
stack.Push(&typed.argument)
|
||||
stack.Push(&typed.abstraction)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func IsViable(e *Expression) (*Abstraction, Expression, bool) {
|
||||
if e == nil {
|
||||
return nil, nil, false
|
||||
@@ -42,18 +15,15 @@ func IsViable(e *Expression) (*Abstraction, Expression, bool) {
|
||||
func ReduceAll(e *Expression, step func()) {
|
||||
it := NewIterator(e)
|
||||
|
||||
for it.Current() != nil {
|
||||
current := it.Current()
|
||||
|
||||
if fn, arg, ok := IsViable(current); !ok {
|
||||
for !it.Done() {
|
||||
if fn, arg, ok := IsViable(it.Current()); !ok {
|
||||
it.Next()
|
||||
} else {
|
||||
*current = Substitute(fn.body, fn.parameter, arg)
|
||||
it.Swap(Substitute(fn.body, fn.parameter, arg))
|
||||
step()
|
||||
|
||||
if _, _, ok := IsViable(it.Parent()); ok {
|
||||
it.Back()
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user