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(params TokenType[] types) { if (isAtEnd()) return false; return Array.IndexOf(types, peek().Type) >= 0; } 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(string message, params TokenType[] types) { if (check(types)) 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(expr, op, right); } return expr; } // Rules private Expr expression() { var expr = equality(); while (match(TokenType.Semicolon)) { var right = equality(); expr = new Sequence(expr, right); } return expr; } private Expr equality() => binaryLeft(comparison, TokenType.BangEqual, TokenType.DoubleEqual); 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, right); } return let(); } private RecordPattern? recordPattern() { if (!match(TokenType.LBrace)) { return null; } var lbrace = previous(); List fields = new List(); while (!check(TokenType.RBrace, TokenType.Pipe)) { var fieldName = consume("Expect identifier as field name.", TokenType.Identifier, TokenType.QuotedIdentifier); var pat = match(TokenType.Equal) ? pattern() : null; fields.Add(new(fieldName, pat)); if (!match(TokenType.Comma)) { break; } } var restPattern = match(TokenType.Pipe) ? simplePattern() : null; consume("Expect '}' at end of record pattern.", TokenType.RBrace); return new(fields.ToArray(), restPattern, lbrace); } private VariantPattern? variantPattern() { if (!match(TokenType.Backtick)) { return null; } var backtick = previous(); var tag = consume("Expect identifier as tag name.", TokenType.Identifier, TokenType.QuotedIdentifier); if (!match(TokenType.LParen)) { return new(tag, null, backtick); } Pattern argument = pattern(); consume("Expect ')' after variant argument.", TokenType.RParen); return new(tag, argument, backtick); } private SimplePattern? simplePattern() { if (match(TokenType.Blank)) { return new SimplePattern(null, previous()); } return match(TokenType.Identifier, TokenType.QuotedIdentifier) ? new(previous(), previous()) : null; } private Pattern pattern() { Pattern? p; if ((p = simplePattern()) != null) { return p; } if ((p = recordPattern()) != null) { return p; } if ((p = variantPattern()) != null) { return p; } if (check(TokenType.String)) { throw error(peek(), "Cannot pattern-match string literals."); } if (check(TokenType.Number)) { throw error(peek(), "Cannot pattern-match numbers."); } throw error(peek(), "Expect pattern."); } private Expr let() { if (!match(TokenType.Let)) { return ifExpr(); } var letToken = previous(); List bindings = new List { parseBinding() }; while (match(TokenType.And)) { bindings.Add(parseBinding()); } consume("Expect 'in' after let-bindings.", TokenType.In); Expr body = expression(); return new Let(bindings.ToArray(), body, letToken); Binding parseBinding() { Pattern p = pattern(); switch (p) { case SimplePattern(var funcName) when funcName != null && match(TokenType.LParen): List funcParams = new List(); while (!check(TokenType.RParen)) { funcParams.Add(pattern()); if (!match(TokenType.Comma)) { break; } } consume("Expect ')' at end of parameters.", TokenType.RParen); consume("Expect '=' after parameters.", TokenType.Equal); return new FuncBinding(funcName, funcParams.ToArray(), expression()); default: consume("Expect '=' after pattern.", TokenType.Equal); return new VarBinding(p, expression()); } } } private Expr ifExpr() { if (!match(TokenType.If)) { return when(); } var ifToken = previous(); Expr condition = expression(); consume("Expect 'then' after condition.", TokenType.Then); Expr thenCase = expression(); consume("Expect 'else' after 'then' case.", TokenType.Else); Expr elseCase = expression(); return new If(condition, thenCase, elseCase, ifToken); } private Expr when() { if (!match(TokenType.When)) { return primary(); } var whenToken = previous(); Expr head = expression(); consume("Expect 'is' after expression.", TokenType.Is); List cases = new List(); cases.Add(parseCase()); while (match(TokenType.Comma)) { cases.Add(parseCase()); } return new When(head, cases.ToArray(), whenToken); VarBinding parseCase() { Pattern pat = pattern(); consume("Expect '=>' after pattern.", TokenType.DoubleArrow); Expr value = expression(); return new VarBinding(pat, value); } } private Expr primary() { Expr expr = operand(); if (match(TokenType.Period)) { var ident = consume("Expect identifier after dot.", TokenType.Identifier, TokenType.QuotedIdentifier); return new Selector(expr, ident); } if (match(TokenType.LBracket)) { var index = expression(); consume("Expect '[' after expression.", TokenType.RBracket); return new Indexer(expr, index); } if (match(TokenType.LParen)) { return finishCall(expr); } return expr; } private Expr finishCall(Expr callee) { var lParen = previous(); var args = new List(); while (!check(TokenType.RParen)) { if (match(TokenType.Blank)) { args.Add(null); } else { args.Add(expression()); } if (!match(TokenType.Comma)) { break; } } consume("Expect ')' after arguments.", TokenType.RParen); const int MaxArgs = 25; if (args.Count > MaxArgs) { error(peek(), $"Can't have more than {MaxArgs} arguments."); } return new Call(callee, args.ToArray(), lParen); } private Expr operand() { if (match(TokenType.Number, TokenType.String)) { var token = previous(); object literal = token.Literal!; return new Literal(previous().Literal!, token); } if (match(TokenType.Identifier, TokenType.QuotedIdentifier)) { return new Variable(previous()); } if (match(TokenType.LParen)) { var lparen = previous(); Expr groupedExpr = expression(); consume("Expect ')' after expression.", TokenType.RParen); return new Grouping(groupedExpr, lparen); } 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; } var lbracket = previous(); 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 List(elements.ToArray(), lbracket); } private Expr? variant() { if (!match(TokenType.Backtick)) { return null; } var backtick = previous(); var tag = consume("Expect identifier as tag name.", TokenType.QuotedIdentifier, TokenType.Identifier); Expr? argument = null; if (match(TokenType.LParen)) { if (!match(TokenType.RParen)) { argument = expression(); consume("Expect ')' after variant argument.", TokenType.RParen); } } return new Variant(tag, argument, backtick); } private Expr? record() { if (!match(TokenType.LBrace)) { return null; } var lbrace = previous(); var extensions = parseFields(TokenType.RBrace, TokenType.Pipe); BaseRecord? baseRecord = null; if (match(TokenType.Pipe)) { var baseExpr = expression(); var updates = match(TokenType.With) ? parseFields(TokenType.RBrace) : Array.Empty(); baseRecord = new(baseExpr, updates); } consume("Expect '}' at end of record literal.", TokenType.RBrace); return new Record(extensions, baseRecord, lbrace); Field[] parseFields(params TokenType[] endAt) { List fields = new List(); while (!check(endAt)) { var fieldName = consume("Expect identifier as field name.", TokenType.Identifier, TokenType.QuotedIdentifier); // Desugar punned field. var value = match(TokenType.Equal) ? expression() : new Variable(fieldName); fields.Add(new(fieldName, value)); if (!match(TokenType.Comma)) { break; } } return fields.ToArray(); } } }