|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Introduction Most of the expression parsers I have seen are designed to parse and evaluate mathematical expressions. Using the codeIn order to parse an expression a user supplied tokenizer is required. The tokenizer provides two functions:
Once the expression has been tokenized an RPN list elements is created using the "Shunting Yard" algorithm. Below are some examples of expressions and the parsing results using the supplied "MathParser" as the
tokenizer.
x = a+b*c -a+(b+c)*2 a>b ? a-b : a+b a+min(x, y) 0: Identifier x 0: Identifier a 0: Identifier a 0: Identifier a 1: Identifier a 1: Operator - 1: Identifier b 1: Identifier x 2: Identifier b 2: Identifier b 2: Operator > 2: Identifier y 3: Identifier c 3: Identifier c 3: CondTrue ? 8 3: Function min 2 4: Operator * 4: Operator + 4: Identifier a 4: Operator + 5: Operator + 5: Constant 2 5: Identifier b 6: Assignment = 6: Operator * 6: Operator - 7: Operator + 7: CondFalse : 11 8: Identifier a 9: Identifier b 10: Operator + What is shown above is the result of applying the "ToString" method to the elements returned by the parser. The "Identifier", "Constant" and "Operator" items are self explanatory. The "CondTrue" and CondFalse" elements act as a "If, Else" construct. The number on the "CondTrue" element is the index of the first element to be evaluated if the condition is false. The number of the "CondFalse" element is the index of the first element following the conditional expression. The "CondFalse" element in effect acts as a "GoTo" the element specified. The number on the "Function" element is the number of arguments to be passed to the named function. The list of elements in an expression is generated by calling the parser. For example from the test application: . . .
ITokeniser parser = new MathParser();
List<RpnElement> tokens = Parser.ParseExpression(parser,rpnExpression.Text);
. . .
Evaluating the ExpressionThe act of evaluating an expression involves the following steps:
private RpnParser.RpnOperand RpnEval(List<RpnElement> tokens) {
Stack<RpnOperand> oprndStack = new Stack<RpnOperand>();
RpnOperand oprnd = null;
for (int nextElem = 0; nextElem < tokens.Count; ++nextElem) {
RpnElement token = tokens[nextElem];
switch (token.ElementType) {
// create an operand from the token and push onto the operand stack
case ElementType.Literal:
case ElementType.Constant:
oprnd = new RpnOperand((RpnToken)token);
break;
// Get the current value of the identifier and
// push it onto the operand stack
case ElementType.Identifier:
oprnd = EvalIdentifier((RpnToken)token);
break;
// Pop the left and right sides of the operator from operand stack
// Perform the operation and push the results onto the operand stack
case ElementType.Operator:
RpnOperator oper = (RpnOperator)token;
RpnOperand lhs, rhs;
lhs = rhs = null;
if (oper.IsMonadic && oprndStack.Count >= 1) {
lhs = new RpnOperand(0);
rhs = oprndStack.Pop();
} else
if (oprndStack.Count >= 2) {
rhs = oprndStack.Pop();
lhs = oprndStack.Pop();
}
if (lhs == null)
StackError("operator", token.StrValue);
else
oprnd = EvalOperator(oper, lhs, rhs);
break;
// Determine if True or False condition has be met
// Set element index to the next element to be processed
case ElementType.CondTrue:
case ElementType.CondFalse:
oper = (RpnOperator)token;
if (oprndStack.Count < 1)
StackError("operator", token.StrValue);
if (oper.OpType == OperatorType.CondTrue) {
lhs = oprndStack.Pop();
if (lhs.NumValue == 0)
nextElem = oper.CondGoto - 1; // for loop will add one
} else
nextElem = oper.CondGoto - 1; // for loop will add one
continue;
// Pop the function arguments from the operand stack
// and place into an array
// Evalualate the function and push the result onto operand stack
case ElementType.Function:
RpnFunction func = (RpnFunction)token;
if (oprndStack.Count >= func.ArgCount) {
RpnOperand[] args = new RpnOperand[func.ArgCount];
for (int i = func.ArgCount - 1; i >= 0; --i)
args[i] = oprndStack.Pop();
if (evalCBox.Checked)
oprnd = EvalFunction(func, args);
} else
StackError("function", token.StrValue);
break;
// Pop Identifier and value to be assigned fron the stack
// Push the resulting operand onto the operand stack
case RpnParser.ElementType.Assignment:
if (oprndStack.Count > 1) {
rhs = oprndStack.Pop(); // value to be assigned
lhs = oprndStack.Pop(); // identifier to be assigned to
oprnd = AssignOperator((RpnOperator)token, lhs, rhs);
} else
StackError("assignment", token.StrValue);
break;
}
oprndStack.Push(oprnd);
}
if (oprndStack.Count != 1)
StackError("result", "");
return oprndStack.Pop();
}
private void StackError(string token, string value) {
string text = String.Format("Insufficiant operands on stack for {0}: {1}",
token, value);
throw new ApplicationException(text);
}
SummaryHopefully this article demonstrates a generalized expression parser. Where the elements that make up an expression and the evaluation of that expression are controlled by the user. The only job of the parser is to present the elements in a standard, consistent manner. References
C# Expression Parser using RPN HistoryJuly 9, 2008 - Version 1 released. July 11, 2008 - Removed signing requirement from project.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||