Compare commits
2 Commits
feat/upgra
...
feat/regis
| Author | SHA1 | Date | |
|---|---|---|---|
|
7054442dc6
|
|||
| a3ee34732e |
@@ -48,7 +48,7 @@ linters:
|
|||||||
# More information: https://golangci-lint.run/usage/false-positives/#comments
|
# More information: https://golangci-lint.run/usage/false-positives/#comments
|
||||||
#
|
#
|
||||||
# Please uncomment the following line if your code is not using the godoc format
|
# Please uncomment the following line if your code is not using the godoc format
|
||||||
- comments
|
# - comments
|
||||||
|
|
||||||
# Common false positives
|
# Common false positives
|
||||||
# feel free to remove this if you don't have any false positives
|
# feel free to remove this if you don't have any false positives
|
||||||
@@ -126,6 +126,9 @@ linters:
|
|||||||
# Blank import should be only in a main or test package, or have a comment justifying it.
|
# Blank import should be only in a main or test package, or have a comment justifying it.
|
||||||
- name: blank-imports
|
- name: blank-imports
|
||||||
|
|
||||||
|
# Packages should have comments of the form "Package x ...".
|
||||||
|
- name: package-comments
|
||||||
|
|
||||||
# context.Context() should be the first parameter of a function when provided as argument.
|
# context.Context() should be the first parameter of a function when provided as argument.
|
||||||
- name: context-as-argument
|
- name: context-as-argument
|
||||||
arguments:
|
arguments:
|
||||||
|
|||||||
33
Makefile
33
Makefile
@@ -1,30 +1,53 @@
|
|||||||
BINARY_NAME=lambda
|
BINARY_NAME=lambda
|
||||||
|
TEST=simple
|
||||||
|
|
||||||
.PHONY: help build docs test bench clean
|
.PHONY: help build run profile explain graph docs test bench lint clean
|
||||||
.DEFAULT_GOAL := help
|
.DEFAULT_GOAL := help
|
||||||
.SILENT:
|
.SILENT:
|
||||||
|
|
||||||
help:
|
help:
|
||||||
echo "Available targets:"
|
echo "Available targets:"
|
||||||
echo " build - Build the lambda executable"
|
echo " build - Build the lambda executable"
|
||||||
|
echo " run - Build and run the lambda runtime (use TEST=<name> to specify sample)"
|
||||||
|
echo " profile - Build and run with CPU profiling enabled"
|
||||||
|
echo " explain - Build and run with explanation mode and profiling"
|
||||||
|
echo " graph - Generate and open CPU profile visualization"
|
||||||
echo " docs - Start local godoc server on port 6060"
|
echo " docs - Start local godoc server on port 6060"
|
||||||
echo " test - Run tests"
|
echo " test - Run tests for all samples"
|
||||||
echo " bench - Run benchmarks"
|
echo " bench - Run benchmarks for all samples"
|
||||||
|
echo " lint - Run golangci-lint on all packages"
|
||||||
echo " clean - Remove all build artifacts"
|
echo " clean - Remove all build artifacts"
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build -o ${BINARY_NAME} ./cmd/lambda
|
go build -o ${BINARY_NAME} ./cmd/lambda
|
||||||
chmod +x ${BINARY_NAME}
|
chmod +x ${BINARY_NAME}
|
||||||
|
|
||||||
|
run: build
|
||||||
|
./${BINARY_NAME} -s -f ./tests/$(TEST).test -o program.out
|
||||||
|
|
||||||
|
profile: build
|
||||||
|
./${BINARY_NAME} -p profile/cpu.prof -f ./tests/$(TEST).test -o program.out
|
||||||
|
|
||||||
|
explain: build
|
||||||
|
./${BINARY_NAME} -x -p profile/cpu.prof -f ./tests/$(TEST).test -o program.out > explain.out
|
||||||
|
|
||||||
|
graph:
|
||||||
|
go tool pprof -raw -output=profile/cpu.raw profile/cpu.prof
|
||||||
|
go tool pprof -svg profile/cpu.prof > profile/cpu.svg
|
||||||
|
echo ">>> View at 'file://$(PWD)/profile/cpu.svg'"
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
echo ">>> View at 'http://localhost:6060/pkg/git.maximhutz.com/max/lambda/'"
|
echo ">>> View at 'http://localhost:6060/pkg/git.maximhutz.com/max/lambda/'"
|
||||||
go run golang.org/x/tools/cmd/godoc@latest -http=:6060
|
go run golang.org/x/tools/cmd/godoc@latest -http=:6060
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -v ./...
|
go test -v ./cmd/lambda
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
go test -bench=. -benchtime=10x -cpu=4 ./...
|
go test -bench=. -benchtime=10x -cpu=4 ./cmd/lambda
|
||||||
|
|
||||||
|
lint:
|
||||||
|
go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest run ./...
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f ${BINARY_NAME}
|
rm -f ${BINARY_NAME}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var convertCmd = &cobra.Command{
|
|
||||||
Use: "convert [input] [output]",
|
|
||||||
Short: "Convert between lambda calculus representations",
|
|
||||||
Long: `Convert lambda calculus expressions between different representations.
|
|
||||||
|
|
||||||
The format is inferred from file extensions when possible.
|
|
||||||
Use --from and --to flags to override format detection.`,
|
|
||||||
Example: ` lambda convert input.sch output.dbi
|
|
||||||
lambda convert input.txt output.txt --from saccharine --to lambda`,
|
|
||||||
Args: cobra.ExactArgs(2),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Fprintln(os.Stderr, "not implemented")
|
|
||||||
os.Exit(1)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
convertCmd.Flags().String("from", "", "source format")
|
|
||||||
convertCmd.Flags().String("to", "", "target format")
|
|
||||||
rootCmd.AddCommand(convertCmd)
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var engineCmd = &cobra.Command{
|
|
||||||
Use: "engine",
|
|
||||||
Short: "List available evaluation engines",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Fprintln(os.Stderr, "not implemented")
|
|
||||||
os.Exit(1)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(engineCmd)
|
|
||||||
}
|
|
||||||
31
cmd/lambda/lambda.go
Normal file
31
cmd/lambda/lambda.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Lambda() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "lambda",
|
||||||
|
Short: "Lambda calculus interpreter",
|
||||||
|
Long: "A lambda calculus interpreter supporting multiple representations.",
|
||||||
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
return cmd.Help()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(LambdaConvert())
|
||||||
|
cmd.AddCommand(LambdaEngine())
|
||||||
|
cmd.AddCommand(LambdaReduce())
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
lambda := Lambda()
|
||||||
|
if err := lambda.Execute(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
94
cmd/lambda/lambda_convert.go
Normal file
94
cmd/lambda/lambda_convert.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// inferReprFromPath returns the repr type based on file extension.
|
||||||
|
func inferReprFromPath(path string) (string, error) {
|
||||||
|
switch ext := strings.ToLower(filepath.Ext(path)); ext {
|
||||||
|
case ".lambda", ".lam", ".lc":
|
||||||
|
return "lambda", nil
|
||||||
|
case ".saccharine", ".sch":
|
||||||
|
return "saccharine", nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unknown file extension '%s'", ext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LambdaConvert() *cobra.Command {
|
||||||
|
var inputReprFlag, outputReprFlag string
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "convert <input-file> <output-file>",
|
||||||
|
Short: "Convert between lambda calculus representations",
|
||||||
|
SilenceUsage: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) != 2 {
|
||||||
|
return cmd.Help()
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
inputPath, outputPath := args[0], args[1]
|
||||||
|
|
||||||
|
// Use flag if provided, otherwise infer from extension.
|
||||||
|
inputRepr := inputReprFlag
|
||||||
|
if inputRepr == "" {
|
||||||
|
if inputRepr, err = inferReprFromPath(inputPath); err != nil {
|
||||||
|
return fmt.Errorf("input file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputRepr := outputReprFlag
|
||||||
|
if outputRepr == "" {
|
||||||
|
if outputRepr, err = inferReprFromPath(outputPath); err != nil {
|
||||||
|
return fmt.Errorf("output file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read input file.
|
||||||
|
input, err := os.ReadFile(inputPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading input file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := GetRegistry()
|
||||||
|
|
||||||
|
// Parse input into syntax tree.
|
||||||
|
repr, err := r.Unmarshal(string(input), inputRepr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parsing input: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to output repr if different.
|
||||||
|
result, err := r.ConvertTo(repr, outputRepr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("converting %s to %s: %w", inputRepr, outputRepr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal output.
|
||||||
|
output, err := r.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unmarshaling output: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write output file.
|
||||||
|
err = os.WriteFile(outputPath, []byte(output), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writing output file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&inputReprFlag, "from", "", "Input representation (inferred from extension if unset)")
|
||||||
|
cmd.Flags().StringVar(&outputReprFlag, "to", "", "Output representation (inferred from extension if unset)")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
19
cmd/lambda/lambda_engine.go
Normal file
19
cmd/lambda/lambda_engine.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LambdaEngine() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "engine",
|
||||||
|
Short: "Information about available engines",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return cmd.Help()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(LambdaEngineList())
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
26
cmd/lambda/lambda_engine_list.go
Normal file
26
cmd/lambda/lambda_engine_list.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LambdaEngineList() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Short: "List available engines",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
r := GetRegistry()
|
||||||
|
|
||||||
|
for engine := range r.ListEngines() {
|
||||||
|
fmt.Println(engine.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
109
cmd/lambda/lambda_reduce.go
Normal file
109
cmd/lambda/lambda_reduce.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"git.maximhutz.com/max/lambda/internal/cli"
|
||||||
|
"git.maximhutz.com/max/lambda/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LambdaReduce() *cobra.Command {
|
||||||
|
var inputReprFlag string
|
||||||
|
var engineFlag string
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "reduce <input-file>",
|
||||||
|
Short: "Reduce a lambda calculus expression",
|
||||||
|
SilenceUsage: true,
|
||||||
|
Aliases: []string{"run"},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
var err error
|
||||||
|
if len(args) != 1 {
|
||||||
|
return cmd.Help()
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPath := args[0]
|
||||||
|
|
||||||
|
// Get input source.
|
||||||
|
var source config.Source
|
||||||
|
if inputPath == "-" {
|
||||||
|
source = config.StdinSource{}
|
||||||
|
} else {
|
||||||
|
source = config.FileSource{Path: inputPath}
|
||||||
|
}
|
||||||
|
|
||||||
|
destination := config.StdoutDestination{}
|
||||||
|
|
||||||
|
r := GetRegistry()
|
||||||
|
|
||||||
|
// Get input.
|
||||||
|
input, err := source.Extract()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use flag if provided, otherwise infer from extension.
|
||||||
|
inputRepr := inputReprFlag
|
||||||
|
if inputRepr == "" {
|
||||||
|
if inputRepr, err = inferReprFromPath(inputPath); err != nil {
|
||||||
|
return fmt.Errorf("input file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find engine.
|
||||||
|
var engine cli.Engine
|
||||||
|
if engineFlag == "" {
|
||||||
|
if engine, err = r.GetDefaultEngine(inputRepr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if engine, err = r.GetEngine(engineFlag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse code into syntax tree.
|
||||||
|
repr, err := r.Unmarshal(input, inputRepr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile expression to lambda calculus.
|
||||||
|
compiled, err := r.ConvertTo(repr, "lambda")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create process.
|
||||||
|
process := engine.Load()
|
||||||
|
err = process.Set(compiled)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run reduction.
|
||||||
|
for process.Step(1) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the final reduced result.
|
||||||
|
result, err := process.Get()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := r.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination.Write(output)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&inputReprFlag, "from", "", "Input representation (inferred from extension if unset)")
|
||||||
|
cmd.Flags().StringVarP(&engineFlag, "engine", "e", "", "Reduction engine (inferred from '--input' if unset)")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
26
cmd/lambda/registry.go
Normal file
26
cmd/lambda/registry.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetRegistry() *registry.Registry {
|
||||||
|
r := registry.New()
|
||||||
|
|
||||||
|
// Codecs
|
||||||
|
r.MustAddConversions(cli.ConvertCodec(convert.Saccharine2Lambda{}, "saccharine", "lambda")...)
|
||||||
|
|
||||||
|
// Engines
|
||||||
|
r.MustAddEngine(cli.ConvertEngine(normalorder.Engine{}, "normalorder", "lambda"))
|
||||||
|
|
||||||
|
// Marshalers
|
||||||
|
r.MustAddMarshaler(cli.ConvertMarshaler(lambda.Marshaler{}, "lambda"))
|
||||||
|
r.MustAddMarshaler(cli.ConvertMarshaler(saccharine.Marshaler{}, "saccharine"))
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var replCmd = &cobra.Command{
|
|
||||||
Use: "repl",
|
|
||||||
Short: "Start an interactive lambda calculus REPL",
|
|
||||||
Long: `Start an interactive Read-Eval-Print Loop for lambda calculus.
|
|
||||||
|
|
||||||
Enter lambda expressions to evaluate them.
|
|
||||||
Type 'exit' or 'quit' to leave the REPL.
|
|
||||||
Press Ctrl+D to exit.`,
|
|
||||||
Example: ` lambda repl`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Fprintln(os.Stderr, "not implemented")
|
|
||||||
os.Exit(1)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(replCmd)
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var reprCmd = &cobra.Command{
|
|
||||||
Use: "repr",
|
|
||||||
Short: "List available representations",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Fprintln(os.Stderr, "not implemented")
|
|
||||||
os.Exit(1)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(reprCmd)
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
|
||||||
Use: "lambda",
|
|
||||||
Short: "A lambda calculus interpreter and toolkit",
|
|
||||||
Long: `Lambda is a CLI tool for working with lambda calculus expressions.`,
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var runCmd = &cobra.Command{
|
|
||||||
Use: "run [expression]",
|
|
||||||
Short: "Evaluate a lambda calculus expression",
|
|
||||||
Long: `Evaluate a lambda calculus expression and print the result.
|
|
||||||
|
|
||||||
The expression can be provided as an argument, read from a file with --file,
|
|
||||||
or piped through stdin.`,
|
|
||||||
Example: ` lambda run "\x.x"
|
|
||||||
echo "\x.x" | lambda run
|
|
||||||
lambda run --file program.sch
|
|
||||||
lambda run --file program.sch --engine krivine`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Fprintln(os.Stderr, "not implemented")
|
|
||||||
os.Exit(1)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
runCmd.Flags().StringP("file", "f", "", "read expression from file")
|
|
||||||
runCmd.Flags().StringP("engine", "e", "normal", "evaluation engine to use")
|
|
||||||
rootCmd.AddCommand(runCmd)
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var traceCmd = &cobra.Command{
|
|
||||||
Use: "trace [file]",
|
|
||||||
Short: "Trace the evaluation of a lambda calculus expression",
|
|
||||||
Long: `Trace the step-by-step evaluation of a lambda calculus expression.
|
|
||||||
|
|
||||||
This command shows each reduction step during evaluation.`,
|
|
||||||
Example: ` lambda trace program.sch
|
|
||||||
lambda trace program.sch --engine krivine`,
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Fprintln(os.Stderr, "not implemented")
|
|
||||||
os.Exit(1)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
traceCmd.Flags().StringP("engine", "e", "normal", "evaluation engine to use")
|
|
||||||
rootCmd.AddCommand(traceCmd)
|
|
||||||
}
|
|
||||||
8
go.mod
8
go.mod
@@ -2,13 +2,9 @@ module git.maximhutz.com/max/lambda
|
|||||||
|
|
||||||
go 1.25.5
|
go 1.25.5
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.11.1
|
require github.com/spf13/cobra v1.10.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/spf13/cobra v1.10.2 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.9 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
11
go.sum
11
go.sum
@@ -1,18 +1,11 @@
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
|
|||||||
67
internal/cli/conversion.go
Normal file
67
internal/cli/conversion.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
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},
|
||||||
|
}
|
||||||
|
}
|
||||||
27
internal/cli/engine.go
Normal file
27
internal/cli/engine.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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}
|
||||||
|
}
|
||||||
45
internal/cli/marshaler.go
Normal file
45
internal/cli/marshaler.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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}
|
||||||
|
}
|
||||||
41
internal/cli/process.go
Normal file
41
internal/cli/process.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/engine"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Process interface {
|
||||||
|
engine.Process[Repr]
|
||||||
|
|
||||||
|
InType() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type convertedProcess[T any] struct {
|
||||||
|
process engine.Process[T]
|
||||||
|
inType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e convertedProcess[T]) InType() string { return e.inType }
|
||||||
|
|
||||||
|
func (b convertedProcess[T]) Get() (Repr, error) {
|
||||||
|
s, err := b.process.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
21
internal/cli/repr.go
Normal file
21
internal/cli/repr.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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} }
|
||||||
12
internal/config/config.go
Normal file
12
internal/config/config.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// 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.
|
||||||
|
}
|
||||||
27
internal/config/destination.go
Normal file
27
internal/config/destination.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A method of writing output to the user.
|
||||||
|
type Destination interface {
|
||||||
|
// Write data to this destination.
|
||||||
|
Write(data string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// A destination writing to stdout.
|
||||||
|
type StdoutDestination struct{}
|
||||||
|
|
||||||
|
func (d StdoutDestination) Write(data string) error {
|
||||||
|
fmt.Println(data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A destination writing to a file.
|
||||||
|
type FileDestination struct{ Path string }
|
||||||
|
|
||||||
|
func (d FileDestination) Write(data string) error {
|
||||||
|
return os.WriteFile(d.Path, []byte(data+"\n"), 0644)
|
||||||
|
}
|
||||||
23
internal/config/get_logger.go
Normal file
23
internal/config/get_logger.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
56
internal/config/parse_from_args.go
Normal file
56
internal/config/parse_from_args.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
41
internal/config/source.go
Normal file
41
internal/config/source.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A method of extracting input from the user.
|
||||||
|
type Source interface {
|
||||||
|
// Fetch data from this source.
|
||||||
|
Extract() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A source defined by a string.
|
||||||
|
type StringSource struct{ Data string }
|
||||||
|
|
||||||
|
func (s StringSource) Extract() (string, error) { return s.Data, nil }
|
||||||
|
|
||||||
|
// A source pulling from standard input.
|
||||||
|
type StdinSource struct{}
|
||||||
|
|
||||||
|
func (s StdinSource) Extract() (string, error) {
|
||||||
|
data, err := io.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A source reading from a file.
|
||||||
|
type FileSource struct{ Path string }
|
||||||
|
|
||||||
|
func (s FileSource) Extract() (string, error) {
|
||||||
|
data, err := os.ReadFile(s.Path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
27
internal/registry/converter.go
Normal file
27
internal/registry/converter.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.maximhutz.com/max/lambda/internal/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Converter struct {
|
||||||
|
data map[string][]cli.Conversion
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConverter() *Converter {
|
||||||
|
return &Converter{data: map[string][]cli.Conversion{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Converter) Add(c cli.Conversion) {
|
||||||
|
conversionsFromIn, ok := g.data[c.InType()]
|
||||||
|
if !ok {
|
||||||
|
conversionsFromIn = []cli.Conversion{}
|
||||||
|
}
|
||||||
|
|
||||||
|
conversionsFromIn = append(conversionsFromIn, c)
|
||||||
|
g.data[c.InType()] = conversionsFromIn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Converter) ConversionsFrom(t string) []cli.Conversion {
|
||||||
|
return g.data[t]
|
||||||
|
}
|
||||||
173
internal/registry/registry.go
Normal file
173
internal/registry/registry.go
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
marshalers: map[string]cli.Marshaler{},
|
||||||
|
converter: NewConverter(),
|
||||||
|
engines: map[string]cli.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) {
|
||||||
|
e, ok := r.engines[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("engine '%s' not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Registry) ListEngines() iter.Seq[cli.Engine] {
|
||||||
|
return maps.Values(r.engines)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) GetDefaultEngine(id string) (cli.Engine, error) {
|
||||||
|
for _, engine := range r.engines {
|
||||||
|
if engine.InType() == id {
|
||||||
|
return engine, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := repr
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) ConversionPath(from, to string) ([]cli.Conversion, error) {
|
||||||
|
backtrack := map[string]cli.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 := []cli.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
|
||||||
|
}
|
||||||
9
internal/registry_new/conversion.go
Normal file
9
internal/registry_new/conversion.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package registrynew
|
||||||
|
|
||||||
|
// Conversion
|
||||||
|
type Conversion interface {
|
||||||
|
InRepr() string
|
||||||
|
OutRepr() string
|
||||||
|
|
||||||
|
Run(Expr) (Expr, error)
|
||||||
|
}
|
||||||
12
internal/registry_new/engine.go
Normal file
12
internal/registry_new/engine.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package registrynew
|
||||||
|
|
||||||
|
type Process interface {
|
||||||
|
Get() (Expr, error)
|
||||||
|
Step(int) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Engine interface {
|
||||||
|
Load(Expr) Process
|
||||||
|
Name() string
|
||||||
|
InRepr() string
|
||||||
|
}
|
||||||
7
internal/registry_new/expr.go
Normal file
7
internal/registry_new/expr.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package registrynew
|
||||||
|
|
||||||
|
type Expr interface {
|
||||||
|
Repr() string
|
||||||
|
|
||||||
|
Data() any
|
||||||
|
}
|
||||||
5
internal/registry_new/marshaler.go
Normal file
5
internal/registry_new/marshaler.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package registrynew
|
||||||
|
|
||||||
|
type Marshaler interface {
|
||||||
|
InType() string
|
||||||
|
}
|
||||||
10
internal/registry_new/registry.go
Normal file
10
internal/registry_new/registry.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package registrynew
|
||||||
|
|
||||||
|
import "iter"
|
||||||
|
|
||||||
|
type Registry interface {
|
||||||
|
ListEngines() iter.Seq[string]
|
||||||
|
GetEngine(name string) (Engine, error)
|
||||||
|
|
||||||
|
ListReprs() iter.Seq[string]
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package repr
|
package registrynew
|
||||||
|
|
||||||
type Repr interface {
|
type Repr interface {
|
||||||
}
|
}
|
||||||
8
pkg/codec/codec.go
Normal file
8
pkg/codec/codec.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package codec
|
||||||
|
|
||||||
|
type Codec[T, U any] interface {
|
||||||
|
Encode(T) (U, error)
|
||||||
|
Decode(U) (T, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Marshaler[T any] = Codec[T, string]
|
||||||
@@ -3,16 +3,17 @@ package convert
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/repr/lambda"
|
"git.maximhutz.com/max/lambda/pkg/codec"
|
||||||
"git.maximhutz.com/max/lambda/pkg/repr/saccharine"
|
"git.maximhutz.com/max/lambda/pkg/lambda"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
||||||
)
|
)
|
||||||
|
|
||||||
func convertAtom(n *saccharine.Variable) lambda.Expression {
|
func encodeAtom(n *saccharine.Atom) lambda.Expression {
|
||||||
return lambda.NewVariable(n.Name)
|
return lambda.NewVariable(n.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertAbstraction(n *saccharine.Abstraction) lambda.Expression {
|
func encodeAbstraction(n *saccharine.Abstraction) lambda.Expression {
|
||||||
result := SaccharineToLambda(n.Body)
|
result := encodeExpression(n.Body)
|
||||||
|
|
||||||
parameters := n.Parameters
|
parameters := n.Parameters
|
||||||
|
|
||||||
@@ -31,13 +32,13 @@ func convertAbstraction(n *saccharine.Abstraction) lambda.Expression {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertApplication(n *saccharine.Application) lambda.Expression {
|
func encodeApplication(n *saccharine.Application) lambda.Expression {
|
||||||
result := SaccharineToLambda(n.Abstraction)
|
result := encodeExpression(n.Abstraction)
|
||||||
|
|
||||||
arguments := []lambda.Expression{}
|
arguments := []lambda.Expression{}
|
||||||
for _, argument := range n.Arguments {
|
for _, argument := range n.Arguments {
|
||||||
convertedArgument := SaccharineToLambda(argument)
|
encodeedArgument := encodeExpression(argument)
|
||||||
arguments = append(arguments, convertedArgument)
|
arguments = append(arguments, encodeedArgument)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, argument := range arguments {
|
for _, argument := range arguments {
|
||||||
@@ -51,9 +52,9 @@ func reduceLet(s *saccharine.LetStatement, e lambda.Expression) lambda.Expressio
|
|||||||
var value lambda.Expression
|
var value lambda.Expression
|
||||||
|
|
||||||
if len(s.Parameters) == 0 {
|
if len(s.Parameters) == 0 {
|
||||||
value = SaccharineToLambda(s.Body)
|
value = encodeExpression(s.Body)
|
||||||
} else {
|
} else {
|
||||||
value = convertAbstraction(saccharine.NewAbstraction(s.Parameters, s.Body))
|
value = encodeAbstraction(saccharine.NewAbstraction(s.Parameters, s.Body))
|
||||||
}
|
}
|
||||||
|
|
||||||
return lambda.NewApplication(
|
return lambda.NewApplication(
|
||||||
@@ -67,7 +68,7 @@ func reduceDeclare(s *saccharine.DeclareStatement, e lambda.Expression) lambda.E
|
|||||||
|
|
||||||
return lambda.NewApplication(
|
return lambda.NewApplication(
|
||||||
lambda.NewAbstraction(freshVar, e),
|
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 {
|
func encodeClause(n *saccharine.Clause) lambda.Expression {
|
||||||
result := SaccharineToLambda(n.Returns)
|
result := encodeExpression(n.Returns)
|
||||||
|
|
||||||
for i := len(n.Statements) - 1; i >= 0; i-- {
|
for i := len(n.Statements) - 1; i >= 0; i-- {
|
||||||
result = reduceStatement(n.Statements[i], result)
|
result = reduceStatement(n.Statements[i], result)
|
||||||
@@ -92,17 +93,46 @@ func convertClause(n *saccharine.Clause) lambda.Expression {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaccharineToLambda(n saccharine.Expression) lambda.Expression {
|
func encodeExpression(s saccharine.Expression) lambda.Expression {
|
||||||
switch n := n.(type) {
|
switch s := s.(type) {
|
||||||
case *saccharine.Variable:
|
case *saccharine.Atom:
|
||||||
return convertAtom(n)
|
return encodeAtom(s)
|
||||||
case *saccharine.Abstraction:
|
case *saccharine.Abstraction:
|
||||||
return convertAbstraction(n)
|
return encodeAbstraction(s)
|
||||||
case *saccharine.Application:
|
case *saccharine.Application:
|
||||||
return convertApplication(n)
|
return encodeApplication(s)
|
||||||
case *saccharine.Clause:
|
case *saccharine.Clause:
|
||||||
return convertClause(n)
|
return encodeClause(s)
|
||||||
default:
|
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)
|
||||||
|
|||||||
46
pkg/emitter/emitter.go
Normal file
46
pkg/emitter/emitter.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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]]{},
|
||||||
|
}
|
||||||
|
}
|
||||||
19
pkg/emitter/listener.go
Normal file
19
pkg/emitter/listener.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
@@ -1,23 +1,11 @@
|
|||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
type Engine[T any] interface {
|
||||||
"fmt"
|
Load() Process[T]
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/repr"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Engine[R repr.Repr] interface {
|
|
||||||
Run(trace bool, count int) (Trace, bool)
|
|
||||||
Step() uint64
|
|
||||||
State() (repr.Repr, error)
|
|
||||||
|
|
||||||
fmt.Stringer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Trace interface {
|
type Process[T any] interface {
|
||||||
Before() string
|
Get() (T, error)
|
||||||
After() string
|
Set(T) error
|
||||||
Step() uint64
|
Step(int) bool
|
||||||
TransitionName() string
|
|
||||||
TransitionNodes() string
|
|
||||||
}
|
}
|
||||||
|
|||||||
42
pkg/engine/normalorder/engine.go
Normal file
42
pkg/engine/normalorder/engine.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package normalorder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/engine"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/lambda"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Process struct {
|
||||||
|
expr lambda.Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Process) Get() (lambda.Expression, error) {
|
||||||
|
return e.expr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Process) Set(l lambda.Expression) error {
|
||||||
|
e.expr = l
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Process) Step(i int) bool {
|
||||||
|
for range i {
|
||||||
|
next, reduced := ReduceOnce(e.expr)
|
||||||
|
if !reduced {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
e.expr = next
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type Engine struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Engine) Load() engine.Process[lambda.Expression] {
|
||||||
|
return &Process{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ engine.Process[lambda.Expression] = (*Process)(nil)
|
||||||
|
var _ engine.Engine[lambda.Expression] = (*Engine)(nil)
|
||||||
34
pkg/engine/normalorder/reduce_one.go
Normal file
34
pkg/engine/normalorder/reduce_one.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
package lambda
|
package lambda
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.maximhutz.com/max/lambda/pkg/repr"
|
"fmt"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/set"
|
"git.maximhutz.com/max/lambda/pkg/set"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Expression is the interface for all lambda calculus expression types.
|
// Expression is the interface for all lambda calculus expression types.
|
||||||
// It embeds the general expr.Expression interface for cross-mode compatibility.
|
// It embeds the general expr.Expression interface for cross-mode compatibility.
|
||||||
type Expression interface {
|
type Expression interface {
|
||||||
repr.Repr
|
fmt.Stringer
|
||||||
|
|
||||||
// Substitute replaces all free occurrences of the target variable with the
|
// Substitute replaces all free occurrences of the target variable with the
|
||||||
// replacement expression. Alpha-renaming is performed automatically to
|
// replacement expression. Alpha-renaming is performed automatically to
|
||||||
@@ -28,12 +29,6 @@ type Expression interface {
|
|||||||
IsFree(n string) bool
|
IsFree(n string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
_ Expression = Abstraction{}
|
|
||||||
_ Expression = Application{}
|
|
||||||
_ Expression = Variable{}
|
|
||||||
)
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
/** ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
type Abstraction struct {
|
type Abstraction struct {
|
||||||
@@ -41,6 +36,8 @@ type Abstraction struct {
|
|||||||
body Expression
|
body Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Expression = Abstraction{}
|
||||||
|
|
||||||
func (a Abstraction) Parameter() string {
|
func (a Abstraction) Parameter() string {
|
||||||
return a.parameter
|
return a.parameter
|
||||||
}
|
}
|
||||||
@@ -49,6 +46,10 @@ func (a Abstraction) Body() Expression {
|
|||||||
return a.body
|
return a.body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a Abstraction) String() string {
|
||||||
|
return "\\" + a.parameter + "." + a.body.String()
|
||||||
|
}
|
||||||
|
|
||||||
func NewAbstraction(parameter string, body Expression) Abstraction {
|
func NewAbstraction(parameter string, body Expression) Abstraction {
|
||||||
return Abstraction{parameter, body}
|
return Abstraction{parameter, body}
|
||||||
}
|
}
|
||||||
@@ -60,6 +61,8 @@ type Application struct {
|
|||||||
argument Expression
|
argument Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Expression = Application{}
|
||||||
|
|
||||||
func (a Application) Abstraction() Expression {
|
func (a Application) Abstraction() Expression {
|
||||||
return a.abstraction
|
return a.abstraction
|
||||||
}
|
}
|
||||||
@@ -68,6 +71,10 @@ func (a Application) Argument() Expression {
|
|||||||
return a.argument
|
return a.argument
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a Application) String() string {
|
||||||
|
return "(" + a.abstraction.String() + " " + a.argument.String() + ")"
|
||||||
|
}
|
||||||
|
|
||||||
func NewApplication(abstraction Expression, argument Expression) Application {
|
func NewApplication(abstraction Expression, argument Expression) Application {
|
||||||
return Application{abstraction, argument}
|
return Application{abstraction, argument}
|
||||||
}
|
}
|
||||||
@@ -78,10 +85,16 @@ type Variable struct {
|
|||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Expression = Variable{}
|
||||||
|
|
||||||
func (v Variable) Name() string {
|
func (v Variable) Name() string {
|
||||||
return v.name
|
return v.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v Variable) String() string {
|
||||||
|
return v.name
|
||||||
|
}
|
||||||
|
|
||||||
func NewVariable(name string) Variable {
|
func NewVariable(name string) Variable {
|
||||||
return Variable{name}
|
return Variable{name}
|
||||||
}
|
}
|
||||||
19
pkg/lambda/marshaler.go
Normal file
19
pkg/lambda/marshaler.go
Normal file
@@ -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)
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package saccharine
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// All tokens in the pseudo-lambda language.
|
|
||||||
type Type int
|
|
||||||
|
|
||||||
const (
|
|
||||||
TokenOpenParen Type = iota // Denotes the '(' token.
|
|
||||||
TokenCloseParen // Denotes the ')' token.
|
|
||||||
TokenOpenBrace // Denotes the '{' token.
|
|
||||||
TokenCloseBrace // Denotes the '}' token.
|
|
||||||
TokenHardBreak // Denotes the ';' token.
|
|
||||||
TokenAssign // Denotes the ':=' token.
|
|
||||||
TokenAtom // Denotes an alpha-numeric variable.
|
|
||||||
TokenSlash // Denotes the '/' token.
|
|
||||||
TokenDot // Denotes the '.' token.
|
|
||||||
TokenSoftBreak // Denotes a new-line.
|
|
||||||
)
|
|
||||||
|
|
||||||
// A representation of a token in source code.
|
|
||||||
type Token struct {
|
|
||||||
Column int // Where the token begins in the source text.
|
|
||||||
Type Type // What type the token is.
|
|
||||||
Value string // The value of the token.
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpenParen(column int) *Token {
|
|
||||||
return &Token{Type: TokenOpenParen, Column: column, Value: "("}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCloseParen(column int) *Token {
|
|
||||||
return &Token{Type: TokenCloseParen, Column: column, Value: ")"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpenBrace(column int) *Token {
|
|
||||||
return &Token{Type: TokenOpenBrace, Column: column, Value: "{"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCloseBrace(column int) *Token {
|
|
||||||
return &Token{Type: TokenCloseBrace, Column: column, Value: "}"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDot(column int) *Token {
|
|
||||||
return &Token{Type: TokenDot, Column: column, Value: "."}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHardBreak(column int) *Token {
|
|
||||||
return &Token{Type: TokenHardBreak, Column: column, Value: ";"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAssign(column int) *Token {
|
|
||||||
return &Token{Type: TokenAssign, Column: column, Value: ":="}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSlash(column int) *Token {
|
|
||||||
return &Token{Type: TokenSlash, Column: column, Value: "\\"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAtom(name string, column int) *Token {
|
|
||||||
return &Token{Type: TokenAtom, Column: column, Value: name}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSoftBreak(column int) *Token {
|
|
||||||
return &Token{Type: TokenSoftBreak, Column: column, Value: "\\n"}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Name(typ Type) string {
|
|
||||||
switch typ {
|
|
||||||
case TokenOpenParen:
|
|
||||||
return "("
|
|
||||||
case TokenCloseParen:
|
|
||||||
return ")"
|
|
||||||
case TokenSlash:
|
|
||||||
return "\\"
|
|
||||||
case TokenDot:
|
|
||||||
return "."
|
|
||||||
case TokenAtom:
|
|
||||||
return "ATOM"
|
|
||||||
case TokenSoftBreak:
|
|
||||||
return "\\n"
|
|
||||||
case TokenHardBreak:
|
|
||||||
return ";"
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("unknown token type %v", typ))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Token) Name() string {
|
|
||||||
return Name(t.Type)
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,9 @@
|
|||||||
package saccharine
|
package saccharine
|
||||||
|
|
||||||
import (
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/repr"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Expression interface {
|
type Expression interface {
|
||||||
repr.Repr
|
IsExpression()
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
_ Expression = Abstraction{}
|
|
||||||
_ Expression = Application{}
|
|
||||||
_ Expression = Variable{}
|
|
||||||
_ Expression = Clause{}
|
|
||||||
)
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
/** ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
type Abstraction struct {
|
type Abstraction struct {
|
||||||
@@ -22,38 +11,39 @@ type Abstraction struct {
|
|||||||
Body Expression
|
Body Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAbstraction(parameter []string, body Expression) *Abstraction {
|
|
||||||
return &Abstraction{Parameters: parameter, Body: body}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
Abstraction Expression
|
Abstraction Expression
|
||||||
Arguments []Expression
|
Arguments []Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApplication(abstraction Expression, arguments []Expression) *Application {
|
type Atom struct {
|
||||||
return &Application{Abstraction: abstraction, Arguments: arguments}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
type Variable struct {
|
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVariable(name string) *Variable {
|
|
||||||
return &Variable{Name: name}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
type Clause struct {
|
type Clause struct {
|
||||||
Statements []Statement
|
Statements []Statement
|
||||||
Returns Expression
|
Returns Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (Abstraction) IsExpression() {}
|
||||||
|
func (Application) IsExpression() {}
|
||||||
|
func (Atom) IsExpression() {}
|
||||||
|
func (Clause) IsExpression() {}
|
||||||
|
|
||||||
|
/** ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
func NewAbstraction(parameter []string, body Expression) *Abstraction {
|
||||||
|
return &Abstraction{Parameters: parameter, Body: body}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewApplication(abstraction Expression, arguments []Expression) *Application {
|
||||||
|
return &Application{Abstraction: abstraction, Arguments: arguments}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAtom(name string) *Atom {
|
||||||
|
return &Atom{Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
func NewClause(statements []Statement, returns Expression) *Clause {
|
func NewClause(statements []Statement, returns Expression) *Clause {
|
||||||
return &Clause{Statements: statements, Returns: returns}
|
return &Clause{Statements: statements, Returns: returns}
|
||||||
}
|
}
|
||||||
24
pkg/saccharine/marshaler.go
Normal file
24
pkg/saccharine/marshaler.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Package "saccharine" provides a simple language built on top of λ-calculus,
|
||||||
|
// to facilitate productive coding using it.
|
||||||
|
package saccharine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Marshaler struct{}
|
||||||
|
|
||||||
|
func (m Marshaler) Decode(s string) (Expression, error) {
|
||||||
|
tokens, err := scan(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Marshaler) Encode(e Expression) (string, error) {
|
||||||
|
return stringifyExpression(e), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ codec.Marshaler[Expression] = (*Marshaler)(nil)
|
||||||
@@ -10,12 +10,12 @@ import (
|
|||||||
|
|
||||||
type TokenIterator = iterator.Iterator[Token]
|
type TokenIterator = iterator.Iterator[Token]
|
||||||
|
|
||||||
func parseRawToken(i *TokenIterator, expected Type) (*Token, error) {
|
func parseRawToken(i *TokenIterator, expected TokenType) (*Token, error) {
|
||||||
return iterator.Do(i, func(i *TokenIterator) (*Token, error) {
|
return iterator.Do(i, func(i *TokenIterator) (*Token, error) {
|
||||||
if tok, err := i.Next(); err != nil {
|
if tok, err := i.Next(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if tok.Type != expected {
|
} else if tok.Type != expected {
|
||||||
return nil, fmt.Errorf("expected token %v, got %v'", Name(expected), tok.Value)
|
return nil, fmt.Errorf("expected token %v, got %v'", expected.Name(), tok.Value)
|
||||||
} else {
|
} else {
|
||||||
return &tok, nil
|
return &tok, nil
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ func passSoftBreaks(i *TokenIterator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseToken(i *TokenIterator, expected Type, ignoreSoftBreaks bool) (*Token, error) {
|
func parseToken(i *TokenIterator, expected TokenType, ignoreSoftBreaks bool) (*Token, error) {
|
||||||
return iterator.Do(i, func(i *TokenIterator) (*Token, error) {
|
return iterator.Do(i, func(i *TokenIterator) (*Token, error) {
|
||||||
if ignoreSoftBreaks {
|
if ignoreSoftBreaks {
|
||||||
passSoftBreaks(i)
|
passSoftBreaks(i)
|
||||||
@@ -103,11 +103,11 @@ func parseApplication(i *TokenIterator) (*Application, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAtom(i *TokenIterator) (*Variable, error) {
|
func parseAtom(i *TokenIterator) (*Atom, error) {
|
||||||
if tok, err := parseToken(i, TokenAtom, true); err != nil {
|
if tok, err := parseToken(i, TokenAtom, true); err != nil {
|
||||||
return nil, trace.Wrap(err, "no variable (col %d)", i.Index())
|
return nil, trace.Wrap(err, "no variable (col %d)", i.Index())
|
||||||
} else {
|
} else {
|
||||||
return NewVariable(tok.Value), nil
|
return NewAtom(tok.Value), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,13 +225,3 @@ func parse(tokens []Token) (Expression, error) {
|
|||||||
|
|
||||||
return exp, nil
|
return exp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a piece of valid saccharine code into an expression.
|
|
||||||
func Parse(code string) (Expression, error) {
|
|
||||||
tokens, err := Scan(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return parse(tokens)
|
|
||||||
}
|
|
||||||
@@ -56,27 +56,27 @@ func scanToken(i *iterator.Iterator[rune]) (*Token, error) {
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case letter == '(':
|
case letter == '(':
|
||||||
return NewOpenParen(index), nil
|
return NewTokenOpenParen(index), nil
|
||||||
case letter == ')':
|
case letter == ')':
|
||||||
return NewCloseParen(index), nil
|
return NewTokenCloseParen(index), nil
|
||||||
case letter == '.':
|
case letter == '.':
|
||||||
return NewDot(index), nil
|
return NewTokenDot(index), nil
|
||||||
case letter == '\\':
|
case letter == '\\':
|
||||||
return NewSlash(index), nil
|
return NewTokenSlash(index), nil
|
||||||
case letter == '\n':
|
case letter == '\n':
|
||||||
return NewSoftBreak(index), nil
|
return NewTokenSoftBreak(index), nil
|
||||||
case letter == '{':
|
case letter == '{':
|
||||||
return NewOpenBrace(index), nil
|
return NewTokenOpenBrace(index), nil
|
||||||
case letter == '}':
|
case letter == '}':
|
||||||
return NewCloseBrace(index), nil
|
return NewTokenCloseBrace(index), nil
|
||||||
case letter == ':':
|
case letter == ':':
|
||||||
if _, err := scanCharacter(i, '='); err != nil {
|
if _, err := scanCharacter(i, '='); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
return NewAssign(index), nil
|
return NewTokenAssign(index), nil
|
||||||
}
|
}
|
||||||
case letter == ';':
|
case letter == ';':
|
||||||
return NewHardBreak(index), nil
|
return NewTokenHardBreak(index), nil
|
||||||
case letter == '#':
|
case letter == '#':
|
||||||
// Skip everything until the next newline or EOF.
|
// Skip everything until the next newline or EOF.
|
||||||
for !i.Done() {
|
for !i.Done() {
|
||||||
@@ -105,14 +105,14 @@ func scanToken(i *iterator.Iterator[rune]) (*Token, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewAtom(string(atom), index), nil
|
return NewTokenAtom(string(atom), index), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown character '%v'", string(letter))
|
return nil, fmt.Errorf("unknown character '%v'", string(letter))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a string into tokens.
|
// scan a string into tokens.
|
||||||
func Scan(input string) ([]Token, error) {
|
func scan(input string) ([]Token, error) {
|
||||||
i := iterator.Of([]rune(input))
|
i := iterator.Of([]rune(input))
|
||||||
tokens := []Token{}
|
tokens := []Token{}
|
||||||
errorList := []error{}
|
errorList := []error{}
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
package saccharine
|
package saccharine
|
||||||
|
|
||||||
import (
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/repr"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Statement interface {
|
type Statement interface {
|
||||||
repr.Repr
|
IsStatement()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
/** ------------------------------------------------------------------------- */
|
||||||
@@ -16,14 +12,17 @@ type LetStatement struct {
|
|||||||
Body Expression
|
Body Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLet(name string, parameters []string, body Expression) *LetStatement {
|
type DeclareStatement struct {
|
||||||
return &LetStatement{Name: name, Parameters: parameters, Body: body}
|
Value Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (LetStatement) IsStatement() {}
|
||||||
|
func (DeclareStatement) IsStatement() {}
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
/** ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
type DeclareStatement struct {
|
func NewLet(name string, parameters []string, body Expression) *LetStatement {
|
||||||
Value Expression
|
return &LetStatement{Name: name, Parameters: parameters, Body: body}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeclare(value Expression) *DeclareStatement {
|
func NewDeclare(value Expression) *DeclareStatement {
|
||||||
@@ -5,30 +5,30 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func stringifyAtom(n *Variable) string {
|
func stringifyAtom(n *Atom) string {
|
||||||
return n.Name
|
return n.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringifyAbstraction(n *Abstraction) string {
|
func stringifyAbstraction(n *Abstraction) string {
|
||||||
return "\\" + strings.Join(n.Parameters, " ") + "." + Stringify(n.Body)
|
return "\\" + strings.Join(n.Parameters, " ") + "." + stringifyExpression(n.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringifyApplication(n *Application) string {
|
func stringifyApplication(n *Application) string {
|
||||||
arguments := []string{Stringify(n.Abstraction)}
|
arguments := []string{stringifyExpression(n.Abstraction)}
|
||||||
|
|
||||||
for _, argument := range n.Arguments {
|
for _, argument := range n.Arguments {
|
||||||
arguments = append(arguments, Stringify(argument))
|
arguments = append(arguments, stringifyExpression(argument))
|
||||||
}
|
}
|
||||||
|
|
||||||
return "(" + strings.Join(arguments, " ") + ")"
|
return "(" + strings.Join(arguments, " ") + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringifyLet(s *LetStatement) string {
|
func stringifyLet(s *LetStatement) string {
|
||||||
return s.Name + " " + strings.Join(s.Parameters, " ") + " := " + Stringify(s.Body)
|
return s.Name + " " + strings.Join(s.Parameters, " ") + " := " + stringifyExpression(s.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringifyDeclare(s *DeclareStatement) string {
|
func stringifyDeclare(s *DeclareStatement) string {
|
||||||
return Stringify(s.Value)
|
return stringifyExpression(s.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringifyStatement(s Statement) string {
|
func stringifyStatement(s Statement) string {
|
||||||
@@ -49,13 +49,13 @@ func stringifyClause(n *Clause) string {
|
|||||||
stmts += stringifyStatement(statement) + "; "
|
stmts += stringifyStatement(statement) + "; "
|
||||||
}
|
}
|
||||||
|
|
||||||
return "{ " + stmts + Stringify(n.Returns) + " }"
|
return "{ " + stmts + stringifyExpression(n.Returns) + " }"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert an expression back into valid source code.
|
// Convert an expression back into valid source code.
|
||||||
func Stringify(n Expression) string {
|
func stringifyExpression(n Expression) string {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *Variable:
|
case *Atom:
|
||||||
return stringifyAtom(n)
|
return stringifyAtom(n)
|
||||||
case *Abstraction:
|
case *Abstraction:
|
||||||
return stringifyAbstraction(n)
|
return stringifyAbstraction(n)
|
||||||
91
pkg/saccharine/token.go
Normal file
91
pkg/saccharine/token.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package saccharine
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// All tokens in the pseudo-lambda language.
|
||||||
|
type TokenType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenOpenParen TokenType = iota // Denotes the '(' token.
|
||||||
|
TokenCloseParen // Denotes the ')' token.
|
||||||
|
TokenOpenBrace // Denotes the '{' token.
|
||||||
|
TokenCloseBrace // Denotes the '}' token.
|
||||||
|
TokenHardBreak // Denotes the ';' token.
|
||||||
|
TokenAssign // Denotes the ':=' token.
|
||||||
|
TokenAtom // Denotes an alpha-numeric variable.
|
||||||
|
TokenSlash // Denotes the '/' token.
|
||||||
|
TokenDot // Denotes the '.' token.
|
||||||
|
TokenSoftBreak // Denotes a new-line.
|
||||||
|
)
|
||||||
|
|
||||||
|
// A representation of a token in source code.
|
||||||
|
type Token struct {
|
||||||
|
Column int // Where the token begins in the source text.
|
||||||
|
Type TokenType // What type the token is.
|
||||||
|
Value string // The value of the token.
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenOpenParen(column int) *Token {
|
||||||
|
return &Token{Type: TokenOpenParen, Column: column, Value: "("}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenCloseParen(column int) *Token {
|
||||||
|
return &Token{Type: TokenCloseParen, Column: column, Value: ")"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenOpenBrace(column int) *Token {
|
||||||
|
return &Token{Type: TokenOpenBrace, Column: column, Value: "{"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenCloseBrace(column int) *Token {
|
||||||
|
return &Token{Type: TokenCloseBrace, Column: column, Value: "}"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenDot(column int) *Token {
|
||||||
|
return &Token{Type: TokenDot, Column: column, Value: "."}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenHardBreak(column int) *Token {
|
||||||
|
return &Token{Type: TokenHardBreak, Column: column, Value: ";"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenAssign(column int) *Token {
|
||||||
|
return &Token{Type: TokenAssign, Column: column, Value: ":="}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenSlash(column int) *Token {
|
||||||
|
return &Token{Type: TokenSlash, Column: column, Value: "\\"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenAtom(name string, column int) *Token {
|
||||||
|
return &Token{Type: TokenAtom, Column: column, Value: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenSoftBreak(column int) *Token {
|
||||||
|
return &Token{Type: TokenSoftBreak, Column: column, Value: "\\n"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TokenType) Name() string {
|
||||||
|
switch t {
|
||||||
|
case TokenOpenParen:
|
||||||
|
return "("
|
||||||
|
case TokenCloseParen:
|
||||||
|
return ")"
|
||||||
|
case TokenSlash:
|
||||||
|
return "\\"
|
||||||
|
case TokenDot:
|
||||||
|
return "."
|
||||||
|
case TokenAtom:
|
||||||
|
return "ATOM"
|
||||||
|
case TokenSoftBreak:
|
||||||
|
return "\\n"
|
||||||
|
case TokenHardBreak:
|
||||||
|
return ";"
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unknown token type %v", t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Token) Name() string {
|
||||||
|
return t.Type.Name()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user