Interpreter Design Pattern in Java

This article explains Interpreter design pattern in java with UML class diagram. It then takes an example scenario in java and explains it with class diagram and code.
Introduction Interpreter Design Pattern is a behavioral design pattern among the Gang Of Four(GOF) Design PatternsClick to read: Gang Of Four(GOF) Design Patterns. Being a behavioral design pattern, the Interpreter pattern deals with how objects work together - here, to evaluate sentences of a small language. What is Interpreter Design Pattern The Interpreter design pattern defines a representation for the grammar of a simple language, along with an interpreter which uses that representation to interpret sentences in the language. Each rule of the grammar becomes a class. A sentence - an expression such as (x + 3) * y - is represented as a tree of objects of these classes, the abstract syntax tree, and interpreting the sentence means calling interpret() on the root of the tree, which calls it recursively on its children. The structure is a CompositeClick to read: Composite: simple expressions are leaves, and compound expressions contain other expressions. Scenarios in which Interpreter Design Pattern can be used
  1. A simple language must be interpreted: When the application needs to evaluate expressions or rules written in a small language - formulas in a spreadsheet, filter expressions, business rules, simple query or configuration languages.
  2. The grammar is small and stable: When the grammar has few rules, so that one class per rule stays manageable. For large grammars, parser generators such as ANTLR are a better fit.
  3. Efficiency is not critical: When interpreting a tree of objects is fast enough. Performance-critical languages are usually compiled to another form instead.
Class Diagram for Interpreter Design Pattern
Interpreter Design Pattern Class Diagram
Explanation of Interpreter Design Pattern's Class Diagram
  • AbstractExpression declares the interpret(Context) operation shared by every node of the syntax tree.
  • TerminalExpression implements interpret() for the grammar's terminal symbols - the leaves of the tree, such as numbers and variable names.
  • NonterminalExpression represents a grammar rule made of other expressions, such as addition. It holds its operands and interprets itself by interpreting them and combining the results.
  • Context holds information global to the interpretation, such as the values of variables.
  • Client builds the syntax tree for a sentence - often with a parser - and calls interpret() on its root.
Interpreter Design Pattern in Java - Class Diagram
Interpreter Design Pattern in Java Class Diagram
The example interprets arithmetic expressions with variables. To keep the parser short, expressions are written in Reverse Polish Notation (RPN), where operators follow their operands: (x + 3) * y is written x 3 + y *.
Code for the classes shown in Java Example's Class Diagram
Expression.java - the abstract expression
//Expression.java
import java.util.Map;

public interface Expression {
 int interpret(Map<String, Integer> context);     // the context: variable values
}
Number.java and Variable.java - the terminal expressions
//Number.java
import java.util.Map;

class Number implements Expression {
 private final int value;
 Number(int value) { this.value = value; }
 public int interpret(Map<String, Integer> context) { return value; }
 public String toString() { return String.valueOf(value); }
}
//Variable.java
class Variable implements Expression {
 private final String name;
 Variable(String name) { this.name = name; }
 public int interpret(Map<String, Integer> context) {
  Integer value = context.get(name);
  if (value == null) throw new IllegalArgumentException("no value for variable " + name);
  return value;
 }
 public String toString() { return name; }
}
Add.java and Multiply.java - the nonterminal expressions
//Add.java
import java.util.Map;

class Add implements Expression {
 private final Expression left, right;
 Add(Expression left, Expression right) { this.left = left; this.right = right; }
 public int interpret(Map<String, Integer> context) {
  return left.interpret(context) + right.interpret(context);
 }
 public String toString() { return "(" + left + " + " + right + ")"; }
}
//Multiply.java
class Multiply implements Expression {
 private final Expression left, right;
 Multiply(Expression left, Expression right) { this.left = left; this.right = right; }
 public int interpret(Map<String, Integer> context) {
  return left.interpret(context) * right.interpret(context);
 }
 public String toString() { return left + " * " + right; }
}
ExpressionParser.java (with the main() method)
//ExpressionParser.java
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;

public class ExpressionParser {
 // Builds the syntax tree from an expression in Reverse Polish Notation
 static Expression parse(String rpn) {
  Deque<Expression> stack = new ArrayDeque<>();
  for (String token : rpn.trim().split("\\s+")) {
   switch (token) {
    case "+" -> { Expression right = stack.pop(); stack.push(new Add(stack.pop(), right)); }
    case "*" -> { Expression right = stack.pop(); stack.push(new Multiply(stack.pop(), right)); }
    default -> stack.push(token.matches("-?\\d+") ? new Number(Integer.parseInt(token)) : new Variable(token));
   }
  }
  if (stack.size() != 1) throw new IllegalArgumentException("invalid expression: " + rpn);
  return stack.pop();
 }

 public static void main(String[] args) {
  Expression price = parse("quantity unitPrice * shipping +");
  System.out.println("Syntax tree: " + price);
  System.out.println("2 x 450 + 99 = " + price.interpret(Map.of("quantity", 2, "unitPrice", 450, "shipping", 99)));
  System.out.println("5 x 120 + 0  = " + price.interpret(Map.of("quantity", 5, "unitPrice", 120, "shipping", 0)));

  Expression formula = parse("x 3 + y *");
  System.out.println("Syntax tree: " + formula + " with x=4, y=10 -> " + formula.interpret(Map.of("x", 4, "y", 10)));
  try {
   formula.interpret(Map.of("x", 4));
  } catch (IllegalArgumentException e) {
   System.out.println("Error: " + e.getMessage());
  }
 }
}
 OUTPUT on running ExpressionParser.java
Syntax tree: (quantity * unitPrice + shipping)
2 x 450 + 99 = 999
5 x 120 + 0  = 600
Syntax tree: (x + 3) * y with x=4, y=10 -> 70
Error: no value for variable y
Explanation of Java Example's Class Diagram & Code
  • Expression is the abstract expression. The context is a Map from variable names to their values.
  • Number and Variable are terminal expressions: a number interprets to itself, and a variable to its value in the context, or an error if it has none.
  • Add and Multiply are nonterminal expressions. Each interprets its two operands - which may themselves be any expressions - and combines the results.
  • ExpressionParser is the client. Its parse() method turns an RPN string into a syntax tree using a stack: numbers and variables are pushed, and an operator pops its two operands and pushes the combined expression.
  • The tree is built once and can be interpreted many times with different contexts - here, the same pricing formula for two different orders. That separation of parsing from evaluation is what makes the pattern useful for rules and formulas which are defined once and evaluated often.
  • Adding a new operation, such as subtraction, means adding one class and one parser case. If many different operations over the tree were needed - evaluation, pretty-printing, simplification - the Visitor patternClick to read: Visitor pattern, or Java 21's record patterns over a sealed interface, would keep them out of the expression classes.
Note - In the JDK, java.util.regex.Pattern is an interpreter: Pattern.compile() parses a regular expression into a tree of node objects, which Matcher then interprets against input strings.
Summary In the above tutorial we understood what is Interpreter design pattern and the main scenarios in which it is applicable. We then looked at the UML class diagram for Interpreter Design Pattern & its explanation, a Java use case which parses arithmetic expressions with variables into a syntax tree and interprets them, with its class diagram, code and output, followed by an explanation of both the class diagram & code. This concludes the tutorial on Interpreter design pattern.