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; } } }