1
0
mirror of https://github.com/chylex/Query.git synced 2024-10-16 07:42:46 +02:00
Query/Calculator/Parser/Parser.cs

213 lines
5.1 KiB
C#

using System;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Calculator.Math;
namespace Calculator.Parser;
public sealed class Parser(ImmutableArray<Token> tokens) {
private int current = 0;
private bool IsEOF => current >= tokens.Length;
private static readonly ImmutableArray<SimpleTokenType> PLUS_MINUS = [
SimpleTokenType.PLUS,
SimpleTokenType.MINUS
];
private static readonly ImmutableArray<SimpleTokenType> STAR_SLASH_PERCENT = [
SimpleTokenType.STAR,
SimpleTokenType.SLASH,
SimpleTokenType.PERCENT
];
private static readonly ImmutableArray<SimpleTokenType> CARET = [
SimpleTokenType.CARET
];
private bool Match(SimpleTokenType expectedTokenType, [NotNullWhen(true)] out Token.Simple? token) {
return Match(simpleToken => simpleToken.Type == expectedTokenType, out token);
}
private bool Match(ImmutableArray<SimpleTokenType> expectedTokenTypes, [NotNullWhen(true)] out Token.Simple? token) {
return Match(simpleToken => expectedTokenTypes.Contains(simpleToken.Type), out token);
}
private bool Match<T>([NotNullWhen(true)] out T? token) where T : Token {
return Match(static _ => true, out token);
}
private bool Match<T>(Predicate<T> predicate, [NotNullWhen(true)] out T? token) where T : Token {
if (!IsEOF && tokens[current] is T t && predicate(t)) {
current++;
token = t;
return true;
}
token = null;
return false;
}
public Expression Parse() {
Expression term = Term();
if (!IsEOF) {
throw new ParseException("Incomplete expression");
}
return term;
}
private Expression Term() {
return Binary(Factor, PLUS_MINUS);
}
private Expression Factor() {
return Binary(Exponentiation, STAR_SLASH_PERCENT);
}
private Expression Exponentiation() {
return Binary(Conversion, CARET);
}
private Expression Binary(Func<Expression> term, ImmutableArray<SimpleTokenType> expectedTokenTypes) {
Expression left = term();
while (Match(expectedTokenTypes, out Token.Simple? op)) {
Expression right = term();
left = new Expression.Binary(left, op, right);
}
return left;
}
private Expression Conversion() {
Expression left = Unary();
while (MatchUnitConversionOperator()) {
if (!MatchUnit(out Unit? unit)) {
throw new ParseException("Expected a unit literal");
}
left = new Expression.UnitConversion(left, unit);
}
return left;
}
private Expression Unary() {
if (Match(PLUS_MINUS, out Token.Simple? op)) {
Expression right = Unary();
return new Expression.Unary(op, right);
}
else {
return Primary();
}
}
private Expression Primary() {
if (Match(LiteralPredicate, out Token? literal)) {
if (literal is not Token.Number number) {
throw new ParseException("Expected a number literal");
}
Expression expression;
if (!MatchUnit(out Unit? unit)) {
expression = new Expression.Number(number);
}
else {
var numbersWithUnits = ImmutableArray.CreateBuilder<(Token.Number, Unit)>();
numbersWithUnits.Add((number, unit));
while (MatchNumberWithUnit(out var numberWithUnit)) {
numbersWithUnits.Add(numberWithUnit.Value);
}
expression = new Expression.NumbersWithUnits(numbersWithUnits.ToImmutable());
}
if (Match(SimpleTokenType.LEFT_PARENTHESIS, out _)) {
expression = new Expression.Binary(expression, new Token.Simple(SimpleTokenType.STAR), InsideParentheses());
}
return expression;
}
if (Match(SimpleTokenType.LEFT_PARENTHESIS, out _)) {
return new Expression.Grouping(InsideParentheses());
}
throw new ParseException("Unexpected token type: " + tokens[current]);
}
private static bool LiteralPredicate(Token token) {
return token is Token.Text or Token.Number;
}
private Expression InsideParentheses() {
Expression term = Term();
if (!Match(SimpleTokenType.RIGHT_PARENTHESIS, out _)) {
throw new ParseException("Expected ')' after expression.");
}
int position = current;
if (MatchUnitConversionOperator()) {
if (MatchUnit(out Unit? toUnit)) {
return new Expression.UnitConversion(term, toUnit);
}
else {
current = position;
}
}
if (MatchUnit(out Unit? unit)) {
return new Expression.UnitAssignment(term, unit);
}
else {
return term;
}
}
private bool MatchNumberWithUnit([NotNullWhen(true)] out (Token.Number, Unit)? numberWithUnit) {
if (!Match(out Token.Number? number)) {
numberWithUnit = null;
return false;
}
if (!MatchUnit(out Unit? unit)) {
throw new ParseException("Expected a unit literal");
}
numberWithUnit = (number, unit);
return true;
}
private bool MatchUnit([NotNullWhen(true)] out Unit? unit) {
int position = current;
UnitUniverses.WordLookupTrieNode node = Units.All.UnitLookupByWords;
// ReSharper disable once AccessToModifiedClosure
while (Match(token => node.Children.ContainsKey(token.Value), out Token.Text? text)) {
node = node.Children[text.Value];
}
unit = node.Unit;
if (unit != null) {
return true;
}
else {
current = position;
return false;
}
}
private bool MatchUnitConversionOperator() {
return Match<Token.Text>(static text => text.Value is "to" or "in", out _);
}
}