diff --git a/ASTPrinter.cs b/ASTPrinter.cs index a5ae86c..a29998d 100644 --- a/ASTPrinter.cs +++ b/ASTPrinter.cs @@ -6,7 +6,7 @@ namespace Finn; class ASTPrinter : IVisitor { - string print(Expr expr) + public string print(Expr expr) { return expr.accept(this); } diff --git a/Parser.cs b/Parser.cs new file mode 100644 index 0000000..502e1ed --- /dev/null +++ b/Parser.cs @@ -0,0 +1,173 @@ +namespace Finn; + +using Finn.AST; +using System; +using System.Collections.Generic; + +class Parser +{ + private class ParseError : Exception { } + + private readonly List tokens; + private int current = 0; + + public Parser(List tokens) + { + this.tokens = tokens; + } + + public Expr? parse() + { + try + { + return expression(); + } + catch (ParseError) + { + return null; + } + } + + // Parsing primitives + + private bool match(params TokenType[] types) + { + foreach (TokenType type in types) + { + if (check(type)) + { + advance(); + return true; + } + } + + return false; + } + + private bool check(TokenType type) + { + if (isAtEnd()) return false; + return peek().type == type; + } + + private Token advance() + { + if (!isAtEnd()) current++; + return previous(); + } + + private bool isAtEnd() + { + return peek().type == TokenType.EOF; + } + + private Token peek() + { + return tokens[current]; + } + + private Token previous() + { + return tokens[current - 1]; + } + + private Token consume(TokenType type, string message) + { + if (check(type)) return advance(); + + throw error(peek(), message); + } + + private ParseError error(Token token, string message) + { + Program.error(token, message); + return new ParseError(); + } + + private void synchronize() + { + advance(); + + while (!isAtEnd()) + { + switch (peek().type) + { + case TokenType.Def: + case TokenType.Type: + return; + } + + advance(); + } + } + + // Helpers + + private Expr binaryLeft(Func next, params TokenType[] types) + { + Expr expr = next(); + + while (match(types)) + { + Token op = previous(); + Expr right = next(); + expr = new Binary { Left = expr, Op = op, Right = right }; + } + + return expr; + } + + // Rules + + private Expr expression() + { + return equality(); + } + + private Expr equality() + { + Expr expr = comparison(); + + while (match(TokenType.BangEqual, TokenType.DoubleEqual)) + { + Token op = previous(); + Expr right = comparison(); + expr = new Binary { Left = expr, Op = op, Right = right }; + } + return expr; + } + + private Expr comparison() => + binaryLeft(sum, TokenType.Greater, TokenType.GreaterEqual, TokenType.Less, TokenType.LessEqual); + + private Expr sum() => binaryLeft(product, TokenType.Minus, TokenType.Plus); + + private Expr product() => binaryLeft(unary, TokenType.Slash, TokenType.Asterisk); + private Expr unary() + { + if (match(TokenType.Bang, TokenType.Minus)) + { + Token op = previous(); + Expr right = unary(); + return new Unary { Op = op, Right = right }; + } + return primary(); + } + + private Expr primary() + { + if (match(TokenType.Number, TokenType.String)) + { + return new Literal { Value = previous().literal }; + } + + if (match(TokenType.LParen)) + { + Expr expr = expression(); + consume(TokenType.RParen, "Expect ')' after expression."); + return new Grouping { Expression = expr }; + } + + throw error(peek(), "Expect expression."); + } +} diff --git a/Program.cs b/Program.cs index 376ed78..9c71511 100644 --- a/Program.cs +++ b/Program.cs @@ -1,7 +1,7 @@ using System; using System.IO; -using System.Collections; using System.Collections.Generic; +using Finn.AST; namespace Finn; @@ -36,11 +36,12 @@ class Program { var scanner = new Scanner(src); List tokens = scanner.scanTokens(); + Parser parser = new Parser(tokens); + Expr? expression = parser.parse(); - foreach (Token token in tokens) - { - Console.WriteLine(token); - } + if (hadError || expression == null) return; + + Console.WriteLine(new ASTPrinter().print(expression)); } static void runPrompt() @@ -62,6 +63,18 @@ class Program report(line, "", message); } + public static void error(Token token, String message) + { + if (token.type == TokenType.EOF) + { + report(token.line, " at end", message); + } + else + { + report(token.line, " at '" + token.lexeme + "'", message); + } + } + static void report(int line, String where, String message) { Console.WriteLine($"[line {line}] Error{where}: {message}"); @@ -102,6 +115,7 @@ public enum TokenType Type, Alias, Recurse, + Def, Blank, Identifier, Number, String, EOF @@ -119,6 +133,7 @@ class Scanner private static readonly Dictionary keywords = new Dictionary() { + {"def", TokenType.Def}, {"when", TokenType.When}, {"is", TokenType.Is}, {"let", TokenType.Let},