Interpreter Pattern – Building Expression Evaluators and DSLs

Learn how the Interpreter Pattern helps evaluate expressions and define grammar for simple languages. Includes real-world examples, UML, and C#/Python code.

Interpreter Pattern

The Interpreter Pattern provides a way to evaluate language grammar or expressions. It is used to define a representation of grammar along with an interpreter that uses the representation to interpret sentences in the language.

🧩 What is the Interpreter Pattern?

The Interpreter Pattern is a behavioral design pattern that specifies how to evaluate sentences in a language. It defines a grammar for a simple language and uses an interpreter to process statements of that language.

🎯 Real-World Analogy

Think of a calculator parsing a mathematical expression like "2 + 3 - 1". Each number and operation has meaning, and the system understands how to compute it by interpreting each token based on grammar rules.

📊 UML Diagram

classDiagram
    class AbstractExpression {
      <<interface>>
      + interpret(context)
    }
    class TerminalExpression {
      + interpret(context)
    }
    class NonTerminalExpression {
      + interpret(context)
    }
    class Context {
      + input
      + output
    }

    AbstractExpression <|-- TerminalExpression
    AbstractExpression <|-- NonTerminalExpression
  

🏗️ Structure and Participants

  • Context: Holds the information that is global to the interpreter. This typically includes the input string or data to be parsed and any output or state needed during interpretation.
  • AbstractExpression: Defines the common interface (usually an interpret(context) method) for all concrete expression classes. Every grammar rule or symbol will implement this interface.
  • TerminalExpression: Implements the interpretation for grammar elements that do not contain other expressions (i.e., leaf nodes in the grammar tree). For example, numbers or variables in a mathematical expression.
  • NonTerminalExpression: Implements grammar rules that involve other expressions (i.e., composite nodes). These classes combine terminal and/or non-terminal expressions to represent more complex grammar constructs, such as addition or subtraction in an arithmetic language.

💡 Use Cases

  • Simple language interpreters (arithmetic, boolean)
  • Rule engines or DSLs (Domain-Specific Languages)
  • Command parsing in chatbots or command-line interfaces
  • Validation engines for expressions or conditions

👨‍💻 Example Code


// Abstract Expression
interface IExpression {
    int Interpret();
}

// Terminal
class Number : IExpression {
    private int _value;
    public Number(int value) => _value = value;
    public int Interpret() => _value;
}

// Non-Terminal
class Add : IExpression {
    private IExpression _left, _right;
    public Add(IExpression left, IExpression right) {
        _left = left;
        _right = right;
    }
    public int Interpret() => _left.Interpret() + _right.Interpret();
}

// Usage
var expression = new Add(new Number(5), new Number(3));
Console.WriteLine(expression.Interpret()); // Output: 8
      

from abc import ABC, abstractmethod

class Expression(ABC):
    @abstractmethod
    def interpret(self):
        pass

class Number(Expression):
    def __init__(self, value):
        self.value = value
    def interpret(self):
        return self.value

class Add(Expression):
    def __init__(self, left, right):
        self.left = left
        self.right = right
    def interpret(self):
        return self.left.interpret() + self.right.interpret()

# Usage
expr = Add(Number(5), Number(3))
print(expr.interpret())  # Output: 8
      

⚖️ Pros and Cons

Pros Cons
Good for simple grammar parsing and expression evaluation Can become complex and hard to maintain for large or evolving grammars
Extensible—easy to add new grammar rules or expressions by introducing new classes Poor performance for complex expressions due to recursive interpretation and object creation
Improves readability and separation of concerns by encapsulating grammar logic Class explosion: each grammar rule or symbol often requires a new class, leading to many small classes
Useful for implementing DSLs, rule engines, and interpreters for custom languages Not suitable for parsing complex languages—better tools exist (e.g., parser generators, ANTLR)
Promotes reusability and testability of grammar components Can be difficult to debug and optimize as the grammar grows

📌 When to Use

  • You have a simple grammar to interpret.
  • You want to implement a small language, query, or expression evaluator.
  • You want a highly extensible solution where new rules are frequently added.

🧠 Quiz Time

📝 Summary

The Interpreter Pattern is powerful when used in the right scenarios, such as custom language parsers and expression evaluation engines. However, for complex grammars, it’s often better to use tools like ANTLR or parser generators.

Next Up: Series Complete – Congrats! 🎉
Explore more on system design, security, and architecture at TechWayFit.