refactor: rename interpreter to runtime and use receiver methods #39

Merged
mvhutz merged 1 commits from refactor/runtime-and-receiver-methods into main 2026-01-18 20:52:35 +00:00
26 changed files with 200 additions and 196 deletions

View File

@@ -1,6 +1,6 @@
--- ---
name: "Bug Report" name: "Bug Report"
about: "Report a bug or unexpected behavior in the lambda interpreter." about: "Report a bug or unexpected behavior in the lambda runtime."
title: "fix: " title: "fix: "
ref: "main" ref: "main"
assignees: [] assignees: []

View File

@@ -1,6 +1,6 @@
--- ---
name: "Feature Request" name: "Feature Request"
about: "Suggest a new feature or enhancement for the lambda interpreter." about: "Suggest a new feature or enhancement for the lambda runtime."
title: "feat: " title: "feat: "
ref: "main" ref: "main"
assignees: [] assignees: []

View File

@@ -48,7 +48,7 @@ The "source code" for a work means the preferred form of the work for making mod
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code runtime used to run it.
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those
subprograms and other parts of the work. subprograms and other parts of the work.

View File

@@ -8,7 +8,7 @@ TEST=simple
help: help:
echo "Available targets:" echo "Available targets:"
echo " build - Build the lambda executable" echo " build - Build the lambda executable"
echo " run - Build and run the lambda interpreter (use TEST=<name> to specify sample)" echo " run - Build and run the lambda runtime (use TEST=<name> to specify sample)"
echo " profile - Build and run with CPU profiling enabled" echo " profile - Build and run with CPU profiling enabled"
echo " explain - Build and run with explanation mode and profiling" echo " explain - Build and run with explanation mode and profiling"
echo " graph - Generate and open CPU profile visualization" echo " graph - Generate and open CPU profile visualization"

View File

@@ -1,6 +1,6 @@
# lambda # lambda
Making a lambda calculus interpreter in Go. Making a lambda calculus runtime in Go.
## Things to talk about ## Things to talk about

View File

@@ -7,7 +7,7 @@ import (
"git.maximhutz.com/max/lambda/internal/config" "git.maximhutz.com/max/lambda/internal/config"
"git.maximhutz.com/max/lambda/internal/plugins" "git.maximhutz.com/max/lambda/internal/plugins"
"git.maximhutz.com/max/lambda/pkg/convert" "git.maximhutz.com/max/lambda/pkg/convert"
"git.maximhutz.com/max/lambda/pkg/lambda" "git.maximhutz.com/max/lambda/pkg/normalorder"
"git.maximhutz.com/max/lambda/pkg/saccharine" "git.maximhutz.com/max/lambda/pkg/saccharine"
) )
@@ -34,34 +34,34 @@ func main() {
logger.Info("compiled λ expression", "tree", compiled.String()) logger.Info("compiled λ expression", "tree", compiled.String())
// Create reducer with the compiled expression. // Create reducer with the compiled expression.
interpreter := lambda.NewInterpreter(compiled) runtime := normalorder.NewRuntime(compiled)
// If the user selected to track CPU performance, attach a profiler. // If the user selected to track CPU performance, attach a profiler.
if options.Profile != "" { if options.Profile != "" {
plugins.NewPerformance(options.Profile, interpreter) plugins.NewPerformance(options.Profile, runtime)
} }
// If the user selected to produce a step-by-step explanation, attach an // If the user selected to produce a step-by-step explanation, attach an
// observer. // observer.
if options.Explanation { if options.Explanation {
plugins.NewExplanation(interpreter) plugins.NewExplanation(runtime)
} }
// If the user opted to track statistics, attach a tracker. // If the user opted to track statistics, attach a tracker.
if options.Statistics { if options.Statistics {
plugins.NewStatistics(interpreter) plugins.NewStatistics(runtime)
} }
// If the user selected for verbose debug logs, attach a reduction tracker. // If the user selected for verbose debug logs, attach a reduction tracker.
if options.Verbose { if options.Verbose {
plugins.NewLogs(logger, interpreter) plugins.NewLogs(logger, runtime)
} }
// Run reduction. // Run reduction.
interpreter.Run() runtime.Run()
// Return the final reduced result. // Return the final reduced result.
result := interpreter.Expression().String() result := runtime.Expression().String()
err = options.Destination.Write(result) err = options.Destination.Write(result)
cli.HandleError(err) cli.HandleError(err)
} }

View File

@@ -7,12 +7,12 @@ import (
"testing" "testing"
"git.maximhutz.com/max/lambda/pkg/convert" "git.maximhutz.com/max/lambda/pkg/convert"
"git.maximhutz.com/max/lambda/pkg/lambda" "git.maximhutz.com/max/lambda/pkg/normalorder"
"git.maximhutz.com/max/lambda/pkg/saccharine" "git.maximhutz.com/max/lambda/pkg/saccharine"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// Helper function to run a single sample through the lambda interpreter. // Helper function to run a single sample through the lambda runtime.
func runSample(samplePath string) (string, error) { func runSample(samplePath string) (string, error) {
// Read the sample file. // Read the sample file.
input, err := os.ReadFile(samplePath) input, err := os.ReadFile(samplePath)
@@ -30,7 +30,7 @@ func runSample(samplePath string) (string, error) {
compiled := convert.SaccharineToLambda(ast) compiled := convert.SaccharineToLambda(ast)
// Create and run the reducer. // Create and run the reducer.
reducer := lambda.NewInterpreter(compiled) reducer := normalorder.NewRuntime(compiled)
reducer.Run() reducer.Run()
return reducer.Expression().String() + "\n", nil return reducer.Expression().String() + "\n", nil

View File

@@ -3,17 +3,17 @@ package plugins
import ( import (
"log/slog" "log/slog"
"git.maximhutz.com/max/lambda/pkg/interpreter" "git.maximhutz.com/max/lambda/pkg/runtime"
) )
type Logs struct { type Logs struct {
logger *slog.Logger logger *slog.Logger
reducer interpreter.Interpreter reducer runtime.Runtime
} }
func NewLogs(logger *slog.Logger, r interpreter.Interpreter) *Logs { func NewLogs(logger *slog.Logger, r runtime.Runtime) *Logs {
plugin := &Logs{logger, r} plugin := &Logs{logger, r}
r.On(interpreter.StepEvent, plugin.Step) r.On(runtime.StepEvent, plugin.Step)
return plugin return plugin
} }

View File

@@ -5,19 +5,19 @@ package plugins
import ( import (
"fmt" "fmt"
"git.maximhutz.com/max/lambda/pkg/interpreter" "git.maximhutz.com/max/lambda/pkg/runtime"
) )
// Track the reductions made by a reduction process. // Track the reductions made by a reduction process.
type Explanation struct { type Explanation struct {
reducer interpreter.Interpreter reducer runtime.Runtime
} }
// Attaches a new explanation tracker to a reducer. // Attaches a new explanation tracker to a reducer.
func NewExplanation(r interpreter.Interpreter) *Explanation { func NewExplanation(r runtime.Runtime) *Explanation {
plugin := &Explanation{reducer: r} plugin := &Explanation{reducer: r}
r.On(interpreter.StartEvent, plugin.Start) r.On(runtime.StartEvent, plugin.Start)
r.On(interpreter.StepEvent, plugin.Step) r.On(runtime.StepEvent, plugin.Step)
return plugin return plugin
} }

View File

@@ -7,7 +7,7 @@ import (
"path/filepath" "path/filepath"
"runtime/pprof" "runtime/pprof"
"git.maximhutz.com/max/lambda/pkg/interpreter" "git.maximhutz.com/max/lambda/pkg/runtime"
) )
// Observes a reduction process, and publishes a CPU performance profile on // 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". // Create a performance tracker that outputs a profile to "file".
func NewPerformance(file string, process interpreter.Interpreter) *Performance { func NewPerformance(file string, process runtime.Runtime) *Performance {
plugin := &Performance{File: file} plugin := &Performance{File: file}
process.On(interpreter.StartEvent, plugin.Start) process.On(runtime.StartEvent, plugin.Start)
process.On(interpreter.StopEvent, plugin.Stop) process.On(runtime.StopEvent, plugin.Stop)
return plugin return plugin
} }

View File

@@ -6,7 +6,7 @@ import (
"time" "time"
"git.maximhutz.com/max/lambda/internal/statistics" "git.maximhutz.com/max/lambda/internal/statistics"
"git.maximhutz.com/max/lambda/pkg/interpreter" "git.maximhutz.com/max/lambda/pkg/runtime"
) )
// An observer, to track reduction performance. // An observer, to track reduction performance.
@@ -16,11 +16,11 @@ type Statistics struct {
} }
// Create a new reduction performance Statistics. // Create a new reduction performance Statistics.
func NewStatistics(r interpreter.Interpreter) *Statistics { func NewStatistics(r runtime.Runtime) *Statistics {
plugin := &Statistics{} plugin := &Statistics{}
r.On(interpreter.StartEvent, plugin.Start) r.On(runtime.StartEvent, plugin.Start)
r.On(interpreter.StepEvent, plugin.Step) r.On(runtime.StepEvent, plugin.Step)
r.On(interpreter.StopEvent, plugin.Stop) r.On(runtime.StopEvent, plugin.Stop)
return plugin return plugin
} }

View File

@@ -19,7 +19,7 @@ func convertAbstraction(n *saccharine.Abstraction) lambda.Expression {
// If the function has no parameters, it is a thunk. Lambda calculus still // If the function has no parameters, it is a thunk. Lambda calculus still
// requires _some_ parameter exists, so generate one. // requires _some_ parameter exists, so generate one.
if len(parameters) == 0 { if len(parameters) == 0 {
freeVars := lambda.GetFreeVariables(result) freeVars := result.GetFree()
freshName := lambda.GenerateFreshName(freeVars) freshName := lambda.GenerateFreshName(freeVars)
parameters = append(parameters, freshName) parameters = append(parameters, freshName)
} }
@@ -63,7 +63,7 @@ func reduceLet(s *saccharine.LetStatement, e lambda.Expression) lambda.Expressio
} }
func reduceDeclare(s *saccharine.DeclareStatement, e lambda.Expression) lambda.Expression { func reduceDeclare(s *saccharine.DeclareStatement, e lambda.Expression) lambda.Expression {
freshVar := lambda.GenerateFreshName(lambda.GetFreeVariables(e)) freshVar := lambda.GenerateFreshName(e.GetFree())
return lambda.NewApplication( return lambda.NewApplication(
lambda.NewAbstraction(freshVar, e), lambda.NewAbstraction(freshVar, e),

View File

@@ -9,7 +9,7 @@ type Emitter[E comparable] interface {
} }
type BaseEmitter[E comparable] struct { type BaseEmitter[E comparable] struct {
listeners map[E]*set.Set[Listener[E]] listeners map[E]set.Set[Listener[E]]
} }
func (e *BaseEmitter[E]) On(kind E, fn func()) Listener[E] { func (e *BaseEmitter[E]) On(kind E, fn func()) Listener[E] {
@@ -41,6 +41,6 @@ func (e *BaseEmitter[E]) Emit(event E) {
func New[E comparable]() *BaseEmitter[E] { func New[E comparable]() *BaseEmitter[E] {
return &BaseEmitter[E]{ return &BaseEmitter[E]{
listeners: map[E]*set.Set[Listener[E]]{}, listeners: map[E]set.Set[Listener[E]]{},
} }
} }

View File

@@ -1,5 +1,5 @@
// Package expr provides the abstract Expression interface for all evaluatable // Package expr provides the abstract Expression interface for all evaluatable
// expression types in the lambda interpreter. // expression types in the lambda runtime.
package expr package expr
import ( import (

View File

@@ -2,12 +2,30 @@ package lambda
import ( import (
"git.maximhutz.com/max/lambda/pkg/expr" "git.maximhutz.com/max/lambda/pkg/expr"
"git.maximhutz.com/max/lambda/pkg/set"
) )
// Expression is the interface for all lambda calculus expression types. // Expression is the interface for all lambda calculus expression types.
// It embeds the general expr.Expression interface for cross-mode compatibility. // It embeds the general expr.Expression interface for cross-mode compatibility.
type Expression interface { type Expression interface {
expr.Expression expr.Expression
// Substitute replaces all free occurrences of the target variable with the
// replacement expression. Alpha-renaming is performed automatically to
// avoid variable capture.
Substitute(target string, replacement Expression) Expression
// GetFree returns the set of all free variable names in the expression.
// This function does not mutate the input expression.
// The returned set is newly allocated and can be modified by the caller.
GetFree() set.Set[string]
// Rename replaces all occurrences of the target variable name with the new name.
Rename(target string, newName string) Expression
// IsFree returns true if the variable name n occurs free in the expression.
// This function does not mutate the input expression.
IsFree(n string) bool
} }
/** ------------------------------------------------------------------------- */ /** ------------------------------------------------------------------------- */

View File

@@ -10,7 +10,7 @@ var ticker uint64 = 0
// GenerateFreshName generates a variable name that is not in the used set. // GenerateFreshName generates a variable name that is not in the used set.
// This function does not mutate the used set. // This function does not mutate the used set.
func GenerateFreshName(used *set.Set[string]) string { func GenerateFreshName(used set.Set[string]) string {
for i := uint64(0); ; i++ { for i := uint64(0); ; i++ {
attempt := "_" + string(strconv.AppendUint(nil, ticker, 10)) attempt := "_" + string(strconv.AppendUint(nil, ticker, 10))

View File

@@ -2,22 +2,18 @@ package lambda
import "git.maximhutz.com/max/lambda/pkg/set" import "git.maximhutz.com/max/lambda/pkg/set"
// GetFreeVariables returns the set of all free variable names in the expression. func (e Variable) GetFree() set.Set[string] {
// This function does not mutate the input expression.
// The returned set is newly allocated and can be modified by the caller.
func GetFreeVariables(e Expression) *set.Set[string] {
switch e := e.(type) {
case Variable:
return set.New(e.Name()) return set.New(e.Name())
case Abstraction: }
vars := GetFreeVariables(e.Body())
func (e Abstraction) GetFree() set.Set[string] {
vars := e.Body().GetFree()
vars.Remove(e.Parameter()) vars.Remove(e.Parameter())
return vars return vars
case Application: }
vars := GetFreeVariables(e.Abstraction())
vars.Merge(GetFreeVariables(e.Argument())) func (e Application) GetFree() set.Set[string] {
vars := e.Abstraction().GetFree()
vars.Merge(e.Argument().GetFree())
return vars return vars
default:
return set.New[string]()
}
} }

View File

@@ -1,16 +1,12 @@
package lambda package lambda
// IsFreeVariable returns true if the variable name n occurs free in the expression. func (e Variable) IsFree(n string) bool {
// This function does not mutate the input expression.
func IsFreeVariable(n string, e Expression) bool {
switch e := e.(type) {
case Variable:
return e.Name() == n return e.Name() == n
case Abstraction:
return e.Parameter() != n && IsFreeVariable(n, e.Body())
case Application:
return IsFreeVariable(n, e.Abstraction()) || IsFreeVariable(n, e.Argument())
default:
return false
} }
func (e Abstraction) IsFree(n string) bool {
return e.Parameter() != n && e.Body().IsFree(n)
}
func (e Application) IsFree(n string) bool {
return e.Abstraction().IsFree(n) || e.Argument().IsFree(n)
} }

View File

@@ -1,32 +0,0 @@
package lambda
func ReduceOnce(e Expression) (Expression, bool) {
switch e := e.(type) {
case Abstraction:
body, reduced := ReduceOnce(e.Body())
if reduced {
return NewAbstraction(e.Parameter(), body), true
}
return e, false
case Application:
if fn, fnOk := e.Abstraction().(Abstraction); fnOk {
return Substitute(fn.Body(), fn.Parameter(), e.Argument()), true
}
abs, reduced := ReduceOnce(e.Abstraction())
if reduced {
return NewApplication(abs, e.Argument()), true
}
arg, reduced := ReduceOnce(e.Argument())
if reduced {
return NewApplication(e.Abstraction(), arg), true
}
return e, false
default:
return e, false
}
}

View File

@@ -1,31 +1,28 @@
package lambda package lambda
// Rename replaces all occurrences of the target variable name with the new name. // Rename replaces all occurrences of the target variable name with the new name.
func Rename(expr Expression, target string, newName string) Expression { func (e Variable) Rename(target string, newName string) Expression {
switch e := expr.(type) {
case Variable:
if e.Name() == target { if e.Name() == target {
return NewVariable(newName) return NewVariable(newName)
} }
return e
case Abstraction: return e
}
func (e Abstraction) Rename(target string, newName string) Expression {
newParam := e.Parameter() newParam := e.Parameter()
if e.Parameter() == target { if e.Parameter() == target {
newParam = newName newParam = newName
} }
newBody := Rename(e.Body(), target, newName) newBody := e.Body().Rename(target, newName)
return NewAbstraction(newParam, newBody) return NewAbstraction(newParam, newBody)
}
case Application: func (e Application) Rename(target string, newName string) Expression {
newAbs := Rename(e.Abstraction(), target, newName) newAbs := e.Abstraction().Rename(target, newName)
newArg := Rename(e.Argument(), target, newName) newArg := e.Argument().Rename(target, newName)
return NewApplication(newAbs, newArg) return NewApplication(newAbs, newArg)
default:
return expr
}
} }

View File

@@ -1,41 +1,35 @@
package lambda package lambda
// Substitute replaces all free occurrences of the target variable with the replacement expression. func (e Variable) Substitute(target string, replacement Expression) Expression {
// Alpha-renaming is performed automatically to avoid variable capture.
func Substitute(expr Expression, target string, replacement Expression) Expression {
switch e := expr.(type) {
case Variable:
if e.Name() == target { if e.Name() == target {
return replacement return replacement
} }
return e
case Abstraction: return e
}
func (e Abstraction) Substitute(target string, replacement Expression) Expression {
if e.Parameter() == target { if e.Parameter() == target {
return e return e
} }
body := e.Body() body := e.Body()
param := e.Parameter() param := e.Parameter()
if IsFreeVariable(param, replacement) { if replacement.IsFree(param) {
freeVars := GetFreeVariables(replacement) freeVars := replacement.GetFree()
freeVars.Merge(GetFreeVariables(body)) freeVars.Merge(body.GetFree())
freshVar := GenerateFreshName(freeVars) freshVar := GenerateFreshName(freeVars)
body = Rename(body, param, freshVar) body = body.Rename(param, freshVar)
param = freshVar param = freshVar
} }
newBody := Substitute(body, target, replacement) newBody := body.Substitute(target, replacement)
return NewAbstraction(param, newBody) return NewAbstraction(param, newBody)
case Application:
newAbs := Substitute(e.Abstraction(), target, replacement)
newArg := Substitute(e.Argument(), target, replacement)
return NewApplication(newAbs, newArg)
default:
return expr
} }
func (e Application) Substitute(target string, replacement Expression) Expression {
abs := e.Abstraction().Substitute(target, replacement)
arg := e.Argument().Substitute(target, replacement)
return NewApplication(abs, arg)
} }

View File

@@ -0,0 +1,34 @@
package normalorder
import "git.maximhutz.com/max/lambda/pkg/lambda"
func ReduceOnce(e lambda.Expression) (lambda.Expression, bool) {
switch e := e.(type) {
case lambda.Abstraction:
body, reduced := ReduceOnce(e.Body())
if reduced {
return lambda.NewAbstraction(e.Parameter(), body), true
}
return e, false
case lambda.Application:
if fn, fnOk := e.Abstraction().(lambda.Abstraction); fnOk {
return fn.Body().Substitute(fn.Parameter(), e.Argument()), true
}
abs, reduced := ReduceOnce(e.Abstraction())
if reduced {
return lambda.NewApplication(abs, e.Argument()), true
}
arg, reduced := ReduceOnce(e.Argument())
if reduced {
return lambda.NewApplication(e.Abstraction(), arg), true
}
return e, false
default:
return e, false
}
}

View File

@@ -1,32 +1,33 @@
package lambda package normalorder
import ( import (
"git.maximhutz.com/max/lambda/pkg/emitter" "git.maximhutz.com/max/lambda/pkg/emitter"
"git.maximhutz.com/max/lambda/pkg/expr" "git.maximhutz.com/max/lambda/pkg/expr"
"git.maximhutz.com/max/lambda/pkg/interpreter" "git.maximhutz.com/max/lambda/pkg/lambda"
"git.maximhutz.com/max/lambda/pkg/runtime"
) )
// NormalOrderReducer implements normal order (leftmost-outermost) reduction // NormalOrderReducer implements normal order (leftmost-outermost) reduction
// for lambda calculus expressions. // for lambda calculus expressions.
type Interpreter struct { type Runtime struct {
emitter.BaseEmitter[interpreter.Event] emitter.BaseEmitter[runtime.Event]
expression Expression expression lambda.Expression
} }
// NewNormalOrderReducer creates a new normal order reducer. // NewNormalOrderReducer creates a new normal order reducer.
func NewInterpreter(expression Expression) *Interpreter { func NewRuntime(expression lambda.Expression) *Runtime {
return &Interpreter{ return &Runtime{
BaseEmitter: *emitter.New[interpreter.Event](), BaseEmitter: *emitter.New[runtime.Event](),
expression: expression, expression: expression,
} }
} }
// Expression returns the current expression state. // Expression returns the current expression state.
func (r *Interpreter) Expression() expr.Expression { func (r *Runtime) Expression() expr.Expression {
return r.expression return r.expression
} }
func (r *Interpreter) Step() bool { func (r *Runtime) Step() bool {
result, done := ReduceOnce(r.expression) result, done := ReduceOnce(r.expression)
r.expression = result r.expression = result
return !done return !done
@@ -34,12 +35,12 @@ func (r *Interpreter) Step() bool {
// Reduce performs normal order reduction on a lambda expression. // Reduce performs normal order reduction on a lambda expression.
// The expression must be a lambda.Expression; other types are returned unchanged. // The expression must be a lambda.Expression; other types are returned unchanged.
func (r *Interpreter) Run() { func (r *Runtime) Run() {
r.Emit(interpreter.StartEvent) r.Emit(runtime.StartEvent)
for !r.Step() { for !r.Step() {
r.Emit(interpreter.StepEvent) r.Emit(runtime.StepEvent)
} }
r.Emit(interpreter.StopEvent) r.Emit(runtime.StopEvent)
} }

View File

@@ -1,4 +1,4 @@
package interpreter package runtime
// Event represents lifecycle events during interpretation. // Event represents lifecycle events during interpretation.
type Event int type Event int

View File

@@ -1,27 +1,27 @@
// Package interpreter provides the abstract Reducer interface for all expression // Package runtime provides the abstract Reducer interface for all expression
// reduction strategies. // reduction strategies.
package interpreter package runtime
import ( import (
"git.maximhutz.com/max/lambda/pkg/emitter" "git.maximhutz.com/max/lambda/pkg/emitter"
"git.maximhutz.com/max/lambda/pkg/expr" "git.maximhutz.com/max/lambda/pkg/expr"
) )
// Reducer defines the interface for expression reduction strategies. // Runtime defines the interface for expression reduction strategies.
// Different evaluation modes (normal order, applicative order, SKI combinators, // Different evaluation modes (normal order, applicative order, SKI combinators,
// etc.) implement this interface with their own reduction logic. // etc.) implement this interface with their own reduction logic.
// //
// Reducers also implement the Emitter interface to allow plugins to observe // Runtimes also implement the Emitter interface to allow plugins to observe
// reduction lifecycle events (Start, Step, Stop). // reduction lifecycle events (Start, Step, Stop).
type Interpreter interface { type Runtime interface {
emitter.Emitter[Event] emitter.Emitter[Event]
// Run a single step. Returns whether the interpreter is complete or not. // Run a single step. Returns whether the runtime is complete or not.
Step() bool Step() bool
// Run until completion. // Run until completion.
Run() Run()
// Expression returns the current expression state. // Copy the state of the runtime.
Expression() expr.Expression Expression() expr.Expression
} }

View File

@@ -4,9 +4,9 @@ import "iter"
type Set[T comparable] map[T]bool type Set[T comparable] map[T]bool
func (s *Set[T]) Add(items ...T) { func (s Set[T]) Add(items ...T) {
for _, item := range items { for _, item := range items {
(*s)[item] = true s[item] = true
} }
} }
@@ -14,14 +14,14 @@ func (s Set[T]) Has(item T) bool {
return s[item] return s[item]
} }
func (s *Set[T]) Remove(items ...T) { func (s Set[T]) Remove(items ...T) {
for _, item := range items { for _, item := range items {
delete(*s, item) delete(s, item)
} }
} }
func (s *Set[T]) Merge(o *Set[T]) { func (s Set[T]) Merge(o Set[T]) {
for item := range *o { for item := range o {
s.Add(item) s.Add(item)
} }
} }
@@ -46,8 +46,8 @@ func (s Set[T]) Items() iter.Seq[T] {
} }
} }
func New[T comparable](items ...T) *Set[T] { func New[T comparable](items ...T) Set[T] {
result := &Set[T]{} result := Set[T]{}
for _, item := range items { for _, item := range items {
result.Add(item) result.Add(item)