/// <summary> /// Parses and extracts a numeric value at the current position /// </summary> /// <param name="parser">TextParser object</param> /// <returns></returns> protected string ParseNumberToken(TextParser parser) { bool hasDecimal = false; int start = parser.Position; while (Char.IsDigit(parser.Peek()) || parser.Peek() == '.') { if (parser.Peek() == '.') { if (hasDecimal) { throw new EvalException(ErrMultipleDecimalPoints, parser.Position); } hasDecimal = true; } parser.MoveAhead(); } // Extract token string token = parser.Extract(start, parser.Position); if (token == ".") { throw new EvalException(ErrInvalidOperand, parser.Position - 1); } return(token); }
/// <summary> /// Parses and extracts a symbol at the current position /// </summary> /// <param name="parser">TextParser object</param> /// <returns></returns> protected string ParseSymbolToken(TextParser parser) { int start = parser.Position; while (Char.IsLetterOrDigit(parser.Peek()) || parser.Peek() == '_') { parser.MoveAhead(); } return(parser.Extract(start, parser.Position)); }
/// <summary> /// Evaluates each parameter of a function's parameter list and returns /// a list of those values. An empty list is returned if no parameters /// were found. It is assumed the current position is at the opening /// parenthesis of the argument list. /// </summary> /// <param name="parser">TextParser object</param> /// <returns></returns> protected List <double> ParseParameters(TextParser parser) { // Move past open parenthesis parser.MoveAhead(); // Look for function parameters List <double> parameters = new List <double>(); parser.MovePastWhitespace(); if (parser.Peek() != ')') { // Parse function parameter list int paramStart = parser.Position; int parenCount = 1; while (!parser.EndOfText) { if (parser.Peek() == ',') { // Note: Ignore commas inside parentheses. They could be // from a parameter list for a function inside the parameters if (parenCount == 1) { parameters.Add(EvaluateParameter(parser, paramStart)); paramStart = parser.Position + 1; } } if (parser.Peek() == ')') { parenCount--; if (parenCount == 0) { parameters.Add(EvaluateParameter(parser, paramStart)); break; } } else if (parser.Peek() == '(') { parenCount++; } parser.MoveAhead(); } } // Make sure we found a closing parenthesis if (parser.Peek() != ')') { throw new EvalException(ErrClosingParenExpected, parser.Position); } // Move past closing parenthesis parser.MoveAhead(); // Return parameter list return(parameters); }
/// <summary> /// Converts a standard infix expression to list of tokens in /// postfix order. /// </summary> /// <param name="expression">Expression to evaluate</param> /// <returns></returns> protected List <string> TokenizeExpression(string expression) { List <string> tokens = new List <string>(); Stack <string> stack = new Stack <string>(); State state = State.None; int parenCount = 0; string temp; TextParser parser = new TextParser(expression); while (!parser.EndOfText) { if (Char.IsWhiteSpace(parser.Peek())) { // Ignore spaces, tabs, etc. } else if (parser.Peek() == '(') { // Cannot follow operand if (state == State.Operand) { throw new EvalException(ErrOperatorExpected, parser.Position); } // Allow additional unary operators after "(" if (state == State.UnaryOperator) { state = State.Operator; } // Push opening parenthesis onto stack stack.Push(parser.Peek().ToString()); // Track number of parentheses parenCount++; } else if (parser.Peek() == ')') { // Must follow operand if (state != State.Operand) { throw new EvalException(ErrOperandExpected, parser.Position); } // Must have matching open parenthesis if (parenCount == 0) { throw new EvalException(ErrUnmatchedClosingParen, parser.Position); } // Pop all operators until matching "(" found temp = stack.Pop(); while (temp != "(") { tokens.Add(temp); temp = stack.Pop(); } // Track number of parentheses parenCount--; } else if ("+-*/".Contains(parser.Peek())) { // Need a bit of extra code to support unary operators if (state == State.Operand) { // Pop operators with precedence >= current operator int currPrecedence = GetPrecedence(parser.Peek().ToString()); while (stack.Count > 0 && GetPrecedence(stack.Peek()) >= currPrecedence) { tokens.Add(stack.Pop()); } stack.Push(parser.Peek().ToString()); state = State.Operator; } else if (state == State.UnaryOperator) { // Don't allow two unary operators together throw new EvalException(ErrOperandExpected, parser.Position); } else { // Test for unary operator if (parser.Peek() == '-') { // Push unary minus stack.Push(UnaryMinus); state = State.UnaryOperator; } else if (parser.Peek() == '+') { // Just ignore unary plus state = State.UnaryOperator; } else { throw new EvalException(ErrOperandExpected, parser.Position); } } } else if (Char.IsDigit(parser.Peek()) || parser.Peek() == '.') { if (state == State.Operand) { // Cannot follow other operand throw new EvalException(ErrOperatorExpected, parser.Position); } // Parse number temp = ParseNumberToken(parser); tokens.Add(temp); state = State.Operand; continue; } else { double result; // Parse symbols and functions if (state == State.Operand) { // Symbol or function cannot follow other operand throw new EvalException(ErrOperatorExpected, parser.Position); } if (!(Char.IsLetter(parser.Peek()) || parser.Peek() == '_')) { // Invalid character temp = String.Format(ErrUnexpectedCharacter, parser.Peek()); throw new EvalException(temp, parser.Position); } // Save start of symbol for error reporting int symbolPos = parser.Position; // Parse this symbol temp = ParseSymbolToken(parser); // Skip whitespace parser.MovePastWhitespace(); // Check for parameter list if (parser.Peek() == '(') { // Found parameter list, evaluate function result = EvaluateFunction(parser, temp, symbolPos); } else { // No parameter list, evaluate symbol (variable) result = EvaluateSymbol(temp, symbolPos); } // Handle negative result if (result < 0) { stack.Push(UnaryMinus); result = Math.Abs(result); } tokens.Add(result.ToString()); state = State.Operand; continue; } parser.MoveAhead(); } // Expression cannot end with operator if (state == State.Operator || state == State.UnaryOperator) { throw new EvalException(ErrOperandExpected, parser.Position); } // Check for balanced parentheses if (parenCount > 0) { throw new EvalException(ErrClosingParenExpected, parser.Position); } // Retrieve remaining operators from stack while (stack.Count > 0) { tokens.Add(stack.Pop()); } return(tokens); }
/// <summary> /// Evaluates each parameter of a function's parameter list and returns /// a list of those values. An empty list is returned if no parameters /// were found. It is assumed the current position is at the opening /// parenthesis of the argument list. /// </summary> /// <param name="parser">TextParser object</param> /// <returns></returns> protected List<double> ParseParameters(TextParser parser) { // Move past open parenthesis parser.MoveAhead(); // Look for function parameters List<double> parameters = new List<double>(); parser.MovePastWhitespace(); if(parser.Peek() != ')') { // Parse function parameter list int paramStart = parser.Position; int parenCount = 1; while(!parser.EndOfText) { if(parser.Peek() == ',') { // Note: Ignore commas inside parentheses. They could be // from a parameter list for a function inside the parameters if(parenCount == 1) { parameters.Add(EvaluateParameter(parser, paramStart)); paramStart = parser.Position + 1; } } if(parser.Peek() == ')') { parenCount--; if(parenCount == 0) { parameters.Add(EvaluateParameter(parser, paramStart)); break; } } else if(parser.Peek() == '(') { parenCount++; } parser.MoveAhead(); } } // Make sure we found a closing parenthesis if(parser.Peek() != ')') throw new EvalException(ErrClosingParenExpected, parser.Position); // Move past closing parenthesis parser.MoveAhead(); // Return parameter list return parameters; }
/// <summary> /// Parses and extracts a symbol at the current position /// </summary> /// <param name="parser">TextParser object</param> /// <returns></returns> protected string ParseSymbolToken(TextParser parser) { int start = parser.Position; while(Char.IsLetterOrDigit(parser.Peek()) || parser.Peek() == '_') parser.MoveAhead(); return parser.Extract(start, parser.Position); }
/// <summary> /// Parses and extracts a numeric value at the current position /// </summary> /// <param name="parser">TextParser object</param> /// <returns></returns> protected string ParseNumberToken(TextParser parser) { bool hasDecimal = false; int start = parser.Position; while(Char.IsDigit(parser.Peek()) || parser.Peek() == '.') { if(parser.Peek() == '.') { if(hasDecimal) throw new EvalException(ErrMultipleDecimalPoints, parser.Position); hasDecimal = true; } parser.MoveAhead(); } // Extract token string token = parser.Extract(start, parser.Position); if(token == ".") throw new EvalException(ErrInvalidOperand, parser.Position - 1); return token; }
/// <summary> /// Converts a standard infix expression to list of tokens in /// postfix order. /// </summary> /// <param name="expression">Expression to evaluate</param> /// <returns></returns> protected List<string> TokenizeExpression(string expression) { List<string> tokens = new List<string>(); Stack<string> stack = new Stack<string>(); State state = State.None; int parenCount = 0; string temp; TextParser parser = new TextParser(expression); while(!parser.EndOfText) { if(Char.IsWhiteSpace(parser.Peek())) { // Ignore spaces, tabs, etc. } else if(parser.Peek() == '(') { // Cannot follow operand if(state == State.Operand) throw new EvalException(ErrOperatorExpected, parser.Position); // Allow additional unary operators after "(" if(state == State.UnaryOperator) state = State.Operator; // Push opening parenthesis onto stack stack.Push(parser.Peek().ToString()); // Track number of parentheses parenCount++; } else if(parser.Peek() == ')') { // Must follow operand if(state != State.Operand) throw new EvalException(ErrOperandExpected, parser.Position); // Must have matching open parenthesis if(parenCount == 0) throw new EvalException(ErrUnmatchedClosingParen, parser.Position); // Pop all operators until matching "(" found temp = stack.Pop(); while(temp != "(") { tokens.Add(temp); temp = stack.Pop(); } // Track number of parentheses parenCount--; } else if("+-*/%?:!<>=&|".Contains(parser.Peek())) { // Need a bit of extra code to support unary operators if(state == State.Operand) { // Pop operators with precedence >= current operator string tok; if(parser.Remaining>0 && (("!<>=".Contains(parser.Peek()) && parser.Peek(1)=='=') || (parser.Peek()=='&' && parser.Peek(1)=='&') || (parser.Peek()=='|' && parser.Peek(1)=='|'))) { tok=string.Concat(parser.Peek(), parser.Peek(1)); parser.MoveAhead(); } else { tok=parser.Peek().ToString(); } int currPrecedence = GetPrecedence(tok); if(currPrecedence==0){ throw new EvalException(ErrInvalidOperand, parser.Position); } while(stack.Count > 0 && GetPrecedence(stack.Peek()) >= currPrecedence) tokens.Add(stack.Pop()); stack.Push(tok); state = State.Operator; } else if(state == State.UnaryOperator) { // Don't allow two unary operators together throw new EvalException(ErrOperandExpected, parser.Position); } else { // Test for unary operator if(parser.Peek() == '-') { // Push unary minus stack.Push(UnaryMinus); state = State.UnaryOperator; } else if(parser.Peek() == '+') { // Just ignore unary plus state = State.UnaryOperator; } else { throw new EvalException(ErrOperandExpected, parser.Position); } } } else if(Char.IsDigit(parser.Peek()) || parser.Peek() == '.') { if(state == State.Operand) { // Cannot follow other operand throw new EvalException(ErrOperatorExpected, parser.Position); } // Parse number temp = ParseNumberToken(parser); tokens.Add(temp); state = State.Operand; continue; } else { double result; // Parse symbols and functions if(state == State.Operand) { // Symbol or function cannot follow other operand throw new EvalException(ErrOperatorExpected, parser.Position); } if(!(Char.IsLetter(parser.Peek()) || parser.Peek() == '_')) { // Invalid character temp = String.Format(ErrUnexpectedCharacter, parser.Peek()); throw new EvalException(temp, parser.Position); } // Save start of symbol for error reporting int symbolPos = parser.Position; // Parse this symbol temp = ParseSymbolToken(parser); // Skip whitespace parser.MovePastWhitespace(); // Check for parameter list if(parser.Peek() == '(') { // Found parameter list, evaluate function result = EvaluateFunction(parser, temp, symbolPos); } else { // No parameter list, evaluate symbol (variable) result = EvaluateSymbol(temp, symbolPos); } // Handle negative result if(result < 0) { stack.Push(UnaryMinus); result = Math.Abs(result); } tokens.Add(result.ToString("F5", CultureInfo.InvariantCulture)); state = State.Operand; continue; } parser.MoveAhead(); } // Expression cannot end with operator if(state == State.Operator || state == State.UnaryOperator) throw new EvalException(ErrOperandExpected, parser.Position); // Check for balanced parentheses if(parenCount > 0) throw new EvalException(ErrClosingParenExpected, parser.Position); // Retrieve remaining operators from stack while(stack.Count > 0) tokens.Add(stack.Pop()); return tokens; }