Interpreter Design Pattern (Behavioral)
Interpreter is a behavioral design pattern that defines a grammatical representation for a language and provides an interpreter to deal with this grammar. It is used to evaluate sentences in a language by representing each grammar rule as a class.
Problem
Section titled βProblemβSuppose you have a specific, recurring set of problems that can be solved by processing a simple language or set of rules (e.g., SQL parsing, mathematical expressions, or regular expressions). Building a hard-coded solution for every possible variation of these rules is inefficient and difficult to maintain.
If the problem occurs frequently enough, you might want to express instances of the problem as sentences in a simple language. However, you then need a mechanism to parse and execute these sentences.
Solution
Section titled βSolutionβThe Interpreter pattern suggests defining a class for each symbol (terminal or non-terminal) in the language. You represent the grammar of the language as an Abstract Syntax Tree (AST).
Each class in the tree implements an interpret() method. This method takes a context (global information) and performs the evaluation. Non-terminal expressions delegate the work to their child expressions, effectively traversing the tree until the entire sentence is evaluated.
Real-World Analogy
Section titled βReal-World AnalogyβThink of a musical score. The sheet music acts as the language. Each note, rest, and dynamic marking is a symbol with a specific meaning. A musician acts as the interpreter. They read the symbols (the grammar) and translate them into sounds (the execution), using their knowledge of music theory as the context.
Structure
Section titled βStructureβ- Abstract Expression: Declares an abstract
interpret()method that all nodes in the syntax tree must implement. - Terminal Expression: Implements the
interpret()method for symbols that do not contain other expressions (the βleavesβ of the grammar). - Non-terminal Expression: Represents grammar rules that combine other expressions. Its
interpret()method typically callsinterpret()on its children and combines the results. - Context: Contains global information or data that is required by the interpreter to perform its task.
- Client: Builds the Abstract Syntax Tree representing a specific sentence in the defined language and triggers the interpretation.
Structure (Class Diagram)
Section titled βStructure (Class Diagram)βclassDiagram
class Context {
}
class AbstractExpression {
<<interface>>
+interpret(Context)
}
class TerminalExpression {
+interpret(Context)
}
class NonTerminalExpression {
-child1: AbstractExpression
-child2: AbstractExpression
+interpret(Context)
}
AbstractExpression <|-- TerminalExpression
AbstractExpression <|-- NonTerminalExpression
NonTerminalExpression o-- AbstractExpression
Client --> AbstractExpression
Client --> Context
Pseudocode
Section titled βPseudocodeβ# The Context holds data used during interpretation.class Context: variables = {} method set(name, value): variables[name] = value method get(name): return variables[name]
# The Abstract Expression interface.interface Expression: method interpret(context): int
# Terminal expression for variables.class VariableExpression implements Expression: field name: string method interpret(context): return context.get(name)
# Non-terminal expression for addition.class AddExpression implements Expression: field left: Expression field right: Expression method interpret(context): return left.interpret(context) + right.interpret(context)
# Client builds the tree: (x + y)# Context provides values: x=10, y=5Applicability
Section titled βApplicabilityβ- Simple Grammars: Use the pattern when the grammar of the language is simple. Complex grammars are better handled by specialized parser generators.
- Efficiency is not the main concern: Interpreting an AST is generally slower than translating the language into a lower-level format.
- Rule Engines: Use it when you need to represent and evaluate business rules that can be expressed as a tree structure.
How to Implement
Section titled βHow to Implementβ- Define the grammar of the language.
- Create an abstract interface for the expressions with an
interpret(context)method. - Create terminal expression classes for the literal symbols in the language.
- Create non-terminal expression classes for each rule in the grammar. These classes should store references to other expressions.
- Create a context class to store any global state.
- The client constructs the Abstract Syntax Tree and calls
interpret()on the root node.
Pros and Cons
Section titled βPros and ConsβPros:
- Easy to Change Grammar: Because each rule is represented by a class, you can change or extend the grammar using inheritance.
- Simplifies Implementation: Defining the grammar rules as classes makes the logic for evaluating expressions clear and organized.
Cons:
- Complexity with Large Grammars: A class is required for every rule; for complex languages, the class hierarchy becomes unmanageable.
- Performance: Building and traversing an AST is resource-intensive compared to other parsing techniques.
Relations with other Design Patterns
Section titled βRelations with other Design Patternsβ- Composite: The Abstract Syntax Tree is often an instance of the Composite pattern.
- Flyweight: You can use Flyweight to share terminal symbols within the AST to save memory.
- Iterator: The interpreter can use an Iterator to traverse the structure.
- Visitor: You can use Visitor to encapsulate operations on the AST instead of putting the logic inside the expression classes.
Code Examples
Section titled βCode ExamplesβCode Examples (C#)
Section titled βCode Examples (C#)β// Contextpublic class Context{ public string Input { get; set; } public int Output { get; set; }}
// Abstract expressionpublic abstract class Expression{ public abstract void Interpret(Context context);}
// Concrete expressionspublic class OneExpression : Expression{ public override void Interpret(Context context) { if (context.Input.StartsWith("1")) { context.Output += 1; context.Input = context.Input.Substring(1); } }}
public class TwoExpression : Expression{ public override void Interpret(Context context) { if (context.Input.StartsWith("2")) { context.Output += 2; context.Input = context.Input.Substring(1); } }}
// Usageclass Program{ static void Main() { var context = new Context { Input = "12121" };
var tree = new List<Expression> { new OneExpression(), new TwoExpression() };
while (!string.IsNullOrEmpty(context.Input)) { foreach (var exp in tree) exp.Interpret(context); }
Console.WriteLine($"Resultado: {context.Output}"); }}Code Examples (Python)
Section titled βCode Examples (Python)βfrom abc import ABC, abstractmethod
class Expression(ABC): """ Abstract Expression. """ @abstractmethod def interpret(self, context: dict) -> int: pass
class Number(Expression): """ Terminal Expression for numbers. """ def __init__(self, value: int): self.value = value
def interpret(self, context: dict) -> int: return self.value
class Add(Expression): """ Non-terminal Expression for addition. """ def __init__(self, left: Expression, right: Expression): self.left = left self.right = right
def interpret(self, context: dict) -> int: return self.left.interpret(context) + self.right.interpret(context)
class Subtract(Expression): """ Non-terminal Expression for subtraction. """ def __init__(self, left: Expression, right: Expression): self.left = left self.right = right
def interpret(self, context: dict) -> int: return self.left.interpret(context) - self.right.interpret(context)
if __name__ == "__main__": # Representing the expression: (10 + 5) - 2 # Tree Structure: # Subtract # / \ # Add Number(2) # / \ # 10 5
expression = Subtract( Add(Number(10), Number(5)), Number(2) )
result = expression.interpret({}) print(f"Result: {result}") # Output: Result: 13Code Examples (C++)
Section titled βCode Examples (C++)β#include <iostream>#include <string>#include <vector>#include <memory>
// Contextclass Context {public: std::string Input; int Output = 0;};
// Abstract Expressionclass Expression {public: virtual ~Expression() = default; virtual void Interpret(Context& context) = 0;};
// Concrete Expressionsclass OneExpression : public Expression {public: void Interpret(Context& context) override { if (!context.Input.empty() && context.Input.rfind("1", 0) == 0) { context.Output += 1; context.Input.erase(0, 1); } }};
class TwoExpression : public Expression {public: void Interpret(Context& context) override { if (!context.Input.empty() && context.Input.rfind("2", 0) == 0) { context.Output += 2; context.Input.erase(0, 1); } }};
// Usageint main() { Context context; context.Input = "12121";
std::vector<std::unique_ptr<Expression>> tree; tree.push_back(std::make_unique<OneExpression>()); tree.push_back(std::make_unique<TwoExpression>());
while (!context.Input.empty()) { for (auto& exp : tree) { exp->Interpret(context); } }
std::cout << "Resultado: " << context.Output << std::endl;
return 0;}Code Examples (Java)
Section titled βCode Examples (Java)β// Contextpublic class Context { public String input; public int output;}
// Abstract Expressionpublic abstract class Expression { public abstract void interpret(Context context);}
// Concrete Expressionspublic class OneExpression extends Expression {
@Override public void interpret(Context context) { if (context.input.startsWith("1")) { context.output += 1; context.input = context.input.substring(1); } }}
public class TwoExpression extends Expression {
@Override public void interpret(Context context) { if (context.input.startsWith("2")) { context.output += 2; context.input = context.input.substring(1); } }}
// Usageimport java.util.ArrayList;import java.util.List;
public class Program { public static void main(String[] args) {
Context context = new Context(); context.input = "12121";
List<Expression> tree = new ArrayList<>(); tree.add(new OneExpression()); tree.add(new TwoExpression());
while (!context.input.isEmpty()) { for (Expression exp : tree) { exp.interpret(context); } }
System.out.println("Resultado: " + context.output); }}Code Examples (JavaScript)
Section titled βCode Examples (JavaScript)β// Contextclass Context { constructor(input) { this.input = input; this.output = 0; }}
// Abstract Expressionclass Expression { interpret(context) { throw new Error("interpret() must be implemented"); }}
// Concrete Expressionsclass OneExpression extends Expression { interpret(context) { if (context.input.startsWith("1")) { context.output += 1; context.input = context.input.substring(1); } }}
class TwoExpression extends Expression { interpret(context) { if (context.input.startsWith("2")) { context.output += 2; context.input = context.input.substring(1); } }}
// Usageconst context = new Context("12121");
const tree = [ new OneExpression(), new TwoExpression()];
while (context.input.length > 0) { for (const exp of tree) { exp.interpret(context); }}
console.log(`Resultado: ${context.output}`);Code Examples (Visual Basic)
Section titled βCode Examples (Visual Basic)β' ============================' Context' ============================Public Class Context Public Property Input As String Public Property Output As IntegerEnd Class
' ============================' Abstract Expression' ============================Public MustInherit Class Expression Public MustOverride Sub Interpret(context As Context)End Class
' ============================' Concrete Expressions' ============================Public Class OneExpression Inherits Expression
Public Overrides Sub Interpret(context As Context) If context.Input.StartsWith("1") Then context.Output += 1 context.Input = context.Input.Substring(1) End If End SubEnd Class
Public Class TwoExpression Inherits Expression
Public Overrides Sub Interpret(context As Context) If context.Input.StartsWith("2") Then context.Output += 2 context.Input = context.Input.Substring(1) End If End SubEnd Class
' ============================' Usage' ============================Module Program Sub Main() Dim context As New Context With {.Input = "12121"}
Dim tree As New List(Of Expression) From { New OneExpression(), New TwoExpression() }
While Not String.IsNullOrEmpty(context.Input) For Each exp In tree exp.Interpret(context) Next End While
Console.WriteLine($"Resultado: {context.Output}") End SubEnd ModuleCode Examples (Rust)
Section titled βCode Examples (Rust)β// Contextstruct Context { input: String, output: i32,}
impl Context { fn new(input: &str) -> Self { Self { input: input.to_string(), output: 0, } }}
// Abstract Expressiontrait Expression { fn interpret(&self, context: &mut Context);}
// Concrete Expressionsstruct OneExpression;
impl Expression for OneExpression { fn interpret(&self, context: &mut Context) { if context.input.starts_with('1') { context.output += 1; context.input.remove(0); } }}
struct TwoExpression;
impl Expression for TwoExpression { fn interpret(&self, context: &mut Context) { if context.input.starts_with('2') { context.output += 2; context.input.remove(0); } }}
// Usagefn main() { let mut context = Context::new("12121");
let tree: Vec<Box<dyn Expression>> = vec![ Box::new(OneExpression), Box::new(TwoExpression), ];
while !context.input.is_empty() { for exp in &tree { exp.interpret(&mut context); } }
println!("Resultado: {}", context.output);}Code Examples (PHP)
Section titled βCode Examples (PHP)β<?php
// Contextclass Context{ public string $input; public int $output = 0;}
// Abstract Expressionabstract class Expression{ abstract public function interpret(Context $context): void;}
// Concrete Expressionsclass OneExpression extends Expression{ public function interpret(Context $context): void { if (str_starts_with($context->input, "1")) { $context->output += 1; $context->input = substr($context->input, 1); } }}
class TwoExpression extends Expression{ public function interpret(Context $context): void { if (str_starts_with($context->input, "2")) { $context->output += 2; $context->input = substr($context->input, 1); } }}
// Usage$context = new Context();$context->input = "12121";
$tree = [ new OneExpression(), new TwoExpression()];
while ($context->input !== "") { foreach ($tree as $exp) { $exp->interpret($context); }}
echo "Resultado: {$context->output}\n";Code Examples (Go)
Section titled βCode Examples (Go)βpackage main
import ( "fmt" "strings")
// Contexttype Context struct { Input string Output int}
// Expression interfacetype Expression interface { Interpret(ctx *Context)}
// Concrete Expression: "1"type OneExpression struct{}
func (OneExpression) Interpret(ctx *Context) { if strings.HasPrefix(ctx.Input, "1") { ctx.Output += 1 ctx.Input = ctx.Input[1:] }}
// Concrete Expression: "2"type TwoExpression struct{}
func (TwoExpression) Interpret(ctx *Context) { if strings.HasPrefix(ctx.Input, "2") { ctx.Output += 2 ctx.Input = ctx.Input[1:] }}
// Usagefunc main() { ctx := &Context{Input: "12121"}
tree := []Expression{ OneExpression{}, TwoExpression{}, }
for ctx.Input != "" { for _, exp := range tree { exp.Interpret(ctx) } }
fmt.Printf("Resultado: %d\n", ctx.Output)}Code Examples (Ruby)
Section titled βCode Examples (Ruby)β# Contextclass Contextattr_accessor :input, :output
def initialize(input) @input = input @output = 0endend
# Abstract Expressionclass Expressiondef interpret(context) raise NotImplementedError, "Subclasses must implement interpret"endend
# Concrete Expressionsclass OneExpression < Expressiondef interpret(context) if context.input.start_with?("1") context.output += 1 context.input = context.input[1..] # remove first char endendend
class TwoExpression < Expressiondef interpret(context) if context.input.start_with?("2") context.output += 2 context.input = context.input[1..] endendend
# Usagecontext = Context.new("12121")
tree = [OneExpression.new,TwoExpression.new]
while !context.input.empty?tree.each { |exp| exp.interpret(context) }end
puts "Resultado: #{context.output}"Code Examples (Perl)
Section titled βCode Examples (Perl)β#!/usr/bin/perluse strict;use warnings;
############################# Context############################package Context;
sub new { my ($class, $input) = @_; return bless { input => $input, output => 0 }, $class;}
sub get_input { $_[0]->{input} }sub set_input { $_[0]->{input} = $_[1] }sub add_output { $_[0]->{output} += $_[1] }sub get_output { $_[0]->{output} }
############################# Abstract Expression############################package Expression;
sub interpret { die "interpret() must be implemented by subclass";}
############################# Concrete Expression: OneExpression############################package OneExpression;our @ISA = ('Expression');
sub interpret { my ($self, $context) = @_; my $input = $context->get_input();
if ($input =~ /^1/) { $context->add_output(1); $context->set_input(substr($input, 1)); }}
############################# Concrete Expression: TwoExpression############################package TwoExpression;our @ISA = ('Expression');
sub interpret { my ($self, $context) = @_; my $input = $context->get_input();
if ($input =~ /^2/) { $context->add_output(2); $context->set_input(substr($input, 1)); }}
############################# Usage############################package main;
my $context = Context->new("12121");
my @tree = ( OneExpression->new(), TwoExpression->new());
while ($context->get_input() ne "") { foreach my $exp (@tree) { $exp->interpret($context); }}
print "Resultado: " . $context->get_output() . "\n";Code Examples (TypeScript)
Section titled βCode Examples (TypeScript)β// Contextclass Context { input: string; output: number = 0;
constructor(input: string) { this.input = input; }}
// Abstract Expressionabstract class Expression { abstract interpret(context: Context): void;}
// Concrete Expressionsclass OneExpression extends Expression { interpret(context: Context): void { if (context.input.startsWith("1")) { context.output += 1; context.input = context.input.substring(1); } }}
class TwoExpression extends Expression { interpret(context: Context): void { if (context.input.startsWith("2")) { context.output += 2; context.input = context.input.substring(1); } }}
// Usageconst context = new Context("12121");
const tree: Expression[] = [ new OneExpression(), new TwoExpression()];
while (context.input.length > 0) { for (const exp of tree) { exp.interpret(context); }}
console.log(`Resultado: ${context.output}`);