docs: document remaining packages and simplify AST types (#45)
## Summary - Added doc comments across the codebase: `pkg/lambda`, `pkg/saccharine`, `pkg/codec`, `pkg/engine`, `pkg/iterator`, `pkg/set`, `pkg/convert`, `internal/registry`, and `cmd/lambda`. - Made lambda and saccharine expression structs use public fields instead of getters, matching `go/ast` conventions. - Removed superfluous constructors for saccharine and lambda expression/statement types in favor of struct literals. - Consolidated saccharine token constructors into a single `NewToken` function. - Removed the unused `trace` package. ## Test plan - [x] `go build ./...` passes. - [x] `go test ./...` passes. - [ ] Verify `go doc` output renders correctly for documented packages. Reviewed-on: #45 Co-authored-by: M.V. Hutz <git@maximhutz.me> Co-committed-by: M.V. Hutz <git@maximhutz.me>
This commit was merged in pull request #45.
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
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
|
||||
}
|
||||
|
||||
func (r baseExpr) Repr() string { return r.id }
|
||||
func (e baseExpr) Repr() string { return e.id }
|
||||
|
||||
func (r baseExpr) Data() any { return r.data }
|
||||
func (e baseExpr) Data() any { return e.data }
|
||||
|
||||
// NewExpr creates an Expr with the given representation name and data.
|
||||
func NewExpr(id string, data any) Expr { return baseExpr{id, data} }
|
||||
|
||||
@@ -4,28 +4,32 @@ import (
|
||||
"git.maximhutz.com/max/lambda/pkg/engine"
|
||||
)
|
||||
|
||||
// A Process is a type-erased reduction process that operates on Expr values.
|
||||
type Process interface {
|
||||
engine.Process[Expr]
|
||||
|
||||
// InType returns the name of the representation this process operates on.
|
||||
InType() string
|
||||
}
|
||||
|
||||
type convertedProcess[T any] struct {
|
||||
// A registeredProcess adapts a typed engine.Process[T] into the type-erased
|
||||
// Process interface. It wraps the result of Get into an Expr.
|
||||
type registeredProcess[T any] struct {
|
||||
process engine.Process[T]
|
||||
inType string
|
||||
}
|
||||
|
||||
func (e convertedProcess[T]) InType() string { return e.inType }
|
||||
func (p registeredProcess[T]) InType() string { return p.inType }
|
||||
|
||||
func (b convertedProcess[T]) Get() (Expr, error) {
|
||||
s, err := b.process.Get()
|
||||
func (p registeredProcess[T]) Get() (Expr, error) {
|
||||
s, err := p.process.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewExpr(b.inType, s), nil
|
||||
return NewExpr(p.inType, s), nil
|
||||
}
|
||||
|
||||
func (b convertedProcess[T]) Step(i int) bool {
|
||||
return b.process.Step(i)
|
||||
func (p registeredProcess[T]) Step(i int) bool {
|
||||
return p.process.Step(i)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Package registry defines a structure to hold all available representations,
|
||||
// engines, and conversions between them.
|
||||
package registry
|
||||
|
||||
import (
|
||||
@@ -6,12 +8,15 @@ import (
|
||||
"maps"
|
||||
)
|
||||
|
||||
// A Registry holds all representations, conversions, codecs, and engines
|
||||
// available to the program.
|
||||
type Registry struct {
|
||||
codecs map[string]Codec
|
||||
converter *Converter
|
||||
engines map[string]Engine
|
||||
}
|
||||
|
||||
// New makes an empty registry.
|
||||
func New() *Registry {
|
||||
return &Registry{
|
||||
codecs: map[string]Codec{},
|
||||
@@ -20,6 +25,8 @@ func New() *Registry {
|
||||
}
|
||||
}
|
||||
|
||||
// GetEngine finds an engine based on its name. Returns an error if an engine
|
||||
// with that name cannot be found.
|
||||
func (r Registry) GetEngine(name string) (Engine, error) {
|
||||
e, ok := r.engines[name]
|
||||
if !ok {
|
||||
@@ -29,10 +36,13 @@ func (r Registry) GetEngine(name string) (Engine, error) {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// ListEngines returns all available engines to the registry.
|
||||
func (r Registry) ListEngines() iter.Seq[Engine] {
|
||||
return maps.Values(r.engines)
|
||||
}
|
||||
|
||||
// GetDefaultEngine infers the preferred engine for a representation. Returns an
|
||||
// error if one cannot be chosen.
|
||||
func (r *Registry) GetDefaultEngine(id string) (Engine, error) {
|
||||
for _, engine := range r.engines {
|
||||
if engine.InType() == id {
|
||||
@@ -45,6 +55,12 @@ func (r *Registry) GetDefaultEngine(id string) (Engine, error) {
|
||||
// return nil, fmt.Errorf("no engine for '%s'", id)
|
||||
}
|
||||
|
||||
// ConvertTo attempts to convert an expression of one type of representation to
|
||||
// another. Returns the converted expression, otherwise an error.
|
||||
//
|
||||
// It can convert between any two types of representations, given there is a
|
||||
// valid conversion path between them. It uses BFS to traverse a graph of
|
||||
// conversion edges, and converts along the shortest path.
|
||||
func (r *Registry) ConvertTo(expr Expr, outType string) (Expr, error) {
|
||||
path, err := r.ConversionPath(expr.Repr(), outType)
|
||||
if err != nil {
|
||||
@@ -62,6 +78,8 @@ func (r *Registry) ConvertTo(expr Expr, outType string) (Expr, error) {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Marshal serializes an expression, given that representation has a codec.
|
||||
// Returns an error if the representation is not registered, or it has no codec.
|
||||
func (r *Registry) Marshal(expr Expr) (string, error) {
|
||||
m, ok := r.codecs[expr.Repr()]
|
||||
if !ok {
|
||||
@@ -71,6 +89,8 @@ func (r *Registry) Marshal(expr Expr) (string, error) {
|
||||
return m.Encode(expr)
|
||||
}
|
||||
|
||||
// Unmarshal deserializes an expression. Returns an error if the representation
|
||||
// or a codec for it is not registered.
|
||||
func (r *Registry) Unmarshal(s string, outType string) (Expr, error) {
|
||||
m, ok := r.codecs[outType]
|
||||
if !ok {
|
||||
@@ -94,6 +114,9 @@ func reverse[T any](list []T) []T {
|
||||
return reversed
|
||||
}
|
||||
|
||||
// ConversionPath attempts to find a set of valid conversions that (if applied)
|
||||
// convert one representation to another. Returns an error if no path can be
|
||||
// found.
|
||||
func (r *Registry) ConversionPath(from, to string) ([]Conversion, error) {
|
||||
backtrack := map[string]Conversion{}
|
||||
iteration := []string{from}
|
||||
|
||||
Reference in New Issue
Block a user