/// <summary> /// Parses and extracts a numeric value at the current position /// </summary> /// <param name="parser">TextParser object</param> /// <returns></returns> private 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 FormulaException(ErrMultipleDecimalPoints, parser.Position); } hasDecimal = true; } parser.MoveAhead(); } // Extract token string token = parser.Extract(start, parser.Position); if (token == ".") { throw new FormulaException(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> private static string ParseSymbolToken(TextParser parser) { int start = parser.Position; while (char.IsLetterOrDigit(parser.Peek()) || parser.Peek() == '_') { parser.MoveAhead(); } return(parser.Extract(start, parser.Position)); }
/// <summary> /// Converts a standard infix expression to list of tokens in /// postfix order. /// </summary> /// <param name="expression">Expression to evaluate</param> /// <returns></returns> private List <string> TokenizeExpression(string expression) { var tokens = new List <string>(); var stack = new Stack <string>(); var state = State.None; int parenDepth = 0; string temp; var 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 FormulaException(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 parenDepth++; } else if (parser.Peek() == ')') { // Must follow operand if (state != State.Operand) { throw new FormulaException(ErrOperandExpected, parser.Position); } // Must have matching open parenthesis if (parenDepth == 0) { throw new FormulaException(ErrUnmatchedClosingParen, parser.Position); } // Pop all operators until matching "(" found temp = stack.Pop(); while (temp != "(") { tokens.Add(temp); temp = stack.Pop(); } // Track number of parentheses parenDepth--; } 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 FormulaException(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 FormulaException(ErrOperandExpected, parser.Position); } } } else if (char.IsDigit(parser.Peek()) || parser.Peek() == '.') { if (state == State.Operand) { // Cannot follow other operand throw new FormulaException(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 FormulaException(ErrOperatorExpected, parser.Position); } if (!(char.IsLetter(parser.Peek()) || parser.Peek() == '_')) { // Invalid character temp = string.Format(ErrUnexpectedCharacter, parser.Peek()); throw new FormulaException(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 FormulaException(ErrOperandExpected, parser.Position); } // Check for balanced parentheses if (parenDepth > 0) { throw new FormulaException(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> private List <double> ParseParameters(TextParser parser) { // Move past open parenthesis parser.MoveAhead(); // Look for function parameters var parameters = new List <double>(); parser.MovePastWhitespace(); if (parser.Peek() != ')') { // Parse function parameter list int paramStart = parser.Position; int pardepth = 1; while (!parser.EndOfText) { if (parser.Peek() == ':') { // assume current token and next token are cell references var p1 = parser.Position; var cell1 = parser.Extract(paramStart, parser.Position); parser.MoveAhead(); var p2 = parser.Position; var cell2 = ParseSymbolToken(parser); paramStart = parser.Position; parameters.AddRange(EvaluateCellReferences(cell1, cell2, p1, p2)); } else if (parser.Peek() == ',') { // Note: Ignore commas inside parentheses. They could be // from a parameter list for a function inside the parameters if (pardepth == 1) { parameters.Add(EvaluateParameter(parser, paramStart)); paramStart = parser.Position + 1; } } if (parser.Peek() == ')') { pardepth--; if (pardepth == 0) { if (paramStart < parser.Position) { parameters.Add(EvaluateParameter(parser, paramStart)); } break; } } else if (parser.Peek() == '(') { pardepth++; } parser.MoveAhead(); } } // Make sure we found a closing parenthesis if (parser.Peek() != ')') { throw new FormulaException(ErrClosingParenExpected, parser.Position); } // Move past closing parenthesis parser.MoveAhead(); // Return parameter list return(parameters); }