diff --git a/internal/registry/codec.go b/internal/registry/codec.go index 302d3d6..e8e9c79 100644 --- a/internal/registry/codec.go +++ b/internal/registry/codec.go @@ -7,18 +7,24 @@ import ( "git.maximhutz.com/max/lambda/pkg/codec" ) +// A Codec is a type-erased codec that serializes and deserializes expressions +// as Expr values, regardless of the underlying representation type. type Codec interface { codec.Codec[Expr] + // InType returns the name of the representation this codec handles. InType() string } -type convertedCodec[T any] struct { +// A registeredCodec adapts a typed codec.Codec[T] into the type-erased Codec +// interface. It wraps decoded values into Expr on decode, and extracts the +// underlying T from an Expr on encode. +type registeredCodec[T any] struct { codec codec.Codec[T] inType string } -func (c convertedCodec[T]) Decode(s string) (Expr, error) { +func (c registeredCodec[T]) Decode(s string) (Expr, error) { t, err := c.codec.Decode(s) if err != nil { return nil, err @@ -27,7 +33,7 @@ func (c convertedCodec[T]) Decode(s string) (Expr, error) { return NewExpr(c.inType, t), nil } -func (c convertedCodec[T]) Encode(r Expr) (string, error) { +func (c registeredCodec[T]) Encode(r Expr) (string, error) { t, ok := r.Data().(T) if !ok { dataType := reflect.TypeOf(r.Data()) @@ -38,13 +44,15 @@ func (c convertedCodec[T]) Encode(r Expr) (string, error) { return c.codec.Encode(t) } -func (c convertedCodec[T]) InType() string { return c.inType } +func (c registeredCodec[T]) InType() string { return c.inType } +// RegisterCodec registers a typed codec under the given representation name. +// Returns an error if a codec for that representation is already registered. func RegisterCodec[T any](registry *Registry, m codec.Codec[T], inType string) error { if _, ok := registry.codecs[inType]; ok { return fmt.Errorf("Codec for '%s' already registered", inType) } - registry.codecs[inType] = convertedCodec[T]{m, inType} + registry.codecs[inType] = registeredCodec[T]{m, inType} return nil } diff --git a/internal/registry/conversion.go b/internal/registry/conversion.go index d5b4d13..75674eb 100644 --- a/internal/registry/conversion.go +++ b/internal/registry/conversion.go @@ -6,19 +6,29 @@ import ( "git.maximhutz.com/max/lambda/pkg/codec" ) +// A Conversion is a type-erased transformation from one representation to +// another. It operates on Expr values, hiding the underlying representation +// types. type Conversion interface { + // InType returns the name of the source representation. InType() string + // OutType returns the name of the target representation. OutType() string + // Run applies the conversion to the given expression. Returns an error if + // the expression's data does not match the expected source type. Run(Expr) (Expr, error) } -type convertedConversion[T, U any] struct { +// A registeredConversion adapts a typed codec.Conversion[T, U] into the +// type-erased Conversion interface. It extracts the underlying T from an Expr, +// applies the conversion, and wraps the result as a new Expr. +type registeredConversion[T, U any] struct { conversion codec.Conversion[T, U] inType, outType string } -func (c convertedConversion[T, U]) Run(expr Expr) (Expr, error) { +func (c registeredConversion[T, U]) Run(expr Expr) (Expr, error) { t, ok := expr.Data().(T) if !ok { return nil, fmt.Errorf("could not parse '%v' as '%s'", t, c.inType) @@ -32,12 +42,18 @@ func (c convertedConversion[T, U]) Run(expr Expr) (Expr, error) { return NewExpr(c.outType, u), nil } -func (c convertedConversion[T, U]) InType() string { return c.inType } +func (c registeredConversion[T, U]) InType() string { return c.inType } -func (c convertedConversion[T, U]) OutType() string { return c.outType } +func (c registeredConversion[T, U]) OutType() string { return c.outType } -func RegisterConversion[T, U any](registry *Registry, conversion func(T) (U, error), inType, outType string) error { - registry.converter.Add(convertedConversion[T, U]{conversion, inType, outType}) +// RegisterConversion registers a typed conversion function between two +// representations. +func RegisterConversion[T, U any]( + registry *Registry, + conversion codec.Conversion[T, U], + inType, outType string, +) error { + registry.converter.Add(registeredConversion[T, U]{conversion, inType, outType}) return nil } diff --git a/internal/registry/converter.go b/internal/registry/converter.go index 5ae74bf..f97de98 100644 --- a/internal/registry/converter.go +++ b/internal/registry/converter.go @@ -1,13 +1,18 @@ package registry +// A Converter is a directed graph of conversions between representations. Each +// node is a representation name, and each edge is a Conversion. type Converter struct { data map[string][]Conversion } +// NewConverter creates an empty Converter with no registered conversions. func NewConverter() *Converter { return &Converter{data: map[string][]Conversion{}} } +// Add registers a conversion, adding an edge from its source representation +// to its target representation. func (g *Converter) Add(c Conversion) { conversionsFromIn, ok := g.data[c.InType()] if !ok { @@ -18,6 +23,8 @@ func (g *Converter) Add(c Conversion) { g.data[c.InType()] = conversionsFromIn } +// ConversionsFrom returns all conversions that have the given representation +// as their source type. func (g *Converter) ConversionsFrom(t string) []Conversion { return g.data[t] } diff --git a/internal/registry/engine.go b/internal/registry/engine.go index 9931916..43ff48c 100644 --- a/internal/registry/engine.go +++ b/internal/registry/engine.go @@ -6,26 +6,36 @@ import ( "git.maximhutz.com/max/lambda/pkg/engine" ) +// An Engine is a type-erased evaluation engine that can load an expression +// into a runnable Process. type Engine interface { + // Load prepares an expression for evaluation, returning a Process. Returns + // an error if the expression's data does not match the engine's expected + // representation type. Load(Expr) (Process, error) + // Name returns the name of this engine. Name() string + // InType returns the name of the representation this engine operates on. InType() string } -type convertedEngine[T any] struct { +// A registeredEngine adapts a typed engine.Engine[T] into the type-erased +// Engine interface. It extracts the underlying T from an Expr before passing it +// to the engine. +type registeredEngine[T any] struct { engine engine.Engine[T] name string inType string } -func (e convertedEngine[T]) InType() string { return e.inType } +func (e registeredEngine[T]) InType() string { return e.inType } -func (e convertedEngine[T]) Name() string { return e.name } +func (e registeredEngine[T]) Name() string { return e.name } -func (e convertedEngine[T]) Load(expr Expr) (Process, error) { +func (e registeredEngine[T]) Load(expr Expr) (Process, error) { t, ok := expr.Data().(T) if !ok { - return nil, fmt.Errorf("'ncorrent format '%s' for engine '%s'", expr.Repr(), e.inType) + return nil, fmt.Errorf("incorrect format '%s' for engine '%s'", expr.Repr(), e.inType) } process, err := e.engine(t) @@ -33,14 +43,16 @@ func (e convertedEngine[T]) Load(expr Expr) (Process, error) { return nil, err } - return convertedProcess[T]{process, e.inType}, nil + return registeredProcess[T]{process, e.inType}, nil } +// RegisterEngine registers a typed engine under the given name. Returns an +// error if an engine with that name is already registered. func RegisterEngine[T any](registry *Registry, e engine.Engine[T], name, inType string) error { if _, ok := registry.engines[name]; ok { return fmt.Errorf("engine '%s' already registered", name) } - registry.engines[name] = &convertedEngine[T]{e, name, inType} + registry.engines[name] = ®isteredEngine[T]{e, name, inType} return nil } diff --git a/internal/registry/expr.go b/internal/registry/expr.go index 49377ae..71db96d 100644 --- a/internal/registry/expr.go +++ b/internal/registry/expr.go @@ -1,17 +1,18 @@ package registry -// A Expr is a lambda calculus expression. It can have any type of -// Expresentation, so long as that class is known to the registry it is handled +// An Expr is a type-erased lambda calculus expression. It can have any type of +// representation, so long as that type is known to the registry it is handled // by. type Expr interface { - // Repr returns the name of the underlying Expresentation. It is assumed if - // two expressions have the same Repr(), they have the same Expresentation. + // Repr returns the name of the underlying representation. Two expressions + // with the same Repr() are assumed to have the same representation type. Repr() string - // The base expression data. + // Data returns the underlying expression data. Data() any } +// A baseExpr is the default implementation of Expr. type baseExpr struct { id string data any @@ -21,4 +22,5 @@ func (r baseExpr) Repr() string { return r.id } func (r baseExpr) Data() any { return r.data } +// NewExpr creates an Expr with the given representation name and data. func NewExpr(id string, data any) Expr { return baseExpr{id, data} } diff --git a/internal/registry/process.go b/internal/registry/process.go index 9e4a0f0..2a83685 100644 --- a/internal/registry/process.go +++ b/internal/registry/process.go @@ -10,14 +10,14 @@ type Process interface { InType() string } -type convertedProcess[T any] struct { +type registeredProcess[T any] struct { process engine.Process[T] inType string } -func (e convertedProcess[T]) InType() string { return e.inType } +func (e registeredProcess[T]) InType() string { return e.inType } -func (b convertedProcess[T]) Get() (Expr, error) { +func (b registeredProcess[T]) Get() (Expr, error) { s, err := b.process.Get() if err != nil { return nil, err @@ -26,6 +26,6 @@ func (b convertedProcess[T]) Get() (Expr, error) { return NewExpr(b.inType, s), nil } -func (b convertedProcess[T]) Step(i int) bool { +func (b registeredProcess[T]) Step(i int) bool { return b.process.Step(i) }