The Interpreter pattern is one of the behavioral design patterns that provides a way to evaluate language grammar or expressions. It defines a grammatical representation for a language and an interpreter to interpret sentences in the language. This pattern is particularly useful when you need to parse and execute simple languages or domain-specific languages (DSLs).
The Interpreter pattern involves creating an abstract syntax tree (AST) that represents the grammar of the language. Each node in the AST corresponds to a rule in the grammar, and each leaf node represents a terminal symbol. The interpreter then traverses this tree to evaluate the expression.
Let's walk through a simple example where we create an interpreter for a basic arithmetic expression language that supports addition and subtraction.
First, we define an interface for our expressions:
1interface Expression {2interpret(context: Context): number;3}
Next, we implement terminal expressions for numbers:
1class NumberExpression implements Expression {2private value: number;34constructor(value: string) {5this.value = parseInt(value);6}78interpret(context: Context): number {9return this.value;10}11}
Then, we implement nonterminal expressions for addition and subtraction:
1class AddExpression implements Expression {2private left: Expression;3private right: Expression;45constructor(left: Expression, right: Expression) {6this.left = left;7this.right = right;8}910interpret(context: Context): number {11return this.left.interpret(context) + this.right.interpret(context);12}13}1415class SubtractExpression implements Expression {16private left: Expression;17private right: Expression;1819constructor(left: Expression, right: Expression) {20this.left = left;21this.right = right;22}2324interpret(context: Context): number {25return this.left.interpret(context) - this.right.interpret(context);26}27}
The context can be a simple object that holds any global information needed during interpretation:
1class Context {2// This could hold variables or other state3}
Finally, we build an expression tree and interpret it:
1function parseExpression(input: string): Expression {2const tokens = input.split(' ');3let left: Expression;4let right: Expression;56if (tokens.length === 3) {7left = new NumberExpression(tokens[0]);8right = new NumberExpression(tokens[2]);910switch (tokens[1]) {11case '+':12return new AddExpression(left, right);13case '-':14return new SubtractExpression(left, right);15default:16throw new Error('Unsupported operation');17}18}1920throw new Error('Invalid input');21}2223const expression = parseExpression("3 + 5");24const context = new Context();25console.log(expression.interpret(context)); // Output: 8
In this example, we define a simple language that supports addition and subtraction. We create an abstract syntax tree where each node is an instance of Expression. The NumberExpression class represents terminal symbols (numbers), while AddExpression and SubtractExpression represent nonterminal symbols (operations). The interpret method is recursively called on the nodes to evaluate the expression.
In the next section, we will explore the Iterator Pattern, which provides a way to traverse a collection of objects without exposing its underlying representation. This pattern is useful for accessing elements in a sequence without knowing the structure of the sequence.
Stay tuned!