|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThe Interpreter pattern is used to model a grammar using a set of classes that represent possible expressions within the grammar. For instance, for the algebraic expression 4 + 4, an Without getting too tied down by words, you can see how the possible variety of algebraic expressions can be represented using classes. Additionally, expressions themselves can be used as operands to other expressions based on a defined order of operations. When it comes to representing variable expressions, things get a bit trickier since the evaluation of a variable expression changes based on the current value of the variable. The Interpreter pattern provides a clean solution to this problem by defining a context class that provides the current value of variables that may appear within the expression being evaluated. In this article, I will show how the Interpreter pattern can be used to develop a full-featured algebraic expression parser. It is a tractable, non-trivial problem, which makes it a great candidate for demonstrating the Interpreter pattern. Furthermore, instances naturally arise for the utilization of other design patterns such as factory, template method, and composite, which makes it an even better candidate for demonstrating the disciplined use of design patterns in general. The standard warning regarding design patterns should not be forgotten. They should only be applied where appropriate. It is almost always the case that good solutions to non-trivial problems will not follow an ideal object-oriented design. Trade-offs are often necessary. More often than not, simpler designs are better than complex designs and designs that included a tangle of design patterns are usually the most complex. General StructureThe structure of the project follows the general structure of a parser in compiler terms. At a high level, a parser transforms a character sequence in a well-defined grammar into an intermediate data structure that can be evaluated at a later point while varying its input. At a lower level, the first step in the parsing process is to transform the character sequence into a token sequence. This step is called lexical analysis. The next step is to transform the token sequence into the intermediate data structure that can be evaluated. The Once a valid token sequence is obtained, the Expression Base ClassAll The One obstacle is the way the I choose to use the Template Method pattern. In this case, the template method is The following is the implementation of the public abstract class Expression
{
protected readonly double ZERO_THRESHOLD = 1.0 * Math.Pow(10.0, -14.0);
protected bool _isBound;
public Expression()
{
_isBound = false;
}
public bool IsBound()
{
return _isBound;
}
public void Bind(params object[] arguments)
{
InnerBind(arguments);
_isBound = true;
}
public double Evaluate(ExpressionContext context)
{
double result = InnerEvaluate(context);
if (Math.Abs(result) <= ZERO_THRESHOLD)
return 0;
else
return result;
}
protected abstract void InnerBind(params object[] arguments);
protected abstract double InnerEvaluate(ExpressionContext context);
}
Here is where things are not quite perfect. A professional algebraic expression parser would have to use a better math library that returns known zeros from trigonometric functions. That could be done in the individual expression classes too, but that makes the job unnecessarily difficult. Additionally, the design of the Expression Derived ClassesAmong the For instance, the following is the definition of the public sealed class AddExpression : BinaryExpression
{
protected override double InnerEvaluate(ExpressionContext context)
{
return _operand1.Evaluate(context) + _operand2.Evaluate(context);
}
}
As another example, the following is the definition of the public sealed class CosExpression : FunctionExpression
{
protected override double InnerEvaluate(ExpressionContext context)
{
return Math.Cos(_operand.Evaluate(context));
}
}
VariableExpression and ExpressionContextFor the most part, the The public double Lookup(Variable variable)
{
if (_bindings.ContainsKey(variable))
return _bindings[variable];
else
return double.NaN;
}
When the public sealed class VariableExpression : Expression
{
private Variable _variable;
protected override void InnerBind(params object[] arguments)
{
_variable = (Variable)arguments[0];
}
protected override double InnerEvaluate(ExpressionContext context)
{
return context.Lookup(_variable);
}
}
TokenizationHaving introduced the Tokens are defined as sequences of characters that match a given regular expression pattern. The I wanted this step to be completely deterministic, regardless of caveats that may occur. For instance, distinguishing the negation operation on a number from a subtract operation. With this in mind, the The following is the implementation of the public List<string> Tokenize(string text)
{
List<string> tokens = new List<string>();
for (int i = 0; i < text.Length; i++)
{
bool matched = false;
for (int j = text.Length - i; j > 0 && !matched; j--)
{
foreach(KeyValuePair<string,Regex> pair in _patterns)
{
if (pair.Value.IsMatch(text.Substring(i, j)))
{
matched = true;
tokens.Add(text.Substring(i, j));
i += j - 1;
break;
}
}
}
if (!matched)
{
throw new AXTokenException(i,
"Unrecognized character sequence starting at index " +
i.ToString() + ".");
}
}
return tokens;
}
The ExpressionFactoryOnce a token sequence is obtained from the Just as the By iterating over the list of tokens returned from the public Expression Create(string token)
{
if (!string.IsNullOrEmpty(token))
{
foreach (KeyValuePair<string, KeyValuePair<Regex,Type>> pair in _associations)
{
if (pair.Value.Key.IsMatch(token))
{
ConstructorInfo info = pair.Value.Value.GetConstructor(Type.EmptyTypes);
return (Expression)info.Invoke(null);
}
}
}
return null;
}
ParsingYou may have noticed that the
Besides the The first thing the Building of the expression tree takes place in a single while loop that iterates until there is only one For a high-level view of the parsing process, following is the public Expression Parse(string text)
{
string copy = text.Replace(" ", string.Empty).Trim();
List<string> tokens = _tokenizer.Tokenize(copy);
List<Expression> expressions = TokensToExpressions(tokens);
AXValidator.Validate(expressions); //throws
RemoveExcessParens(expressions);
while (expressions.Count > 1)
{
int i = DetermineHighestPrecedence(expressions);
CollapseExpression(expressions, i);
RemoveExcessParens(expressions);
}
return expressions[0];
}
Earlier I mentioned the private void CollapseExpression(List<Expression> expressions, int i)
{
Expression current = expressions[i];
Expression previous = new NullExpression();
Expression next = new NullExpression();
if (i - 1 >= 0)
previous = expressions[i - 1];
if (i + 1 < expressions.Count)
next = expressions[i + 1];
if (current is SubtractExpression && !previous.IsBound() && !(
previous is RightParenExpression))
{
SubtractExpression expression = (SubtractExpression)current;
NumericExpression zero = new NumericExpression();
zero.Bind(0.0);
expression.Bind(zero, next);
expressions.RemoveAt(i + 1);
}
else if (current is FunctionExpression)
{
FunctionExpression expression = (FunctionExpression)current;
expression.Bind(next);
expressions.RemoveAt(i + 1);
}
else if (current is BinaryExpression)
{
BinaryExpression expression = (BinaryExpression)current;
expression.Bind(previous, next);
expressions.RemoveAt(i + 1);
expressions.RemoveAt(i - 1);
}
}
The same considerations for the Another important point to recognize is how the How to UseAt the top of the article I have included a link to the project files. The project compiles into a class library with the name AXLibrary.dll. I have not included a demo project, since it is my hope to use the AXLibrary library in a WPF 3-D graphing calculator article for The Code Project. Additionally, using the library is very simple. Following is an example of how to use the library. AXParser parser = new AXParser();
Expression expression = parser.Parse("cos(PI/4)*(5+x)");
ExpressionContext context = new ExpressionContext();
context.Bind(Variable.x, 4);
MessageBox.Show(expression.Evaluate(context).ToString());
The project is compiled in Visual Studio 2008 against the .NET framework version 3.5. However, the project can easily be converted to any other .NET framework version, since it uses vanilla features of the C# language. Unfortunately, you will have to create a container project first and copy the files over manually, as there is no tool for converting to an older version that I know of. History
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||