feat: progress
This commit is contained in:
30
cmd/lambda/convert.go
Normal file
30
cmd/lambda/convert.go
Normal file
@@ -0,0 +1,30 @@
|
||||
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)
|
||||
}
|
||||
21
cmd/lambda/engine.go
Normal file
21
cmd/lambda/engine.go
Normal file
@@ -0,0 +1,21 @@
|
||||
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)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"git.maximhutz.com/max/lambda/internal/cli"
|
||||
"git.maximhutz.com/max/lambda/internal/config"
|
||||
"git.maximhutz.com/max/lambda/internal/plugins"
|
||||
"git.maximhutz.com/max/lambda/pkg/convert"
|
||||
"git.maximhutz.com/max/lambda/pkg/normalorder"
|
||||
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Parse CLI arguments.
|
||||
options, err := config.FromArgs()
|
||||
cli.HandleError(err)
|
||||
|
||||
logger := options.GetLogger()
|
||||
logger.Info("using program arguments", "args", os.Args)
|
||||
logger.Info("parsed CLI options", "options", options)
|
||||
|
||||
// Get input.
|
||||
input, err := options.Source.Extract()
|
||||
cli.HandleError(err)
|
||||
|
||||
// Parse code into syntax tree.
|
||||
ast, err := saccharine.Parse(input)
|
||||
cli.HandleError(err)
|
||||
logger.Info("parsed syntax tree", "tree", ast)
|
||||
|
||||
// Compile expression to lambda calculus.
|
||||
compiled := convert.SaccharineToLambda(ast)
|
||||
logger.Info("compiled λ expression", "tree", compiled.String())
|
||||
|
||||
// Create reducer with the compiled expression.
|
||||
runtime := normalorder.NewRuntime(compiled)
|
||||
|
||||
// If the user selected to track CPU performance, attach a profiler.
|
||||
if options.Profile != "" {
|
||||
plugins.NewPerformance(options.Profile, runtime)
|
||||
}
|
||||
|
||||
// If the user selected to produce a step-by-step explanation, attach an
|
||||
// observer.
|
||||
if options.Explanation {
|
||||
plugins.NewExplanation(runtime)
|
||||
}
|
||||
|
||||
// If the user opted to track statistics, attach a tracker.
|
||||
if options.Statistics {
|
||||
plugins.NewStatistics(runtime)
|
||||
}
|
||||
|
||||
// If the user selected for verbose debug logs, attach a reduction tracker.
|
||||
if options.Verbose {
|
||||
plugins.NewLogs(logger, runtime)
|
||||
}
|
||||
|
||||
// Run reduction.
|
||||
runtime.Run()
|
||||
|
||||
// Return the final reduced result.
|
||||
result := runtime.Expression().String()
|
||||
err = options.Destination.Write(result)
|
||||
cli.HandleError(err)
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.maximhutz.com/max/lambda/pkg/convert"
|
||||
"git.maximhutz.com/max/lambda/pkg/normalorder"
|
||||
"git.maximhutz.com/max/lambda/pkg/saccharine"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Helper function to run a single sample through the lambda runtime.
|
||||
func runSample(samplePath string) (string, error) {
|
||||
// Read the sample file.
|
||||
input, err := os.ReadFile(samplePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Parse code into syntax tree.
|
||||
ast, err := saccharine.Parse(string(input))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Compile expression to lambda calculus.
|
||||
compiled := convert.SaccharineToLambda(ast)
|
||||
|
||||
// Create and run the reducer.
|
||||
reducer := normalorder.NewRuntime(compiled)
|
||||
reducer.Run()
|
||||
|
||||
return reducer.Expression().String() + "\n", nil
|
||||
}
|
||||
|
||||
// Test that all samples produce expected output.
|
||||
func TestSamplesValidity(t *testing.T) {
|
||||
// Discover all .test files in the tests directory.
|
||||
testFiles, err := filepath.Glob("../../tests/*.test")
|
||||
assert.NoError(t, err, "Failed to read tests directory.")
|
||||
assert.NotEmpty(t, testFiles, "No '*.test' files found in directory.")
|
||||
|
||||
for _, testPath := range testFiles {
|
||||
// Build expected file path.
|
||||
expectedPath := strings.TrimSuffix(testPath, filepath.Ext(testPath)) + ".expected"
|
||||
|
||||
name := strings.TrimSuffix(filepath.Base(testPath), filepath.Ext(testPath))
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Run the sample and capture output.
|
||||
actual, err := runSample(testPath)
|
||||
assert.NoError(t, err, "Failed to run sample.")
|
||||
|
||||
// Read expected output.
|
||||
expectedBytes, err := os.ReadFile(expectedPath)
|
||||
assert.NoError(t, err, "Failed to read expected output.")
|
||||
expected := string(expectedBytes)
|
||||
|
||||
// Compare outputs.
|
||||
assert.Equal(t, expected, actual, "Output does not match expected.")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark all samples using sub-benchmarks.
|
||||
func BenchmarkSamples(b *testing.B) {
|
||||
// Discover all .test files in the tests directory.
|
||||
testFiles, err := filepath.Glob("../../tests/*.test")
|
||||
assert.NoError(b, err, "Failed to read tests directory.")
|
||||
assert.NotEmpty(b, testFiles, "No '*.test' files found in directory.")
|
||||
|
||||
for _, path := range testFiles {
|
||||
name := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))
|
||||
|
||||
b.Run(name, func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
_, err := runSample(path)
|
||||
assert.NoError(b, err, "Failed to run sample.")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
27
cmd/lambda/repl.go
Normal file
27
cmd/lambda/repl.go
Normal file
@@ -0,0 +1,27 @@
|
||||
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)
|
||||
}
|
||||
21
cmd/lambda/repr.go
Normal file
21
cmd/lambda/repr.go
Normal file
@@ -0,0 +1,21 @@
|
||||
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)
|
||||
}
|
||||
19
cmd/lambda/root.go
Normal file
19
cmd/lambda/root.go
Normal file
@@ -0,0 +1,19 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
31
cmd/lambda/run.go
Normal file
31
cmd/lambda/run.go
Normal file
@@ -0,0 +1,31 @@
|
||||
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)
|
||||
}
|
||||
28
cmd/lambda/trace.go
Normal file
28
cmd/lambda/trace.go
Normal file
@@ -0,0 +1,28 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user