Interpreter Pattern
Interpreter Pattern
GoF intent (p.243): "Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language."
When NOT to Use
This is the most important section for this pattern. The Interpreter pattern does not scale and is rarely appropriate in production code.
Do not use Interpreter when:
- The grammar has more than ~5-10 rule types. Each rule requires its own class; grammars grow quickly. Use a parser generator instead: ANTLR, PEG.js, or Lark.
- Performance is a concern. Every expression node creates an object. Tree traversal is slow at scale. Parser generators produce far more efficient parsers.
- The grammar changes frequently. Updating grammar rules means modifying or adding expression classes across the codebase. Parser generators centralise rule changes in a single grammar file.
- You are building a production parser. Production parsers for SQL, query languages, templating, or DSLs should use a parser generator, not hand-coded Interpreter trees.
If you catch yourself building more than a handful of expression types, stop and use a parser generator.
When to Use
- The grammar is simple (5 or fewer rule types) and stable — rules will not change.
- Efficiency is not a concern — throughput and latency are acceptable with tree traversal overhead.
- You are implementing a learning exercise or studying compiler front-end concepts.
- You need a DSL with a very constrained expression set: simple config rule evaluators, basic query filters, permission rule engines with a fixed operator set.
How It Works
The Interpreter pattern is a recursive application of Composite-Pattern — the expression tree is a Composite tree where interpret() is the operation.
Participants:
| Role | Responsibility |
|---|---|
AbstractExpression | Interface declaring interpret(context) |
TerminalExpression | Leaf — interprets a single token (no children) |
NonTerminalExpression | Composite — holds child expressions; calls interpret() recursively |
Context | Global state shared during interpretation (variable bindings, etc.) |
Client | Builds the expression tree and calls interpret() on the root |
Structure: NonTerminalExpression nodes are the composite nodes; TerminalExpression nodes are the leaves. The Client assembles the tree, then a single call to root.interpret(context) walks the entire tree.
Class Diagram
classDiagram
class AbstractExpression {
<<interface>>
+interpret(context : Context)*
}
class TerminalExpression {
-value
+interpret(context : Context)
}
class NonterminalExpression {
-left : AbstractExpression
-right : AbstractExpression
+interpret(context : Context)
}
class Context {
-variables : Map
+lookup(name) value
+assign(name, value)
}
class Client {
-ast : AbstractExpression
}
AbstractExpression <|.. TerminalExpression
AbstractExpression <|.. NonterminalExpression
NonterminalExpression o-- AbstractExpression : left, right (recursive)
Client --> AbstractExpression : builds and interprets AST
AbstractExpression --> Context : reads/writes during interpret
note for NonterminalExpression "Composite structure:\neach node interprets by\nrecursing on children"
TypeScript Example
Boolean expression evaluator (GoF p.243).
// AbstractExpression
interface BoolExpression {
interpret(): boolean;
}
// TerminalExpression — leaf node holding a literal value
class Literal implements BoolExpression {
constructor(private value: boolean) {}
interpret(): boolean {
return this.value;
}
}
// NonTerminalExpression — AND
class AndExpression implements BoolExpression {
constructor(private left: BoolExpression, private right: BoolExpression) {}
interpret(): boolean {
return this.left.interpret() && this.right.interpret();
}
}
// NonTerminalExpression — OR
class OrExpression implements BoolExpression {
constructor(private left: BoolExpression, private right: BoolExpression) {}
interpret(): boolean {
return this.left.interpret() || this.right.interpret();
}
}
// Client builds the tree for: "true AND false"
const expression: BoolExpression = new AndExpression(
new Literal(true),
new Literal(false)
);
console.log(expression.interpret()); // falseJava Example
Same boolean expression evaluator in Java (GoF p.243).
// AbstractExpression
interface BoolExpression {
boolean interpret();
}
// TerminalExpression
class Literal implements BoolExpression {
private final boolean value;
Literal(boolean value) { this.value = value; }
public boolean interpret() { return value; }
}
// NonTerminalExpression — AND
class AndExpression implements BoolExpression {
private final BoolExpression left, right;
AndExpression(BoolExpression left, BoolExpression right) {
this.left = left; this.right = right;
}
public boolean interpret() { return left.interpret() && right.interpret(); }
}
// NonTerminalExpression — OR
class OrExpression implements BoolExpression {
private final BoolExpression left, right;
OrExpression(BoolExpression left, BoolExpression right) {
this.left = left; this.right = right;
}
public boolean interpret() { return left.interpret() || right.interpret(); }
}
// Client — builds tree for "true AND false"
BoolExpression expression = new AndExpression(
new Literal(true),
new Literal(false)
);
System.out.println(expression.interpret()); // falseRelated Concepts
| Pattern | Relationship |
|---|---|
| Composite-Pattern | NonTerminalExpression IS a Composite — Interpreter trees are Composite trees with interpret() as the operation |
| Visitor-Pattern | Visitor can traverse and operate on Interpreter expression trees without modifying the expression classes |
| Iterator-Pattern | Iterator can traverse the expression tree nodes sequentially |
Sources
- Gamma, Helm, Johnson, Vlissides — Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994, p.243