Summary
Pattern Name | Interpreter Pattern |
Pattern Type | Behavioral Pattern |
Scope | Class |
Tagline | Interpreter for language/operation syntax |
Use cases | 1. When we need to interpret/parse language syntax 2. When we need to represent language statements as a syntax tree |
Related Patterns | Composite Iterator Visitor Flyweight |
Difficulty Level | Difficult |
Implementations |
Definition
Interpreter pattern is used when we need to represent some language expression or syntax in an Object-Oriented way. Like representing some logical operation or SQL syntax.
Based on the implementation this design pattern can evaluate a specific language syntax, and provide the desired response.
In the Interpreter pattern, we define the grammatical representation of a language and provide an interpreter to evaluate that grammar.
Use Cases
Here are a few use cases of the Interpreter pattern-
- When we need to define any simple syntax and parse that.
- Parsing script with simplicity as a priority.
- When we need to implement some scripting language.
Implementation
For the implementation, we need to understand the Terminal and Non-Terminal expressions.
When we want to interpret some language/formula/string we need to parse that, and then need to consider all the elements involved in that. After parsing the language/string it is represented as AST (Abstract Syntax Tree). As is constructed of some Terminal and Non-Terminal expressions.
Terminal expression: terminal expression is the leaf of the tree.
Non-Terminal expression: non-terminal expressions are the operation that operates on terminal expressions.
Like, in a simple mathematical expression, we can consider the numbers as Terminal expressions, and the operations(+, -, *, /) as Non-terminal expressions. For example, let’s consider (9 + 2 + 4 – 6 + 10 – 7). We can represent this as AST below-
Now, let’s take a look at the actual implementation process.
Interpreter pattern has 4 elements involved in the implementation.
- Context Class: context class is responsible for storing the values/data that are globally available for elements of Interpreter implementation. If the data is simple, then this context can be ignored in some cases.
- Expression Interface: interface that ensures common type for terminal and nonterminal classes. This can be an interface or abstract class.
- Terminal Class: classes that define the base expression and hold the actual operation. One terminal expression class is required for each base expression. These classes are used by nonterminal classes.
- Nonterminal Class: classes that represent specific sub-operations. One nonterminal class is created for each sub-expression/operation.
Take a look at the implementation diagram.
Follow the steps below to implement Interpreter pattern:
- Create context class if required. Create variables to hold all global data.
- Create an interface to ensure a common interface for terminal and all nonterminal classes. Declare a function(generally named as ‘interpret’) for the interpretation operation.
- Create a terminal class. Implement the interpreter method, and add the actual operations here.
- Create nonterminal classes. One class for each operation. Implement the interface and use the terminal class methods in the interpreter method.
- In the client create a context, use that context, and create terminal class objects which use different values of the context.
- In the client create the object of the nonterminal class, and pass the terminal objects to the nonterminal to set operations. Use interpret method of the nonterminal object to get the final result.
If the context is very simple(like a simple String, int), then we can ignore creating a separate class for context.
We can use an abstract class instead of an interface. Then we can define the ‘interpret’ method there.
Examples
Let’s take a look at a few examples of interpreter pattern.
Example #1: Logical Operation
Let’s represent logical operations like AND, OR, XOR, etc. using Interpreter pattern.
We are not using a separate context class here, as just passing a string as context will work in our case.
Operation Interface and Classes
// Operation Interface
interface Operation
execute(opContext: String): boolean
end interface
// Terminal Operation Class
class TerminalOperation implements Operation
var mainData: String
constructor(mainContextParam: String)
data = mainContextParam
end constructor
method execute(opContext: String): boolean
// check if the string exists in data
return opContext.contains(data)
end method
end class
// AND Operation Class
class AndOperation implements Operation
var op1: Operation
var op2: Operation
constructor(opParam1: Operation, opParam2: Operation)
op1 = opParam1
op2 = opParam2
end constructor
method execute(opContext: String)
return op1.execute(opContext) && op2.execute(opContext)
end method
end class
// OR Operation Class
class OrOperation implements Operation
var op1: Operation
var op2: Operation
constructor(opParam1: Operation, opParam2: Operation)
op1 = opParam1
op2 = opParam2
end constructor
method execute(opContext: String)
return op1.execute(opContext) || op2.execute(opContext)
end method
end class
// XOR Operation Class
class XorOperation implements Operation
var op1: Operation
var op2: Operation
constructor(opParam1: Operation, opParam2: Operation)
op1 = opParam1
op2 = opParam2
end constructor
method execute(opContext: String)
return op1.execute(opContext) ^ op2.execute(opContext)
end method
end class
Demo
var op1: Operation = new TerminalOperation("Big")
var op2: Operation = new TerminalOperation("Box")
var andChecker: Operation = new AndOperation(op1, op2)
var orChecker: Operation = new OrOperation(op1, op2)
var xorChecker: Operation = new XorOperation(op1, op2)
var checkStr1: String = "Big Box Code"
var checkStr2: String = "Only Big Code"
var checkStr3: String = "Only Box Code"
var checkStr4: String = "No Code"
// Check AND operation
var andResult1: boolean = andChecker.execute(checkStr1)
var andResult2: boolean = andChecker.execute(checkStr2)
var andResult3: boolean = andChecker.execute(checkStr3)
var andResult4: boolean = andChecker.execute(checkStr4)
output "Data: " + checkStr1 + "; AND Result: " + andResult1
output "Data: " + checkStr2 + "; AND Result: " + andResult2
output "Data: " + checkStr3 + "; AND Result: " + andResult3
output "Data: " + checkStr4 + "; AND Result: " + andResult4
// Check OR operation
var orResult1: boolean = orChecker.execute(checkStr1)
var orResult2: boolean = orChecker.execute(checkStr2)
var orResult3: boolean = orChecker.execute(checkStr3)
var orResult4: boolean = orChecker.execute(checkStr4)
output "Data: " + checkStr1 + "; OR Result: " + orResult1
output "Data: " + checkStr2 + "; OR Result: " + orResult2
output "Data: " + checkStr3 + "; OR Result: " + orResult3
output "Data: " + checkStr4 + "; OR Result: " + orResult4
// Check XOR operation
var xorResult1: boolean = xorChecker.execute(checkStr1)
var xorResult2: boolean = xorChecker.execute(checkStr2)
var xorResult3: boolean = xorChecker.execute(checkStr3)
var xorResult4: boolean = xorChecker.execute(checkStr4)
output "Data: " + checkStr1 + "; XOR Result: " + xorResult1
output "Data: " + checkStr2 + "; XOR Result: " + xorResult2
output "Data: " + checkStr3 + "; XOR Result: " + xorResult3
output "Data: " + checkStr4 + "; XOR Result: " + xorResult4
Output
Data: Big Box Code; AND Result: true
Data: Only Big Code; AND Result: false
Data: Only Box Code; AND Result: false
Data: No Code; AND Result: false
----------------------------------------------
Data: Big Box Code; OR Result: true
Data: Only Big Code; OR Result: true
Data: Only Box Code; OR Result: true
Data: No Code; OR Result: false
----------------------------------------------
Data: Big Box Code; XOR Result: false
Data: Only Big Code; XOR Result: true
Data: Only Box Code; XOR Result: true
Data: No Code; XOR Result: false
Code Implementations
Use the following links to check Interpreter pattern implementation in specific programming languages.