The Interpreter Design Pattern is a behavioral design pattern used to define and evaluate the grammar of a language. It represents language rules as classes and interprets expressions by combining smaller expressions into a tree-like structure.
- Each grammar rule is represented by a class, using terminal expressions (basic elements) and non-terminal expressions (combined elements).
- It is useful for parsing and evaluating structured expressions such as mathematical formulas, command languages, and simple query languages.
Example: A calculator application can use the Interpreter Pattern to evaluate expressions like 5 + 3 - 2 by representing numbers and operators as expression objects and interpreting the result.
Components
These components work together to parse, interpret, and evaluate expressions based on a defined grammar.
- AbstractExpression: An abstract class or interface that declares the
interpret()method. It provides a common interface for all expression classes. - TerminalExpression: Represents the basic elements (terminal symbols) of the language. These classes implement the interpretation logic for simple expressions.
- NonterminalExpression: Represents composite expressions made up of multiple sub-expressions. It combines and coordinates the interpretation of child expressions to produce the final result.
- Context: Stores global information required during interpretation, such as variables, data structures, or state information.
- Client: Creates the Abstract Syntax Tree (AST) and starts the interpretation process by calling the
interpret()method on the root expression. - Interpreter: Manages the interpretation process by handling the context, traversing the expression tree, and evaluating expressions according to the grammar rules.
Real-Life Example
Consider yourself visiting a foreign nation where you are not fluent in the local tongue. To properly interact with the natives in such a situation, you might require the support of an interpreter.
Here's how the Interpreter pattern relates to this situation
- Language Grammar: Every spoken language has its own grammar and syntax, much like a programming language has its own rules.
- Interpreter: In this scenario, the individual who acts as an intermediary for you and the locals is the interpreter. Both the local language (the target language) and your language (the input language) are understood by them.
- Expressions: Your spoken sentences or phrases are like expressions in a programming language. They represent the information or instructions you want to convey to the locals.
- Context: The situational or cultural background in which the communication occurs could be the context in this analogy. The interpreter can better grasp the conversation's details and complexities due to this context.
- Translation Process: The interpreter listens to your spoken expressions, interprets their meaning, and then translates them into the local language. They may break down your sentences into smaller units (words or phrases), understand their meaning, and then rephrase them in the target language using the appropriate grammar and vocabulary.
Benefits of using the Interpreter Pattern
The Interpreter Pattern improves the organization, flexibility, and maintainability of language-processing applications.
- Modularity: Components such as terminal and non-terminal expressions can be easily added or modified to support new language constructs or operations.
- Separation of Concerns: The pattern separates the grammar interpretation from the client, allowing the client to focus on providing input expressions while leaving the interpretation logic to the interpreter components.
.webp)
Implementation Example
Problem Statement:
Suppose we have a simple language that supports basic arithmetic operations, such as addition (+), subtraction (-), multiplication (*), and division (/). We want to create a calculator program that can interpret and evaluate arithmetic expressions written in this language.
Communication flow of the Interpreter Design pattern using expression " 2+3*4 ":
The interpreter processes the expression by building and evaluating an expression tree step by step.
- Client Input: Client passes the expression (2 + 3 × 4) to the interpreter.
- Parsing & Tree Creation: Expression is converted into a tree with terminal (2, 3, 4) and non-terminal (+, ×) nodes.
- Evaluation & Result: Interpreter evaluates
3 × 4 = 12, then2 + 12 = 14, and returns 14 to the client.
Let's break down into the component wise code:
1. Client
The client provides input expressions and interacts with the interpreter.
#include <iostream>
class Expression {
public:
virtual int interpret() = 0;
};
class NumberExpression : public Expression {
private:
int number;
public:
NumberExpression(int number) : number(number) {}
int interpret() override {
return number;
}
};
class AdditionExpression : public Expression {
private:
Expression* left;
Expression* right;
public:
AdditionExpression(Expression* left, Expression* right) : left(left), right(right) {}
int interpret() override {
return left->interpret() + right->interpret();
}
};
class MultiplicationExpression : public Expression {
private:
Expression* left;
Expression* right;
public:
MultiplicationExpression(Expression* left, Expression* right) : left(left), right(right) {}
int interpret() override {
return left->interpret() * right->interpret();
}
};
int main() {
// Manually building AST for 2 + 3 * 4
Expression* expression = new AdditionExpression(
new NumberExpression(2),
new MultiplicationExpression(
new NumberExpression(3),
new NumberExpression(4)
)
);
int result = expression->interpret();
std::cout << "Result: " << result << std::endl;
return 0;
}
public class Client {
public static void main(String[] args) {
// Manually building AST for 2 + 3 * 4
Expression expression = new AdditionExpression(
new NumberExpression(2),
new MultiplicationExpression(
new NumberExpression(3),
new NumberExpression(4)
)
);
int result = expression.interpret();
System.out.println("Result: " + result);
}
}
class Expression:
def interpret(self):
pass
class NumberExpression(Expression):
def __init__(self, number):
self.number = number
def interpret(self):
return self.number
class AdditionExpression(Expression):
def __init__(self, left, right):
self.left = left
self.right = right
def interpret(self):
return self.left.interpret() + self.right.interpret()
class MultiplicationExpression(Expression):
def __init__(self, left, right):
self.left = left
self.right = right
def interpret(self):
return self.left.interpret() * self.right.interpret()
# Manually building AST for 2 + 3 * 4
expression = AdditionExpression(
NumberExpression(2),
MultiplicationExpression(
NumberExpression(3),
NumberExpression(4)
)
)
result = expression.interpret()
print("Result:", result)
class Expression {
interpret() {}
}
class NumberExpression extends Expression {
constructor(number) {
super();
this.number = number;
}
interpret() {
return this.number;
}
}
class AdditionExpression extends Expression {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() + this.right.interpret();
}
}
class MultiplicationExpression extends Expression {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() * this.right.interpret();
}
}
// Manually building AST for 2 + 3 * 4
const expression = new AdditionExpression(
new NumberExpression(2),
new MultiplicationExpression(
new NumberExpression(3),
new NumberExpression(4)
)
);
const result = expression.interpret();
console.log('Result:', result);
2. Abstract Expression
Defines the common interface for interpreting expressions.
#include <iostream>
class Expression {
public:
virtual int interpret() = 0;
};
public interface Expression {
int interpret();
}
from abc import ABC, abstractmethod
class Expression(ABC):
@abstractmethod
def interpret(self):
pass
class Expression {
interpret() {
throw new Error('Method interpret() must be implemented.');
}
}
3. Terminal Expression
Represents basic language elements.
#include <iostream>
class NumberExpression {
private:
int number;
public:
NumberExpression(int number) : number(number) {}
int interpret() {
return number;
}
};
public class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret() {
return number;
}
}
class NumberExpression:
def __init__(self, number):
self.number = number
def interpret(self):
return self.number
class NumberExpression {
constructor(number) {
this.number = number;
}
interpret() {
return this.number;
}
}
4. Non-Terminal Expression
Represents composite language constructs.
#include <iostream>
using namespace std;
class Expression {
public:
virtual int interpret() = 0;
};
class AdditionExpression : public Expression {
private:
Expression* left;
Expression* right;
public:
AdditionExpression(Expression* left, Expression* right) : left(left), right(right) {}
int interpret() override {
return left->interpret() + right->interpret();
}
};
class MultiplicationExpression : public Expression {
private:
Expression* left;
Expression* right;
public:
MultiplicationExpression(Expression* left, Expression* right) : left(left), right(right) {}
int interpret() override {
return left->interpret() * right->interpret();
}
};
public class AdditionExpression implements Expression {
private Expression left;
private Expression right;
public AdditionExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}
public class MultiplicationExpression implements Expression {
private Expression left;
private Expression right;
public MultiplicationExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() * right.interpret();
}
}
from abc import ABC, abstractmethod
class Expression(ABC):
@abstractmethod
def interpret(self):
pass
class AdditionExpression(Expression):
def __init__(self, left, right):
self.left = left
self.right = right
def interpret(self):
return self.left.interpret() + self.right.interpret()
class MultiplicationExpression(Expression):
def __init__(self, left, right):
self.left = left
self.right = right
def interpret(self):
return self.left.interpret() * self.right.interpret()
class Expression {
interpret() {
throw new Error('Method interpret() must be implemented.');
}
}
class AdditionExpression extends Expression {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() + this.right.interpret();
}
}
class MultiplicationExpression extends Expression {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() * this.right.interpret();
}
}
Complete code for the above example
Tomplete code for the above example is
#include <iostream>
class Expression {
public:
virtual int interpret() = 0;
};
class NumberExpression : public Expression {
private:
int number;
public:
NumberExpression(int number) : number(number) {}
int interpret() override {
return number;
}
};
class AdditionExpression : public Expression {
private:
Expression* left;
Expression* right;
public:
AdditionExpression(Expression* left, Expression* right) : left(left), right(right) {}
int interpret() override {
return left->interpret() + right->interpret();
}
};
class MultiplicationExpression : public Expression {
private:
Expression* left;
Expression* right;
public:
MultiplicationExpression(Expression* left, Expression* right) : left(left), right(right) {}
int interpret() override {
return left->interpret() * right->interpret();
}
};
int main() {
// Manually building AST for 2 + 3 * 4
Expression* expression = new AdditionExpression(
new NumberExpression(2),
new MultiplicationExpression(
new NumberExpression(3),
new NumberExpression(4)
)
);
int result = expression->interpret();
std::cout << "Result: " << result << std::endl;
return 0;
}
interface Expression {
int interpret();
}
class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret() {
return number;
}
}
class AdditionExpression implements Expression {
private Expression left;
private Expression right;
public AdditionExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}
class MultiplicationExpression implements Expression {
private Expression left;
private Expression right;
public MultiplicationExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() * right.interpret();
}
}
public class Client {
public static void main(String[] args) {
// Manually building AST for 2 + 3 * 4
Expression expression = new AdditionExpression(
new NumberExpression(2),
new MultiplicationExpression(
new NumberExpression(3),
new NumberExpression(4)
)
);
int result = expression.interpret();
System.out.println("Result: " + result);
}
}
from abc import ABC, abstractmethod
class Expression(ABC):
@abstractmethod
def interpret(self):
pass
class NumberExpression(Expression):
def __init__(self, number):
self.number = number
def interpret(self):
return self.number
class AdditionExpression(Expression):
def __init__(self, left, right):
self.left = left
self.right = right
def interpret(self):
return self.left.interpret() + self.right.interpret()
class MultiplicationExpression(Expression):
def __init__(self, left, right):
self.left = left
self.right = right
def interpret(self):
return self.left.interpret() * self.right.interpret()
if __name__ == "__main__":
# Manually building AST for 2 + 3 * 4
expression = AdditionExpression(
NumberExpression(2),
MultiplicationExpression(
NumberExpression(3),
NumberExpression(4)
)
)
result = expression.interpret()
print(f"Result: {result}")
class Expression {
interpret() {
throw new Error('Method not implemented.');
}
}
class NumberExpression extends Expression {
constructor(number) {
super();
this.number = number;
}
interpret() {
return this.number;
}
}
class AdditionExpression extends Expression {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() + this.right.interpret();
}
}
class MultiplicationExpression extends Expression {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() * this.right.interpret();
}
}
// Manually building AST for 2 + 3 * 4
const expression = new AdditionExpression(
new NumberExpression(2),
new MultiplicationExpression(
new NumberExpression(3),
new NumberExpression(4)
)
);
const result = expression.interpret();
console.log(`Result: ${result}`);
Output
Result: 14
When to use
Use this pattern when you need to define and evaluate structured language grammar in a flexible way.
- Domain-Specific Language (DSL): Use it when you are creating a small custom language with its own rules and commands. It helps you define and execute those rules clearly.
- Well-Defined Grammar: Use it when expressions follow fixed grammar rules that need to be parsed and evaluated step by step. It makes handling structured input easier.
- Frequent New Operations: Use it when you often need to add new commands or operations. You can extend the system without changing existing code.
- Avoid Complex Parsers: Use it when building a full compiler or parser feels too complex for your needs. It provides a simpler way to interpret expressions.
When not to use
Avoid this pattern when it adds unnecessary complexity or performance overhead.
- For Simple Computations: If built-in language features or libraries can handle the task easily, using the Interpreter Pattern may add unnecessary complexity.
- When Performance is Critical: Interpreting expressions can introduce overhead, making it less suitable for high-performance applications.
- When the Grammar is Too Complex: A large and complex grammar can result in too many expression classes, increasing code complexity.
- When Extensibility is Not Needed: If the application's requirements are fixed and unlikely to change, the Interpreter Pattern may be unnecessary.