174 lines
2.8 KiB
C#
174 lines
2.8 KiB
C#
|
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;
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
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<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()
|
||
|
{
|
||
|
return equality();
|
||
|
}
|
||
|
|
||
|
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(unary, TokenType.Slash, TokenType.Asterisk);
|
||
|
private Expr unary()
|
||
|
{
|
||
|
if (match(TokenType.Bang, TokenType.Minus))
|
||
|
{
|
||
|
Token op = previous();
|
||
|
Expr right = unary();
|
||
|
return new Unary { Op = op, Right = right };
|
||
|
}
|
||
|
return primary();
|
||
|
}
|
||
|
|
||
|
private Expr primary()
|
||
|
{
|
||
|
if (match(TokenType.Number, TokenType.String))
|
||
|
{
|
||
|
return new Literal { Value = previous().literal };
|
||
|
}
|
||
|
|
||
|
if (match(TokenType.LParen))
|
||
|
{
|
||
|
Expr expr = expression();
|
||
|
consume(TokenType.RParen, "Expect ')' after expression.");
|
||
|
return new Grouping { Expression = expr };
|
||
|
}
|
||
|
|
||
|
throw error(peek(), "Expect expression.");
|
||
|
}
|
||
|
}
|