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(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(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; } List fields = new List(); while (!check(TokenType.RBrace, TokenType.Pipe)) { var fieldName = name(); if (fieldName == null) { throw error(peek(), "Expect identifier as field name."); } 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(TokenType.RBrace, "Expect '}' at end of record pattern."); return new(fields.ToArray(), restPattern); } private VariantPattern? variantPattern() { if (!match(TokenType.Backtick)) { return null; } Name? tag = name(); if (tag == null) { throw error(peek(), "Expect identifier as tag name."); } if (!match(TokenType.LParen)) { return new(tag, null); } Pattern argument = pattern(); consume(TokenType.RParen, "Expect ')' after variant argument."); return new(tag, argument); } private SimplePattern? simplePattern() { if (match(TokenType.Blank)) { return new SimplePattern(null); } var identifier = name(); if (identifier != null) { return new SimplePattern(identifier); } return 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(); } List bindings = new List(); bindings.Add(parseBinding()); while (match(TokenType.And)) { bindings.Add(parseBinding()); } consume(TokenType.In, "Expect 'in' after let-bindings."); Expr body = expression(); return new Let(bindings.ToArray(), body); 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(TokenType.RParen, "Expect ')' at end of parameters."); consume(TokenType.Equal, "Expect '=' after parameters."); return new FuncBinding(funcName, funcParams.ToArray(), expression()); default: consume(TokenType.Equal, "Expect '=' after pattern."); return new VarBinding(p, expression()); } } } private Expr ifExpr() { if (!match(TokenType.If)) { return when(); } 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, thenCase, elseCase); } private Expr when() { if (!match(TokenType.When)) { return primary(); } Expr head = expression(); consume(TokenType.Is, "Expect 'is' after expression."); List cases = new List(); cases.Add(parseCase()); while (match(TokenType.Comma)) { cases.Add(parseCase()); } return new When(head, cases.ToArray()); VarBinding parseCase() { Pattern pat = pattern(); consume(TokenType.DoubleArrow, "Expect '=>' after pattern."); Expr value = expression(); return new VarBinding(pat, value); } } 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(expr, ident); } if (match(TokenType.LBracket)) { var index = expression(); consume(TokenType.RBracket, "Expect '[' after expression."); return new Indexer(expr, index); } if (match(TokenType.LParen)) { 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(TokenType.RParen, "Expect ')' after arguments."); return new Call(expr, args.ToArray()); } return expr; } private Expr operand() { if (match(TokenType.Number, TokenType.String)) { object literal = previous().literal!; return new Literal(previous().literal!); } var ident = name(); if (ident != null) { return new Variable(ident); } if (match(TokenType.LParen)) { Expr groupedExpr = expression(); consume(TokenType.RParen, "Expect ')' after expression."); return new Grouping(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.ToArray()); } private Expr? variant() { if (!match(TokenType.Backtick)) { return null; } Name? tag = name(); Expr? argument = null; if (tag == null) { throw error(peek(), "Expect identifier as tag name."); } if (match(TokenType.LParen)) { if (!match(TokenType.RParen)) { argument = expression(); consume(TokenType.RParen, "Expect ')' after variant argument."); } } return new Variant(tag, argument); } private Expr? record() { if (!match(TokenType.LBrace)) { return null; } 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(TokenType.RBrace, "Expect '}' at end of record literal."); return new Record(extensions, baseRecord); Field[] parseFields(params TokenType[] endAt) { List fields = new List(); while (!check(endAt)) { var fieldName = name(); if (fieldName == null) { throw error(peek(), "Expect identifier as field name."); } var value = match(TokenType.Equal) ? expression() : null; fields.Add(new(fieldName, value)); if (!match(TokenType.Comma)) { break; } } return fields.ToArray(); } } }