## Description
This PR refactors the event emitter system from a string-based message passing approach to a type-safe generic implementation using typed events.
The previous system relied on string message names which were error-prone and lacked compile-time safety.
This refactoring introduces a generic `BaseEmitter[E comparable]` that provides type safety while consolidating the various tracker packages into a unified plugins architecture.
Key changes:
- Replace `Emitter` with generic `BaseEmitter[E comparable]` for type-safe event handling.
- Add `Event` type enumeration with `StartEvent`, `StepEvent`, and `StopEvent` constants.
- Create `Listener[E]` interface with `BaseListener` implementation for better abstraction.
- Consolidate `explanation`, `performance`, and `statistics` packages into unified `internal/plugins` package.
- Simplify CLI initialization by using plugin constructors that handle their own event subscriptions.
- Add `Items()` iterator method to `Set` for idiomatic Go 1.23+ range loops over sets.
### Decisions
Use generics for type-safe event handling.
This provides compile-time guarantees that event types match their handlers while maintaining flexibility for future event types.
Consolidate trackers into plugins architecture.
Previously separate packages (`explanation`, `performance`, `statistics`) now live under `internal/plugins`, making the plugin pattern explicit and easier to extend.
Plugin constructors self-register with engine.
Each plugin's `New*` constructor now handles its own event subscriptions, reducing boilerplate in the main CLI.
## Benefits
Type safety prevents runtime errors from typos in event names.
The compiler now catches mismatched event types at compile time rather than failing silently at runtime.
Cleaner plugin architecture makes adding new features easier.
New plugins follow a consistent pattern and live in a single location.
Reduced boilerplate in main CLI.
Plugin initialization is now a single function call rather than manual event registration.
Better testability through interface-based design.
The `Listener[E]` interface allows for easier mocking and testing of event handlers.
## Checklist
- [x] Code follows conventional commit format.
- [x] Branch follows naming convention (`<type>/<description>`).
- [x] Tests pass (if applicable).
- [x] Documentation updated (if applicable).
Reviewed-on: #28
Co-authored-by: M.V. Hutz <git@maximhutz.me>
Co-committed-by: M.V. Hutz <git@maximhutz.me>
## 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>
## Description
This PR refactors the lambda calculus reduction engine to use a more efficient LIFO (Last-In-First-Out) stack-based iteration strategy.
Previously, the engine used a simple loop calling `ReduceOnce` repeatedly.
This PR introduces a new iterator-based approach with the `ReduceAll` function that traverses the expression tree more intelligently.
Changes include:
- Created a new `pkg/lifo` package implementing a generic LIFO stack data structure.
- Added `pkg/lambda/iterator.go` with an `Iterator` type for traversing lambda expressions.
- Refactored `pkg/lambda/reduce.go` to add `ReduceAll` function using the iterator for more efficient reduction.
- Updated `internal/engine/engine.go` to use `ReduceAll` instead of looping `ReduceOnce`.
- Renamed sample test files from `.txt` to `.test` extension.
- Fixed `.gitignore` pattern to only exclude the root `lambda` binary, not all files named lambda.
- Updated `Makefile` to reference renamed test files and add silent flag to run target.
### Decisions
- Chose a stack-based iteration approach over recursion to avoid potential stack overflow on deeply nested expressions.
- Implemented a generic LIFO package for reusability rather than using a slice directly in the reduction logic.
- Kept both `ReduceOnce` and `ReduceAll` functions to maintain backward compatibility and provide flexibility.
## Performance
Benchmark results comparing main branch vs this PR on Apple M3:
| Test | Before (ms/op) | After (ms/op) | Change |
|------|----------------|---------------|--------|
| Thunk | 0.014 | 0.014 | 0.00% |
| Fast | 1.29 | 1.20 | **-7.04%** |
| Simple | 21.51 | 6.45 | **-70.01%** |
| Church | 157.67 | 43.00 | -76.788% |
| Saccharine | 185.25 | 178.99 | **-3.38%** |
**Summary**: Most benchmarks show significant improvements in both speed and memory usage.
The Church benchmark shows a regression that needs investigation.
## Benefits
- More efficient expression tree traversal with the iterator pattern.
- Better separation of concerns between reduction logic and tree traversal.
- Generic LIFO stack can be reused in other parts of the codebase.
- Cleaner engine implementation with callback-based step emission.
## Checklist
- [x] Code follows conventional commit format.
- [x] Branch follows naming convention (`<type>/<description>`). Always use underscores.
- [ ] Tests pass (if applicable).
- [ ] Documentation updated (if applicable).
Reviewed-on: #15
Co-authored-by: M.V. Hutz <git@maximhutz.me>
Co-committed-by: M.V. Hutz <git@maximhutz.me>
## Description
This PR adds benchmarking capabilities to the lambda interpreter.
The benchmarks measure performance across all sample files in the samples folder.
This enables consistent performance testing and helps track optimization improvements over time.
Changes in this PR:
- Added new `bench` target to Makefile for running Go benchmarks.
- Created `benchmark_test.go` with sub-benchmarks for each sample file (Church, Fast, Saccharine, Simple, Thunk).
- Used `b.Run` for organizing sample-specific sub-benchmarks and `b.Loop` for efficient iteration.
- Configured benchmarks to use fixed iterations (10x) and 4 CPU cores for reproducible results.
### Decisions
Used `b.Loop()` instead of traditional `for i := 0; i < b.N; i++` pattern.
This is the modern Go benchmarking idiom that provides better performance measurement.
Benchmarks run the full pipeline (parse, compile, execute, stringify) to measure end-to-end performance for each sample.
## Benefits
Provides quantitative performance metrics for the lambda interpreter.
Enables tracking performance improvements or regressions across different sample complexities.
Consistent benchmark configuration (fixed iterations, CPU cores) ensures reproducible results for comparison.
## Checklist
- [x] Code follows conventional commit format.
- [x] Branch follows naming convention (`<type>/<description>`). Always use underscores.
- [x] Tests pass (if applicable).
- [x] Documentation updated (if applicable).
Reviewed-on: #14
Co-authored-by: M.V. Hutz <git@maximhutz.me>
Co-committed-by: M.V. Hutz <git@maximhutz.me>
## Description
The lambda CLI previously only wrote output to stdout using shell redirection.
This PR adds support for writing results to files using the `-o` flag.
This is implemented using a new `Destination` interface that mirrors the existing `Source` pattern.
Changes:
- Added `Destination` interface with `StdoutDestination` and `FileDestination` implementations.
- Added `-o` flag to CLI argument parser for output file specification.
- Updated `Config` to use `Destination` instead of direct output handling.
- Refactored main to use `Destination.Write()` for result output.
- Updated Makefile targets (`run`, `profile`, `explain`) to use `-o` flag instead of shell redirection.
### Decisions
The `-o` flag defaults to stdout when not specified or when set to `-`.
This maintains backward compatibility while providing explicit file output capability.
## Benefits
- Cleaner command-line interface without shell redirection.
- Symmetric design with `Source` interface for input.
- More portable across different shells and environments.
- Explicit output handling improves code clarity.
## Checklist
- [x] Code follows conventional commit format.
- [x] Branch follows naming convention (`<type>/<description>`).
- [ ] Tests pass (if applicable).
- [ ] Documentation updated (if applicable).
Reviewed-on: #13
Co-authored-by: M.V. Hutz <git@maximhutz.me>
Co-committed-by: M.V. Hutz <git@maximhutz.me>