refactor: move event system to reducer, remove engine package #32

Merged
mvhutz merged 7 commits from refactor/move-events-to-reducer into main 2026-01-17 00:27:37 +00:00
12 changed files with 99 additions and 112 deletions
Showing only changes of commit bcc0331149 - Show all commits

View File

@@ -5,7 +5,6 @@ import (
"git.maximhutz.com/max/lambda/internal/cli"
"git.maximhutz.com/max/lambda/internal/config"
"git.maximhutz.com/max/lambda/internal/engine"
"git.maximhutz.com/max/lambda/internal/plugins"
"git.maximhutz.com/max/lambda/pkg/convert"
"git.maximhutz.com/max/lambda/pkg/lambda"
@@ -34,36 +33,35 @@ func main() {
compiled := convert.SaccharineToLambda(ast)
logger.Info("compiled λ expression", "tree", compiled.String())
// Create reduction engine with normal order reducer.
// Create reducer.
reducer := lambda.NewNormalOrderReducer()
process := engine.New(options, compiled, reducer)
// If the user selected to track CPU performance, attach a profiler to the
// process.
// If the user selected to track CPU performance, attach a profiler.
if options.Profile != "" {
plugins.NewPerformance(options.Profile, process)
plugins.NewPerformance(options.Profile, reducer)
}
// If the user selected to produce a step-by-step explanation, attach an
// observer here.
// observer.
if options.Explanation {
plugins.NewExplanation(process)
plugins.NewExplanation(reducer)
}
// If the user opted to track statistics, attach a tracker here, too.
// If the user opted to track statistics, attach a tracker.
if options.Statistics {
plugins.NewStatistics(process)
plugins.NewStatistics(reducer)
}
// If the user selected for verbose debug logs, attach a reduction tracker.
if options.Verbose {
plugins.NewLogs(logger, process)
plugins.NewLogs(logger, reducer)
}
process.Run()
// Run reduction.
reducer.Reduce(compiled)
// Return the final reduced result.
result := process.Expression.String()
result := reducer.Expression().String()
err = options.Destination.Write(result)
cli.HandleError(err)
}

View File

@@ -6,8 +6,6 @@ import (
"strings"
"testing"
"git.maximhutz.com/max/lambda/internal/config"
"git.maximhutz.com/max/lambda/internal/engine"
"git.maximhutz.com/max/lambda/pkg/convert"
"git.maximhutz.com/max/lambda/pkg/lambda"
"git.maximhutz.com/max/lambda/pkg/saccharine"
@@ -31,22 +29,11 @@ func runSample(samplePath string) (string, error) {
// Compile expression to lambda calculus.
compiled := convert.SaccharineToLambda(ast)
// Create minimal config for benchmarking.
cfg := &config.Config{
Source: config.StringSource{Data: ""},
Destination: config.StdoutDestination{},
Profile: "",
Explanation: false,
Statistics: false,
Verbose: false,
}
// Create and run the engine with normal order reducer.
// Create and run the reducer.
reducer := lambda.NewNormalOrderReducer()
process := engine.New(cfg, compiled, reducer)
process.Run()
reducer.Reduce(compiled)
return process.Expression.String() + "\n", nil
return reducer.Expression().String() + "\n", nil
}
// Test that all samples produce expected output.

View File

@@ -1,40 +0,0 @@
// Package "engine" provides an extensible interface for users to interact with
// λ-calculus and other expression evaluation modes.
package engine
import (
"git.maximhutz.com/max/lambda/internal/config"
"git.maximhutz.com/max/lambda/pkg/emitter"
"git.maximhutz.com/max/lambda/pkg/expr"
"git.maximhutz.com/max/lambda/pkg/reducer"
)
// Engine is a process for reducing one expression.
type Engine struct {
Config *config.Config
Expression expr.Expression
Reducer reducer.Reducer
emitter.BaseEmitter[Event]
}
// New creates a new engine with the given expression and reducer.
func New(config *config.Config, expression expr.Expression, reducer reducer.Reducer) *Engine {
return &Engine{
Config: config,
Expression: expression,
Reducer: reducer,
BaseEmitter: *emitter.New[Event](),
}
}
// Run begins the reduction process.
func (e *Engine) Run() {
e.Emit(StartEvent)
e.Expression = e.Reducer.Reduce(e.Expression, func(reduced expr.Expression) {
e.Expression = reduced
e.Emit(StepEvent)
})
e.Emit(StopEvent)
}

View File

@@ -1,9 +0,0 @@
package engine
type Event int
const (
StartEvent Event = iota
StepEvent
StopEvent
)

View File

@@ -3,21 +3,21 @@ package plugins
import (
"log/slog"
"git.maximhutz.com/max/lambda/internal/engine"
"git.maximhutz.com/max/lambda/pkg/reducer"
)
type Logs struct {
logger *slog.Logger
process *engine.Engine
reducer reducer.Reducer
}
func NewLogs(logger *slog.Logger, process *engine.Engine) *Logs {
plugin := &Logs{logger, process}
process.On(engine.StepEvent, plugin.Step)
func NewLogs(logger *slog.Logger, r reducer.Reducer) *Logs {
plugin := &Logs{logger, r}
r.On(reducer.StepEvent, plugin.Step)
return plugin
}
func (t *Logs) Step() {
t.logger.Info("reduction", "tree", t.process.Expression.String())
t.logger.Info("reduction", "tree", t.reducer.Expression().String())
}

View File

@@ -5,27 +5,27 @@ package plugins
import (
"fmt"
"git.maximhutz.com/max/lambda/internal/engine"
"git.maximhutz.com/max/lambda/pkg/reducer"
)
// Track the reductions made by a reduction process.
type Explanation struct {
process *engine.Engine
reducer reducer.Reducer
}
// Attaches a new explanation tracker to a process.
func NewExplanation(process *engine.Engine) *Explanation {
plugin := &Explanation{process: process}
process.On(engine.StartEvent, plugin.Start)
process.On(engine.StepEvent, plugin.Step)
// Attaches a new explanation tracker to a reducer.
func NewExplanation(r reducer.Reducer) *Explanation {
plugin := &Explanation{reducer: r}
r.On(reducer.StartEvent, plugin.Start)
r.On(reducer.StepEvent, plugin.Step)
return plugin
}
func (t *Explanation) Start() {
fmt.Println(t.process.Expression.String())
fmt.Println(t.reducer.Expression().String())
}
func (t *Explanation) Step() {
fmt.Println(" =", t.process.Expression.String())
fmt.Println(" =", t.reducer.Expression().String())
}

View File

@@ -1,4 +1,4 @@
// Package "performance" provides a tracker to observer CPU performance during
// Package "performance" provides a tracker to observe CPU performance during
// execution.
package plugins
@@ -7,7 +7,7 @@ import (
"path/filepath"
"runtime/pprof"
"git.maximhutz.com/max/lambda/internal/engine"
"git.maximhutz.com/max/lambda/pkg/reducer"
)
// Observes a reduction process, and publishes a CPU performance profile on
@@ -19,10 +19,10 @@ type Performance struct {
}
// Create a performance tracker that outputs a profile to "file".
func NewPerformance(file string, process *engine.Engine) *Performance {
func NewPerformance(file string, r reducer.Reducer) *Performance {
plugin := &Performance{File: file}
process.On(engine.StartEvent, plugin.Start)
process.On(engine.StopEvent, plugin.Stop)
r.On(reducer.StartEvent, plugin.Start)
r.On(reducer.StopEvent, plugin.Stop)
return plugin
}

View File

@@ -5,8 +5,8 @@ import (
"os"
"time"
"git.maximhutz.com/max/lambda/internal/engine"
"git.maximhutz.com/max/lambda/internal/statistics"
"git.maximhutz.com/max/lambda/pkg/reducer"
)
// An observer, to track reduction performance.
@@ -16,11 +16,11 @@ type Statistics struct {
}
// Create a new reduction performance Statistics.
func NewStatistics(process *engine.Engine) *Statistics {
func NewStatistics(r reducer.Reducer) *Statistics {
plugin := &Statistics{}
process.On(engine.StartEvent, plugin.Start)
process.On(engine.StepEvent, plugin.Step)
process.On(engine.StopEvent, plugin.Stop)
r.On(reducer.StartEvent, plugin.Start)
r.On(reducer.StepEvent, plugin.Step)
r.On(reducer.StopEvent, plugin.Stop)
return plugin
}

View File

@@ -3,7 +3,7 @@ package emitter
import "git.maximhutz.com/max/lambda/pkg/set"
type Emitter[E comparable] interface {
On(string, func()) Listener[E]
On(E, func()) Listener[E]
Off(Listener[E])
Emit(E)
}
@@ -22,6 +22,13 @@ func (e *BaseEmitter[E]) On(kind E, fn func()) Listener[E] {
return listener
}
func (e *BaseEmitter[E]) Off(listener Listener[E]) {
kind := listener.Kind()
if e.listeners[kind] != nil {
e.listeners[kind].Remove(listener)
}
}
func (e *BaseEmitter[E]) Emit(event E) {
if e.listeners[event] == nil {
e.listeners[event] = set.New[Listener[E]]()

View File

@@ -1,6 +1,7 @@
package lambda
import (
"git.maximhutz.com/max/lambda/pkg/emitter"
"git.maximhutz.com/max/lambda/pkg/expr"
"git.maximhutz.com/max/lambda/pkg/reducer"
)
@@ -10,24 +11,42 @@ var _ reducer.Reducer = (*NormalOrderReducer)(nil)
// NormalOrderReducer implements normal order (leftmost-outermost) reduction
// for lambda calculus expressions.
type NormalOrderReducer struct{}
type NormalOrderReducer struct {
emitter.BaseEmitter[reducer.Event]
expression expr.Expression
}
// NewNormalOrderReducer creates a new normal order reducer.
func NewNormalOrderReducer() *NormalOrderReducer {
return &NormalOrderReducer{}
return &NormalOrderReducer{
BaseEmitter: *emitter.New[reducer.Event](),
}
}
// Expression returns the current expression state.
func (r *NormalOrderReducer) Expression() expr.Expression {
return r.expression
}
// Reduce performs normal order reduction on a lambda expression.
// The expression must be a lambda.Expression; other types are returned unchanged.
func (r *NormalOrderReducer) Reduce(e expr.Expression, onStep func(expr.Expression)) expr.Expression {
func (r *NormalOrderReducer) Reduce(e expr.Expression) expr.Expression {
r.expression = e
lambdaExpr, ok := e.(Expression)
if !ok {
return e
}
r.Emit(reducer.StartEvent)
ReduceAll(&lambdaExpr, func() {
onStep(lambdaExpr)
r.expression = lambdaExpr
r.Emit(reducer.StepEvent)
})
r.expression = lambdaExpr
r.Emit(reducer.StopEvent)
return lambdaExpr
}

13
pkg/reducer/events.go Normal file
View File

@@ -0,0 +1,13 @@
package reducer
// Event represents lifecycle events during reduction.
type Event int
const (
// StartEvent is emitted before reduction begins.
StartEvent Event = iota
// StepEvent is emitted after each reduction step.
StepEvent
// StopEvent is emitted after reduction completes.
StopEvent
)

View File

@@ -2,14 +2,26 @@
// reduction strategies.
package reducer
import "git.maximhutz.com/max/lambda/pkg/expr"
import (
"git.maximhutz.com/max/lambda/pkg/emitter"
"git.maximhutz.com/max/lambda/pkg/expr"
)
// Reducer defines the interface for expression reduction strategies.
// Different evaluation modes (normal order, applicative order, SKI combinators,
// etc.) implement this interface with their own reduction logic.
//
// Reducers also implement the Emitter interface to allow plugins to observe
// reduction lifecycle events (Start, Step, Stop).
type Reducer interface {
// Reduce performs all reduction steps on the expression, calling onStep
// after each reduction.
emitter.Emitter[Event]
// Reduce performs all reduction steps on the expression.
// Emits StartEvent before reduction, StepEvent after each step, and
// StopEvent after completion.
// Returns the final reduced expression.
Reduce(e expr.Expression, onStep func(expr.Expression)) expr.Expression
Reduce(e expr.Expression) expr.Expression
// Expression returns the current expression state.
Expression() expr.Expression
}