using MathEngine.Functions.BuiltIns;
using MathEngine.Tokenizer;
using System.Security;
using static MathEngine.Tokenizer.Token;
namespace MathEngine.Parser
{
///
/// Represents the conversion from a list of Tokens representing Mathematical expression to List in Reverse Polish Notation form
///
internal class Parser
{
///
/// Return the Precdence of a given token operator
///
/// Token to get Precdence of
/// An integer value that represent the precedence value of the token
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);
}
}
}
///
/// Is the operation left associative
///
/// Operation to check
/// True if the token is left associative, False otherwise
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");
}
}
}
///
/// 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).
///
/// The stack holding operators and functions that are yet to be added to the output.
/// The queue that accumulates tokens in RPN order.
/// The current operator token being processed from the input expression.
private static void PopOperatorsToQueue(Stack operatorStack, Queue 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;
}
}
}
///
/// 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.
///
/// The stack holding operators during parsing.
/// The output queue representing the RPN expression.
/// The current operator token being processed.
/// The previous token processed, used to determine if the current operator is unary.
private static void PushAdditionSubtractionWithUnaryCheck(Stack operatorStack, Queue 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);
}
}
///
/// Parses a list of tokens into a valid RPN expression
///
/// List of tokens to parse
/// Returns a representing the Reverse polish notation form of the expression
internal static Queue Parse(List Expression)
{
//Temp holding stack for operators
Stack OperatorStack = new(Expression.Count/2);
//The final stack to return
Queue rpnQueue = new(Expression.Count);
// Stack that is used to handle nested function argument counts
Stack 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;
}
}
}