diff --git a/cmd/lambda/lambda.go b/cmd/lambda/lambda.go
index 0652123..81fcb2b 100644
--- a/cmd/lambda/lambda.go
+++ b/cmd/lambda/lambda.go
@@ -1,55 +1,94 @@
package main
import (
+ "fmt"
"os"
- "git.maximhutz.com/max/lambda/internal/cli"
"git.maximhutz.com/max/lambda/internal/config"
+ "github.com/spf13/cobra"
)
-func main() {
- // Parse CLI arguments.
- options, err := config.FromArgs()
- cli.HandleError(err)
+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, args []string) error {
+ // Legacy behavior when no subcommand is given.
+ options, err := config.FromArgs()
+ if err != nil {
+ return err
+ }
- logger := options.GetLogger()
- logger.Info("using program arguments", "args", os.Args)
- logger.Info("parsed CLI options", "options", options)
+ logger := options.GetLogger()
+ logger.Info("using program arguments", "args", os.Args)
+ logger.Info("parsed CLI options", "options", options)
- r := GetRegistry()
+ r := GetRegistry()
- // Get input.
- input, err := options.Source.Extract()
- cli.HandleError(err)
+ // Get input.
+ input, err := options.Source.Extract()
+ if err != nil {
+ return err
+ }
- // Parse code into syntax tree.
- repr, err := r.Unmarshal(input, "saccharine")
- cli.HandleError(err)
- logger.Info("parsed syntax tree", "tree", repr)
+ // Parse code into syntax tree.
+ repr, err := r.Unmarshal(input, "saccharine")
+ if err != nil {
+ return err
+ }
+ logger.Info("parsed syntax tree", "tree", repr)
- // Compile expression to lambda calculus.
- compiled, err := r.ConvertTo(repr, "lambda")
- cli.HandleError(err)
- logger.Info("compiled λ expression", "tree", compiled)
+ // Compile expression to lambda calculus.
+ compiled, err := r.ConvertTo(repr, "lambda")
+ if err != nil {
+ return err
+ }
+ logger.Info("compiled λ expression", "tree", compiled)
- // Create reducer with the compiled expression.
- engine, err := r.GetDefaultEngine("lambda")
- cli.HandleError(err)
+ // Create reducer with the compiled expression.
+ engine, err := r.GetDefaultEngine("lambda")
+ if err != nil {
+ return err
+ }
- process := engine.Load()
- err = process.Set(compiled)
- cli.HandleError(err)
- // Run reduction.
- for process.Step(1) {
+ 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 options.Destination.Write(output)
+ },
}
- // Return the final reduced result.
- result, err := process.Get()
- cli.HandleError(err)
+ cmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose output")
- output, err := r.Marshal(result)
- cli.HandleError(err)
+ cmd.AddCommand(LambdaConvert())
+ cmd.AddCommand(LambdaEngine())
- err = options.Destination.Write(output)
- cli.HandleError(err)
+ return cmd
+}
+
+func main() {
+ lambda := Lambda()
+ if err := lambda.Execute(); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
}
diff --git a/cmd/lambda/lambda_convert.go b/cmd/lambda/lambda_convert.go
new file mode 100644
index 0000000..c0737c5
--- /dev/null
+++ b/cmd/lambda/lambda_convert.go
@@ -0,0 +1,100 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "git.maximhutz.com/max/lambda/internal/cli"
+ "github.com/spf13/cobra"
+ "github.com/spf13/viper"
+)
+
+// inferReprFromPath returns the repr type based on file extension.
+func inferReprFromPath(path string) (string, error) {
+ switch ext := strings.ToLower(filepath.Ext(path)); ext {
+ case ".lam", ".lambda":
+ return "lambda", nil
+ case ".sac", ".saccharine":
+ return "saccharine", nil
+ default:
+ return "", fmt.Errorf("unknown file extension '%s'", ext)
+ }
+}
+
+func LambdaConvert() *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "convert