diff --git a/cmd/lambda/registry.go b/cmd/lambda/registry.go new file mode 100644 index 0000000..b983bc1 --- /dev/null +++ b/cmd/lambda/registry.go @@ -0,0 +1,24 @@ +package main + +import ( + "git.maximhutz.com/max/lambda/internal/cli" + "git.maximhutz.com/max/lambda/internal/registry" + "git.maximhutz.com/max/lambda/pkg/convert" + "git.maximhutz.com/max/lambda/pkg/engine/normalorder" + "git.maximhutz.com/max/lambda/pkg/lambda" +) + +func MakeRegistry() *registry.Registry { + r := registry.New() + + // Codecs + r.AddCodec(cli.ConvertCodec(convert.Saccharine2Lambda{}, "saccharine", "lambda")) + + // Engines + r.AddEngine(cli.ConvertEngine(normalorder.Engine{}, "normalorder", "lambda")) + + // Marshalers + r.AddMarshaler(cli.ConvertMarshaler(lambda.Marshaler{}, "lambda")) + + return r +} diff --git a/internal/registry/registry.go b/internal/registry/registry.go new file mode 100644 index 0000000..de6256a --- /dev/null +++ b/internal/registry/registry.go @@ -0,0 +1,75 @@ +package registry + +import ( + "fmt" + + "git.maximhutz.com/max/lambda/internal/cli" +) + +type Registry struct { + marshalers map[string]cli.Marshaler + codecs []cli.Codec + engines map[string]cli.Engine +} + +func New() *Registry { + return &Registry{ + marshalers: map[string]cli.Marshaler{}, + codecs: []cli.Codec{}, + engines: map[string]cli.Engine{}, + } +} + +func (r *Registry) AddCodec(c cli.Codec) error { + r.codecs = append(r.codecs, c) + return nil +} + +func (r *Registry) AddMarshaler(c cli.Marshaler) error { + if _, ok := r.marshalers[c.InType()]; ok { + return fmt.Errorf("marshaler for '%s' already registered", c.InType()) + } + + r.marshalers[c.InType()] = c + return nil +} + +func (r *Registry) AddEngine(e cli.Engine) error { + if _, ok := r.engines[e.Name()]; ok { + return fmt.Errorf("engine '%s' already registered", e.Name()) + } + + r.engines[e.Name()] = e + return nil +} + +func (r *Registry) GetEngine(name string) (cli.Engine, error) { + e, ok := r.engines[name] + if !ok { + return nil, fmt.Errorf("engine '%s' not found", name) + } + + return e, nil +} + +func (r *Registry) ConvertTo(repr cli.Repr, outType string) (cli.Repr, error) { + panic("") +} + +func (r *Registry) Marshal(repr cli.Repr) (string, error) { + m, ok := r.marshalers[repr.Id()] + if !ok { + return "", fmt.Errorf("no marshaler for '%s'", repr.Id()) + } + + return m.Encode(repr) +} + +func (r *Registry) Unmarshal(s string, outType string) (cli.Repr, error) { + m, ok := r.marshalers[outType] + if !ok { + return nil, fmt.Errorf("no marshaler for '%s'", outType) + } + + return m.Decode(s) +} diff --git a/pkg/convert/saccharine_to_lambda.go b/pkg/convert/saccharine_to_lambda.go index 8bab6b8..e19e77c 100644 --- a/pkg/convert/saccharine_to_lambda.go +++ b/pkg/convert/saccharine_to_lambda.go @@ -3,16 +3,17 @@ package convert import ( "fmt" + "git.maximhutz.com/max/lambda/pkg/codec" "git.maximhutz.com/max/lambda/pkg/lambda" "git.maximhutz.com/max/lambda/pkg/saccharine" ) -func convertAtom(n *saccharine.Atom) lambda.Expression { +func encodeAtom(n *saccharine.Atom) lambda.Expression { return lambda.NewVariable(n.Name) } -func convertAbstraction(n *saccharine.Abstraction) lambda.Expression { - result := SaccharineToLambda(n.Body) +func encodeAbstraction(n *saccharine.Abstraction) lambda.Expression { + result := encodeExpression(n.Body) parameters := n.Parameters @@ -31,13 +32,13 @@ func convertAbstraction(n *saccharine.Abstraction) lambda.Expression { return result } -func convertApplication(n *saccharine.Application) lambda.Expression { - result := SaccharineToLambda(n.Abstraction) +func encodeApplication(n *saccharine.Application) lambda.Expression { + result := encodeExpression(n.Abstraction) arguments := []lambda.Expression{} for _, argument := range n.Arguments { - convertedArgument := SaccharineToLambda(argument) - arguments = append(arguments, convertedArgument) + encodeedArgument := encodeExpression(argument) + arguments = append(arguments, encodeedArgument) } for _, argument := range arguments { @@ -51,9 +52,9 @@ func reduceLet(s *saccharine.LetStatement, e lambda.Expression) lambda.Expressio var value lambda.Expression if len(s.Parameters) == 0 { - value = SaccharineToLambda(s.Body) + value = encodeExpression(s.Body) } else { - value = convertAbstraction(saccharine.NewAbstraction(s.Parameters, s.Body)) + value = encodeAbstraction(saccharine.NewAbstraction(s.Parameters, s.Body)) } return lambda.NewApplication( @@ -67,7 +68,7 @@ func reduceDeclare(s *saccharine.DeclareStatement, e lambda.Expression) lambda.E return lambda.NewApplication( lambda.NewAbstraction(freshVar, e), - SaccharineToLambda(s.Value), + encodeExpression(s.Value), ) } @@ -82,8 +83,8 @@ func reduceStatement(s saccharine.Statement, e lambda.Expression) lambda.Express } } -func convertClause(n *saccharine.Clause) lambda.Expression { - result := SaccharineToLambda(n.Returns) +func encodeClause(n *saccharine.Clause) lambda.Expression { + result := encodeExpression(n.Returns) for i := len(n.Statements) - 1; i >= 0; i-- { result = reduceStatement(n.Statements[i], result) @@ -92,17 +93,46 @@ func convertClause(n *saccharine.Clause) lambda.Expression { return result } -func SaccharineToLambda(n saccharine.Expression) lambda.Expression { - switch n := n.(type) { +func encodeExpression(s saccharine.Expression) lambda.Expression { + switch s := s.(type) { case *saccharine.Atom: - return convertAtom(n) + return encodeAtom(s) case *saccharine.Abstraction: - return convertAbstraction(n) + return encodeAbstraction(s) case *saccharine.Application: - return convertApplication(n) + return encodeApplication(s) case *saccharine.Clause: - return convertClause(n) + return encodeClause(s) default: - panic(fmt.Errorf("unknown expression type: %T", n)) + panic(fmt.Errorf("unknown expression type: %T", s)) } } + +func decodeExression(l lambda.Expression) saccharine.Expression { + switch l := l.(type) { + case lambda.Variable: + return saccharine.NewAtom(l.Name()) + case lambda.Abstraction: + return saccharine.NewAbstraction( + []string{l.Parameter()}, + decodeExression(l.Body())) + case lambda.Application: + return saccharine.NewApplication( + decodeExression(l.Abstraction()), + []saccharine.Expression{decodeExression(l.Argument())}) + default: + panic(fmt.Errorf("unknown expression type: %T", l)) + } +} + +type Saccharine2Lambda struct{} + +func (c Saccharine2Lambda) Decode(l lambda.Expression) (saccharine.Expression, error) { + return decodeExression(l), nil +} + +func (c Saccharine2Lambda) Encode(s saccharine.Expression) (lambda.Expression, error) { + return encodeExpression(s), nil +} + +var _ codec.Codec[saccharine.Expression, lambda.Expression] = (*Saccharine2Lambda)(nil) diff --git a/pkg/engine/normalorder/engine.go b/pkg/engine/normalorder/engine.go new file mode 100644 index 0000000..41eca85 --- /dev/null +++ b/pkg/engine/normalorder/engine.go @@ -0,0 +1,34 @@ +package normalorder + +import ( + "git.maximhutz.com/max/lambda/pkg/engine" + "git.maximhutz.com/max/lambda/pkg/lambda" +) + +type Engine struct { + expr lambda.Expression +} + +func (e Engine) Get() (lambda.Expression, error) { + return e.expr, nil +} + +func (e Engine) Set(l lambda.Expression) error { + e.expr = l + return nil +} + +func (e Engine) Step(i int) bool { + var reduced bool + + for range i { + e.expr, reduced = ReduceOnce(e.expr) + if !reduced { + return false + } + } + + return true +} + +var _ engine.Engine[lambda.Expression] = (*Engine)(nil) diff --git a/pkg/engine/normalorder/reduce_one.go b/pkg/engine/normalorder/reduce_one.go new file mode 100644 index 0000000..e1a8aff --- /dev/null +++ b/pkg/engine/normalorder/reduce_one.go @@ -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 + } +} diff --git a/pkg/lambda/marshaler.go b/pkg/lambda/marshaler.go new file mode 100644 index 0000000..6ee2c79 --- /dev/null +++ b/pkg/lambda/marshaler.go @@ -0,0 +1,19 @@ +package lambda + +import ( + "fmt" + + "git.maximhutz.com/max/lambda/pkg/codec" +) + +type Marshaler struct{} + +func (m Marshaler) Decode(string) (Expression, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (m Marshaler) Encode(e Expression) (string, error) { + return e.String(), nil +} + +var _ codec.Marshaler[Expression] = (*Marshaler)(nil)