Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
Usage in JavaScript: |
|
low |
The Interpreter pattern offers a scripting language that allows end-users to customize their solution.
Some applications are so complex that they require advanced configuration. You could offer a basic scripting language which allows the end-user to manipulate your application through simple instructions. The Interpreter pattern solves this particular problem – that of creating a simple scripting language.
Certain types of problems lend themselves to be characterized by a language. This language describes the problem domain which should be well-understood and well-defined. To implement this you need to map the language to a grammar. Grammars are usually hierarchical tree-like structures that step through multiple levels and then end up with terminal nodes (also called literals).
Problems like this, expressed as a grammar, can be implemented using the Interpreter design pattern.
Today, if you really need this type of control in JavaScript it is probably easier to use a code generator like ANTLR which will allow you to build your own command interpreters based on a grammar that you provide.
The objective of this example is to build an interpreter which translates roman numerals to decimal numbers: for example, XXXVI = 36.
The Context object maintains the input (the roman numeral) and the resulting output as it is being parsed and interpreted. The Expression object represents the nodes in the grammar tree; it supports the interpret method.
When running the program, a simple grammar tree is being built which then processes a roman numeral and translates it into a numeric.
var Context = function (input) { this.input = input; this.output = 0; } Context.prototype = { startsWith : function (str) { return this.input.substr(0, str.length) === str; } } var Expression = function (name, one, four, five, nine, multiplier) { this.name = name; this.one = one; this.four = four; this.five = five; this.nine = nine; this.multiplier = multiplier; } Expression.prototype = { interpret: function (context) { if (context.input.length == 0) { return; } else if (context.startsWith(this.nine)) { context.output += (9 * this.multiplier); context.input = context.input.substr(2); } else if (context.startsWith(this.four)) { context.output += (4 * this.multiplier); context.input = context.input.substr(2); } else if (context.startsWith(this.five)) { context.output += (5 * this.multiplier); context.input = context.input.substr(1); } while (context.startsWith(this.one)) { context.output += (1 * this.multiplier); context.input = context.input.substr(1); } } } function run() { var roman = "MCMXXVIII" var context = new Context(roman); var tree = []; tree.push(new Expression("thousand", "M", " " , " ", " " , 1000)); tree.push(new Expression("hundred", "C", "CD", "D", "CM", 100)); tree.push(new Expression("ten", "X", "XL", "L", "XC", 10)); tree.push(new Expression("one", "I", "IV", "V", "IX", 1)); for (var i = 0, len = tree.length; i < len; i++) { tree[i].interpret(context); } alert(roman + " = " + context.output); }Run
The Namespace pattern is applied to keep the code out of the global namespace. Our namespace is named Patterns.Classic. A Revealing Module named Interpreter returns (i.e. reveals) only a single item: the evaluate method. All other items are kept private in the module: OO encapsulation at its best.
The Patterns object contains the namespace function which constructs namespaces non-destructively, that is, if a name already exists it won't overwrite it.
The log function is a helper which collects and displays results.
var Patterns = { namespace: function (name) { var parts = name.split("."); var ns = this; for (var i = 0, len = parts.length; i < len; i++) { ns[parts[i]] = ns[parts[i]] || {}; ns = ns[parts[i]]; } return ns; } }; Patterns.namespace("Classic").Interpreter = (function () { var Context = function (input) { this.input = input; this.output = 0; } Context.prototype = { startsWith: function (str) { return this.input.substr(0, str.length) === str; } } var Expression = function (name, one, four, five, nine, multiplier) { this.name = name; this.one = one; this.four = four; this.five = five; this.nine = nine; this.multiplier = multiplier; } Expression.prototype = { interpret: function (context) { if (context.input.length == 0) { return; } else if (context.startsWith(this.nine)) { context.output += (9 * this.multiplier); context.input = context.input.substr(2); } else if (context.startsWith(this.four)) { context.output += (4 * this.multiplier); context.input = context.input.substr(2); } else if (context.startsWith(this.five)) { context.output += (5 * this.multiplier); context.input = context.input.substr(1); } while (context.startsWith(this.one)) { context.output += (1 * this.multiplier); context.input = context.input.substr(1); } } } function evaluate(roman) { var tree = []; var context = new Context(roman); tree.push(new Expression("thousand", "M", " ", " ", " ", 1000)); tree.push(new Expression("hundred", "C", "CD", "D", "CM", 100)); tree.push(new Expression("ten", "X", "XL", "L", "XC", 10)); tree.push(new Expression("one", "I", "IV", "V", "IX", 1)); for (var i = 0, len = tree.length; i < len; i++) { tree[i].interpret(context); } return context.output; } return { evaluate: evaluate }; })(); function run() { var roman = "MCMXXVIII" var result = Patterns.Classic.Interpreter.evaluate(roman); alert(roman + " = " + result); }Run