diff --git a/.golangci.yml b/.golangci.yml index 9c9494b..1ec3d80 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -160,6 +160,8 @@ linters: arguments: # make error messages clearer - "sayRepetitiveInsteadOfStutters" + # require comments on public interface methods + - "checkPublicInterface" # incrementing an integer variable by 1 is recommended to be done using the `++` operator - name: increment-decrement diff --git a/cmd/lambda/lambda_reduce.go b/cmd/lambda/lambda_reduce.go index 29684fa..fbe9929 100644 --- a/cmd/lambda/lambda_reduce.go +++ b/cmd/lambda/lambda_reduce.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" "git.maximhutz.com/max/lambda/internal/cli" - "git.maximhutz.com/max/lambda/internal/config" + "git.maximhutz.com/max/lambda/internal/registry" ) func LambdaReduce() *cobra.Command { @@ -27,14 +27,14 @@ func LambdaReduce() *cobra.Command { inputPath := args[0] // Get input source. - var source config.Source + var source cli.Source if inputPath == "-" { - source = config.StdinSource{} + source = cli.StdinSource{} } else { - source = config.FileSource{Path: inputPath} + source = cli.FileSource{Path: inputPath} } - destination := config.StdoutDestination{} + destination := cli.StdoutDestination{} r := GetRegistry() @@ -53,7 +53,7 @@ func LambdaReduce() *cobra.Command { } // Find engine. - var engine cli.Engine + var engine registry.Engine if engineFlag == "" { if engine, err = r.GetDefaultEngine(inputRepr); err != nil { return err @@ -77,8 +77,7 @@ func LambdaReduce() *cobra.Command { } // Create process. - process := engine.Load() - err = process.Set(compiled) + process, err := engine.Load(compiled) if err != nil { return err } diff --git a/cmd/lambda/registry.go b/cmd/lambda/registry.go index 5603971..e0b3f57 100644 --- a/cmd/lambda/registry.go +++ b/cmd/lambda/registry.go @@ -1,7 +1,6 @@ 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" @@ -13,14 +12,15 @@ func GetRegistry() *registry.Registry { r := registry.New() // Codecs - r.MustAddConversions(cli.ConvertCodec(convert.Saccharine2Lambda{}, "saccharine", "lambda")...) + (registry.RegisterConversion(r, convert.Saccharine2Lambda, "saccharine", "lambda")) + (registry.RegisterConversion(r, convert.Lambda2Saccharine, "lambda", "saccharine")) // Engines - r.MustAddEngine(cli.ConvertEngine(normalorder.Engine{}, "normalorder", "lambda")) + (registry.RegisterEngine(r, normalorder.NewProcess, "normalorder", "lambda")) // Marshalers - r.MustAddMarshaler(cli.ConvertMarshaler(lambda.Marshaler{}, "lambda")) - r.MustAddMarshaler(cli.ConvertMarshaler(saccharine.Marshaler{}, "saccharine")) + (registry.RegisterCodec(r, lambda.Marshaler{}, "lambda")) + (registry.RegisterCodec(r, saccharine.Marshaler{}, "saccharine")) return r } diff --git a/internal/cli/cli.go b/internal/cli/cli.go new file mode 100644 index 0000000..a1ec311 --- /dev/null +++ b/internal/cli/cli.go @@ -0,0 +1,2 @@ +// Package cli package provides various utilities to the 'lambda' program. +package cli diff --git a/internal/cli/conversion.go b/internal/cli/conversion.go deleted file mode 100644 index d198f8e..0000000 --- a/internal/cli/conversion.go +++ /dev/null @@ -1,67 +0,0 @@ -package cli - -import ( - "fmt" - - "git.maximhutz.com/max/lambda/pkg/codec" -) - -type Conversion interface { - InType() string - OutType() string - - Run(Repr) (Repr, error) -} - -type forwardCodec[T, U any] struct { - codec codec.Codec[T, U] - inType, outType string -} - -func (c forwardCodec[T, U]) Run(r Repr) (Repr, error) { - t, ok := r.Data().(T) - if !ok { - return nil, fmt.Errorf("could not parse '%v' as '%s'", t, c.inType) - } - - u, err := c.codec.Encode(t) - if err != nil { - return nil, err - } - - return NewRepr(c.outType, u), nil -} - -func (c forwardCodec[T, U]) InType() string { return c.inType } - -func (c forwardCodec[T, U]) OutType() string { return c.outType } - -type backwardCodec[T, U any] struct { - codec codec.Codec[T, U] - inType, outType string -} - -func (c backwardCodec[T, U]) Run(r Repr) (Repr, error) { - u, ok := r.Data().(U) - if !ok { - return nil, fmt.Errorf("could not parse '%v' as '%s'", r, c.outType) - } - - t, err := c.codec.Decode(u) - if err != nil { - return nil, err - } - - return NewRepr(c.inType, t), nil -} - -func (c backwardCodec[T, U]) InType() string { return c.outType } - -func (c backwardCodec[T, U]) OutType() string { return c.inType } - -func ConvertCodec[T, U any](e codec.Codec[T, U], inType, outType string) []Conversion { - return []Conversion{ - forwardCodec[T, U]{e, inType, outType}, - backwardCodec[T, U]{e, inType, outType}, - } -} diff --git a/internal/config/destination.go b/internal/cli/destination.go similarity index 64% rename from internal/config/destination.go rename to internal/cli/destination.go index e9fd236..4917a12 100644 --- a/internal/config/destination.go +++ b/internal/cli/destination.go @@ -1,27 +1,29 @@ -package config +package cli import ( "fmt" "os" ) -// A method of writing output to the user. +// A Destination is method of writing output to the user. type Destination interface { // Write data to this destination. Write(data string) error } -// A destination writing to stdout. +// An StdoutDestination writes to stdout. type StdoutDestination struct{} +// Write outputs to standard output. func (d StdoutDestination) Write(data string) error { fmt.Println(data) return nil } -// A destination writing to a file. +// A FileDestination writes to a file. type FileDestination struct{ Path string } +// Write outputs to a file. func (d FileDestination) Write(data string) error { return os.WriteFile(d.Path, []byte(data+"\n"), 0644) } diff --git a/internal/cli/engine.go b/internal/cli/engine.go deleted file mode 100644 index 15f1618..0000000 --- a/internal/cli/engine.go +++ /dev/null @@ -1,27 +0,0 @@ -package cli - -import "git.maximhutz.com/max/lambda/pkg/engine" - -type Engine interface { - Load() Process - Name() string - InType() string -} - -type convertedEngine[T any] struct { - engine engine.Engine[T] - name string - inType string -} - -func (e convertedEngine[T]) InType() string { return e.inType } - -func (e convertedEngine[T]) Name() string { return e.name } - -func (e convertedEngine[T]) Load() Process { - return convertedProcess[T]{e.engine.Load(), e.inType} -} - -func ConvertEngine[T any](e engine.Engine[T], name, inType string) Engine { - return &convertedEngine[T]{e, name, inType} -} diff --git a/internal/cli/marshaler.go b/internal/cli/marshaler.go deleted file mode 100644 index a212417..0000000 --- a/internal/cli/marshaler.go +++ /dev/null @@ -1,45 +0,0 @@ -package cli - -import ( - "fmt" - "reflect" - - "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 { - dataType := reflect.TypeOf(r.Data()) - allowedType := reflect.TypeFor[T]() - return "", fmt.Errorf("marshaler for '%s' cannot parse '%s'", allowedType, dataType) - } - - 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 deleted file mode 100644 index c684639..0000000 --- a/internal/cli/repr.go +++ /dev/null @@ -1,21 +0,0 @@ -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/config/source.go b/internal/cli/source.go similarity index 58% rename from internal/config/source.go rename to internal/cli/source.go index 758eaa0..70f72b0 100644 --- a/internal/config/source.go +++ b/internal/cli/source.go @@ -1,24 +1,26 @@ -package config +package cli import ( "io" "os" ) -// A method of extracting input from the user. +// A Source is a method of extracting input from the user. type Source interface { - // Fetch data from this source. + // Extract fetches data from this source. Extract() (string, error) } -// A source defined by a string. +// A StringSource is defined by a string. type StringSource struct{ Data string } +// Extract pulls input data from the internal string. func (s StringSource) Extract() (string, error) { return s.Data, nil } -// A source pulling from standard input. +// A StdinSource pulls from standard input. type StdinSource struct{} +// Extract pulls input data from standard input. func (s StdinSource) Extract() (string, error) { data, err := io.ReadAll(os.Stdin) if err != nil { @@ -28,9 +30,10 @@ func (s StdinSource) Extract() (string, error) { return string(data), nil } -// A source reading from a file. +// A FileSource reads from a file. type FileSource struct{ Path string } +// Extract pulls input data from the file source. func (s FileSource) Extract() (string, error) { data, err := os.ReadFile(s.Path) if err != nil { diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 063ee2e..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,12 +0,0 @@ -// Package "config" parses ad handles the user settings given to the program. -package config - -// Configuration settings for the program. -type Config struct { - Source Source // The source code given to the program. - Destination Destination // The destination for the final result. - Verbose bool // Whether or not to print debug logs. - Explanation bool // Whether or not to print an explanation of the reduction. - Profile string // If not nil, print a CPU profile during execution. - Statistics bool // Whether or not to print statistics. -} diff --git a/internal/config/get_logger.go b/internal/config/get_logger.go deleted file mode 100644 index 8880878..0000000 --- a/internal/config/get_logger.go +++ /dev/null @@ -1,23 +0,0 @@ -package config - -import ( - "log/slog" - "os" -) - -// Returns a structured logger with the appropriate configurations. -func (c Config) GetLogger() *slog.Logger { - // By default, only print out errors. - level := slog.LevelError - - // If the user set the output to be "VERBOSE", return the debug logs. - if c.Verbose { - level = slog.LevelInfo - } - - return slog.New( - slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ - Level: level, - }), - ) -} diff --git a/internal/config/parse_from_args.go b/internal/config/parse_from_args.go deleted file mode 100644 index 908e7e1..0000000 --- a/internal/config/parse_from_args.go +++ /dev/null @@ -1,56 +0,0 @@ -package config - -import ( - "flag" - "fmt" -) - -// Extract the program configuration from the command-line arguments. -func FromArgs() (*Config, error) { - // Relevant flags. - verbose := flag.Bool("v", false, "Verbosity. If set, the program will print logs.") - explanation := flag.Bool("x", false, "Explanation. Whether or not to show all reduction steps.") - statistics := flag.Bool("s", false, "Statistics. If set, the process will print various statistics about the run.") - profile := flag.String("p", "", "CPU profiling. If an output file is defined, the program will profile its execution and dump its results into it.") - file := flag.String("f", "", "File. If set, read source from the specified file.") - output := flag.String("o", "", "Output. If set, write result to the specified file. Use '-' for stdout (default).") - flag.Parse() - - // Parse source type. - var source Source - if *file != "" { - // File flag takes precedence. - if flag.NArg() > 0 { - return nil, fmt.Errorf("cannot specify both -f flag and positional argument") - } - source = FileSource{Path: *file} - } else if flag.NArg() == 0 { - return nil, fmt.Errorf("no input given") - } else if flag.NArg() > 1 { - return nil, fmt.Errorf("more than 1 command-line argument") - } else { - // Positional argument. - if flag.Arg(0) == "-" { - source = StdinSource{} - } else { - source = StringSource{Data: flag.Arg(0)} - } - } - - // Parse destination type. - var destination Destination - if *output == "" || *output == "-" { - destination = StdoutDestination{} - } else { - destination = FileDestination{Path: *output} - } - - return &Config{ - Source: source, - Destination: destination, - Verbose: *verbose, - Explanation: *explanation, - Profile: *profile, - Statistics: *statistics, - }, nil -} diff --git a/internal/registry/codec.go b/internal/registry/codec.go new file mode 100644 index 0000000..f23da98 --- /dev/null +++ b/internal/registry/codec.go @@ -0,0 +1,50 @@ +package registry + +import ( + "fmt" + "reflect" + + "git.maximhutz.com/max/lambda/pkg/codec" +) + +type Codec interface { + codec.Codec[Repr] + + InType() string +} + +type convertedCodec[T any] struct { + codec codec.Codec[T] + inType string +} + +func (c convertedCodec[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 convertedCodec[T]) Encode(r Repr) (string, error) { + t, ok := r.Data().(T) + if !ok { + dataType := reflect.TypeOf(r.Data()) + allowedType := reflect.TypeFor[T]() + return "", fmt.Errorf("Codec for '%s' cannot parse '%s'", allowedType, dataType) + } + + return c.codec.Encode(t) +} + +func (c convertedCodec[T]) InType() string { return c.inType } + +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} + return nil +} diff --git a/internal/registry/conversion.go b/internal/registry/conversion.go new file mode 100644 index 0000000..b793ed2 --- /dev/null +++ b/internal/registry/conversion.go @@ -0,0 +1,43 @@ +package registry + +import ( + "fmt" + + "git.maximhutz.com/max/lambda/pkg/codec" +) + +type Conversion interface { + InType() string + OutType() string + + Run(Repr) (Repr, error) +} + +type convertedConversion[T, U any] struct { + conversion codec.Conversion[T, U] + inType, outType string +} + +func (c convertedConversion[T, U]) Run(r Repr) (Repr, error) { + t, ok := r.Data().(T) + if !ok { + return nil, fmt.Errorf("could not parse '%v' as '%s'", t, c.inType) + } + + u, err := c.conversion(t) + if err != nil { + return nil, err + } + + return NewRepr(c.outType, u), nil +} + +func (c convertedConversion[T, U]) InType() string { return c.inType } + +func (c convertedConversion[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}) + + return nil +} diff --git a/internal/registry/converter.go b/internal/registry/converter.go index 3c29199..5ae74bf 100644 --- a/internal/registry/converter.go +++ b/internal/registry/converter.go @@ -1,27 +1,23 @@ package registry -import ( - "git.maximhutz.com/max/lambda/internal/cli" -) - type Converter struct { - data map[string][]cli.Conversion + data map[string][]Conversion } func NewConverter() *Converter { - return &Converter{data: map[string][]cli.Conversion{}} + return &Converter{data: map[string][]Conversion{}} } -func (g *Converter) Add(c cli.Conversion) { +func (g *Converter) Add(c Conversion) { conversionsFromIn, ok := g.data[c.InType()] if !ok { - conversionsFromIn = []cli.Conversion{} + conversionsFromIn = []Conversion{} } conversionsFromIn = append(conversionsFromIn, c) g.data[c.InType()] = conversionsFromIn } -func (g *Converter) ConversionsFrom(t string) []cli.Conversion { +func (g *Converter) ConversionsFrom(t string) []Conversion { return g.data[t] } diff --git a/internal/registry/engine.go b/internal/registry/engine.go new file mode 100644 index 0000000..16b162c --- /dev/null +++ b/internal/registry/engine.go @@ -0,0 +1,46 @@ +package registry + +import ( + "fmt" + + "git.maximhutz.com/max/lambda/pkg/engine" +) + +type Engine interface { + Load(Repr) (Process, error) + Name() string + InType() string +} + +type convertedEngine[T any] struct { + engine engine.Engine[T] + name string + inType string +} + +func (e convertedEngine[T]) InType() string { return e.inType } + +func (e convertedEngine[T]) Name() string { return e.name } + +func (e convertedEngine[T]) Load(r Repr) (Process, error) { + t, ok := r.Data().(T) + if !ok { + return nil, fmt.Errorf("'ncorrent format '%s' for engine '%s'", r.ID(), e.inType) + } + + process, err := e.engine(t) + if err != nil { + return nil, err + } + + return convertedProcess[T]{process, e.inType}, nil +} + +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} + return nil +} diff --git a/internal/cli/process.go b/internal/registry/process.go similarity index 70% rename from internal/cli/process.go rename to internal/registry/process.go index f3445f7..d2150dd 100644 --- a/internal/cli/process.go +++ b/internal/registry/process.go @@ -1,8 +1,6 @@ -package cli +package registry import ( - "fmt" - "git.maximhutz.com/max/lambda/pkg/engine" ) @@ -28,14 +26,6 @@ func (b convertedProcess[T]) Get() (Repr, error) { return NewRepr(b.inType, s), nil } -func (b convertedProcess[T]) Set(r Repr) error { - if t, ok := r.Data().(T); ok { - return b.process.Set(t) - } - - return fmt.Errorf("Incorrent format '%s' for engine '%s'.", r.Id(), b.inType) -} - func (b convertedProcess[T]) Step(i int) bool { return b.process.Step(i) } diff --git a/internal/registry/registry.go b/internal/registry/registry.go index 705b405..dea5d34 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -4,68 +4,23 @@ import ( "fmt" "iter" "maps" - - "git.maximhutz.com/max/lambda/internal/cli" ) type Registry struct { - marshalers map[string]cli.Marshaler - converter *Converter - engines map[string]cli.Engine + codecs map[string]Codec + converter *Converter + engines map[string]Engine } func New() *Registry { return &Registry{ - marshalers: map[string]cli.Marshaler{}, - converter: NewConverter(), - engines: map[string]cli.Engine{}, + codecs: map[string]Codec{}, + converter: NewConverter(), + engines: map[string]Engine{}, } } -func (r *Registry) AddConversions(conversions ...cli.Conversion) error { - for _, conversion := range conversions { - r.converter.Add(conversion) - } - - return nil -} -func (r *Registry) MustAddConversions(conversions ...cli.Conversion) { - if err := r.AddConversions(conversions...); err != nil { - panic(err) - } -} - -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) MustAddMarshaler(c cli.Marshaler) { - if err := r.AddMarshaler(c); err != nil { - panic(err) - } -} - -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) MustAddEngine(e cli.Engine) { - if err := r.AddEngine(e); err != nil { - panic(err) - } -} - -func (r Registry) GetEngine(name string) (cli.Engine, error) { +func (r Registry) GetEngine(name string) (Engine, error) { e, ok := r.engines[name] if !ok { return nil, fmt.Errorf("engine '%s' not found", name) @@ -74,11 +29,11 @@ func (r Registry) GetEngine(name string) (cli.Engine, error) { return e, nil } -func (r Registry) ListEngines() iter.Seq[cli.Engine] { +func (r Registry) ListEngines() iter.Seq[Engine] { return maps.Values(r.engines) } -func (r *Registry) GetDefaultEngine(id string) (cli.Engine, error) { +func (r *Registry) GetDefaultEngine(id string) (Engine, error) { for _, engine := range r.engines { if engine.InType() == id { return engine, nil @@ -88,8 +43,8 @@ func (r *Registry) GetDefaultEngine(id string) (cli.Engine, error) { return nil, fmt.Errorf("no engine for '%s'", id) } -func (r *Registry) ConvertTo(repr cli.Repr, outType string) (cli.Repr, error) { - path, err := r.ConversionPath(repr.Id(), outType) +func (r *Registry) ConvertTo(repr Repr, outType string) (Repr, error) { + path, err := r.ConversionPath(repr.ID(), outType) if err != nil { return nil, err } @@ -105,17 +60,17 @@ func (r *Registry) ConvertTo(repr cli.Repr, outType string) (cli.Repr, error) { return result, err } -func (r *Registry) Marshal(repr cli.Repr) (string, error) { - m, ok := r.marshalers[repr.Id()] +func (r *Registry) Marshal(repr Repr) (string, error) { + m, ok := r.codecs[repr.ID()] if !ok { - return "", fmt.Errorf("no marshaler for '%s'", repr.Id()) + 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] +func (r *Registry) Unmarshal(s string, outType string) (Repr, error) { + m, ok := r.codecs[outType] if !ok { return nil, fmt.Errorf("no marshaler for '%s'", outType) } @@ -137,8 +92,8 @@ func reverse[T any](list []T) []T { return reversed } -func (r *Registry) ConversionPath(from, to string) ([]cli.Conversion, error) { - backtrack := map[string]cli.Conversion{} +func (r *Registry) ConversionPath(from, to string) ([]Conversion, error) { + backtrack := map[string]Conversion{} iteration := []string{from} for len(iteration) > 0 { nextIteration := []string{} @@ -157,7 +112,7 @@ func (r *Registry) ConversionPath(from, to string) ([]cli.Conversion, error) { iteration = nextIteration } - reversedPath := []cli.Conversion{} + reversedPath := []Conversion{} current := to for current != from { conversion, ok := backtrack[current] diff --git a/internal/registry/repr.go b/internal/registry/repr.go new file mode 100644 index 0000000..1288f3c --- /dev/null +++ b/internal/registry/repr.go @@ -0,0 +1,24 @@ +package registry + +// A Repr is a lambda calculus expression. It can have any type of +// representation, so long as that class is known to the registry it is handled +// by. +type Repr interface { + // ID returns the name of the underlying representation. It is assumed that + // if two expressions have the same Id(), they have the same representation. + ID() string + + // The base expression data. + 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/pkg/codec/codec.go b/pkg/codec/codec.go index 3132409..ecbe258 100644 --- a/pkg/codec/codec.go +++ b/pkg/codec/codec.go @@ -1,8 +1,8 @@ package codec -type Codec[T, U any] interface { - Encode(T) (U, error) - Decode(U) (T, error) -} +type Conversion[T, U any] = func(T) (U, error) -type Marshaler[T any] = Codec[T, string] +type Codec[T any] interface { + Encode(T) (string, error) + Decode(string) (T, error) +} diff --git a/pkg/convert/saccharine_to_lambda.go b/pkg/convert/saccharine_to_lambda.go index e19e77c..73aeb0f 100644 --- a/pkg/convert/saccharine_to_lambda.go +++ b/pkg/convert/saccharine_to_lambda.go @@ -3,7 +3,6 @@ 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" ) @@ -125,14 +124,10 @@ func decodeExression(l lambda.Expression) saccharine.Expression { } } -type Saccharine2Lambda struct{} - -func (c Saccharine2Lambda) Decode(l lambda.Expression) (saccharine.Expression, error) { +func Lambda2Saccharine(l lambda.Expression) (saccharine.Expression, error) { return decodeExression(l), nil } -func (c Saccharine2Lambda) Encode(s saccharine.Expression) (lambda.Expression, error) { +func Saccharine2Lambda(s saccharine.Expression) (lambda.Expression, error) { return encodeExpression(s), nil } - -var _ codec.Codec[saccharine.Expression, lambda.Expression] = (*Saccharine2Lambda)(nil) diff --git a/pkg/emitter/emitter.go b/pkg/emitter/emitter.go deleted file mode 100644 index 2c34b55..0000000 --- a/pkg/emitter/emitter.go +++ /dev/null @@ -1,46 +0,0 @@ -package emitter - -import "git.maximhutz.com/max/lambda/pkg/set" - -type Emitter[E comparable] interface { - On(E, func()) Listener[E] - Off(Listener[E]) - Emit(E) -} - -type BaseEmitter[E comparable] struct { - listeners map[E]set.Set[Listener[E]] -} - -func (e *BaseEmitter[E]) On(kind E, fn func()) Listener[E] { - if e.listeners[kind] == nil { - e.listeners[kind] = set.New[Listener[E]]() - } - - listener := &BaseListener[E]{kind, fn} - e.listeners[kind].Add(listener) - return listener -} - -func (e *BaseEmitter[E]) Off(listener Listener[E]) { - kind := listener.Kind() - if e.listeners[kind] != nil { - e.listeners[kind].Remove(listener) - } -} - -func (e *BaseEmitter[E]) Emit(event E) { - if e.listeners[event] == nil { - e.listeners[event] = set.New[Listener[E]]() - } - - for listener := range e.listeners[event].Items() { - listener.Run() - } -} - -func New[E comparable]() *BaseEmitter[E] { - return &BaseEmitter[E]{ - listeners: map[E]set.Set[Listener[E]]{}, - } -} diff --git a/pkg/emitter/listener.go b/pkg/emitter/listener.go deleted file mode 100644 index 43c95c6..0000000 --- a/pkg/emitter/listener.go +++ /dev/null @@ -1,19 +0,0 @@ -package emitter - -type Listener[E comparable] interface { - Kind() E - Run() -} - -type BaseListener[E comparable] struct { - kind E - fn func() -} - -func (l BaseListener[E]) Kind() E { - return l.kind -} - -func (l BaseListener[E]) Run() { - l.fn() -} diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 0fcebf3..571b09e 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -1,11 +1,12 @@ +// Package engine defines a general process of reducing a lambda calculus +// expression. package engine -type Engine[T any] interface { - Load() Process[T] -} - +// A Process handles the reduction of a type Process[T any] interface { Get() (T, error) - Set(T) error Step(int) bool } + +// An Engine is an object that handles +type Engine[T any] = func(T) (Process[T], error) diff --git a/pkg/engine/normalorder/engine.go b/pkg/engine/normalorder/engine.go index fcdd362..b6d2b96 100644 --- a/pkg/engine/normalorder/engine.go +++ b/pkg/engine/normalorder/engine.go @@ -1,3 +1,5 @@ +// Package normalorder contains an engine that reduces a 'lambda.Expression' +// in the normal order. package normalorder import ( @@ -5,20 +7,20 @@ import ( "git.maximhutz.com/max/lambda/pkg/lambda" ) -type Process struct { +type process struct { expr lambda.Expression } -func (e Process) Get() (lambda.Expression, error) { +func (e process) Get() (lambda.Expression, error) { return e.expr, nil } -func (e *Process) Set(l lambda.Expression) error { +func (e *process) Set(l lambda.Expression) error { e.expr = l return nil } -func (e *Process) Step(i int) bool { +func (e *process) Step(i int) bool { for range i { next, reduced := ReduceOnce(e.expr) if !reduced { @@ -31,12 +33,10 @@ func (e *Process) Step(i int) bool { return true } -type Engine struct { +// NewProcess creates a new redution process. +func NewProcess(expression lambda.Expression) (engine.Process[lambda.Expression], error) { + return &process{expr: expression}, nil } -func (e Engine) Load() engine.Process[lambda.Expression] { - return &Process{} -} - -var _ engine.Process[lambda.Expression] = (*Process)(nil) -var _ engine.Engine[lambda.Expression] = (*Engine)(nil) +var _ engine.Process[lambda.Expression] = (*process)(nil) +var _ engine.Engine[lambda.Expression] = NewProcess diff --git a/pkg/engine/normalorder/reduce_one.go b/pkg/engine/normalorder/reduce_once.go similarity index 75% rename from pkg/engine/normalorder/reduce_one.go rename to pkg/engine/normalorder/reduce_once.go index e1a8aff..734d6a5 100644 --- a/pkg/engine/normalorder/reduce_one.go +++ b/pkg/engine/normalorder/reduce_once.go @@ -2,6 +2,11 @@ package normalorder import "git.maximhutz.com/max/lambda/pkg/lambda" +// ReduceOnce attempts to apply a single reduction to a lambda expression. +// It returns (1) the final expression (reduced, or not), and (2) whether or not +// a reduction was applied. +// +// If a reduction is not applied, it returns the original expression. func ReduceOnce(e lambda.Expression) (lambda.Expression, bool) { switch e := e.(type) { case lambda.Abstraction: diff --git a/pkg/lambda/marshaler.go b/pkg/lambda/codec.go similarity index 84% rename from pkg/lambda/marshaler.go rename to pkg/lambda/codec.go index 6ee2c79..04cd83c 100644 --- a/pkg/lambda/marshaler.go +++ b/pkg/lambda/codec.go @@ -16,4 +16,4 @@ func (m Marshaler) Encode(e Expression) (string, error) { return e.String(), nil } -var _ codec.Marshaler[Expression] = (*Marshaler)(nil) +var _ codec.Codec[Expression] = (*Marshaler)(nil) diff --git a/pkg/saccharine/marshaler.go b/pkg/saccharine/codec.go similarity index 89% rename from pkg/saccharine/marshaler.go rename to pkg/saccharine/codec.go index bc5c3dc..bbdb2c4 100644 --- a/pkg/saccharine/marshaler.go +++ b/pkg/saccharine/codec.go @@ -21,4 +21,4 @@ func (m Marshaler) Encode(e Expression) (string, error) { return stringifyExpression(e), nil } -var _ codec.Marshaler[Expression] = (*Marshaler)(nil) +var _ codec.Codec[Expression] = (*Marshaler)(nil)