Interpreter
The interpreter pattern
The interpreter pattern is a behavioral pattern and used to interpret a simple language structure by having a class for every word of the language. Each word is either a terminal or nonterminal symbol, the terminal symbol can be a variable (X, Y...) and the nonterminal symbol an operator (multiply/add... or/and... or some other operator like Dirac <bra><kets>).The interpreter pattern is quite complex and requires an abstract syntax tree which needs to be 'formed' by a parser which isn't included in the pattern.
The class structure of the pattern diagramatically is below:
The diagram which is similar to the one in the book Design Patterns doesn't really show the complexity of the pattern but I've written an example in C# that should help understand the pattern.
Firstly, I'm planning on evaluating boolean expressions so, I defined the abstract class:
public abstract class BooleanExpression { // AbstractExpression
public abstract bool Evaluate(Context context);
}
public abstract bool Evaluate(Context context);
}
I then defined the class for the nonterminal symbols, inheriting from BooleanExpression:
public class VariableExpression : BooleanExpression { //Nonterminal
private string variableName;
public VariableExpression(string variableName) {
this.variableName=variableName;
}
public string getName (){
return variableName;
}
public override bool Evaluate(Context context) {
return context.Lookup(variableName);
}
}
private string variableName;
public VariableExpression(string variableName) {
this.variableName=variableName;
}
public string getName (){
return variableName;
}
public override bool Evaluate(Context context) {
return context.Lookup(variableName);
}
}
Next up are two terminal classes for a very simplified Boolean language, that just contain and and or (with and taking precedence), both classes inherit from BooleanExpression:
public class AndExpression : BooleanExpression {
private BooleanExpression operand1;
private BooleanExpression operand2;
public AndExpression(BooleanExpression operand1, BooleanExpression operand2) {
this.operand1=operand1;
this.operand2=operand2;
}
public override bool Evaluate(Context context) {
return operand1.Evaluate(context) && operand2.Evaluate(context);
}
}
private BooleanExpression operand1;
private BooleanExpression operand2;
public AndExpression(BooleanExpression operand1, BooleanExpression operand2) {
this.operand1=operand1;
this.operand2=operand2;
}
public override bool Evaluate(Context context) {
return operand1.Evaluate(context) && operand2.Evaluate(context);
}
}
public class OrExpression : BooleanExpression {
private BooleanExpression operand1;
private BooleanExpression operand2;
public OrExpression(BooleanExpression operand1, BooleanExpression operand2) {
this.operand1=operand1;
this.operand2=operand2;
}
public override bool Evaluate(Context context) {
return operand1.Evaluate(context) || operand2.Evaluate(context);
}
}
There's a great deal of code duplication but I'm going to let it pass as the classes are very simple.private BooleanExpression operand1;
private BooleanExpression operand2;
public OrExpression(BooleanExpression operand1, BooleanExpression operand2) {
this.operand1=operand1;
this.operand2=operand2;
}
public override bool Evaluate(Context context) {
return operand1.Evaluate(context) || operand2.Evaluate(context);
}
}
You'll notice that the abstract class BooleanExpression's method Evaluate takes an argument Context this has a method Lookup which returns the Boolean value of a variable used in the current context, an example of an implementation would be:
public class Context {
Dictionary variableExpressions;
public Context() {
variableExpressions=new Dictionary();
}
public bool Lookup(string variableName) {
return variableExpressions[variableName];
}
public void Assign(VariableExpression variableExpression, bool b) {
if (variableExpressions.ContainsKey(variableExpression.getName())) {
variableExpressions[variableExpression.getName()] = b;
} else {
variableExpressions.Add(variableExpression.getName(), b);
}
}
}
Dictionary
public Context() {
variableExpressions=new Dictionary
}
public bool Lookup(string variableName) {
return variableExpressions[variableName];
}
public void Assign(VariableExpression variableExpression, bool b) {
if (variableExpressions.ContainsKey(variableExpression.getName())) {
variableExpressions[variableExpression.getName()] = b;
} else {
variableExpressions.Add(variableExpression.getName(), b);
}
}
}
And example of a test console application is:
static void Main(string[] args) {
BooleanExpression booleanExpression;
Context context = new Context();
VariableExpression x = new VariableExpression("X");
VariableExpression y = new VariableExpression("Y");
VariableExpression z = new VariableExpression("Z");
Context.Assign(x, true);
context.Assign(y, false);
context.Assign(z, true);
Console.WriteLine($"Where X={x.Evaluate(context)}, Y={y.Evaluate(context)} and Z={z.Evaluate(context)}.");
// make the abstract syntax tree (manually)
booleanExpression = new AndExpression(x, new OrExpression(y, z));
Console.WriteLine($"The outcome of X and (Y or Z) is {booleanExpression.Evaluate(context)}");
booleanExpression = new AndExpression(x, new AndExpression(y, new OrExpression(x, z)));
Console.WriteLine($"The outcome of X and (Y and (X OR Z)) is {booleanExpression.Evaluate(context)}");
Console.ReadKey();
}
BooleanExpression booleanExpression;
Context context = new Context();
VariableExpression x = new VariableExpression("X");
VariableExpression y = new VariableExpression("Y");
VariableExpression z = new VariableExpression("Z");
Context.Assign(x, true);
context.Assign(y, false);
context.Assign(z, true);
Console.WriteLine($"Where X={x.Evaluate(context)}, Y={y.Evaluate(context)} and Z={z.Evaluate(context)}.");
// make the abstract syntax tree (manually)
booleanExpression = new AndExpression(x, new OrExpression(y, z));
Console.WriteLine($"The outcome of X and (Y or Z) is {booleanExpression.Evaluate(context)}");
booleanExpression = new AndExpression(x, new AndExpression(y, new OrExpression(x, z)));
Console.WriteLine($"The outcome of X and (Y and (X OR Z)) is {booleanExpression.Evaluate(context)}");
Console.ReadKey();
}
The output of the console test:
Where X=True, Y=False and Z=True.
The outcome of X and (Y or Z) is True
The outcome of X and (Y and (X OR Z)) is False
The outcome of X and (Y or Z) is True
The outcome of X and (Y and (X OR Z)) is False
Summary
The interpreter pattern is quite a complex pattern and for full implementation needs a parser that creates a syntax tree which consists
of the tree of terminal and nonterminal symbols. These are evaluated by filling in the context and then calling Evaluate
on the 'topmost' node (nonterminal symbol).