diff --git a/cmd/lambda/lambda.go b/cmd/lambda/lambda.go index af63446..3f02e5c 100644 --- a/cmd/lambda/lambda.go +++ b/cmd/lambda/lambda.go @@ -28,19 +28,14 @@ func main() { input, err := options.Source.Extract() cli.HandleError(err) - // Parse tokens. - tokens, err := saccharine.GetTokens(input) + // Parse code into syntax tree. + ast, err := saccharine.Parse(input) cli.HandleError(err) - logger.Info("parsed tokens", "tokens", tokens) - - // Turn tokens into syntax tree. - expression, err := saccharine.Parse(tokens) - cli.HandleError(err) - logger.Info("parsed syntax tree", "tree", saccharine.Stringify(expression)) + logger.Info("parsed syntax tree", "tree", ast) // Compile expression to lambda calculus. - compiled := convert.SaccharineToLambda(expression) - logger.Info("compiled lambda expression", "tree", lambda.Stringify(compiled)) + compiled := convert.SaccharineToLambda(ast) + logger.Info("compiled λ expression", "tree", lambda.Stringify(compiled)) // Create reduction engine. process := engine.New(options, &compiled) diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 6ccf421..9fb242e 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -1,5 +1,5 @@ // Package "engine" provides an extensible interface for users to interfact with -// lambda calculus. +// λ-calculus. package engine import ( @@ -8,14 +8,14 @@ import ( "git.maximhutz.com/max/lambda/pkg/lambda" ) -// A process for reducing one lambda expression. +// A process for reducing one λ-expression. type Engine struct { Config *config.Config Expression *lambda.Expression emitter.Emitter } -// Create a new engine, given an unreduced lambda expression. +// Create a new engine, given an unreduced λ-expression. func New(config *config.Config, expression *lambda.Expression) *Engine { return &Engine{Config: config, Expression: expression} } diff --git a/pkg/convert/saccharine_to_lambda.go b/pkg/convert/saccharine_to_lambda.go index b3632cb..f0810f7 100644 --- a/pkg/convert/saccharine_to_lambda.go +++ b/pkg/convert/saccharine_to_lambda.go @@ -4,14 +4,14 @@ import ( "fmt" "git.maximhutz.com/max/lambda/pkg/lambda" - "git.maximhutz.com/max/lambda/pkg/saccharine/ast" + "git.maximhutz.com/max/lambda/pkg/saccharine" ) -func convertAtom(n *ast.Atom) lambda.Expression { +func convertAtom(n *saccharine.Atom) lambda.Expression { return lambda.NewVariable(n.Name) } -func convertAbstraction(n *ast.Abstraction) lambda.Expression { +func convertAbstraction(n *saccharine.Abstraction) lambda.Expression { result := SaccharineToLambda(n.Body) parameters := n.Parameters @@ -31,7 +31,7 @@ func convertAbstraction(n *ast.Abstraction) lambda.Expression { return result } -func convertApplication(n *ast.Application) lambda.Expression { +func convertApplication(n *saccharine.Application) lambda.Expression { result := SaccharineToLambda(n.Abstraction) arguments := []lambda.Expression{} @@ -47,13 +47,13 @@ func convertApplication(n *ast.Application) lambda.Expression { return result } -func reduceLet(s *ast.LetStatement, e lambda.Expression) lambda.Expression { +func reduceLet(s *saccharine.LetStatement, e lambda.Expression) lambda.Expression { var value lambda.Expression if len(s.Parameters) == 0 { value = SaccharineToLambda(s.Body) } else { - value = convertAbstraction(ast.NewAbstraction(s.Parameters, s.Body)) + value = convertAbstraction(saccharine.NewAbstraction(s.Parameters, s.Body)) } return lambda.NewApplication( @@ -62,7 +62,7 @@ func reduceLet(s *ast.LetStatement, e lambda.Expression) lambda.Expression { ) } -func reduceDeclare(s *ast.DeclareStatement, e lambda.Expression) lambda.Expression { +func reduceDeclare(s *saccharine.DeclareStatement, e lambda.Expression) lambda.Expression { freshVar := lambda.GenerateFreshName(lambda.GetFreeVariables(e)) return lambda.NewApplication( @@ -71,18 +71,18 @@ func reduceDeclare(s *ast.DeclareStatement, e lambda.Expression) lambda.Expressi ) } -func reduceStatement(s ast.Statement, e lambda.Expression) lambda.Expression { +func reduceStatement(s saccharine.Statement, e lambda.Expression) lambda.Expression { switch s := s.(type) { - case *ast.DeclareStatement: + case *saccharine.DeclareStatement: return reduceDeclare(s, e) - case *ast.LetStatement: + case *saccharine.LetStatement: return reduceLet(s, e) default: panic(fmt.Errorf("unknown statement type: %v", s)) } } -func convertClause(n *ast.Clause) lambda.Expression { +func convertClause(n *saccharine.Clause) lambda.Expression { result := SaccharineToLambda(n.Returns) for i := len(n.Statements) - 1; i >= 0; i-- { @@ -92,15 +92,15 @@ func convertClause(n *ast.Clause) lambda.Expression { return result } -func SaccharineToLambda(n ast.Expression) lambda.Expression { +func SaccharineToLambda(n saccharine.Expression) lambda.Expression { switch n := n.(type) { - case *ast.Atom: + case *saccharine.Atom: return convertAtom(n) - case *ast.Abstraction: + case *saccharine.Abstraction: return convertAbstraction(n) - case *ast.Application: + case *saccharine.Application: return convertApplication(n) - case *ast.Clause: + case *saccharine.Clause: return convertClause(n) default: panic(fmt.Errorf("unknown expression type: %T", n)) diff --git a/pkg/deltanet/deltanet.go b/pkg/deltanet/deltanet.go new file mode 100644 index 0000000..be3f3ff --- /dev/null +++ b/pkg/deltanet/deltanet.go @@ -0,0 +1,9 @@ +// Package "deltanet" is a reduction strategy using ∆-nets. +package deltanet + +type Graph struct { + nodes []Node +} + +type Node interface { +} diff --git a/pkg/saccharine/ast/expression.go b/pkg/saccharine/expression.go similarity index 98% rename from pkg/saccharine/ast/expression.go rename to pkg/saccharine/expression.go index 4384bfb..80c8f1b 100644 --- a/pkg/saccharine/ast/expression.go +++ b/pkg/saccharine/expression.go @@ -1,4 +1,4 @@ -package ast +package saccharine type Expression interface { IsExpression() diff --git a/pkg/saccharine/parser.go b/pkg/saccharine/parse.go similarity index 77% rename from pkg/saccharine/parser.go rename to pkg/saccharine/parse.go index 4a92e76..eb045d0 100644 --- a/pkg/saccharine/parser.go +++ b/pkg/saccharine/parse.go @@ -5,7 +5,6 @@ import ( "fmt" "git.maximhutz.com/max/lambda/pkg/iterator" - "git.maximhutz.com/max/lambda/pkg/saccharine/ast" "git.maximhutz.com/max/lambda/pkg/saccharine/token" "git.maximhutz.com/max/lambda/pkg/trace" ) @@ -75,8 +74,8 @@ func parseList[U any](i *TokenIterator, fn func(*TokenIterator) (U, error), mini } } -func parseAbstraction(i *TokenIterator) (*ast.Abstraction, error) { - return iterator.Do(i, func(i *TokenIterator) (*ast.Abstraction, error) { +func parseAbstraction(i *TokenIterator) (*Abstraction, error) { + return iterator.Do(i, func(i *TokenIterator) (*Abstraction, error) { if _, err := parseToken(i, token.Slash, true); err != nil { return nil, trace.Wrap(err, "no function slash (col %d)", i.MustGet().Column) } else if parameters, err := parseList(i, parseString, 0); err != nil { @@ -86,13 +85,13 @@ func parseAbstraction(i *TokenIterator) (*ast.Abstraction, error) { } else if body, err := parseExpression(i); err != nil { return nil, err } else { - return ast.NewAbstraction(parameters, body), nil + return NewAbstraction(parameters, body), nil } }) } -func parseApplication(i *TokenIterator) (*ast.Application, error) { - return iterator.Do(i, func(i *TokenIterator) (*ast.Application, error) { +func parseApplication(i *TokenIterator) (*Application, error) { + return iterator.Do(i, func(i *TokenIterator) (*Application, error) { if _, err := parseToken(i, token.OpenParen, true); err != nil { return nil, trace.Wrap(err, "no openning brackets (col %d)", i.MustGet().Column) } else if expressions, err := parseList(i, parseExpression, 1); err != nil { @@ -100,21 +99,21 @@ func parseApplication(i *TokenIterator) (*ast.Application, error) { } else if _, err := parseToken(i, token.CloseParen, true); err != nil { return nil, trace.Wrap(err, "no closing brackets (col %d)", i.MustGet().Column) } else { - return ast.NewApplication(expressions[0], expressions[1:]), nil + return NewApplication(expressions[0], expressions[1:]), nil } }) } -func parseAtom(i *TokenIterator) (*ast.Atom, error) { +func parseAtom(i *TokenIterator) (*Atom, error) { if tok, err := parseToken(i, token.Atom, true); err != nil { return nil, trace.Wrap(err, "no variable (col %d)", i.Index()) } else { - return ast.NewAtom(tok.Value), nil + return NewAtom(tok.Value), nil } } -func parseStatements(i *TokenIterator) ([]ast.Statement, error) { - statements := []ast.Statement{} +func parseStatements(i *TokenIterator) ([]Statement, error) { + statements := []Statement{} //nolint:errcheck parseList(i, parseBreak, 0) @@ -132,15 +131,15 @@ func parseStatements(i *TokenIterator) ([]ast.Statement, error) { return statements, nil } -func parseClause(i *TokenIterator, braces bool) (*ast.Clause, error) { +func parseClause(i *TokenIterator, braces bool) (*Clause, error) { if braces { if _, err := parseToken(i, token.OpenBrace, true); err != nil { return nil, err } } - var stmts []ast.Statement - var last *ast.DeclareStatement + var stmts []Statement + var last *DeclareStatement var err error var ok bool @@ -148,7 +147,7 @@ func parseClause(i *TokenIterator, braces bool) (*ast.Clause, error) { return nil, err } else if len(stmts) == 0 { return nil, fmt.Errorf("no statements in clause") - } else if last, ok = stmts[len(stmts)-1].(*ast.DeclareStatement); !ok { + } else if last, ok = stmts[len(stmts)-1].(*DeclareStatement); !ok { return nil, fmt.Errorf("this clause contains no final return value (col %d)", i.MustGet().Column) } @@ -158,11 +157,11 @@ func parseClause(i *TokenIterator, braces bool) (*ast.Clause, error) { } } - return ast.NewClause(stmts[:len(stmts)-1], last.Value), nil + return NewClause(stmts[:len(stmts)-1], last.Value), nil } -func parseExpression(i *TokenIterator) (ast.Expression, error) { - return iterator.Do(i, func(i *TokenIterator) (ast.Expression, error) { +func parseExpression(i *TokenIterator) (Expression, error) { + return iterator.Do(i, func(i *TokenIterator) (Expression, error) { passSoftBreaks(i) switch peek := i.MustGet(); peek.Type { @@ -180,8 +179,8 @@ func parseExpression(i *TokenIterator) (ast.Expression, error) { }) } -func parseLet(i *TokenIterator) (*ast.LetStatement, error) { - return iterator.Do(i, func(i *TokenIterator) (*ast.LetStatement, error) { +func parseLet(i *TokenIterator) (*LetStatement, error) { + return iterator.Do(i, func(i *TokenIterator) (*LetStatement, error) { if parameters, err := parseList(i, parseString, 1); err != nil { return nil, err } else if _, err := parseToken(i, token.Assign, true); err != nil { @@ -189,20 +188,20 @@ func parseLet(i *TokenIterator) (*ast.LetStatement, error) { } else if body, err := parseExpression(i); err != nil { return nil, err } else { - return ast.NewLet(parameters[0], parameters[1:], body), nil + return NewLet(parameters[0], parameters[1:], body), nil } }) } -func parseDeclare(i *TokenIterator) (*ast.DeclareStatement, error) { +func parseDeclare(i *TokenIterator) (*DeclareStatement, error) { if value, err := parseExpression(i); err != nil { return nil, err } else { - return ast.NewDeclare(value), nil + return NewDeclare(value), nil } } -func parseStatement(i *TokenIterator) (ast.Statement, error) { +func parseStatement(i *TokenIterator) (Statement, error) { if let, letErr := parseLet(i); letErr == nil { return let, nil } else if declare, declErr := parseDeclare(i); declErr == nil { @@ -213,7 +212,7 @@ func parseStatement(i *TokenIterator) (ast.Statement, error) { } // Given a list of tokens, attempt to parse it into an syntax tree. -func Parse(tokens []token.Token) (ast.Expression, error) { +func parse(tokens []token.Token) (Expression, error) { i := iterator.Of(tokens) exp, err := parseClause(i, false) diff --git a/pkg/saccharine/saccharine.go b/pkg/saccharine/saccharine.go new file mode 100644 index 0000000..57b1f17 --- /dev/null +++ b/pkg/saccharine/saccharine.go @@ -0,0 +1,22 @@ +// 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/saccharine/token" +) + +// Convert a piece of valid saccharine code into an expression. +func Parse(code string) (Expression, error) { + tokens, err := token.Parse(code) + if err != nil { + return nil, err + } + + return parse(tokens) +} + +// Convert a parsed saccharine expression back into source code. +func Stringify(expression Expression) string { + return stringifyExpression(expression) +} diff --git a/pkg/saccharine/ast/statement.go b/pkg/saccharine/statement.go similarity index 97% rename from pkg/saccharine/ast/statement.go rename to pkg/saccharine/statement.go index 43f0662..1d0dd15 100644 --- a/pkg/saccharine/ast/statement.go +++ b/pkg/saccharine/statement.go @@ -1,4 +1,4 @@ -package ast +package saccharine type Statement interface { IsStatement() diff --git a/pkg/saccharine/stringify.go b/pkg/saccharine/stringify.go index c710990..5c6f0ca 100644 --- a/pkg/saccharine/stringify.go +++ b/pkg/saccharine/stringify.go @@ -3,67 +3,65 @@ package saccharine import ( "fmt" "strings" - - "git.maximhutz.com/max/lambda/pkg/saccharine/ast" ) -func stringifyAtom(n *ast.Atom) string { +func stringifyAtom(n *Atom) string { return n.Name } -func stringifyAbstraction(n *ast.Abstraction) string { - return "\\" + strings.Join(n.Parameters, " ") + "." + Stringify(n.Body) +func stringifyAbstraction(n *Abstraction) string { + return "\\" + strings.Join(n.Parameters, " ") + "." + stringifyExpression(n.Body) } -func stringifyApplication(n *ast.Application) string { - arguments := []string{Stringify(n.Abstraction)} +func stringifyApplication(n *Application) string { + arguments := []string{stringifyExpression(n.Abstraction)} for _, argument := range n.Arguments { - arguments = append(arguments, Stringify(argument)) + arguments = append(arguments, stringifyExpression(argument)) } return "(" + strings.Join(arguments, " ") + ")" } -func stringifyLet(s *ast.LetStatement) string { - return s.Name + " " + strings.Join(s.Parameters, " ") + " := " + Stringify(s.Body) +func stringifyLet(s *LetStatement) string { + return s.Name + " " + strings.Join(s.Parameters, " ") + " := " + stringifyExpression(s.Body) } -func stringifyDeclare(s *ast.DeclareStatement) string { - return Stringify(s.Value) +func stringifyDeclare(s *DeclareStatement) string { + return stringifyExpression(s.Value) } -func stringifyStatement(s ast.Statement) string { +func stringifyStatement(s Statement) string { switch s := s.(type) { - case *ast.DeclareStatement: + case *DeclareStatement: return stringifyDeclare(s) - case *ast.LetStatement: + case *LetStatement: return stringifyLet(s) default: panic(fmt.Errorf("unknown statement type: %v", s)) } } -func stringifyClause(n *ast.Clause) string { +func stringifyClause(n *Clause) string { stmts := "" for _, statement := range n.Statements { stmts += stringifyStatement(statement) + "; " } - return "{ " + stmts + Stringify(n.Returns) + " }" + return "{ " + stmts + stringifyExpression(n.Returns) + " }" } // Convert an expression back into valid source code. -func Stringify(n ast.Expression) string { +func stringifyExpression(n Expression) string { switch n := n.(type) { - case *ast.Atom: + case *Atom: return stringifyAtom(n) - case *ast.Abstraction: + case *Abstraction: return stringifyAbstraction(n) - case *ast.Application: + case *Application: return stringifyApplication(n) - case *ast.Clause: + case *Clause: return stringifyClause(n) default: panic(fmt.Errorf("unknown expression type: %T", n)) diff --git a/pkg/saccharine/tokenizer.go b/pkg/saccharine/token/parse.go similarity index 76% rename from pkg/saccharine/tokenizer.go rename to pkg/saccharine/token/parse.go index 81c46dc..ef0ad15 100644 --- a/pkg/saccharine/tokenizer.go +++ b/pkg/saccharine/token/parse.go @@ -1,4 +1,4 @@ -package saccharine +package token import ( "errors" @@ -6,7 +6,6 @@ import ( "unicode" "git.maximhutz.com/max/lambda/pkg/iterator" - "git.maximhutz.com/max/lambda/pkg/saccharine/token" "git.maximhutz.com/max/lambda/pkg/trace" ) @@ -43,7 +42,7 @@ func parseCharacter(i *iterator.Iterator[rune], expected rune) (rune, error) { // Pulls the next token from an iterator over runes. If it cannot, it will // return nil. If an error occurs, it will return that. -func getToken(i *iterator.Iterator[rune]) (*token.Token, error) { +func getToken(i *iterator.Iterator[rune]) (*Token, error) { index := i.Index() if i.Done() { @@ -57,27 +56,27 @@ func getToken(i *iterator.Iterator[rune]) (*token.Token, error) { switch { case letter == '(': - return token.NewOpenParen(index), nil + return NewOpenParen(index), nil case letter == ')': - return token.NewCloseParen(index), nil + return NewCloseParen(index), nil case letter == '.': - return token.NewDot(index), nil + return NewDot(index), nil case letter == '\\': - return token.NewSlash(index), nil + return NewSlash(index), nil case letter == '\n': - return token.NewSoftBreak(index), nil + return NewSoftBreak(index), nil case letter == '{': - return token.NewOpenBrace(index), nil + return NewOpenBrace(index), nil case letter == '}': - return token.NewCloseBrace(index), nil + return NewCloseBrace(index), nil case letter == ':': if _, err := parseCharacter(i, '='); err != nil { return nil, err } else { - return token.NewAssign(index), nil + return NewAssign(index), nil } case letter == ';': - return token.NewHardBreak(index), nil + return NewHardBreak(index), nil case unicode.IsSpace(letter): return nil, nil case isVariable(letter): @@ -91,16 +90,16 @@ func getToken(i *iterator.Iterator[rune]) (*token.Token, error) { } } - return token.NewAtom(string(atom), index), nil + return NewAtom(string(atom), index), nil } return nil, fmt.Errorf("unknown character '%v'", string(letter)) } // Parse a string into tokens. -func GetTokens(input string) ([]token.Token, error) { +func Parse(input string) ([]Token, error) { i := iterator.Of([]rune(input)) - tokens := []token.Token{} + tokens := []Token{} errorList := []error{} for !i.Done() {