mirror of
https://github.com/0xJ1M/MathsEngine.git
synced 2026-06-05 01:00:06 +00:00
247 lines
11 KiB
C#
247 lines
11 KiB
C#
using MathEngine.Functions.BuiltIns;
|
|
using MathEngine.Tokenizer;
|
|
using System.Security;
|
|
using static MathEngine.Tokenizer.Token;
|
|
|
|
namespace MathEngine.Parser
|
|
{
|
|
/// <summary>
|
|
/// Represents the conversion from a list of Tokens representing Mathematical expression to List in Reverse Polish Notation form
|
|
/// </summary>
|
|
internal class Parser
|
|
{
|
|
|
|
/// <summary>
|
|
/// Return the Precdence of a given token operator
|
|
/// </summary>
|
|
/// <param name="X">Token to get Precdence of</param>
|
|
/// <returns>An integer value that represent the precedence value of the token</returns>
|
|
private static int OperatorPrecedence(Token X)
|
|
{
|
|
switch (X.Type)
|
|
{
|
|
case TokenType.Addition:
|
|
case TokenType.Subtraction:
|
|
{
|
|
return 1;
|
|
}
|
|
case TokenType.Multiplication:
|
|
case TokenType.Division:
|
|
{
|
|
return 2;
|
|
}
|
|
case TokenType.UnaryPlus:
|
|
case TokenType.UnaryMinus:
|
|
{
|
|
return 3;
|
|
}
|
|
case TokenType.Exponentiation:
|
|
{
|
|
return 4;
|
|
}
|
|
case TokenType.LeftParenthesis:
|
|
case TokenType.RightParenthesis:
|
|
{
|
|
return 5;
|
|
}
|
|
|
|
default:
|
|
{
|
|
throw new Exception("Unknown operator precedence" + X.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is the operation left associative
|
|
/// </summary>
|
|
/// <param name="X">Operation to check</param>
|
|
/// <returns>True if the token is left associative, False otherwise</returns>
|
|
private static bool IsLeftAssociatve(Token X)
|
|
{
|
|
switch (X.Type)
|
|
{
|
|
case TokenType.Addition:
|
|
case TokenType.Subtraction:
|
|
case TokenType.Multiplication:
|
|
case TokenType.Division:
|
|
case TokenType.LeftParenthesis:
|
|
case TokenType.RightParenthesis:
|
|
{
|
|
return true;
|
|
}
|
|
case TokenType.Exponentiation:
|
|
case TokenType.UnaryMinus:
|
|
{
|
|
return false;
|
|
}
|
|
default:
|
|
{
|
|
throw new Exception("If you are seeing this something went wrong when trying to determine if a token was Left Associatve");
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Pops operators from the operator stack to the output queue based on precedence and associativity rules.
|
|
/// This method is part of the Shunting Yard algorithm, ensuring that operators with higher or equal precedence
|
|
/// (when left-associative) are correctly ordered in the resulting Reverse Polish Notation (RPN).
|
|
/// </summary>
|
|
/// <param name="operatorStack">The stack holding operators and functions that are yet to be added to the output.</param>
|
|
/// <param name="outputQueue">The queue that accumulates tokens in RPN order.</param>
|
|
/// <param name="current">The current operator token being processed from the input expression.</param>
|
|
private static void PopOperatorsToQueue(Stack<Token> operatorStack, Queue<Token> outputQueue, Token current)
|
|
{
|
|
while (operatorStack.Count > 0)
|
|
{
|
|
Token top = operatorStack.Peek();
|
|
bool isFunction = top.Type == TokenType.Function;
|
|
bool isHigherPrecedence = OperatorPrecedence(top) > OperatorPrecedence(current);
|
|
bool isEqualPrecedenceAndLeftAssociative = ((OperatorPrecedence(top) == OperatorPrecedence(current)) && IsLeftAssociatve(current));
|
|
bool isNotLeftParenthesis = top.Type != TokenType.LeftParenthesis;
|
|
|
|
if ((isFunction || isHigherPrecedence || isEqualPrecedenceAndLeftAssociative) && isNotLeftParenthesis)
|
|
{
|
|
outputQueue.Enqueue(operatorStack.Pop());
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Handles pushing either an addition or subtraction operator token onto the operator stack, distinguishing
|
|
/// between unary and binary addition or subtract operators based on the previous token context.
|
|
/// </summary>
|
|
/// <param name="operatorStack">The stack holding operators during parsing.</param>
|
|
/// <param name="outputQueue">The output queue representing the RPN expression.</param>
|
|
/// <param name="current">The current operator token being processed.</param>
|
|
/// <param name="previous">The previous token processed, used to determine if the current operator is unary.</param>
|
|
private static void PushAdditionSubtractionWithUnaryCheck(Stack<Token> operatorStack, Queue<Token> outputQueue, Token current, Token previous)
|
|
{
|
|
bool isUnary = previous.Type == TokenType.None ||
|
|
previous.Type == TokenType.LeftParenthesis ||
|
|
previous.Type == TokenType.Addition ||
|
|
previous.Type == TokenType.Subtraction ||
|
|
previous.Type == TokenType.Multiplication ||
|
|
previous.Type == TokenType.Division ||
|
|
previous.Type == TokenType.Exponentiation;
|
|
|
|
if (isUnary)
|
|
{
|
|
var unaryToken = current.Type == TokenType.Subtraction ? Token.UnaryMinus : Token.UnaryPlus;
|
|
PopOperatorsToQueue(operatorStack, outputQueue, unaryToken);
|
|
operatorStack.Push(unaryToken);
|
|
}
|
|
else
|
|
{
|
|
PopOperatorsToQueue(operatorStack, outputQueue, current);
|
|
operatorStack.Push(current);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a list of tokens into a valid RPN expression
|
|
/// </summary>
|
|
/// <param name="Expression">List of tokens to parse</param>
|
|
/// <returns>Returns a <see cref="Queue{Token}"/> representing the Reverse polish notation form of the expression</returns>
|
|
internal static Queue<Token> Parse(List<Token> Expression)
|
|
{
|
|
//Temp holding stack for operators
|
|
Stack<Token> OperatorStack = new(Expression.Count/2);
|
|
//The final stack to return
|
|
Queue<Token> rpnQueue = new(Expression.Count);
|
|
|
|
// Stack that is used to handle nested function argument counts
|
|
Stack<UInt32> arityStack = new();
|
|
|
|
Token PreviousToken = Token.None;
|
|
Token CurrentToken;
|
|
|
|
for (int i = 0; i < Expression.Count; i++)
|
|
{
|
|
CurrentToken = Expression[i];
|
|
switch (CurrentToken.Type)
|
|
{
|
|
case TokenType.Numeric:
|
|
rpnQueue.Enqueue(CurrentToken);
|
|
break;
|
|
case TokenType.Addition:
|
|
case TokenType.Subtraction:
|
|
PushAdditionSubtractionWithUnaryCheck(OperatorStack, rpnQueue, CurrentToken, PreviousToken);
|
|
break;
|
|
|
|
case TokenType.Multiplication:
|
|
case TokenType.Division:
|
|
case TokenType.Exponentiation:
|
|
PopOperatorsToQueue(OperatorStack, rpnQueue, CurrentToken);
|
|
OperatorStack.Push(CurrentToken);
|
|
break;
|
|
case TokenType.GenericIdentifier:
|
|
// few things to consider
|
|
// 1 Check built-ins
|
|
if (i + 1 < Expression.Count && Expression[i + 1].Type == TokenType.LeftParenthesis && BuiltIns.IsFunction(CurrentToken.Value))
|
|
{
|
|
OperatorStack.Push(CurrentToken with { Type = Token.TokenType.Function });
|
|
arityStack.Push(1); // at least one arg must be present
|
|
break;
|
|
}
|
|
// 2 check user defined functions
|
|
// TODO: Implement User definable functions
|
|
|
|
// 3 check to see if it is a user defined variable
|
|
// TODO: Implement user definable variables
|
|
|
|
// 4 assume it is an implict multiplication
|
|
// TODO: Split identifier into letters and insert implicit multiplication
|
|
break;
|
|
case TokenType.FunctionArgumentSeparator:
|
|
if (arityStack.Count == 0)
|
|
throw new ParserException("Unexpected function arguement separator");
|
|
// Pop operators until the last LeftParenthesis
|
|
while (OperatorStack.Count > 0 && OperatorStack.Peek().Type != TokenType.LeftParenthesis)
|
|
rpnQueue.Enqueue(OperatorStack.Pop());
|
|
|
|
// increment the argument counter
|
|
arityStack.Push(arityStack.Pop() + 1);
|
|
break;
|
|
case TokenType.LeftParenthesis:
|
|
OperatorStack.Push(CurrentToken);
|
|
break;
|
|
case TokenType.RightParenthesis:
|
|
while (OperatorStack.Count != 0 && (OperatorStack.Peek() != Token.LeftParenthesis))
|
|
{
|
|
rpnQueue.Enqueue(OperatorStack.Pop());
|
|
}
|
|
if (OperatorStack.Count == 0)
|
|
{
|
|
throw new ParserException("Mismatched parentheses: Missing left parenthesis");
|
|
}
|
|
OperatorStack.Pop(); // Discard the left parenthesis
|
|
|
|
if (OperatorStack.Count > 0 && (OperatorStack.Peek().Type == TokenType.Function))
|
|
{
|
|
rpnQueue.Enqueue(OperatorStack.Pop() with { Arity = arityStack.Pop()});
|
|
}
|
|
break;
|
|
}
|
|
PreviousToken = CurrentToken;
|
|
}
|
|
|
|
while ((OperatorStack.Count > 0))
|
|
{
|
|
if (OperatorStack.Peek().Type == TokenType.LeftParenthesis || OperatorStack.Peek().Type == TokenType.RightParenthesis)
|
|
throw new Exception("Mismatched parentheses; Expected (");
|
|
else
|
|
rpnQueue.Enqueue(OperatorStack.Pop());
|
|
}
|
|
return rpnQueue;
|
|
}
|
|
}
|
|
}
|