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

149 lines
3.3 KiB
C#

using System;
using System.Collections.Immutable;
using System.Globalization;
using System.Numerics;
using System.Text;
using ExtendedNumerics;
namespace Calculator.Parser;
public sealed class Tokenizer(string input) {
private int position = 0;
private bool IsEOF => position >= input.Length;
private char Advance() {
return input[position++];
}
private bool Match(char c) {
return Match(found => found == c, out _);
}
private bool Match(Predicate<char> predicate, out char c) {
if (IsEOF) {
c = default;
return false;
}
c = input[position];
if (!predicate(c)) {
return false;
}
position++;
return true;
}
private void MatchWhile(StringBuilder result, Predicate<char> predicate) {
while (Match(predicate, out char c)) {
result.Append(c);
}
}
private string MatchWhile(Predicate<char> predicate) {
var result = new StringBuilder();
MatchWhile(result, predicate);
return result.ToString();
}
private string MatchRest(char firstChar, Predicate<char> predicate) {
var result = new StringBuilder();
result.Append(firstChar);
MatchWhile(result, predicate);
return result.ToString();
}
public ImmutableArray<Token> Scan() {
ImmutableArray<Token>.Builder tokens = ImmutableArray.CreateBuilder<Token>();
void AddToken(Token token) {
tokens.Add(token);
}
void AddSimpleToken(SimpleTokenType tokenType) {
AddToken(new Token.Simple(tokenType));
}
while (!IsEOF) {
char c = Advance();
switch (c) {
case ' ':
// Ignore whitespace.
break;
case '+':
AddSimpleToken(SimpleTokenType.PLUS);
break;
case '-':
AddSimpleToken(SimpleTokenType.MINUS);
break;
case '*':
AddSimpleToken(SimpleTokenType.STAR);
break;
case '/':
AddSimpleToken(SimpleTokenType.SLASH);
break;
case '%':
AddSimpleToken(SimpleTokenType.PERCENT);
break;
case '^':
AddSimpleToken(SimpleTokenType.CARET);
break;
case '(':
AddSimpleToken(SimpleTokenType.LEFT_PARENTHESIS);
break;
case ')':
AddSimpleToken(SimpleTokenType.RIGHT_PARENTHESIS);
break;
case '"' or '\'':
AddToken(new Token.Text(c.ToString()));
break;
case '°':
case {} when char.IsLetter(c):
AddToken(new Token.Text(MatchRest(c, char.IsLetterOrDigit)));
break;
case {} when char.IsAsciiDigit(c):
string integerPart = MatchRest(c, char.IsAsciiDigit);
if (Match('.')) {
string fractionalPart = MatchWhile(char.IsAsciiDigit);
AddToken(new Token.Number(ParseNumber(integerPart, fractionalPart)));
}
else {
AddToken(new Token.Number(ParseNumber(integerPart)));
}
break;
default:
throw new TokenizationException("Unexpected character: " + c, c);
}
}
return tokens.ToImmutable();
}
internal static BigRational ParseNumber(string integerPart, string? fractionalPart = null) {
if (fractionalPart == null) {
return new BigRational(BigInteger.Parse(integerPart, NumberStyles.Integer, CultureInfo.InvariantCulture));
}
else {
BigInteger numerator = BigInteger.Parse(integerPart + fractionalPart, NumberStyles.Integer, CultureInfo.InvariantCulture);
BigInteger denominator = BigInteger.Pow(10, fractionalPart.Length);
return new BigRational(numerator, denominator);
}
}
}