From 0cdce0e42c93aac58a8e55ef8936725652f71532 Mon Sep 17 00:00:00 2001 From: "M.V. Hutz" Date: Fri, 30 Jan 2026 17:54:47 -0500 Subject: [PATCH] feat: cli versions --- internal/cli/codec.go | 55 ++++++++++++++++++++++++++++ internal/cli/engine.go | 49 +++++++++++++++++++++++++ internal/cli/marshaler.go | 42 ++++++++++++++++++++++ internal/cli/repr.go | 21 +++++++++++ internal/plugins/debug.go | 23 ------------ internal/plugins/explanation.go | 31 ---------------- internal/plugins/performance.go | 59 ------------------------------- internal/plugins/statistics.go | 44 ----------------------- internal/statistics/statistics.go | 28 --------------- pkg/codec/codec.go | 10 ++---- pkg/engine/engine.go | 7 ++++ pkg/lambda/expression.go | 8 ----- pkg/normalorder/reduce_once.go | 34 ------------------ pkg/normalorder/runtime.go | 46 ------------------------ pkg/repr/repr.go | 15 -------- pkg/runtime/events.go | 13 ------- pkg/runtime/runtime.go | 27 -------------- 17 files changed, 177 insertions(+), 335 deletions(-) create mode 100644 internal/cli/codec.go create mode 100644 internal/cli/engine.go create mode 100644 internal/cli/marshaler.go create mode 100644 internal/cli/repr.go delete mode 100644 internal/plugins/debug.go delete mode 100644 internal/plugins/explanation.go delete mode 100644 internal/plugins/performance.go delete mode 100644 internal/plugins/statistics.go delete mode 100644 internal/statistics/statistics.go create mode 100644 pkg/engine/engine.go delete mode 100644 pkg/normalorder/reduce_once.go delete mode 100644 pkg/normalorder/runtime.go delete mode 100644 pkg/repr/repr.go delete mode 100644 pkg/runtime/events.go delete mode 100644 pkg/runtime/runtime.go diff --git a/internal/cli/codec.go b/internal/cli/codec.go new file mode 100644 index 0000000..6f06f29 --- /dev/null +++ b/internal/cli/codec.go @@ -0,0 +1,55 @@ +package cli + +import ( + "fmt" + + "git.maximhutz.com/max/lambda/pkg/codec" +) + +type Codec interface { + codec.Codec[Repr, Repr] + + InType() string + OutType() string +} + +type convertedCodec[T, U any] struct { + codec codec.Codec[T, U] + inType, outType string +} + +func (c convertedCodec[T, U]) Decode(r Repr) (Repr, error) { + u, ok := r.Data().(U) + if !ok { + return nil, fmt.Errorf("could not parse '%v' as '%s'", r, c.inType) + } + + t, err := c.codec.Decode(u) + if err != nil { + return nil, err + } + + return NewRepr(c.outType, t), nil +} + +func (c convertedCodec[T, U]) Encode(r Repr) (Repr, error) { + t, ok := r.Data().(T) + if !ok { + return nil, fmt.Errorf("could not parse '%v' as '%s'", t, c.outType) + } + + u, err := c.codec.Encode(t) + if err != nil { + return nil, err + } + + return NewRepr(c.inType, u), nil +} + +func (c convertedCodec[T, U]) InType() string { return c.inType } + +func (c convertedCodec[T, U]) OutType() string { return c.outType } + +func ConvertCodec[T, U any](e codec.Codec[T, U], inType, outType string) Codec { + return convertedCodec[T, U]{e, inType, outType} +} diff --git a/internal/cli/engine.go b/internal/cli/engine.go new file mode 100644 index 0000000..f0141cd --- /dev/null +++ b/internal/cli/engine.go @@ -0,0 +1,49 @@ +package cli + +import ( + "fmt" + + "git.maximhutz.com/max/lambda/pkg/engine" +) + +type Engine interface { + engine.Engine[Repr] + + Name() string + InType() string +} + +type convertedEngine[T any] struct { + engine engine.Engine[T] + name string + inType string +} + +func (b convertedEngine[T]) InType() string { return b.inType } + +func (b convertedEngine[T]) Name() string { return b.name } + +func (b convertedEngine[T]) Get() (Repr, error) { + s, err := b.engine.Get() + if err != nil { + return nil, err + } + + return NewRepr(b.inType, s), nil +} + +func (b convertedEngine[T]) Set(r Repr) error { + if t, ok := r.Data().(T); ok { + return b.engine.Set(t) + } + + return fmt.Errorf("Incorrent format '%s' for engine '%s'.", r.Id(), b.inType) +} + +func (b convertedEngine[T]) Step(i int) bool { + return b.engine.Step(i) +} + +func ConvertEngine[T any](e engine.Engine[T], name string, inType string) Engine { + return convertedEngine[T]{e, name, inType} +} diff --git a/internal/cli/marshaler.go b/internal/cli/marshaler.go new file mode 100644 index 0000000..e10f7ee --- /dev/null +++ b/internal/cli/marshaler.go @@ -0,0 +1,42 @@ +package cli + +import ( + "fmt" + + "git.maximhutz.com/max/lambda/pkg/codec" +) + +type Marshaler interface { + codec.Marshaler[Repr] + + InType() string +} + +type convertedMarshaler[T any] struct { + codec codec.Marshaler[T] + inType string +} + +func (c convertedMarshaler[T]) Decode(s string) (Repr, error) { + t, err := c.codec.Decode(s) + if err != nil { + return nil, err + } + + return NewRepr(c.inType, t), nil +} + +func (c convertedMarshaler[T]) Encode(r Repr) (string, error) { + t, ok := r.Data().(T) + if !ok { + return "", fmt.Errorf("could not parse '%v' as 'string'", t) + } + + return c.codec.Encode(t) +} + +func (c convertedMarshaler[T]) InType() string { return c.inType } + +func ConvertMarshaler[T any](e codec.Marshaler[T], inType string) Marshaler { + return convertedMarshaler[T]{e, inType} +} diff --git a/internal/cli/repr.go b/internal/cli/repr.go new file mode 100644 index 0000000..c684639 --- /dev/null +++ b/internal/cli/repr.go @@ -0,0 +1,21 @@ +package cli + +type Repr interface { + // Id returns to name of the objects underlying representation. If is + // assumed that if two Repr objects have the same Id(), they share the same + // representation. + Id() string + + Data() any +} + +type baseRepr struct { + id string + data any +} + +func (r baseRepr) Id() string { return r.id } + +func (r baseRepr) Data() any { return r.data } + +func NewRepr(id string, data any) Repr { return baseRepr{id, data} } diff --git a/internal/plugins/debug.go b/internal/plugins/debug.go deleted file mode 100644 index 82cd1b8..0000000 --- a/internal/plugins/debug.go +++ /dev/null @@ -1,23 +0,0 @@ -package plugins - -import ( - "log/slog" - - "git.maximhutz.com/max/lambda/pkg/runtime" -) - -type Logs struct { - logger *slog.Logger - reducer runtime.Runtime -} - -func NewLogs(logger *slog.Logger, r runtime.Runtime) *Logs { - plugin := &Logs{logger, r} - r.On(runtime.StepEvent, plugin.Step) - - return plugin -} - -func (t *Logs) Step() { - t.logger.Info("reduction", "tree", t.reducer.Expression().String()) -} diff --git a/internal/plugins/explanation.go b/internal/plugins/explanation.go deleted file mode 100644 index ba92a99..0000000 --- a/internal/plugins/explanation.go +++ /dev/null @@ -1,31 +0,0 @@ -// Package "explanation" provides an observer to gather the reasoning during the -// reduction, and present a thorough explanation to the user for each step. -package plugins - -import ( - "fmt" - - "git.maximhutz.com/max/lambda/pkg/runtime" -) - -// Track the reductions made by a reduction process. -type Explanation struct { - reducer runtime.Runtime -} - -// Attaches a new explanation tracker to a reducer. -func NewExplanation(r runtime.Runtime) *Explanation { - plugin := &Explanation{reducer: r} - r.On(runtime.StartEvent, plugin.Start) - r.On(runtime.StepEvent, plugin.Step) - - return plugin -} - -func (t *Explanation) Start() { - fmt.Println(t.reducer.Expression().String()) -} - -func (t *Explanation) Step() { - fmt.Println(" =", t.reducer.Expression().String()) -} diff --git a/internal/plugins/performance.go b/internal/plugins/performance.go deleted file mode 100644 index eac3f20..0000000 --- a/internal/plugins/performance.go +++ /dev/null @@ -1,59 +0,0 @@ -// Package "performance" provides a tracker to observer CPU performance during -// execution. -package plugins - -import ( - "os" - "path/filepath" - "runtime/pprof" - - "git.maximhutz.com/max/lambda/pkg/runtime" -) - -// Observes a reduction process, and publishes a CPU performance profile on -// completion. -type Performance struct { - File string - filePointer *os.File - Error error -} - -// Create a performance tracker that outputs a profile to "file". -func NewPerformance(file string, process runtime.Runtime) *Performance { - plugin := &Performance{File: file} - process.On(runtime.StartEvent, plugin.Start) - process.On(runtime.StopEvent, plugin.Stop) - - return plugin -} - -// Begin profiling. -func (t *Performance) Start() { - var absPath string - - absPath, t.Error = filepath.Abs(t.File) - if t.Error != nil { - return - } - - t.Error = os.MkdirAll(filepath.Dir(absPath), 0777) - if t.Error != nil { - return - } - - t.filePointer, t.Error = os.Create(absPath) - if t.Error != nil { - return - } - - t.Error = pprof.StartCPUProfile(t.filePointer) - if t.Error != nil { - return - } -} - -// Stop profiling. -func (t *Performance) Stop() { - pprof.StopCPUProfile() - t.filePointer.Close() -} diff --git a/internal/plugins/statistics.go b/internal/plugins/statistics.go deleted file mode 100644 index 2845738..0000000 --- a/internal/plugins/statistics.go +++ /dev/null @@ -1,44 +0,0 @@ -package plugins - -import ( - "fmt" - "os" - "time" - - "git.maximhutz.com/max/lambda/internal/statistics" - "git.maximhutz.com/max/lambda/pkg/runtime" -) - -// An observer, to track reduction performance. -type Statistics struct { - start time.Time - steps uint64 -} - -// Create a new reduction performance Statistics. -func NewStatistics(r runtime.Runtime) *Statistics { - plugin := &Statistics{} - r.On(runtime.StartEvent, plugin.Start) - r.On(runtime.StepEvent, plugin.Step) - r.On(runtime.StopEvent, plugin.Stop) - - return plugin -} - -func (t *Statistics) Start() { - t.start = time.Now() - t.steps = 0 -} - -func (t *Statistics) Step() { - t.steps++ -} - -func (t *Statistics) Stop() { - results := statistics.Results{ - StepsTaken: t.steps, - TimeElapsed: uint64(time.Since(t.start).Milliseconds()), - } - - fmt.Fprint(os.Stderr, results.String()) -} diff --git a/internal/statistics/statistics.go b/internal/statistics/statistics.go deleted file mode 100644 index 87cf386..0000000 --- a/internal/statistics/statistics.go +++ /dev/null @@ -1,28 +0,0 @@ -// Package "statistics" provides a way to observer reduction speed during -// execution. -package statistics - -import ( - "fmt" - "strings" -) - -// Statistics for a specific reduction. -type Results struct { - StepsTaken uint64 // Number of steps taken during execution. - TimeElapsed uint64 // The time (ms) taken for execution to complete. -} - -// Returns the average number of operations per second of the execution. -func (r Results) OpsPerSecond() float32 { - return float32(r.StepsTaken) / (float32(r.TimeElapsed) / 1000) -} - -// Format the results as a string. -func (r Results) String() string { - builder := strings.Builder{} - fmt.Fprintln(&builder, "Time Spent:", r.TimeElapsed, "ms") - fmt.Fprintln(&builder, "Steps:", r.StepsTaken) - fmt.Fprintln(&builder, "Speed:", r.OpsPerSecond(), "ops") - return builder.String() -} diff --git a/pkg/codec/codec.go b/pkg/codec/codec.go index 7fc8a7f..3132409 100644 --- a/pkg/codec/codec.go +++ b/pkg/codec/codec.go @@ -1,12 +1,8 @@ package codec -import "git.maximhutz.com/max/lambda/pkg/repr" - -type String string - -func (s String) Id() string { return "string" } - -type Codec[T, U repr.Repr] interface { +type Codec[T, U any] interface { Encode(T) (U, error) Decode(U) (T, error) } + +type Marshaler[T any] = Codec[T, string] diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go new file mode 100644 index 0000000..22f716d --- /dev/null +++ b/pkg/engine/engine.go @@ -0,0 +1,7 @@ +package engine + +type Engine[T any] interface { + Get() (T, error) + Set(T) error + Step(int) bool +} diff --git a/pkg/lambda/expression.go b/pkg/lambda/expression.go index 3b78f13..b979a09 100644 --- a/pkg/lambda/expression.go +++ b/pkg/lambda/expression.go @@ -3,14 +3,12 @@ package lambda import ( "fmt" - "git.maximhutz.com/max/lambda/pkg/repr" "git.maximhutz.com/max/lambda/pkg/set" ) // Expression is the interface for all lambda calculus expression types. // It embeds the general expr.Expression interface for cross-mode compatibility. type Expression interface { - repr.Repr fmt.Stringer // Substitute replaces all free occurrences of the target variable with the @@ -40,8 +38,6 @@ type Abstraction struct { var _ Expression = Abstraction{} -func (a Abstraction) Id() string { return "lambda" } - func (a Abstraction) Parameter() string { return a.parameter } @@ -67,8 +63,6 @@ type Application struct { var _ Expression = Application{} -func (a Application) Id() string { return "lambda" } - func (a Application) Abstraction() Expression { return a.abstraction } @@ -93,8 +87,6 @@ type Variable struct { var _ Expression = Variable{} -func (a Variable) Id() string { return "lambda" } - func (v Variable) Name() string { return v.name } diff --git a/pkg/normalorder/reduce_once.go b/pkg/normalorder/reduce_once.go deleted file mode 100644 index e1a8aff..0000000 --- a/pkg/normalorder/reduce_once.go +++ /dev/null @@ -1,34 +0,0 @@ -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 - } -} diff --git a/pkg/normalorder/runtime.go b/pkg/normalorder/runtime.go deleted file mode 100644 index c014e3d..0000000 --- a/pkg/normalorder/runtime.go +++ /dev/null @@ -1,46 +0,0 @@ -package normalorder - -import ( - "git.maximhutz.com/max/lambda/pkg/emitter" - "git.maximhutz.com/max/lambda/pkg/lambda" - "git.maximhutz.com/max/lambda/pkg/repr" - "git.maximhutz.com/max/lambda/pkg/runtime" -) - -// NormalOrderReducer implements normal order (leftmost-outermost) reduction -// for lambda calculus expressions. -type Runtime struct { - emitter.BaseEmitter[runtime.Event] - expression lambda.Expression -} - -// NewNormalOrderReducer creates a new normal order reducer. -func NewRuntime(expression lambda.Expression) *Runtime { - return &Runtime{ - BaseEmitter: *emitter.New[runtime.Event](), - expression: expression, - } -} - -// Expression returns the current expression state. -func (r *Runtime) Expression() repr.Repr { - return r.expression -} - -func (r *Runtime) Step() bool { - result, done := ReduceOnce(r.expression) - r.expression = result - return !done -} - -// Reduce performs normal order reduction on a lambda expression. -// The expression must be a lambda.Expression; other types are returned unchanged. -func (r *Runtime) Run() { - r.Emit(runtime.StartEvent) - - for !r.Step() { - r.Emit(runtime.StepEvent) - } - - r.Emit(runtime.StopEvent) -} diff --git a/pkg/repr/repr.go b/pkg/repr/repr.go deleted file mode 100644 index a1248f0..0000000 --- a/pkg/repr/repr.go +++ /dev/null @@ -1,15 +0,0 @@ -// Package repr defines a general definition of a representation of lambda -// calculus. -package repr - -import "fmt" - -// Repr is a representation of lambda calculus. -type Repr interface { - fmt.Stringer - - // Id returns to name of the objects underlying representation. If is - // assumed that if two Repr objects have the same Id(), they share the same - // representation. - Id() string -} diff --git a/pkg/runtime/events.go b/pkg/runtime/events.go deleted file mode 100644 index 6e884c8..0000000 --- a/pkg/runtime/events.go +++ /dev/null @@ -1,13 +0,0 @@ -package runtime - -// Event represents lifecycle events during interpretation. -type Event int - -const ( - // StartEvent is emitted before interpretation begins. - StartEvent Event = iota - // StepEvent is emitted after each interpretation step. - StepEvent - // StopEvent is emitted after interpretation completes. - StopEvent -) diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go deleted file mode 100644 index 5749045..0000000 --- a/pkg/runtime/runtime.go +++ /dev/null @@ -1,27 +0,0 @@ -// Package runtime provides the abstract Reducer interface for all expression -// reduction strategies. -package runtime - -import ( - "git.maximhutz.com/max/lambda/pkg/emitter" - "git.maximhutz.com/max/lambda/pkg/repr" -) - -// Runtime 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. -// -// Runtimes also implement the Emitter interface to allow plugins to observe -// reduction lifecycle events (Start, Step, Stop). -type Runtime interface { - emitter.Emitter[Event] - - // Run a single step. Returns whether the runtime is complete or not. - Step() bool - - // Run until completion. - Run() - - // Copy the state of the runtime. - Expression() repr.Repr -}