public ParsedExpression Parse(string expression) { IList<IExpressionElement> tokens = Tokenize(expression); if (tokens.Count == 0) { throw new ArgumentException("Empty expression."); } List<IExpressionElement> output = new List<IExpressionElement>(); Stack<IExpressionElement> stack = new Stack<IExpressionElement>(); Stack<int> argumentsStack = new Stack<int>(); Stack<bool> insideFunctionStack = new Stack<bool>(); bool afterFunction = false; bool afterFnParenthesis = false; bool afterNonFnParenthesis = false; bool afterClosingParenthesis = false; bool afterSeparator = false; bool insideFunction = false; int arguments = 0; bool mustBeUnary = true; int tokenIndex = -1; foreach (IExpressionElement token in tokens) { tokenIndex++; IExpressionElement nextToken = tokenIndex + 1 < tokens.Count ? tokens[tokenIndex + 1] : null; // If the token is a number, then add it to the output queue. if (!afterFunction && !afterClosingParenthesis && (token is ILiteral || token is IConstant || token is IVariable)) { output.Add(token); mustBeUnary = false; afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = afterSeparator = false; } // If the token is a function token, then push it onto the stack. else if (!afterFunction && !afterClosingParenthesis && (token is IFunction || (token is MultipleElements && (token as MultipleElements).Elements[0] is IFunction))) { stack.Push(token); afterFunction = true; afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = afterSeparator = false; } // If the token is a function argument separator (e.g., a comma): // • Until the topmost element of the stack is a left parenthesis, pop the element onto the output queue. // If no left parentheses are encountered, either the separator was misplaced or parentheses were mismatched. else if (!afterFunction && insideFunction && token == Symbol.FunctionArgumentSeparator) { if (afterFnParenthesis) { throw new ArgumentException("Missing argument."); } while (stack.Peek() != Symbol.LeftParenthesis) { output.Add(stack.Pop()); } if (stack.Peek() != Symbol.LeftParenthesis) { throw new ArgumentException("Either the separator is misplaced or parentheses are mismatched."); } arguments++; afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = false; afterSeparator = true; } // If the token is an operator, o1, then: // • while there is an operator, o2, at the top of the stack, and either // o1 is associative or left-associative and its precedence is less than (lower precedence) or equal to that of o2, or // o1 is right-associative and its precedence is less than (lower precedence) that of o2, // pop o2 off the stack, onto the output queue; // • push o1 onto the stack. else if (!afterFunction && (token is IOperator || (token is MultipleElements && (token as MultipleElements).Elements[0] is IOperator))) { IOperator op = null; if ((mustBeUnary && token is IUnaryOperator) || (!mustBeUnary && token is IBinaryOperator)) { op = token as IOperator; } else if (token is MultipleElements) { foreach (IExpressionElement elem in (token as MultipleElements).Elements) { if ((mustBeUnary && elem is IUnaryOperator) || (!mustBeUnary && elem is IBinaryOperator)) { op = elem as IOperator; break; } } } if (op == null) { throw new ArgumentException(string.Format("{0} is not {1} operator.", token.Name, mustBeUnary ? "an unary" : "a binary")); } IOperator sop = stack.Count > 0 ? stack.Peek() as IOperator : null; while (sop != null && ( ( op is IBinaryOperator && (op as IBinaryOperator).Associativity != OperatorAssociativity.RightAssociative && op.Precedence <= sop.Precedence ) || ( ( (op is IBinaryOperator && (op as IBinaryOperator).Associativity == OperatorAssociativity.RightAssociative) || op is IUnaryOperator ) && op.Precedence < sop.Precedence ))) { output.Add(stack.Pop()); sop = stack.Count > 0 ? stack.Peek() as IOperator : null; } stack.Push(op); mustBeUnary = true; afterClosingParenthesis = afterFnParenthesis = afterNonFnParenthesis = afterSeparator = false; } // If the token is a left parenthesis, then push it onto the stack. else if (!afterClosingParenthesis && token == Symbol.LeftParenthesis) { stack.Push(token); argumentsStack.Push(arguments); insideFunctionStack.Push(insideFunction); // initial argument count arguments = nextToken == Symbol.RightParenthesis ? 0 : 1; if (afterFunction) { afterNonFnParenthesis = afterFunction = false; insideFunction = afterFnParenthesis = true; } else { afterNonFnParenthesis = true; insideFunction = afterFnParenthesis = false; } mustBeUnary = true; afterClosingParenthesis = afterSeparator = false; } // If the token is a right parenthesis: // • Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue. // • Pop the left parenthesis from the stack, but not onto the output queue. // • If the token at the top of the stack is a function token, pop it and onto the output queue. // • If the stack runs out without finding a left parenthesis, then there are mismatched parentheses. else if (!afterFunction && !afterSeparator && token == Symbol.RightParenthesis) { if (afterNonFnParenthesis) { throw new ArgumentException("Unexpected token: '()'."); } if (stack.Count == 0) { throw new ArgumentException("There are mismatched parentheses."); } while (stack.Peek() != Symbol.LeftParenthesis) { output.Add(stack.Pop()); if (stack.Count == 0) { throw new ArgumentException("There are mismatched parentheses."); } } IExpressionElement parenthesis = stack.Pop(); // parenthesis if (stack.Count > 0) { IExpressionElement elem = stack.Peek(); if (elem is IFunction || (elem is MultipleElements && (elem as MultipleElements).Elements[0] is IFunction)) { elem = stack.Pop(); IExpressionElement func = null; if (elem is IFunction) { if ((elem as IFunction).ParametersCount != arguments) { UndefinedFunctionFoundEventArgs args = new UndefinedFunctionFoundEventArgs(elem.Name, arguments); if (UndefinedFunctionFound != null) { UndefinedFunctionFound(this, args); } if (!args.Handled) { throw new ArgumentException(string.Format("There is no function ‘{0}’ with {1} parameters defined.", elem.Name, arguments)); } elem = new UnknownFunction(elem.Name); (elem as UnknownFunction).ParametersCount = arguments; } func = elem; } else { foreach (IFunction f in (elem as MultipleElements).Elements) { if (f.ParametersCount == arguments) { func = f; break; } } if (func == null) { UndefinedFunctionFoundEventArgs args = new UndefinedFunctionFoundEventArgs((elem as MultipleElements).Elements[0].Name, arguments); if (UndefinedFunctionFound != null) { UndefinedFunctionFound(this, args); } if (!args.Handled) { throw new ArgumentException(string.Format("There is no function ‘{0}’ with {1} parameters defined.", args.Name, arguments)); } func = new UnknownFunction(args.Name); (func as UnknownFunction).ParametersCount = arguments; } } output.Add(func); } } arguments = argumentsStack.Count > 0 ? argumentsStack.Pop() : 0; insideFunction = insideFunctionStack.Pop(); afterFnParenthesis = afterNonFnParenthesis = mustBeUnary = afterSeparator = false; afterClosingParenthesis = true; } else { IExpressionElement elem = token; if (token is MultipleElements) { elem = (token as MultipleElements).Elements[0]; } throw new ArgumentException(string.Format("Unexpected token: ‘{0}’", elem.Name)); } } // When there are no more tokens to read: // • While there are still operator tokens in the stack: // • If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses. // • Pop the operator onto the output queue. while (stack.Count > 0) { if (stack.Peek() == Symbol.LeftParenthesis || stack.Peek() == Symbol.RightParenthesis) { var r = stack.Peek(); throw new ArgumentException("There are mismatched parentheses."); } output.Add(stack.Pop()); } return new ParsedExpression(output, ZeroOnError); }
private IList<IExpressionElement> Tokenize(string expression) { Dictionary<string, List<IExpressionElement>> dict = new Dictionary<string, List<IExpressionElement>>(); foreach (IExpressionElement element in Constants.Cast<IExpressionElement>(). Concat(Variables.Cast<IExpressionElement>()). Concat(Operators.Cast<IExpressionElement>()). Concat(Functions.Cast<IExpressionElement>()). Concat(Statements.Cast<IExpressionElement>())) { string name = element.Name; if (!dict.ContainsKey(name)) { dict[name] = new List<IExpressionElement>(); } if (dict[name].Count > 0) { if (!(element is IOperator || element is IFunction)) { throw new ArgumentException("Only operators and functions can be overloaded."); } IExpressionElement elem = dict[name][0]; if ((elem is IOperator && element is IFunction) || (elem is IFunction && element is IOperator)) { throw new ArgumentException("Function and operator cannot have the same name."); } if (element is IOperator) { foreach (IExpressionElement eelem in dict[name]) { if ((element is IUnaryOperator && eelem is IUnaryOperator) || (element is IBinaryOperator && eelem is IBinaryOperator)) { throw new ArgumentException("There cannot be two identical operators."); } } } if (element is IFunction) { IFunction felement = element as IFunction; foreach (IExpressionElement eelem in dict[name]) { IFunction felem = eelem as IFunction; if (felem != null && felem.Name == felement.Name && felem.ParametersCount == felement.ParametersCount) { throw new ArgumentException("There cannot be two identical functions."); } } } } dict[name].Add(element); } List<IExpressionElement> tokens = new List<IExpressionElement>(); int i = 0; int l = expression.Length; while (i < l) { char c = expression[i]; if (char.IsWhiteSpace(c)) { /* eat */ } else if (NumberChars.IndexOf(c) >= 0) { int pos = i; StringBuilder token = new StringBuilder(); while (i < l && NumberChars.IndexOf(expression[i]) >= 0) { token.Append(expression[i]); i++; } i--; decimal value = 0; string strtoken = token.ToString(); if (decimal.TryParse(strtoken, NumberStyles.Float, CultureInfo.InvariantCulture, out value)) { tokens.Add(new Literal(value)); } else { throw new ArgumentException(string.Format(ParseExceptionText, "number", pos, strtoken)); } } else if (c == '(') { tokens.Add(Symbol.LeftParenthesis); } else if (c == ')') { tokens.Add(Symbol.RightParenthesis); } else if (c == ',') { tokens.Add(Symbol.FunctionArgumentSeparator); } else if (char.IsLetter(c) || AdditionalVariableChars.IndexOf(c) >= 0) { StringBuilder token = new StringBuilder(); while (i < l && (char.IsLetterOrDigit(expression[i]) || AdditionalVariableChars.IndexOf(expression[i]) >= 0)) { token.Append(expression[i]); i++; } i--; string strtoken = token.ToString(); if (dict.ContainsKey(strtoken)) { List<IExpressionElement> elements = dict[strtoken]; IExpressionElement element; if (elements.Count == 1) { element = elements[0]; } else { element = new MultipleElements(elements); } tokens.Add(element); } else { int nx = i + 1; while (nx < l && char.IsWhiteSpace(expression[nx])) { nx++; } IExpressionElement element = null; if (nx < l && expression[nx] == '(') { element = new UnknownFunction(strtoken); } else { UndefinedVariableFoundEventArgs args = new UndefinedVariableFoundEventArgs(strtoken); if (UndefinedVariableFound != null) { UndefinedVariableFound(this, args); } if (!args.Handled) { throw new ArgumentException(string.Format("There is no variable ‘{0}’ defined.", args.Name)); } element = args.HandledVariable ?? new UnknownVariable(strtoken); Variables.Add((IVariable)element); if (!dict.ContainsKey(element.Name)) { dict[element.Name] = new List<IExpressionElement>(); } if (dict[element.Name].Count > 0 && !(element is IOperator || element is IFunction)) { throw new ArgumentException("Only operators and functions can be overloaded."); } dict[element.Name].Add(element); } tokens.Add(element); } } else if (char.IsSymbol(c) || char.IsPunctuation(c)) { int j = i; StringBuilder token = new StringBuilder(); while (i < l && (char.IsSymbol(expression[i]) || char.IsPunctuation(expression[i]))) { token.Append(expression[i]); i++; } i--; string strop = token.ToString(); while (!dict.ContainsKey(strop)) { strop = strop.Substring(0, strop.Length - 1); i--; if (strop.Length == 0) { throw new ArgumentException(string.Format(ParseExceptionText, "operator", j, expression[j])); } } if (dict.ContainsKey(strop)) { List<IExpressionElement> ops = dict[strop]; IExpressionElement op; if (ops.Count == 1) { op = ops[0]; } else { op = new MultipleElements(ops); } tokens.Add(op); } else { throw new ArgumentException(string.Format(ParseExceptionText, "operator", j, strop)); } } else { throw new ArgumentException(string.Format(ParseExceptionText, "character", i, c)); } i++; } return tokens.AsReadOnly(); }
public ParsedExpression Parse(string expression) { IList <IExpressionElement> tokens = Tokenize(expression); if (tokens.Count == 0) { throw new ArgumentException("Empty expression."); } List <IExpressionElement> output = new List <IExpressionElement>(); Stack <IExpressionElement> stack = new Stack <IExpressionElement>(); Stack <int> argumentsStack = new Stack <int>(); Stack <bool> insideFunctionStack = new Stack <bool>(); bool afterFunction = false; bool afterFnParenthesis = false; bool afterNonFnParenthesis = false; bool afterClosingParenthesis = false; bool afterSeparator = false; bool insideFunction = false; int arguments = 0; bool mustBeUnary = true; int tokenIndex = -1; foreach (IExpressionElement token in tokens) { tokenIndex++; IExpressionElement nextToken = tokenIndex + 1 < tokens.Count ? tokens[tokenIndex + 1] : null; // If the token is a number, then add it to the output queue. if (!afterFunction && !afterClosingParenthesis && (token is ILiteral || token is IConstant || token is IVariable)) { output.Add(token); mustBeUnary = false; afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = afterSeparator = false; } // If the token is a function token, then push it onto the stack. else if (!afterFunction && !afterClosingParenthesis && (token is IFunction || (token is MultipleElements && (token as MultipleElements).Elements[0] is IFunction))) { stack.Push(token); afterFunction = true; afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = afterSeparator = false; } // If the token is a function argument separator (e.g., a comma): // • Until the topmost element of the stack is a left parenthesis, pop the element onto the output queue. // If no left parentheses are encountered, either the separator was misplaced or parentheses were mismatched. else if (!afterFunction && insideFunction && token == Symbol.FunctionArgumentSeparator) { if (afterFnParenthesis) { throw new ArgumentException("Missing argument."); } while (stack.Peek() != Symbol.LeftParenthesis) { output.Add(stack.Pop()); } if (stack.Peek() != Symbol.LeftParenthesis) { throw new ArgumentException("Either the separator is misplaced or parentheses are mismatched."); } arguments++; afterClosingParenthesis = afterNonFnParenthesis = afterFnParenthesis = false; afterSeparator = true; } // If the token is an operator, o1, then: // • while there is an operator, o2, at the top of the stack, and either // o1 is associative or left-associative and its precedence is less than (lower precedence) or equal to that of o2, or // o1 is right-associative and its precedence is less than (lower precedence) that of o2, // pop o2 off the stack, onto the output queue; // • push o1 onto the stack. else if (!afterFunction && (token is IOperator || (token is MultipleElements && (token as MultipleElements).Elements[0] is IOperator))) { IOperator op = null; if ((mustBeUnary && token is IUnaryOperator) || (!mustBeUnary && token is IBinaryOperator)) { op = token as IOperator; } else if (token is MultipleElements) { foreach (IExpressionElement elem in (token as MultipleElements).Elements) { if ((mustBeUnary && elem is IUnaryOperator) || (!mustBeUnary && elem is IBinaryOperator)) { op = elem as IOperator; break; } } } if (op == null) { throw new ArgumentException(string.Format("{0} is not {1} operator.", token.Name, mustBeUnary ? "an unary" : "a binary")); } IOperator sop = stack.Count > 0 ? stack.Peek() as IOperator : null; while (sop != null && ( ( op is IBinaryOperator && (op as IBinaryOperator).Associativity != OperatorAssociativity.RightAssociative && op.Precedence <= sop.Precedence ) || ( ( (op is IBinaryOperator && (op as IBinaryOperator).Associativity == OperatorAssociativity.RightAssociative) || op is IUnaryOperator ) && op.Precedence < sop.Precedence ))) { output.Add(stack.Pop()); sop = stack.Count > 0 ? stack.Peek() as IOperator : null; } stack.Push(op); mustBeUnary = true; afterClosingParenthesis = afterFnParenthesis = afterNonFnParenthesis = afterSeparator = false; } // If the token is a left parenthesis, then push it onto the stack. else if (!afterClosingParenthesis && token == Symbol.LeftParenthesis) { stack.Push(token); argumentsStack.Push(arguments); insideFunctionStack.Push(insideFunction); // initial argument count arguments = nextToken == Symbol.RightParenthesis ? 0 : 1; if (afterFunction) { afterNonFnParenthesis = afterFunction = false; insideFunction = afterFnParenthesis = true; } else { afterNonFnParenthesis = true; insideFunction = afterFnParenthesis = false; } mustBeUnary = true; afterClosingParenthesis = afterSeparator = false; } // If the token is a right parenthesis: // • Until the token at the top of the stack is a left parenthesis, pop operators off the stack onto the output queue. // • Pop the left parenthesis from the stack, but not onto the output queue. // • If the token at the top of the stack is a function token, pop it and onto the output queue. // • If the stack runs out without finding a left parenthesis, then there are mismatched parentheses. else if (!afterFunction && !afterSeparator && token == Symbol.RightParenthesis) { if (afterNonFnParenthesis) { throw new ArgumentException("Unexpected token: '()'."); } if (stack.Count == 0) { throw new ArgumentException("There are mismatched parentheses."); } while (stack.Peek() != Symbol.LeftParenthesis) { output.Add(stack.Pop()); if (stack.Count == 0) { throw new ArgumentException("There are mismatched parentheses."); } } IExpressionElement parenthesis = stack.Pop(); // parenthesis if (stack.Count > 0) { IExpressionElement elem = stack.Peek(); if (elem is IFunction || (elem is MultipleElements && (elem as MultipleElements).Elements[0] is IFunction)) { elem = stack.Pop(); IExpressionElement func = null; if (elem is IFunction) { if ((elem as IFunction).ParametersCount != arguments) { UndefinedFunctionFoundEventArgs args = new UndefinedFunctionFoundEventArgs(elem.Name, arguments); if (UndefinedFunctionFound != null) { UndefinedFunctionFound(this, args); } if (!args.Handled) { throw new ArgumentException(string.Format("There is no function ‘{0}’ with {1} parameters defined.", elem.Name, arguments)); } elem = new UnknownFunction(elem.Name); (elem as UnknownFunction).ParametersCount = arguments; } func = elem; } else { foreach (IFunction f in (elem as MultipleElements).Elements) { if (f.ParametersCount == arguments) { func = f; break; } } if (func == null) { UndefinedFunctionFoundEventArgs args = new UndefinedFunctionFoundEventArgs((elem as MultipleElements).Elements[0].Name, arguments); if (UndefinedFunctionFound != null) { UndefinedFunctionFound(this, args); } if (!args.Handled) { throw new ArgumentException(string.Format("There is no function ‘{0}’ with {1} parameters defined.", args.Name, arguments)); } func = new UnknownFunction(args.Name); (func as UnknownFunction).ParametersCount = arguments; } } output.Add(func); } } arguments = argumentsStack.Count > 0 ? argumentsStack.Pop() : 0; insideFunction = insideFunctionStack.Pop(); afterFnParenthesis = afterNonFnParenthesis = mustBeUnary = afterSeparator = false; afterClosingParenthesis = true; } else { IExpressionElement elem = token; if (token is MultipleElements) { elem = (token as MultipleElements).Elements[0]; } throw new ArgumentException(string.Format("Unexpected token: ‘{0}’", elem.Name)); } } // When there are no more tokens to read: // • While there are still operator tokens in the stack: // • If the operator token on the top of the stack is a parenthesis, then there are mismatched parentheses. // • Pop the operator onto the output queue. while (stack.Count > 0) { if (stack.Peek() == Symbol.LeftParenthesis || stack.Peek() == Symbol.RightParenthesis) { var r = stack.Peek(); throw new ArgumentException("There are mismatched parentheses."); } output.Add(stack.Pop()); } return(new ParsedExpression(output, ZeroOnError)); }