diff --git a/cmd/lambda/lambda.go b/cmd/lambda/lambda.go index 2679d27..3d8570f 100644 --- a/cmd/lambda/lambda.go +++ b/cmd/lambda/lambda.go @@ -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) } diff --git a/cmd/lambda/lambda_test.go b/cmd/lambda/lambda_test.go index 6c84fd6..3855fc5 100644 --- a/cmd/lambda/lambda_test.go +++ b/cmd/lambda/lambda_test.go @@ -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. diff --git a/internal/engine/engine.go b/internal/engine/engine.go deleted file mode 100644 index ea075b4..0000000 --- a/internal/engine/engine.go +++ /dev/null @@ -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) -} diff --git a/internal/engine/events.go b/internal/engine/events.go deleted file mode 100644 index 995ebea..0000000 --- a/internal/engine/events.go +++ /dev/null @@ -1,9 +0,0 @@ -package engine - -type Event int - -const ( - StartEvent Event = iota - StepEvent - StopEvent -) diff --git a/internal/plugins/debug.go b/internal/plugins/debug.go index 1c1a95b..62fbffd 100644 --- a/internal/plugins/debug.go +++ b/internal/plugins/debug.go @@ -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()) } diff --git a/internal/plugins/explanation.go b/internal/plugins/explanation.go index ef46bb9..08285c2 100644 --- a/internal/plugins/explanation.go +++ b/internal/plugins/explanation.go @@ -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()) } diff --git a/internal/plugins/performance.go b/internal/plugins/performance.go index 0dc69d4..08cc46b 100644 --- a/internal/plugins/performance.go +++ b/internal/plugins/performance.go @@ -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 } diff --git a/internal/plugins/statistics.go b/internal/plugins/statistics.go index 62223e4..b54ab62 100644 --- a/internal/plugins/statistics.go +++ b/internal/plugins/statistics.go @@ -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 } diff --git a/pkg/emitter/emitter.go b/pkg/emitter/emitter.go index 594a18a..d387fba 100644 --- a/pkg/emitter/emitter.go +++ b/pkg/emitter/emitter.go @@ -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]]() diff --git a/pkg/lambda/reducer.go b/pkg/lambda/reducer.go index b806c62..2fe0d80 100644 --- a/pkg/lambda/reducer.go +++ b/pkg/lambda/reducer.go @@ -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 } diff --git a/pkg/reducer/events.go b/pkg/reducer/events.go new file mode 100644 index 0000000..ad432fe --- /dev/null +++ b/pkg/reducer/events.go @@ -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 +) diff --git a/pkg/reducer/reducer.go b/pkg/reducer/reducer.go index 75d2dd9..ec2d02a 100644 --- a/pkg/reducer/reducer.go +++ b/pkg/reducer/reducer.go @@ -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 }