diff --git a/.gitignore b/.gitignore index 5b90e79..71c04ed 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ go.work.sum # env file .env +*.log diff --git a/cmd/lambda/lambda.go b/cmd/lambda/lambda.go index 1f35c85..1ea2978 100644 --- a/cmd/lambda/lambda.go +++ b/cmd/lambda/lambda.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "time" "git.maximhutz.com/max/lambda/internal/cli" "git.maximhutz.com/max/lambda/pkg/lambda" @@ -35,9 +36,21 @@ func main() { cli.HandleError(err) logger.Info("Parsed syntax tree.", "tree", lambda.Stringify(expression)) - for lambda.ReduceOnce(&expression) { - logger.Info("Reduction.", "tree", lambda.Stringify(expression)) + start := time.Now() + + if options.Explanation { + fmt.Println(lambda.Stringify(expression)) } + for lambda.ReduceOnce(&expression) { + logger.Info("Reduction.", "tree", lambda.Stringify(expression)) + if options.Explanation { + fmt.Println(" =", lambda.Stringify(expression)) + } + } + + elapsed := time.Since(start).Milliseconds() + fmt.Println(lambda.Stringify(expression)) + fmt.Fprintln(os.Stderr, "Time Spent:", elapsed, "ms") } diff --git a/internal/cli/arguments.go b/internal/cli/arguments.go index 7db3943..56ed199 100644 --- a/internal/cli/arguments.go +++ b/internal/cli/arguments.go @@ -6,13 +6,15 @@ import ( ) type CLIOptions struct { - Input string - Verbose bool + Input string + Verbose bool + Explanation bool } func ParseOptions(args []string) (*CLIOptions, error) { // Parse flags and arguments. 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.") flag.Parse() if flag.NArg() == 0 { @@ -22,7 +24,8 @@ func ParseOptions(args []string) (*CLIOptions, error) { } return &CLIOptions{ - Input: flag.Arg(0), - Verbose: *verbose, + Input: flag.Arg(0), + Verbose: *verbose, + Explanation: *explanation, }, nil } diff --git a/pkg/lambda/substitute.go b/pkg/lambda/substitute.go index a8ba42f..29e2308 100644 --- a/pkg/lambda/substitute.go +++ b/pkg/lambda/substitute.go @@ -20,7 +20,7 @@ func Substitute(e *Expression, target string, replacement Expression) { used := GetFreeVariables(typed.Body) used.Union(replacement_free_vars) fresh_var := GenerateFreshName(used) - Rename(typed.Body, typed.Parameter, fresh_var) + Rename(typed, typed.Parameter, fresh_var) Substitute(&typed.Body, target, replacement) case *Application: Substitute(&typed.Abstraction, target, replacement) diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index 7a3301d..0743039 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -20,18 +20,23 @@ func ParseExpression(i *iterator.Iterator[tokenizer.Token]) (lambda.Expression, case tokenizer.TokenDot: return nil, fmt.Errorf("Token '.' found without a corresponding slash (column %d).", token.Index) case tokenizer.TokenSlash: - atom, atom_err := i.Next() - if atom_err != nil { - return nil, fmt.Errorf("Could not find parameter of function: %w", atom_err) - } else if atom.Type != tokenizer.TokenVariable { - return nil, fmt.Errorf("Expected function parameter, got '%v' (column %d).", atom.Value, atom.Index) + atoms := []string{} + + for { + atom, atom_err := i.Next() + if atom_err != nil { + return nil, fmt.Errorf("Could not find parameter or terminator of function: %w", atom_err) + } else if atom.Type == tokenizer.TokenVariable { + atoms = append(atoms, atom.Value) + } else if atom.Type == tokenizer.TokenDot { + break + } else { + return nil, fmt.Errorf("Expected function parameter or terminator, got '%v' (column %d).", atom.Value, atom.Index) + } } - dot, dot_err := i.Next() - if dot_err != nil { - return nil, fmt.Errorf("Could not find function parameter terminator: %w", dot_err) - } else if dot.Type != tokenizer.TokenDot { - return nil, fmt.Errorf("Expected function parameter terminator, got '%v' (column %v).", dot.Value, dot.Index) + if len(atoms) == 0 { + return nil, fmt.Errorf("Every function must have atleast one parameter (column %d)", token.Index) } body, body_err := ParseExpression(i) @@ -39,16 +44,32 @@ func ParseExpression(i *iterator.Iterator[tokenizer.Token]) (lambda.Expression, return nil, fmt.Errorf("Could not parse function body: %w", body_err) } - return lambda.NewAbstraction(atom.Value, body), nil + // Construction. + result := body + for i := len(atoms) - 1; i >= 0; i-- { + result = lambda.NewAbstraction(atoms[i], result) + } + + return result, nil case tokenizer.TokenOpenParen: fn, fn_err := ParseExpression(i) if fn_err != nil { return nil, fmt.Errorf("Could not parse call function: %w", fn_err) } - arg, arg_err := ParseExpression(i) - if arg_err != nil { - return nil, fmt.Errorf("Could not parse call argument: %w", arg_err) + args := []lambda.Expression{} + + for { + if next, next_err := i.Peek(); next_err == nil && next.Type == tokenizer.TokenCloseParen { + break + } + + arg, arg_err := ParseExpression(i) + if arg_err != nil { + return nil, fmt.Errorf("Could not parse call argument: %w", arg_err) + } + + args = append(args, arg) } close, close_err := i.Next() @@ -58,7 +79,13 @@ 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.NewApplication(fn, arg), nil + // Construction. + result := fn + for _, arg := range args { + result = lambda.NewApplication(result, arg) + } + + return result, nil case tokenizer.TokenCloseParen: return nil, fmt.Errorf("Token ')' found without a corresponding openning parenthesis (column %d).", token.Index) default: diff --git a/samples/simple.txt b/samples/simple.txt index be9d8a3..c2fad93 100644 --- a/samples/simple.txt +++ b/samples/simple.txt @@ -1 +1,16 @@ -(\add.(add (add (add \f.\x.x))) \n.\f.\x.(f ((n f) x))) \ No newline at end of file +(\0. + (\inc. + (\add. + (\mult. + (\exp. + (exp (inc (inc (inc (inc (inc 0))))) (inc (inc (inc (inc (inc 0)))))) + \n m.(m n) + ) + \m n f.(m (n f)) + ) + \n m.(m inc n) + ) + \n f x.(f (n f x)) + ) + \f x.x +) \ No newline at end of file