finn-lang/Parser.cs

526 lines
9.8 KiB
C#
Raw Normal View History

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-02 23:09:34 +00:00
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();
2023-07-02 20:27:12 +00:00
expr = new Binary(expr, op, right);
2023-06-28 18:12:14 +00:00
}
return expr;
}
// Rules
private Expr expression()
{
2023-06-28 23:18:31 +00:00
var expr = equality();
while (match(TokenType.Semicolon))
{
var right = equality();
2023-07-02 20:27:12 +00:00
expr = new Sequence(expr, right);
2023-06-28 23:18:31 +00:00
}
return expr;
2023-06-28 18:12:14 +00:00
}
2023-07-02 20:27:12 +00:00
private Expr equality() =>
binaryLeft(comparison, TokenType.BangEqual, TokenType.DoubleEqual);
2023-06-28 18:12:14 +00:00
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();
2023-07-02 20:27:12 +00:00
return new Unary(op, right);
2023-06-28 18:12:14 +00:00
}
return let();
}
private RecordPattern? recordPattern()
{
if (!match(TokenType.LBrace))
{
return null;
}
2023-07-02 23:07:46 +00:00
List<FieldPattern> fields = new List<FieldPattern>();
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);
}
2023-07-02 23:07:46 +00:00
private SimplePattern? simplePattern()
{
if (match(TokenType.Blank))
{
return new SimplePattern(null);
}
var identifier = name();
if (identifier != null)
{
return new SimplePattern(identifier);
}
2023-07-02 23:07:46 +00:00
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;
}
2023-07-02 23:07:46 +00:00
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))
{
2023-07-05 14:52:14 +00:00
return ifExpr();
}
List<Binding> bindings = new List<Binding>();
2023-07-05 16:18:27 +00:00
bindings.Add(parseBinding());
while (match(TokenType.And))
{
2023-07-05 16:18:27 +00:00
bindings.Add(parseBinding());
}
consume(TokenType.In, "Expect 'in' after let-bindings.");
Expr body = expression();
return new Let(bindings.ToArray(), body);
2023-07-05 16:18:27 +00:00
Binding parseBinding()
{
Pattern p = pattern();
switch (p)
{
case (SimplePattern(var funcName)) when funcName != null && match(TokenType.LParen):
List<Pattern> funcParams = new List<Pattern>();
2023-07-05 16:18:27 +00:00
while (!check(TokenType.RParen))
{
funcParams.Add(pattern());
2023-07-05 16:18:27 +00:00
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());
}
}
2023-06-28 22:26:08 +00:00
}
2023-07-05 14:52:14 +00:00
private Expr ifExpr()
2023-06-28 23:25:28 +00:00
{
2023-07-05 14:52:14 +00:00
if (!match(TokenType.If))
2023-06-28 23:25:28 +00:00
{
2023-07-05 14:52:14 +00:00
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();
2023-06-28 23:25:28 +00:00
}
2023-07-05 14:52:14 +00:00
Expr head = expression();
consume(TokenType.Is, "Expect 'is' after expression.");
2023-06-28 23:25:28 +00:00
2023-07-11 19:55:06 +00:00
List<VarBinding> cases = new List<VarBinding>();
2023-07-05 14:52:14 +00:00
cases.Add(parseCase());
while (match(TokenType.Comma))
{
cases.Add(parseCase());
}
return new When(head, cases.ToArray());
2023-07-11 19:55:06 +00:00
VarBinding parseCase()
2023-07-05 14:52:14 +00:00
{
Pattern pat = pattern();
consume(TokenType.DoubleArrow, "Expect '=>' after pattern.");
Expr value = expression();
2023-07-05 16:18:27 +00:00
return new VarBinding(pat, value);
2023-07-05 14:52:14 +00:00
}
2023-06-28 23:25:28 +00:00
}
private Name? name()
2023-06-28 22:26:08 +00:00
{
if (match(TokenType.Identifier))
2023-06-28 22:26:08 +00:00
{
return new(previous().lexeme, false);
2023-06-28 22:26:08 +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);
}
return null;
2023-06-28 18:12:14 +00:00
}
private Expr primary()
{
Expr expr = operand();
2023-07-01 06:26:32 +00:00
if (match(TokenType.Period))
{
Name? ident = name();
if (ident == null)
{
throw error(advance(), "Expect identifier after dot.");
}
2023-07-02 20:27:12 +00:00
return new Selector(expr, ident);
}
2023-07-01 06:26:32 +00:00
if (match(TokenType.LBracket))
{
2023-07-02 17:46:18 +00:00
var index = expression();
consume(TokenType.RBracket, "Expect '[' after expression.");
2023-07-02 20:27:12 +00:00
return new Indexer(expr, index);
}
2023-07-01 06:26:32 +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;
}
}
2023-07-05 16:18:27 +00:00
consume(TokenType.RParen, "Expect ')' after arguments.");
2023-07-02 20:27:12 +00:00
return new Call(expr, args.ToArray());
}
2023-07-01 06:26:32 +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!;
2023-07-02 20:27:12 +00:00
return new Literal(previous().literal!);
2023-06-28 18:12:14 +00:00
}
var ident = name();
if (ident != null)
{
2023-07-08 03:29:17 +00:00
return new Variable(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 20:27:12 +00:00
return new Grouping(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.");
}
}
}
2023-07-02 20:27:12 +00:00
return new Finn.AST.List(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 as tag name.");
2023-07-02 05:30:24 +00:00
}
if (match(TokenType.LParen))
{
if (!match(TokenType.RParen))
{
argument = expression();
consume(TokenType.RParen, "Expect ')' after variant argument.");
}
}
2023-07-02 20:27:12 +00:00
return new Variant(tag, 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.");
2023-07-02 20:27:12 +00:00
return new Record(extensions, baseRecord);
2023-07-02 17:38:48 +00:00
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
}