154 lines
3.9 KiB
Go
154 lines
3.9 KiB
Go
// Package registry defines a structure to hold all available representations,
|
|
// engines, and conversions between them.
|
|
package registry
|
|
|
|
import (
|
|
"fmt"
|
|
"iter"
|
|
"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{},
|
|
converter: NewConverter(),
|
|
engines: map[string]Engine{},
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
return nil, fmt.Errorf("engine '%s' not found", name)
|
|
}
|
|
|
|
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 {
|
|
return engine, nil
|
|
}
|
|
}
|
|
|
|
return r.GetEngine("normalorder")
|
|
|
|
// 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 {
|
|
return nil, err
|
|
}
|
|
|
|
result := expr
|
|
for _, conversion := range path {
|
|
result, err = conversion.Run(result)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("converting '%s' to '%s': %w", conversion.InType(), conversion.OutType(), err)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
return "", fmt.Errorf("no marshaler for '%s'", expr.Repr())
|
|
}
|
|
|
|
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 {
|
|
return nil, fmt.Errorf("no marshaler for '%s'", outType)
|
|
}
|
|
|
|
return m.Decode(s)
|
|
}
|
|
|
|
func reverse[T any](list []T) []T {
|
|
if list == nil {
|
|
return list
|
|
}
|
|
|
|
reversed := []T{}
|
|
|
|
for i := len(list) - 1; i >= 0; i-- {
|
|
reversed = append(reversed, list[i])
|
|
}
|
|
|
|
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}
|
|
for len(iteration) > 0 {
|
|
nextIteration := []string{}
|
|
|
|
for _, item := range iteration {
|
|
for _, conversion := range r.converter.ConversionsFrom(item) {
|
|
if _, ok := backtrack[conversion.OutType()]; ok {
|
|
continue
|
|
}
|
|
|
|
nextIteration = append(nextIteration, conversion.OutType())
|
|
backtrack[conversion.OutType()] = conversion
|
|
}
|
|
}
|
|
|
|
iteration = nextIteration
|
|
}
|
|
|
|
reversedPath := []Conversion{}
|
|
current := to
|
|
for current != from {
|
|
conversion, ok := backtrack[current]
|
|
if !ok {
|
|
return nil, fmt.Errorf("no valid conversion from '%s' to '%s'", from, to)
|
|
}
|
|
|
|
reversedPath = append(reversedPath, conversion)
|
|
current = conversion.InType()
|
|
}
|
|
|
|
return reverse(reversedPath), nil
|
|
}
|