diff --git a/Makefile b/Makefile index 326f5be..ac569fb 100644 --- a/Makefile +++ b/Makefile @@ -11,4 +11,4 @@ thunk: it @ ./lambda.exe - < ./samples/thunk.txt saccharine: it - @ ./lambda.exe - < ./samples/saccharine.txt \ No newline at end of file + @ ./lambda.exe -v - < ./samples/saccharine.txt \ No newline at end of file diff --git a/pkg/convert/saccharine_to_lambda.go b/pkg/convert/saccharine_to_lambda.go index 2d06fcd..b311d89 100644 --- a/pkg/convert/saccharine_to_lambda.go +++ b/pkg/convert/saccharine_to_lambda.go @@ -56,6 +56,6 @@ func SaccharineToLambda(n ast.Expression) lambda.Expression { case *ast.Application: return convertApplication(n) default: - panic(fmt.Errorf("unknown expression type: %v", n)) + panic(fmt.Errorf("unknown expression type: %T", n)) } } diff --git a/pkg/saccharine/ast/statement.go b/pkg/saccharine/ast/statement.go index e367db1..43f0662 100644 --- a/pkg/saccharine/ast/statement.go +++ b/pkg/saccharine/ast/statement.go @@ -20,3 +20,11 @@ func (LetStatement) IsStatement() {} func (DeclareStatement) IsStatement() {} /** ------------------------------------------------------------------------- */ + +func NewLet(name string, parameters []string, body Expression) *LetStatement { + return &LetStatement{Name: name, Parameters: parameters, Body: body} +} + +func NewDeclare(value Expression) *DeclareStatement { + return &DeclareStatement{Value: value} +} diff --git a/pkg/saccharine/parser.go b/pkg/saccharine/parser.go index ad27ed7..08899a9 100644 --- a/pkg/saccharine/parser.go +++ b/pkg/saccharine/parser.go @@ -1,6 +1,7 @@ package saccharine import ( + "errors" "fmt" "git.maximhutz.com/max/lambda/pkg/iterator" @@ -41,43 +42,44 @@ func parseToken(i *TokenIterator, expected token.Type, ignoreSoftBreaks bool) (* }) } -func parseExpression(i *TokenIterator) (ast.Expression, error) { - return iterator.Do(i, func(i *TokenIterator) (ast.Expression, error) { - passSoftBreaks(i) - peek := i.MustGet() - - switch peek.Type { - case token.OpenParen: - return parseApplication(i) - case token.Slash: - return parseAbstraction(i) - case token.Atom: - return parseAtom(i) - default: - return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column) - } - }) +func parseString(i *TokenIterator) (string, error) { + if tok, err := parseToken(i, token.Atom, true); err != nil { + return "", trace.Wrap(err, "no variable (col %d)", i.Index()) + } else { + return tok.Value, nil + } } -func parseParameters(i *TokenIterator) ([]string, error) { - return iterator.Do(i, func(i *TokenIterator) ([]string, error) { - variables := []string{} +func parseBreak(i *TokenIterator) (*token.Token, error) { + if tok, softErr := parseRawToken(i, token.SoftBreak); softErr == nil { + return tok, nil + } else if tok, hardErr := parseRawToken(i, token.HardBreak); hardErr == nil { + return tok, nil + } else { + return nil, errors.Join(softErr, hardErr) + } +} - for { - if tok, err := parseToken(i, token.Atom, true); err != nil { - return variables, nil - } else { - variables = append(variables, tok.Value) +func parseList[U any](i *TokenIterator, fn func(*TokenIterator) (U, error), minimum int) ([]U, error) { + results := []U{} + + for { + if u, err := fn(i); err != nil { + if len(results) < minimum { + return nil, trace.Wrap(err, "expected at least '%v' items, got only '%v'", minimum, len(results)) } + return results, nil + } else { + results = append(results, u) } - }) + } } func parseAbstraction(i *TokenIterator) (*ast.Abstraction, error) { return iterator.Do(i, func(i *TokenIterator) (*ast.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 := parseParameters(i); err != nil { + } else if parameters, err := parseList(i, parseString, 0); err != nil { return nil, err } else if _, err = parseToken(i, token.Dot, true); err != nil { return nil, trace.Wrap(err, "no function dot (col %d)", i.MustGet().Column) @@ -91,28 +93,15 @@ func parseAbstraction(i *TokenIterator) (*ast.Abstraction, error) { func parseApplication(i *TokenIterator) (*ast.Application, error) { return iterator.Do(i, func(i *TokenIterator) (*ast.Application, error) { - expressions := []ast.Expression{} - if _, err := parseToken(i, token.OpenParen, true); err != nil { return nil, trace.Wrap(err, "no openning brackets (col %d)", i.MustGet().Column) - } - - for { - if exp, err := parseExpression(i); err != nil { - if len(expressions) == 0 { - return nil, trace.Wrap(err, "application has no arguments") - } - break - } else { - expressions = append(expressions, exp) - } - } - - if _, err := parseToken(i, token.CloseParen, true); err != nil { + } else if expressions, err := parseList(i, parseExpression, 1); err != nil { + return nil, err + } 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 ast.NewApplication(expressions[0], expressions[1:]), nil }) } @@ -124,10 +113,109 @@ func parseAtom(i *TokenIterator) (*ast.Atom, error) { } } +func parseStatements(i *TokenIterator) ([]ast.Statement, error) { + statements := []ast.Statement{} + + //nolint:errcheck + parseList(i, parseBreak, 0) + + for { + if statement, err := parseStatement(i); err != nil { + break + } else if _, err := parseList(i, parseBreak, 1); err != nil { + break + } else { + statements = append(statements, statement) + } + } + + return statements, nil +} + +func parseClause(i *TokenIterator, braces bool) (*ast.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 err error + var ok bool + + if stmts, err = parseStatements(i); err != nil { + 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 { + return nil, fmt.Errorf("this clause contains no final return value") + } + + if braces { + if _, err := parseToken(i, token.CloseBrace, true); err != nil { + return nil, err + } + } + + return ast.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) { + passSoftBreaks(i) + + switch peek := i.MustGet(); peek.Type { + case token.OpenParen: + return parseApplication(i) + case token.Slash: + return parseAbstraction(i) + case token.Atom: + return parseAtom(i) + case token.OpenBrace: + return parseClause(i, true) + default: + return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column) + } + }) +} + +func parseLet(i *TokenIterator) (*ast.LetStatement, error) { + return iterator.Do(i, func(i *TokenIterator) (*ast.LetStatement, error) { + if parameters, err := parseList(i, parseString, 1); err != nil { + return nil, err + } else if _, err := parseToken(i, token.Assign, true); err != nil { + return nil, err + } else if body, err := parseExpression(i); err != nil { + return nil, err + } else { + return ast.NewLet(parameters[0], parameters[1:], body), nil + } + }) +} + +func parseDeclare(i *TokenIterator) (*ast.DeclareStatement, error) { + if value, err := parseExpression(i); err != nil { + return nil, err + } else { + return ast.NewDeclare(value), nil + } +} + +func parseStatement(i *TokenIterator) (ast.Statement, error) { + if let, letErr := parseLet(i); letErr == nil { + return let, nil + } else if declare, declErr := parseDeclare(i); declErr == nil { + return declare, nil + } else { + return nil, errors.Join(letErr, declErr) + } +} + func Parse(tokens []token.Token) (ast.Expression, error) { i := iterator.Of(tokens) - exp, err := parseExpression(i) + exp, err := parseClause(i, false) if err != nil { return nil, err } diff --git a/pkg/saccharine/stringify.go b/pkg/saccharine/stringify.go index 820dcd7..67d985d 100644 --- a/pkg/saccharine/stringify.go +++ b/pkg/saccharine/stringify.go @@ -25,6 +25,35 @@ func stringifyApplication(n *ast.Application) string { return "(" + strings.Join(arguments, " ") + ")" } +func stringifyLet(s *ast.LetStatement) string { + return s.Name + " " + strings.Join(s.Parameters, " ") + " := " + Stringify(s.Body) +} + +func stringifyDeclare(s *ast.DeclareStatement) string { + return Stringify(s.Value) +} + +func stringifyStatement(s ast.Statement) string { + switch s := s.(type) { + case *ast.DeclareStatement: + return stringifyDeclare(s) + case *ast.LetStatement: + return stringifyLet(s) + default: + panic(fmt.Errorf("unknown statement type: %v", s)) + } +} + +func stringifyClause(n *ast.Clause) string { + stmts := "" + + for _, statement := range n.Statements { + stmts += stringifyStatement(statement) + "; " + } + + return "{ " + stmts + Stringify(n.Returns) + " }" +} + func Stringify(n ast.Expression) string { switch n := n.(type) { case *ast.Atom: @@ -33,7 +62,9 @@ func Stringify(n ast.Expression) string { return stringifyAbstraction(n) case *ast.Application: return stringifyApplication(n) + case *ast.Clause: + return stringifyClause(n) default: - panic(fmt.Errorf("unknown expression type: %v", n)) + panic(fmt.Errorf("unknown expression type: %T", n)) } } diff --git a/samples/saccharine.txt b/samples/saccharine.txt index 20b3c31..8f1fe12 100644 --- a/samples/saccharine.txt +++ b/samples/saccharine.txt @@ -1,16 +1,18 @@ -(true x y) := x -(false x y) := y +true x y := x +false x y := y -(pair a b) := \c.(c a b) -(left p) := (p true) -(false p) := (p false) +pair a b := \c.(c a b) +left p := (p true) +false p := (p false) -zero 0 1 x = x +zero 0 1 x := x inc n := \0 1 x.{ initial := (pair true x) - (on_zero p) := (pair false ((left p 1 0) (right p))) - (on_one p) := (pair (left p) (1 (right p))) + onZero p := (pair false ((left p 1 0) (right p))) + onOne p := (pair (left p) (1 (right p))) - (n on_zero on_one initial) + (n onZero onOne initial) } + +inc