2023-06-28 18:12:14 +00:00
|
|
|
namespace Finn;
|
|
|
|
|
|
|
|
using Finn.AST;
|
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
class Parser
|
|
|
|
{
|
|
|
|
private class ParseError : Exception { }
|
|
|
|
|
|
|
|
private readonly List<Token> tokens;
|
|
|
|
private int current = 0;
|
|
|
|
|
|
|
|
public Parser(List<Token> 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;
|
|
|
|
}
|
|
|
|
|
2023-07-02 17:38:48 +00:00
|
|
|
private bool check(params TokenType[] types)
|
2023-06-28 18:12:14 +00:00
|
|
|
{
|
|
|
|
if (isAtEnd()) return false;
|
2023-07-02 17:38:48 +00:00
|
|
|
return Array.IndexOf(types, peek().type) >= 0;
|
2023-06-28 18:12:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2023-07-01 06:12:02 +00:00
|
|
|
return consume(new TokenType[] { type }, message);
|
|
|
|
}
|
|
|
|
|
|
|
|
private Token consume(TokenType[] types, string message)
|
|
|
|
{
|
|
|
|
foreach (var type in types)
|
|
|
|
{
|
|
|
|
if (check(type)) return advance();
|
|
|
|
}
|
2023-06-28 18:12:14 +00:00
|
|
|
|
|
|
|
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<Expr> 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()
|
|
|
|
{
|
2023-06-28 23:18:31 +00:00
|
|
|
var expr = equality();
|
|
|
|
while (match(TokenType.Semicolon))
|
|
|
|
{
|
|
|
|
var right = equality();
|
|
|
|
expr = new Sequence { Left = expr, Right = right };
|
|
|
|
}
|
|
|
|
return expr;
|
2023-06-28 18:12:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2023-06-28 22:38:00 +00:00
|
|
|
private Expr product() => binaryLeft(concat, TokenType.Slash, TokenType.Asterisk);
|
|
|
|
|
|
|
|
private Expr concat() => binaryLeft(unary, TokenType.PlusPlus);
|
2023-06-28 22:26:08 +00:00
|
|
|
|
2023-06-28 18:12:14 +00:00
|
|
|
private Expr unary()
|
|
|
|
{
|
|
|
|
if (match(TokenType.Bang, TokenType.Minus))
|
|
|
|
{
|
|
|
|
Token op = previous();
|
|
|
|
Expr right = unary();
|
|
|
|
return new Unary { Op = op, Right = right };
|
|
|
|
}
|
2023-07-01 06:12:02 +00:00
|
|
|
return control();
|
2023-06-28 22:26:08 +00:00
|
|
|
}
|
|
|
|
|
2023-06-28 23:25:28 +00:00
|
|
|
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:
|
2023-07-01 06:26:32 +00:00
|
|
|
throw new NotImplementedException("TODO when");
|
2023-06-28 23:25:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return primary();
|
|
|
|
}
|
|
|
|
|
2023-07-01 06:12:02 +00:00
|
|
|
private Name? name()
|
2023-06-28 22:26:08 +00:00
|
|
|
{
|
2023-07-01 06:12:02 +00:00
|
|
|
if (match(TokenType.Identifier))
|
2023-06-28 22:26:08 +00:00
|
|
|
{
|
2023-07-01 06:12:02 +00:00
|
|
|
return new(previous().lexeme, false);
|
2023-06-28 22:26:08 +00:00
|
|
|
}
|
2023-07-01 06:12:02 +00:00
|
|
|
if (match(TokenType.At))
|
|
|
|
{
|
|
|
|
Token literal = consume(TokenType.String, "Expect string literal after '@'.");
|
2023-07-02 19:19:42 +00:00
|
|
|
return new((string)(literal.literal!), true);
|
2023-07-01 06:12:02 +00:00
|
|
|
}
|
|
|
|
return null;
|
2023-06-28 18:12:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private Expr primary()
|
2023-07-01 06:12:02 +00:00
|
|
|
{
|
|
|
|
Expr expr = operand();
|
2023-07-01 06:26:32 +00:00
|
|
|
|
2023-07-01 06:12:02 +00:00
|
|
|
if (match(TokenType.Period))
|
|
|
|
{
|
|
|
|
Name? ident = name();
|
|
|
|
if (ident == null)
|
|
|
|
{
|
|
|
|
throw error(advance(), "Expect identifier after dot.");
|
|
|
|
}
|
|
|
|
return new Selector { Left = expr, FieldName = ident };
|
|
|
|
}
|
2023-07-01 06:26:32 +00:00
|
|
|
|
2023-07-01 06:12:02 +00:00
|
|
|
if (match(TokenType.LBracket))
|
|
|
|
{
|
2023-07-02 17:46:18 +00:00
|
|
|
var index = expression();
|
|
|
|
consume(TokenType.RBracket, "Expect '[' after expression.");
|
|
|
|
return new Indexer { Left = expr, Index = index };
|
2023-07-01 06:12:02 +00:00
|
|
|
}
|
2023-07-01 06:26:32 +00:00
|
|
|
|
2023-07-01 06:12:02 +00:00
|
|
|
if (match(TokenType.LParen))
|
|
|
|
{
|
2023-07-02 18:05:46 +00:00
|
|
|
var args = new List<Expr?>();
|
|
|
|
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 { Left = expr, Arguments = args.ToArray() };
|
2023-07-01 06:12:02 +00:00
|
|
|
}
|
2023-07-01 06:26:32 +00:00
|
|
|
|
2023-07-01 06:12:02 +00:00
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr operand()
|
2023-06-28 18:12:14 +00:00
|
|
|
{
|
|
|
|
if (match(TokenType.Number, TokenType.String))
|
|
|
|
{
|
2023-07-02 19:19:42 +00:00
|
|
|
object literal = previous().literal!;
|
|
|
|
return new Literal { Value = previous().literal! };
|
2023-06-28 18:12:14 +00:00
|
|
|
}
|
|
|
|
|
2023-07-01 06:12:02 +00:00
|
|
|
var ident = name();
|
|
|
|
if (ident != null)
|
|
|
|
{
|
|
|
|
return new Identifier { Value = ident };
|
|
|
|
}
|
|
|
|
|
2023-06-28 18:12:14 +00:00
|
|
|
if (match(TokenType.LParen))
|
|
|
|
{
|
2023-07-02 05:13:24 +00:00
|
|
|
Expr groupedExpr = expression();
|
2023-06-28 18:12:14 +00:00
|
|
|
consume(TokenType.RParen, "Expect ')' after expression.");
|
2023-07-02 05:13:24 +00:00
|
|
|
return new Grouping { Expression = groupedExpr };
|
2023-06-28 18:12:14 +00:00
|
|
|
}
|
|
|
|
|
2023-07-01 06:26:32 +00:00
|
|
|
Expr? expr;
|
|
|
|
if ((expr = record()) != null)
|
|
|
|
{
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
if ((expr = variant()) != null)
|
|
|
|
{
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
if ((expr = list()) != null)
|
|
|
|
{
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
2023-06-28 18:12:14 +00:00
|
|
|
throw error(peek(), "Expect expression.");
|
|
|
|
}
|
2023-07-01 06:26:32 +00:00
|
|
|
|
|
|
|
private Expr? list()
|
|
|
|
{
|
2023-07-02 05:13:24 +00:00
|
|
|
if (!match(TokenType.LBracket))
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Expr> elements = new List<Expr>();
|
|
|
|
|
|
|
|
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() };
|
2023-07-01 06:26:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private Expr? variant()
|
|
|
|
{
|
2023-07-02 05:30:24 +00:00
|
|
|
if (!match(TokenType.Backtick))
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
Name? tag = name();
|
|
|
|
Expr? argument = null;
|
|
|
|
if (tag == null)
|
|
|
|
{
|
|
|
|
throw error(peek(), "Expect identifier after backtick.");
|
|
|
|
}
|
|
|
|
if (match(TokenType.LParen))
|
|
|
|
{
|
|
|
|
if (!match(TokenType.RParen))
|
|
|
|
{
|
|
|
|
argument = expression();
|
|
|
|
consume(TokenType.RParen, "Expect ')' after variant argument.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new Variant { Tag = tag, Argument = argument };
|
2023-07-01 06:26:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private Expr? record()
|
|
|
|
{
|
2023-07-02 17:38:48 +00:00
|
|
|
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<Field>();
|
|
|
|
baseRecord = new(baseExpr, updates);
|
|
|
|
}
|
|
|
|
|
|
|
|
consume(TokenType.RBrace, "Expect '}' at end of record literal.");
|
|
|
|
return new Record
|
|
|
|
{
|
|
|
|
Extensions = extensions,
|
|
|
|
Base = baseRecord,
|
|
|
|
};
|
|
|
|
|
|
|
|
Field[] parseFields(params TokenType[] endAt)
|
|
|
|
{
|
|
|
|
List<Field> fields = new List<Field>();
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
2023-07-01 06:26:32 +00:00
|
|
|
}
|
2023-06-28 18:12:14 +00:00
|
|
|
}
|