From 2c3ce9baf761cff8e26ba057feaa2c9f01eac56f Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 24 Dec 2025 14:55:33 -0500 Subject: [PATCH] feat: wogihrsoiuvjsroirgj --- cmd/lambda/lambda.go | 23 +++++++--- {pkg => internal}/cli/arguments.go | 22 +++++----- {pkg => internal}/cli/exit.go | 0 internal/cli/logger.go | 21 +++++++++ pkg/lambda/evaluate.go | 70 ++++++++++++++++++++++++++++++ pkg/lambda/expression.go | 52 +++++++++++++++++++--- pkg/lambda/print.go | 36 +++++++++++++++ pkg/parser/parser.go | 6 +-- 8 files changed, 204 insertions(+), 26 deletions(-) rename {pkg => internal}/cli/arguments.go (53%) rename {pkg => internal}/cli/exit.go (100%) create mode 100644 internal/cli/logger.go create mode 100644 pkg/lambda/evaluate.go create mode 100644 pkg/lambda/print.go diff --git a/cmd/lambda/lambda.go b/cmd/lambda/lambda.go index f39cfde..72e0f34 100644 --- a/cmd/lambda/lambda.go +++ b/cmd/lambda/lambda.go @@ -2,28 +2,37 @@ package main import ( "errors" - "log/slog" + "fmt" "os" - "git.maximhutz.com/max/lambda/pkg/cli" + "git.maximhutz.com/max/lambda/internal/cli" + "git.maximhutz.com/max/lambda/pkg/lambda" "git.maximhutz.com/max/lambda/pkg/parser" "git.maximhutz.com/max/lambda/pkg/tokenizer" ) func main() { - slog.Info("Using program arguments.", "args", os.Args) options, err := cli.ParseOptions(os.Args[1:]) cli.HandleError(err) - slog.Info("Parsed CLI options.", "options", options) + + logger := cli.GetLogger(*options) + logger.Info("Using program arguments.", "args", os.Args) + logger.Info("Parsed CLI options.", "options", options) tokens, fails := tokenizer.GetTokens([]rune(options.Input)) if len(fails) > 0 { cli.HandleError(errors.Join(fails...)) } - slog.Info("Parsed tokens.", "tokens", tokens) + logger.Info("Parsed tokens.", "tokens", tokens) - ast, err := parser.GetTree(tokens) + expression, err := parser.GetTree(tokens) cli.HandleError(err) - slog.Info("Parsed syntax tree.", "tree", ast) + logger.Info("Parsed syntax tree.", "tree", expression) + + evaluated := lambda.Evaluate(expression) + cli.HandleError(err) + logger.Info("Evaluated expression.", "tree", evaluated) + + fmt.Println(lambda.ToString(evaluated)) } diff --git a/pkg/cli/arguments.go b/internal/cli/arguments.go similarity index 53% rename from pkg/cli/arguments.go rename to internal/cli/arguments.go index a52acff..7db3943 100644 --- a/pkg/cli/arguments.go +++ b/internal/cli/arguments.go @@ -3,26 +3,26 @@ package cli import ( "flag" "fmt" - "log/slog" - "os" ) type CLIOptions struct { - Input string + Input string + Verbose bool } func ParseOptions(args []string) (*CLIOptions, error) { - slog.Info("Parsing CLI arguments.", "args", os.Args) - // Parse flags and arguments. + verbose := flag.Bool("v", false, "Verbosity. If set, the program will print logs.") flag.Parse() - switch flag.NArg() { - case 0: + if flag.NArg() == 0 { return nil, fmt.Errorf("No input given.") - case 1: - return &CLIOptions{Input: flag.Arg(0)}, nil - default: + } else if flag.NArg() > 1 { return nil, fmt.Errorf("More than 1 command-line argument.") } -} \ No newline at end of file + + return &CLIOptions{ + Input: flag.Arg(0), + Verbose: *verbose, + }, nil +} diff --git a/pkg/cli/exit.go b/internal/cli/exit.go similarity index 100% rename from pkg/cli/exit.go rename to internal/cli/exit.go diff --git a/internal/cli/logger.go b/internal/cli/logger.go new file mode 100644 index 0000000..97437a4 --- /dev/null +++ b/internal/cli/logger.go @@ -0,0 +1,21 @@ +package cli + +import ( + "log/slog" + "os" +) + +func GetLogger(o CLIOptions) *slog.Logger { + var level slog.Level + if o.Verbose { + level = slog.LevelInfo + } else { + level = slog.LevelError + } + + return slog.New( + slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: level, + }), + ) +} diff --git a/pkg/lambda/evaluate.go b/pkg/lambda/evaluate.go new file mode 100644 index 0000000..97822f9 --- /dev/null +++ b/pkg/lambda/evaluate.go @@ -0,0 +1,70 @@ +package lambda + +// func replaceUnbound(name string, replacement Expression, e Expression) Expression { +// switch e := e.(type) { +// case Atom: +// if e.Value == name { +// return replacement +// } else { +// return e +// } +// case Function: +// if e.Parameter == name { +// return e +// } else { +// return Function{ +// Parameter: e.Parameter, +// Body: replaceUnbound(name, replacement, e.Body), +// } +// } +// case Call: +// return Call{ +// Function: replaceUnbound(name, replacement, e.Function), +// Argument: replaceUnbound(name, replacement, e.Argument), +// } +// } +// } + +// func evaluateAtom(a Atom) Expression { +// return a +// } + +// func evaluateFunction(f Function) Expression { +// return Function{ +// Parameter: f.Parameter, +// Body: Evaluate(f.Body), +// } +// } + +// func evaluateCall(c Call) Expression { +// fn := c.Function + +// as_fn, as_fn_ok := fn.(Function) +// if !as_fn_ok { +// fn = Evaluate(fn) + +// if as_fn, as_fn_ok = fn.(Function); !as_fn_ok { +// return Call{ +// Function: fn, +// Argument: Evaluate(c.Argument), +// } +// } +// } + +// return replaceUnboundAtoms(as_fn.Parameter, c.Argument, as_fn.Body) +// } + +func Evaluate(e Expression) Expression { + // switch e := e.(type) { + // case Atom: + // return e + // case Call: + + // case Function: + // return Function{ + // Parameter: e.Parameter, + // Body: Evaluate(e.Body), + // } + // } + return e +} diff --git a/pkg/lambda/expression.go b/pkg/lambda/expression.go index 91dfe51..fc02016 100644 --- a/pkg/lambda/expression.go +++ b/pkg/lambda/expression.go @@ -1,23 +1,65 @@ package lambda type Expression interface { - isExpression() + Accept(ExpressionVisitor) } +/** ------------------------------------------------------------------------- */ + type Function struct { Parameter string - Body Expression + Body Expression } +func NewFunction(parameter string, body Expression) *Function { + return &Function{ + Parameter: parameter, + Body: body, + } +} + +func (f *Function) Accept(v ExpressionVisitor) { + v.VisitFunction(f) +} + +/** ------------------------------------------------------------------------- */ + type Call struct { Function Expression Argument Expression } +func NewCall(function Expression, argument Expression) *Call { + return &Call{ + Function: function, + Argument: argument, + } +} + +func (c *Call) Accept(v ExpressionVisitor) { + v.VisitCall(c) +} + +/** ------------------------------------------------------------------------- */ + type Atom struct { Value string } -func (_ Function) isExpression() {} -func (_ Call) isExpression() {} -func (_ Atom) isExpression() {} \ No newline at end of file +func NewAtom(name string) *Atom { + return &Atom{ + Value: name, + } +} + +func (a *Atom) Accept(v ExpressionVisitor) { + v.VisitAtom(a) +} + +/** ------------------------------------------------------------------------- */ + +type ExpressionVisitor interface { + VisitFunction(*Function) + VisitCall(*Call) + VisitAtom(*Atom) +} diff --git a/pkg/lambda/print.go b/pkg/lambda/print.go new file mode 100644 index 0000000..0690bc1 --- /dev/null +++ b/pkg/lambda/print.go @@ -0,0 +1,36 @@ +package lambda + +import "strings" + +type StringifyVisitor struct { + builder strings.Builder +} + +func (v *StringifyVisitor) VisitAtom(a *Atom) { + v.builder.WriteString(a.Value) +} + +func (v *StringifyVisitor) VisitFunction(f *Function) { + v.builder.WriteRune('\\') + v.builder.WriteString(f.Parameter) + v.builder.WriteRune('.') + f.Body.Accept(v) +} + +func (v *StringifyVisitor) VisitCall(c *Call) { + v.builder.WriteRune('(') + c.Function.Accept(v) + v.builder.WriteRune(' ') + c.Argument.Accept(v) + v.builder.WriteRune(')') +} + +func ToString(e Expression) string { + b := StringifyVisitor{ + builder: strings.Builder{}, + } + + e.Accept(&b) + + return b.builder.String() +} diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index 0e5a7a1..87e20ca 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -16,7 +16,7 @@ func ParseExpression(i *iterator.Iterator[tokenizer.Token]) (lambda.Expression, switch token.Type { case tokenizer.TokenAtom: - return lambda.Atom{Value: token.Value}, nil + return lambda.NewAtom(token.Value), nil case tokenizer.TokenDot: return nil, fmt.Errorf("Token '.' found without a corresponding slash (column %d).", token.Index) case tokenizer.TokenSlash: @@ -39,7 +39,7 @@ func ParseExpression(i *iterator.Iterator[tokenizer.Token]) (lambda.Expression, return nil, fmt.Errorf("Could not parse function body: %w", body_err) } - return lambda.Function{Parameter: atom.Value, Body: body}, nil + return lambda.NewFunction(atom.Value, body), nil case tokenizer.TokenOpenParen: fn, fn_err := ParseExpression(i) if fn_err != nil { @@ -58,7 +58,7 @@ func ParseExpression(i *iterator.Iterator[tokenizer.Token]) (lambda.Expression, return nil, fmt.Errorf("Expected call terminating parenthesis, got '%v' (column %v).", close.Value, close.Index) } - return lambda.Call{Function: fn, Argument: arg}, nil + return lambda.NewCall(fn, arg), nil case tokenizer.TokenCloseParen: return nil, fmt.Errorf("Token ')' found without a corresponding openning parenthesis (column %d).", token.Index) default: