Browse TypeScript Design Patterns & Application Architecture

Implementing Interpreter Pattern in TypeScript: A Comprehensive Guide

Explore how to implement the Interpreter Pattern in TypeScript, with examples on parsing and interpreting expressions.

6.3.1 Implementing Interpreter in TypeScript

The Interpreter Pattern is a powerful design pattern used to define a grammar for a language and provide an interpreter to process sentences in that language. In this section, we’ll delve into how to implement the Interpreter Pattern in TypeScript, focusing on parsing and interpreting expressions. We’ll explore a simple language for mathematical expressions, demonstrating the pattern’s core concepts and practical applications.

Introduction to the Interpreter Pattern

The Interpreter Pattern is part of the behavioral design patterns family. It is used to evaluate sentences in a language defined by a grammar. This pattern is particularly useful when you need to interpret expressions or commands in a language, such as mathematical expressions, boolean logic queries, or even simple scripting languages.

Key Concepts

  1. Abstract Syntax Tree (AST): A tree representation of the abstract syntactic structure of source code. Each node of the tree denotes a construct occurring in the source code.
  2. Expression Interfaces and Classes: Define the grammar of the language. These include AbstractExpression, TerminalExpression, and NonTerminalExpression.
  3. Interpret Method: Each expression class implements an interpret method to evaluate or process the expression.
  4. Client: Constructs the AST and invokes the interpretation process.

Building a Simple Mathematical Interpreter

Let’s build a simple interpreter for mathematical expressions using the Interpreter Pattern. We’ll define a grammar that supports addition and subtraction of integers.

Step 1: Define Expression Interfaces and Classes

First, we define the interfaces and classes that represent the expressions in our language.

 1// AbstractExpression interface
 2interface Expression {
 3  interpret(context: Map<string, number>): number;
 4}
 5
 6// TerminalExpression for numbers
 7class NumberExpression implements Expression {
 8  private number: number;
 9
10  constructor(number: number) {
11    this.number = number;
12  }
13
14  interpret(context: Map<string, number>): number {
15    return this.number;
16  }
17}
18
19// NonTerminalExpression for addition
20class AddExpression implements Expression {
21  private leftOperand: Expression;
22  private rightOperand: Expression;
23
24  constructor(left: Expression, right: Expression) {
25    this.leftOperand = left;
26    this.rightOperand = right;
27  }
28
29  interpret(context: Map<string, number>): number {
30    return this.leftOperand.interpret(context) + this.rightOperand.interpret(context);
31  }
32}
33
34// NonTerminalExpression for subtraction
35class SubtractExpression implements Expression {
36  private leftOperand: Expression;
37  private rightOperand: Expression;
38
39  constructor(left: Expression, right: Expression) {
40    this.leftOperand = left;
41    this.rightOperand = right;
42  }
43
44  interpret(context: Map<string, number>): number {
45    return this.leftOperand.interpret(context) - this.rightOperand.interpret(context);
46  }
47}

Step 2: Implement the Interpret Method

Each class implements the interpret method, which processes the expression. Terminal expressions return a constant value, while non-terminal expressions perform operations on their operands.

Step 3: Constructing the Abstract Syntax Tree (AST)

The AST represents the structure of the expression. Let’s build an AST for the expression 5 + 3 - 2.

 1// Client code to construct the AST
 2const context = new Map<string, number>();
 3
 4const five = new NumberExpression(5);
 5const three = new NumberExpression(3);
 6const two = new NumberExpression(2);
 7
 8const addition = new AddExpression(five, three);
 9const subtraction = new SubtractExpression(addition, two);
10
11const result = subtraction.interpret(context);
12console.log(`Result: ${result}`); // Output: Result: 6

Recursive Interpretation

The interpretation process often involves recursion, especially when dealing with nested expressions. In our example, the interpret method of AddExpression and SubtractExpression recursively calls the interpret method of their operands.

Handling Variables and Constants

In more complex languages, you might need to handle variables. This can be achieved by storing variable values in a context, such as a Map<string, number>, which is passed to the interpret method.

TypeScript Considerations

TypeScript’s type system provides type safety, which is beneficial when implementing the Interpreter Pattern. Ensure that your expression classes and methods are well-typed to avoid runtime errors.

Try It Yourself

Experiment with the code by adding new operations, such as multiplication or division. Consider how you might extend the grammar to support variables or more complex expressions.

Visualizing the Abstract Syntax Tree

To better understand the structure of the AST, let’s visualize it using a diagram.

    graph TD;
	    A["SubtractExpression"] --> B["AddExpression"]
	    B --> C["NumberExpression: 5"]
	    B --> D["NumberExpression: 3"]
	    A --> E["NumberExpression: 2"]

Figure 1: Abstract Syntax Tree for the expression 5 + 3 - 2.

Further Reading and Resources

Knowledge Check

  • How does the Interpreter Pattern differ from other behavioral patterns?
  • What role does recursion play in the interpretation process?
  • How can you extend the interpreter to handle more complex expressions?

Conclusion

The Interpreter Pattern provides a structured approach to evaluating expressions in a language. By defining a clear grammar and implementing an interpreter, you can build powerful tools for processing and evaluating expressions. Remember, this is just the beginning. As you progress, you’ll be able to tackle more complex languages and interpretations. Keep experimenting, stay curious, and enjoy the journey!

Quiz Time!

Loading quiz…
Revised on Thursday, April 23, 2026