The interpreter pattern is not really used, but it can be really useful. Usually, this pattern is described in terms of formal grammar but the area where it can be applied, can be extended.
You can refer to the following for more information: Lecture de chiffre Romain (http://www.oodesign.com/interpreter-pattern.html) and https://en.wikipedia.org/wiki/Roman_numerals.
The interpreter pattern defines an object representation of a language grammar in order to evaluate some expression written in this language by interpreting them.
This pattern can be used to interpret some expressions that are represented as a hierarchical tree. It may be applied when:
Some examples where this pattern can be used are:
The implementation of this pattern is seen in the use of the composite pattern applied to represent a grammar (refer to the The composite pattern in Chapter 3, Structural Patterns – Composite and Flyweight). The difference is that the interpreter pattern defines the behavior while the composite defines only the structure.
The UML class diagram of the pattern is as follows:
Participants to this pattern are as follows:
AbstractExpression
: This defines the interpret()
method that is common to all the nodes in the abstract syntax tree.TerminalExpression
: This implements the interpret method associated with terminal symbols of the grammar. Each terminal symbol of the grammar requires a concrete class.NonTerminalExpression
: This implements the interpret method and can also contain other AbstractExpression
instances.Context
: This contains information that is global to the interpreter. For example, the actual values of variables.Client
: This builds an abstract syntax tree that is assembled from instances of NonTerminalExpression
and TerminalExpression
. It will be our demo usage of the pattern.The client builds the abstract syntax tree, initializes the context of the interpreter, and then invokes the interpret method.
The interpret method at the TerminalExpression
and NonTerminalExpression
node uses the context to store and access the state of the interpreter.
We want to create a roman number converter; you know the ones that interpret that XIV means 14 in decimals. The main purpose of our example is to write a roman number and our converter will tell us the decimal value.
Open the InterpreterPattern.xcodeproj
file and see how we have implemented the pattern.
Before starting to understand the code, have a look at the following link to understand how roman numbers work and how they are written.
You can see some rules about the roman numerals and also a roman numeral chart at http://4thgradecrocs.weebly.com/roman-numerals.html and how the roman system works can be seen at https://en.wikipedia.org/wiki/Roman_numerals.
For the pattern, we will use one string extension that lets us do a substring easily just by passing the number of characters we want to ignore in the argument:
extension String { func subStringFrom(pos: Int) -> String { var substr = "" let start = self.startIndex.advancedBy(pos) let end = self.endIndex let range = start..<end substr = self[range] return substr } }
The expression to be interpreted is a string that is put in the context:
class Context { var input: String! var output: Int = 0 init(input: String){ self.input = input } }
This class will help us to work while we apply the pattern; it consists of the remaining unparsed roman numeral strings and also the result of the numerals that are already parsed.
The context is passed to one of four sub-interpreters based on the type of interpreting (thousand, hundred, ten, and one). In this example, only TerminalExpressions
are used.
Next, we will define our AbstractExpression
class. This class must implement the interpret()
method and define the methods that will be overridden in the subclasses:
class Expression { func interpret(context: Context) { if context.input.characters.count == 0 { return } if context.input.hasPrefix(nine()){ context.output = context.output + (9 * multiplier()) context.input = context.input.subStringFrom(2) } else if context.input.hasPrefix(four()){ context.output = context.output + (4 * multiplier()) context.input = context.input.subStringFrom(2) } else if context.input.hasPrefix(five()){ context.output = context.output + (5 * multiplier()) context.input = context.input.subStringFrom(1) } while context.input.hasPrefix(one()) { context.output = context.output + (1 * multiplier()) context.input = context.input.subStringFrom(1) } } func one() -> String { fatalError("this method must be implemented in a subclass") } func four() -> String { fatalError("this method must be implemented in a subclass") } func five() -> String { fatalError("this method must be implemented in a subclass") } func nine() -> String { fatalError("this method must be implemented in a subclass") } func multiplier() -> Int { fatalError("this method must be implemented in a subclass") } }
The Expression
class consists of the interpret
method, which receives the context. Based on the current object, it uses specific values for thousand, hundred, ten, one, and also a specific multiplier.
The one()
, four()
, five()
, nine()
, and multiplier()
methods of the Expression
class are abstract. They will be implemented in our Concrete TerminalExpressions
class
We can now implement our four TerminalExpression
classes. Each of them override the one()
, four()
, five()
, nine()
and the multiplier()
methods. These methods will then be interpreted depending on if we are in a thousand, hundred, ten or one expressions. Indeed, these classes are used to define each specific expression. Usually, these classes implement the interpret
method, but here it is already defined in the base expression class and each of the TerminalExpression
class defines its behavior by implementing the abstract methods: one()
, four()
, five()
, nine()
, and multiplier()
. This is a template method (refer to the The template method section in Chapter 5, Behavioral Patterns – Strategy, State, and Template Method):
class ThousandExpression: Expression { override func one() -> String { return "M" } override func four() -> String { return " " } override func five() -> String { return " " } override func nine() -> String { return " " } override func multiplier() -> Int { return 1000 } } class HundredExpression: Expression { override func one() -> String { return "C" } override func four() -> String { return "CD" } override func five() -> String { return "D" } override func nine() -> String { return "CM" } override func multiplier() -> Int { return 100 } } class TenExpression: Expression { override func one() -> String { return "X" } override func four() -> String { return "XL" } override func five() -> String { return "L" } override func nine() -> String { return "XC" } override func multiplier() -> Int { return 10 } } class OneExpression: Expression { override func one() -> String { return "I" } override func four() -> String { return "IV" } override func five() -> String { return "V" } override func nine() -> String { return "IX" } override func multiplier() -> Int { return 1 } }
The pattern is now written; we only have to test it. Before writing our test code, we will create a helper RomanToDecimalConverter()
class having a calculate()
method that returns the result of the conversion:
class RomanToDecimalConverter { var tree = [ThousandExpression(), HundredExpression(), TenExpression(),OneExpression()] func calculate(romanString: String) -> Int { let context = Context(input: romanString) for t in tree { t.interpret(context) } return context.output } }
This class is responsible to build the syntax tree representing our specific value; the roman number, in the language defined by the grammar.
Note that we can only add our terminal expressions to the array in a specific order: from thousand to one expression, as we will parse the roman string from left to right.
We call the interpret method after the syntax tree is build. We return the context.output
value once all expressions of the pattern are executed, which corresponds to the decimal result.
We will add a new method to our RomanToDecimalConverter
before writing our test code. This will validate that the roman number that we are trying to convert is correct: otherwise, a message will be displayed informing us that our number is not a roman number. The added code is highlighted in the following:
enum FormatError: ErrorType { case RomanNumberFormatError } //Helper class RomanToDecimalConverter { static let pattern = "^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$" let validation = NSPredicate(format: "SELF MATCHES %@", pattern) var tree = [ThousandExpression(), HundredExpression(), TenExpression(),OneExpression()] func calculate(romanString: String) throws -> Int { guard validate(romanString) else { throw FormatError.RomanNumberFormatError } let context = Context(input: romanString) for t in tree { t.interpret(context) } return context.output } func validate(romanString: String) -> Bool { return validation.evaluateWithObject(romanString) } }
We first define a new enum
function that we call FormatError
of type ErrorType
. We will use this to throw an exception when the format of the roman number is incorrect.
Then we have a static string pattern
in the RomanToDecimalConverter
class that contains our regular expression and also an instance constant called validation
that will handle the validation process through an NSPredicate
object. The purpose of this object is to define the logical conditions needed to constrain a search either for a fetch or for an in-memory filtering.
In the calculate
method, we had a guard
statement that executed the validate
function, if the validate
function returns false
(meaning the number is incorrect), a FormatError.RomanNumberFormatError
exception is raised; otherwise, we will continue the operation and calculate the decimal value using the pattern. Note the addition of the throws
keyword just before the return type of the method. It means that this method can throw errors.
The validate()
method calls the evaluateWithObject
method of the NSPredicate
instance by passing the roman number in an argument. This method returns true
if the format is correct, otherwise it will return false
.
The regex expression used in this code comes from the following URL and is very well explained at: http://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression
For more information about NSPredicate, you can refer to this site: https://realm.io/news/nspredicate-cheatsheet/
Now we can write our test code like the following:
let romanNumberToTest = ["XIV", "MCCMXXVIII","MCMXXVIII"] var converter = RomanToDecimalConverter() for roman in romanNumberToTest { var decimal = try? converter.calculate(roman) guard (decimal != nil) else { print("(roman) is not a correct roman number") continue } print(decimal!) }
We initialize an array with some roman numbers and then initialize our RomanToDecimalConverter
converter object and loop over all elements to make the calculations.
We are using the following statement:
var decimal = try? converter.calculate(roman)
The decimal will return nil
if an exception is raised. So, the next three lines help us to print an appropriate message when a roman number doesn't have the correct format.
With the three values of our array and after building and running the project, you'll obtain the following result:
The preceding result given by the program inform us that MCCMXXVIII has a wrong roman number format and the two others numbers are well converted with the 14 decimal value for the first roman number and 1928 for the second roman number.
You can try running the code using the roman numeral chart given at the beginning of this section.
You can see that this pattern is really interesting. You have already seen that the composite and template method patterns have also been used to build and implement our interpreter pattern with our use case.