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) { return consume(new TokenType[] { type }, message); } private Token consume(TokenType[] types, string message) { foreach (var type in types) { 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() { var expr = equality(); while (match(TokenType.Semicolon)) { var right = equality(); expr = new Sequence { Left = expr, Right = right }; } return expr; } 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(concat, TokenType.Slash, TokenType.Asterisk); private Expr concat() => binaryLeft(unary, TokenType.PlusPlus); private Expr unary() { if (match(TokenType.Bang, TokenType.Minus)) { Token op = previous(); Expr right = unary(); return new Unary { Op = op, Right = right }; } return control(); } private Expr control() { switch (peek().type) { case TokenType.If: advance(); Expr condition = expression(); consume(TokenType.Then, "Expect 'then' after condition."); Expr thenCase = expression(); consume(TokenType.Else, "Expect 'else' after 'then' case."); Expr elseCase = expression(); return new If { Condition = condition, Then = thenCase, Else = elseCase }; case TokenType.When: throw new NotImplementedException("TODO when"); } return primary(); } private Name? name() { if (match(TokenType.Identifier)) { return new(previous().lexeme, false); } if (match(TokenType.At)) { Token literal = consume(TokenType.String, "Expect string literal after '@'."); return new((string)(literal.literal ?? ""), true); } return null; } private Expr primary() { Expr expr = operand(); if (match(TokenType.Period)) { Name? ident = name(); if (ident == null) { throw error(advance(), "Expect identifier after dot."); } return new Selector { Left = expr, FieldName = ident }; } if (match(TokenType.LBracket)) { throw new NotImplementedException("TODO index"); } if (match(TokenType.LParen)) { throw new NotImplementedException("TODO apply"); } return expr; } private Expr operand() { if (match(TokenType.Number, TokenType.String)) { return new Literal { Value = previous().literal }; } var ident = name(); if (ident != null) { return new Identifier { Value = ident }; } if (match(TokenType.LParen)) { Expr groupedExpr = expression(); consume(TokenType.RParen, "Expect ')' after expression."); return new Grouping { Expression = groupedExpr }; } Expr? expr; if ((expr = record()) != null) { return expr; } if ((expr = variant()) != null) { return expr; } if ((expr = list()) != null) { return expr; } throw error(peek(), "Expect expression."); } private Expr? list() { if (!match(TokenType.LBracket)) { return null; } List elements = new List(); if (!match(TokenType.RBracket)) { elements.Add(expression()); while (!match(TokenType.RBracket)) { if (match(TokenType.Comma)) { if (match(TokenType.RBracket)) { break; } else { elements.Add(expression()); } } else { throw error(previous(), "Expect comma between list elements."); } } } return new Finn.AST.List { Elements = elements.ToArray() }; } private Expr? variant() { return null; // throw new NotImplementedException("TODO variant"); } private Expr? record() { return null; // throw new NotImplementedException("TODO record"); } }