Jump to content

Interpreter pattern: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
m Replaced outdated html codes with wikicodes. Please see Category:Articles with HTML markup.
 
(26 intermediate revisions by 15 users not shown)
Line 1: Line 1:
{{Short description|Approach in computer programming}}
{{Refimprove|date=November 2008}}
{{Refimprove|date=November 2008}}


Line 16: Line 17:
that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse.
that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse.


<big>What problems can the Interpreter design pattern solve?</big>
===What problems can the Interpreter design pattern solve?===

<ref>{{cite web|title=The Interpreter design pattern - Problem, Solution, and Applicability|url=http://w3sdesign.com/?gr=b03&ugr=proble|website=w3sDesign.com|accessdate=2017-08-12}}</ref>
Source:<ref>{{cite web|title=The Interpreter design pattern - Problem, Solution, and Applicability|url=http://w3sdesign.com/?gr=b03&ugr=proble|website=w3sDesign.com|access-date=2017-08-12}}</ref>


* A [[Backus-Naur form|grammar]] for a simple language should be defined
* A [[Backus-Naur form|grammar]] for a simple language should be defined
Line 30: Line 32:
because it commits the class to particular expressions and makes it impossible to specify new expressions or change existing ones independently from (without having to change) the class.
because it commits the class to particular expressions and makes it impossible to specify new expressions or change existing ones independently from (without having to change) the class.


<big>What solution does the Interpreter design pattern describe?</big>
===What solution does the Interpreter design pattern describe?===

* Define a grammar for a simple language by defining an <code>Expression</code> class hierarchy and implementing an <code>interpret()</code> operation.
* Define a grammar for a simple language by defining an <code>Expression</code> class hierarchy and implementing an <code>interpret()</code> operation.
* Represent a sentence in the language by an abstract syntax tree (AST) made up of <code>Expression</code> instances.
* Represent a sentence in the language by an abstract syntax tree (AST) made up of <code>Expression</code> instances.
Line 47: Line 48:
* Specialized database query languages such as [[SQL]].
* Specialized database query languages such as [[SQL]].
* Specialized computer languages that are often used to describe communication protocols.
* Specialized computer languages that are often used to describe communication protocols.
* Most general-purpose computer languages actually incorporate several specialized languages.
* Most general-purpose computer languages actually incorporate several specialized languages{{Citation needed|reason=Need reference to a language doing this|date=September 2022}}.


== Structure ==
== Structure ==
=== UML class and object diagram ===
=== UML class and object diagram ===
[[File:w3sDesign Interpreter Design Pattern UML.jpg|frame|none|A sample UML class and object diagram for the Interpreter design pattern.<ref>{{cite web|title=The Interpreter design pattern - Structure and Collaboration|url=http://w3sdesign.com/?gr=b03&ugr=struct|website=w3sDesign.com|accessdate=2017-08-12}}</ref>]]
[[File:w3sDesign Interpreter Design Pattern UML.jpg|frame|none|A sample UML class and object diagram for the Interpreter design pattern.<ref>{{cite web|title=The Interpreter design pattern - Structure and Collaboration|url=http://w3sdesign.com/?gr=b03&ugr=struct|website=w3sDesign.com|access-date=2017-08-12}}</ref>]]


In the above [[Unified Modeling Language|UML]] [[class diagram]], the <code>Client</code> class refers to the common <code>AbstractExpression</code> interface for interpreting an expression
In the above [[Unified Modeling Language|UML]] [[class diagram]], the <code>Client</code> class refers to the common <code>AbstractExpression</code> interface for interpreting an expression
Line 72: Line 73:
[[Image:Interpreter_UML_class_diagram.svg]]
[[Image:Interpreter_UML_class_diagram.svg]]


== Examples ==
== Example ==

<!-- Wikipedia is not a list of examples. Do not add examples from favorite programming languages here; this page exists to explain the design pattern, not to show how it interacts with subtleties of every language extant. Feel free to add examples at: http://en.wikibooks.org/wiki/Computer_Science_Design_Patterns/Interpreter -->
<!-- Wikipedia is not a list of examples. Do not add examples from favorite programming languages here; this page exists to explain the design pattern, not to show how it interacts with subtleties of every language extant. Feel free to add examples at: http://en.wikibooks.org/wiki/Computer_Science_Design_Patterns/Interpreter -->


This C++11 implementation is based on the pre C++98 sample code in the book.
=== BNF ===
The following [[Backus–Naur form]] example illustrates the interpreter pattern. The grammar


<syntaxhighlight lang="bnf">
<syntaxhighlight lang="c++">
#include <iostream>
expression ::= plus | minus | variable | number
#include <map>
plus ::= expression expression '+'
#include <cstring>
minus ::= expression expression '-'
variable ::= 'a' | 'b' | 'c' | ... | 'z'
digit = '0' | '1' | ... | '9'
number ::= digit | digit number
</syntaxhighlight>
defines a language that contains [[Reverse Polish Notation]] expressions like:


class Context;
<pre>
a b +
a b c + -
a b + c a - -
</pre>


class BooleanExp {
=== C# ===
public:
This structural code demonstrates the Interpreter patterns, which using a defined grammar, provides the interpreter that processes parsed statements.
BooleanExp() = default;
virtual ~BooleanExp() = default;
virtual bool evaluate(Context&) = 0;
virtual BooleanExp* replace(const char*, BooleanExp&) = 0;
virtual BooleanExp* copy() const = 0;
};


class VariableExp;
<syntaxhighlight lang="csharp">
namespace DesignPatterns.Interpreter
{
// "Context"
class Context
{
}


class Context {
// "AbstractExpression"
public:
abstract class AbstractExpression
{
Context() :m() {}
bool lookup(const VariableExp* key) { return m.at(key); }
public abstract void Interpret(Context context);
void assign(VariableExp* key, bool value) { m[key] = value; }
}
private:
std::map<const VariableExp*, bool> m;
};


class VariableExp : public BooleanExp {
// "TerminalExpression"
public:
class TerminalExpression : AbstractExpression
VariableExp(const char* name_) :name(nullptr) {
{
name = strdup(name_);
public override void Interpret(Context context)
{
}
virtual ~VariableExp() = default;
Console.WriteLine("Called Terminal.Interpret()");
virtual bool evaluate(Context& aContext) {
}
return aContext.lookup(this);
}
virtual BooleanExp* replace( const char* name_, BooleanExp& exp ) {
if (0 == strcmp(name_, name)) {
return exp.copy();
} else {
return new VariableExp(name);
}
}
}
virtual BooleanExp* copy() const {
return new VariableExp(name);
}
VariableExp(const VariableExp&) = delete; // rule of three
VariableExp& operator=(const VariableExp&) = delete;
private:
char* name;
};


class AndExp : public BooleanExp {
// "NonterminalExpression"
public:
class NonterminalExpression : AbstractExpression
AndExp(BooleanExp* op1, BooleanExp* op2)
{
:operand1(nullptr), operand2(nullptr) {
public override void Interpret(Context context)
{
operand1 = op1;
operand2 = op2;
Console.WriteLine("Called Nonterminal.Interpret()");
}
}
virtual ~AndExp() = default;
}
virtual bool evaluate(Context& aContext) {
return operand1->evaluate(aContext) && operand2->evaluate(aContext);
}
virtual BooleanExp* replace(const char* name_, BooleanExp& exp) {
return new AndExp(
operand1->replace(name_, exp),
operand2->replace(name_, exp)
);
}
virtual BooleanExp* copy() const {
return new AndExp(operand1->copy(), operand2->copy());
}
AndExp(const AndExp&) = delete; // rule of three
AndExp& operator=(const AndExp&) = delete;
private:
BooleanExp* operand1;
BooleanExp* operand2;
};


int main() {
class MainApp
BooleanExp* expression;
{
Context context;
static void Main()
VariableExp* x = new VariableExp("X");
{
var context = new Context();
VariableExp* y = new VariableExp("Y");
expression = new AndExp(x, y);


context.assign(x, false);
// Usually a tree
context.assign(y, true);
var list = new List<AbstractExpression>();
bool result = expression->evaluate(context);
std::cout << result << '\n';


context.assign(x, true);
// Populate 'abstract syntax tree'
context.assign(y, true);
list.Add(new TerminalExpression());
result = expression->evaluate(context);
list.Add(new NonterminalExpression());
std::cout << result << '\n';
list.Add(new TerminalExpression());
list.Add(new TerminalExpression());

// Interpret
foreach (AbstractExpression exp in list)
{
exp.Interpret(context);
}
}
}
}
}
</syntaxhighlight>
</syntaxhighlight>


The program output is:
=== Java ===
Following the interpreter pattern, we need to implement the interface Expr with a lambda (it can be a class) for each grammar rule.


<syntaxhighlight lang="java">
<syntaxhighlight lang="c++">
0
public class Interpreter {
1
@FunctionalInterface
public interface Expr {
int interpret(Map<String, Integer> context);
static Expr number(int number) {
return context -> number;
}
static Expr plus(Expr left, Expr right) {
return context -> left.interpret(context) + right.interpret(context);
}
static Expr minus(Expr left, Expr right) {
return context -> left.interpret(context) - right.interpret(context);
}
static Expr variable(String name) {
return context -> context.getOrDefault(name, 0);
}
}
</syntaxhighlight>

While the interpreter pattern does not address parsing,<ref name=GoF/>{{rp|247}} a parser is provided for completeness.

<syntaxhighlight lang="java">
private static Expr parseToken(String token, ArrayDeque<Expr> stack) {
Expr left, right;
switch(token) {
case "+":
// It's necessary to remove first the right operand from the stack
right = stack.pop();
// ...and then the left one
left = stack.pop();
return Expr.plus(left, right);
case "-":
right = stack.pop();
left = stack.pop();
return Expr.minus(left, right);
default:
return Expr.variable(token);
}
}
public static Expr parse(String expression) {
ArrayDeque<Expr> stack = new ArrayDeque<Expr>();
for (String token : expression.split(" ")) {
stack.push(parseToken(token, stack));
}
return stack.pop();
}
</syntaxhighlight>

Finally evaluating the expression "w x z - +" with w = 5, x = 10, and z = 42.

<syntaxhighlight lang="java">
public static void main(final String[] args) {
Expr expr = parse("w x z - +");
Map<String, Integer> context = Map.of("w", 5, "x", 10, "z", 42);
int result = expr.interpret(context);
System.out.println(result); // -27
}
}
</syntaxhighlight>
<!-- Wikipedia is not a list of examples. Do not add examples from your favorite programming language here; this page exists to explain the design pattern, not to show how it interacts with subtleties of every language under the sun. Feel free to add examples here: http://en.wikibooks.org/wiki/Computer_Science_Design_Patterns/Interpreter -->

=== PHP (Example 1) ===
<syntaxhighlight lang="php">
/**
* AbstractExpression
*/
interface Expression
{
public function interpret(array $context): int;
}
</syntaxhighlight>

<syntaxhighlight lang="php">
/**
* TerminalExpression
*/
class TerminalExpression implements Expression
{
/** @var string */
private $name;

public function __construct(string $name)
{
$this->name = $name;
}

public function interpret(array $context): int
{
return intval($context[$this->name]);
}
}
</syntaxhighlight>

<syntaxhighlight lang="php">
/**
* NonTerminalExpression
*/
abstract class NonTerminalExpression implements Expression
{
/** @var Expression $left */
protected $left;

/** @var ?Expression $right */
protected $right;

public function __construct(Expression $left, ?Expression $right)
{
$this->left = $left;
$this->right = $right;
}

abstract public function interpret(array $context): int;
public function getRight()
{
return $this->right;
}

public function setRight($right): void
{
$this->right = $right;
}
}
</syntaxhighlight>

<syntaxhighlight lang="php">
/**
* NonTerminalExpression - PlusExpression
*/
class PlusExpression extends NonTerminalExpression
{
public function interpret(array $context): int
{
return intval($this->left->interpret($context) + $this->right->interpret($context));
}
}
</syntaxhighlight>

<syntaxhighlight lang="php">
/**
* NonTerminalExpression - MinusExpression
*/
class MinusExpression extends NonTerminalExpression
{
public function interpret(array $context): int
{
return intval($this->left->interpret($context) - $this->right->interpret($context));
}
}
</syntaxhighlight>

<syntaxhighlight lang="php">
/**
* Client
*/
class InterpreterClient
{
protected function parseList(array &$stack, array $list, int &$index)
{
/** @var string $token */
$token = $list[$index];

switch($token) {
case '-':
list($left, $right) = $this->fetchArguments($stack, $list, $index);
return new MinusExpression($left, $right);
case '+':
list($left, $right) = $this->fetchArguments($stack, $list, $index);
return new PlusExpression($left, $right);
default:
return new TerminalExpression($token);
}
}

protected function fetchArguments(array &$stack, array $list, int &$index): array
{
/** @var Expression $left */
$left = array_pop($stack);
/** @var Expression $right */
$right = array_pop($stack);
if ($right === null) {
++$index;
$this->parseListAndPush($stack, $list, $index);
$right = array_pop($stack);
}

return array($left, $right);
}

protected function parseListAndPush(array &$stack, array $list, int &$index)
{
array_push($stack, $this->parseList($stack, $list, $index));
}

protected function parse(string $data): Expression
{
$stack = [];
$list = explode(' ', $data);
for ($index=0; $index<count($list); $index++) {
$this->parseListAndPush($stack, $list, $index);
}

return array_pop($stack);
}

public function main()
{
$data = "u + v - w + z";
$expr = $this->parse($data);
$context = ['u' => 3, 'v' => 7, 'w' => 35, 'z' => 9];
$res = $expr->interpret($context);
echo "result: $res" . PHP_EOL;
}
}
</syntaxhighlight>

<syntaxhighlight lang="php">

// test.php

function loadClass($className)
{
require_once __DIR__ . "/$className.php";
}

spl_autoload_register('loadClass');

(new InterpreterClient())->main();
//result: -16
</syntaxhighlight>

=== PHP (Example 2) ===
based on the example above with another realization of the Client

<syntaxhighlight lang="php">
/**
* Client
*/
class InterpreterClient
{
public function parseToken(string $token, array &$stack): Expression
{
switch($token) {
case '-':
/** @var Expression $left */
$left = array_pop($stack);
/** @var Expression $right */
$right = array_pop($stack);
return new MinusExpression($left, $right);
case '+':
/** @var Expression $left */
$left = array_pop($stack);
/** @var Expression $right */
$right = array_pop($stack);
return new PlusExpression($left, $right);
default:
return new TerminalExpression($token);
}
}

public function parse(string $data): Expression
{
$unfinishedData = null;
$stack = [];
$list = explode(' ', $data);
foreach ($list as $token) {
$data = $this->parseToken($token, $stack);
if (
($unfinishedData instanceof NonTerminalExpression) &&
($data instanceof TerminalExpression)
) {
$unfinishedData->setRight($data);
array_push($stack, $unfinishedData);
$unfinishedData = null;
continue;
}
if ($data instanceof NonTerminalExpression) {
if ($data->getRight() === null) {
$unfinishedData = $data;
continue;
}
}
array_push($stack, $data);
}

return array_pop($stack);
}

public function main()
{
$data = "u + v - w + z";
$expr = $this->parse($data);
$context = ['u' => 3, 'v' => 7, 'w' => 35, 'z' => 9];
$res = $expr->interpret($context);
echo "result: $res" . PHP_EOL;
}
}
</syntaxhighlight>
</syntaxhighlight>


Line 473: Line 194:


== External links ==
== External links ==
{{wikibooks|Computer Science Design Patterns|Interpreter|Interpreterimplementations in various languages}}
{{wikibooks|Computer Science Design Patterns|Interpreter}}
* [http://lukaszwrobel.pl/blog/interpreter-design-pattern Interpreter implementation] in [[Ruby (programming language)|Ruby]]
* [http://lukaszwrobel.pl/blog/interpreter-design-pattern Interpreter implementation] in [[Ruby (programming language)|Ruby]]
* [https://github.com/jamesdhutton/Interpreter Interpreter implementation] in [[C++]]
* [https://github.com/jamesdhutton/Interpreter Interpreter implementation] in [[C++]]

Latest revision as of 00:16, 28 March 2024

In computer programming, the interpreter pattern is a design pattern that specifies how to evaluate sentences in a language. The basic idea is to have a class for each symbol (terminal or nonterminal) in a specialized computer language. The syntax tree of a sentence in the language is an instance of the composite pattern and is used to evaluate (interpret) the sentence for a client.[1]: 243  See also Composite pattern.

Overview

[edit]

The Interpreter [2] design pattern is one of the twenty-three well-known GoF design patterns that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse.

What problems can the Interpreter design pattern solve?

[edit]

Source:[3]

  • A grammar for a simple language should be defined
  • so that sentences in the language can be interpreted.

When a problem occurs very often, it could be considered to represent it as a sentence in a simple language (Domain Specific Languages) so that an interpreter can solve the problem by interpreting the sentence.

For example, when many different or complex search expressions must be specified. Implementing (hard-wiring) them directly into a class is inflexible because it commits the class to particular expressions and makes it impossible to specify new expressions or change existing ones independently from (without having to change) the class.

What solution does the Interpreter design pattern describe?

[edit]
  • Define a grammar for a simple language by defining an Expression class hierarchy and implementing an interpret() operation.
  • Represent a sentence in the language by an abstract syntax tree (AST) made up of Expression instances.
  • Interpret a sentence by calling interpret() on the AST.

The expression objects are composed recursively into a composite/tree structure that is called abstract syntax tree (see Composite pattern).
The Interpreter pattern doesn't describe how to build an abstract syntax tree. This can be done either manually by a client or automatically by a parser.

See also the UML class and object diagram below.

Uses

[edit]
  • Specialized database query languages such as SQL.
  • Specialized computer languages that are often used to describe communication protocols.
  • Most general-purpose computer languages actually incorporate several specialized languages[citation needed].

Structure

[edit]

UML class and object diagram

[edit]
A sample UML class and object diagram for the Interpreter design pattern.[4]

In the above UML class diagram, the Client class refers to the common AbstractExpression interface for interpreting an expression interpret(context).
The TerminalExpression class has no children and interprets an expression directly.
The NonTerminalExpression class maintains a container of child expressions (expressions) and forwards interpret requests to these expressions.

The object collaboration diagram shows the run-time interactions: The Client object sends an interpret request to the abstract syntax tree. The request is forwarded to (performed on) all objects downwards the tree structure.
The NonTerminalExpression objects (ntExpr1,ntExpr2) forward the request to their child expressions.
The TerminalExpression objects (tExpr1,tExpr2,…) perform the interpretation directly.

UML class diagram

[edit]

Example

[edit]

This C++11 implementation is based on the pre C++98 sample code in the book.

#include <iostream>
#include <map>
#include <cstring>

class Context;

class BooleanExp {
public:
  BooleanExp() = default;
  virtual ~BooleanExp() = default;
  virtual bool evaluate(Context&) = 0;
  virtual BooleanExp* replace(const char*, BooleanExp&) = 0;
  virtual BooleanExp* copy() const = 0;
};

class VariableExp;

class Context {
public:
  Context() :m() {}
  bool lookup(const VariableExp* key) { return m.at(key); }
  void assign(VariableExp* key, bool value) { m[key] = value; }
private:
  std::map<const VariableExp*, bool> m;
};

class VariableExp : public BooleanExp {
public:
  VariableExp(const char* name_) :name(nullptr) {
    name = strdup(name_);
  }
  virtual ~VariableExp() = default;
  virtual bool evaluate(Context& aContext) {
    return aContext.lookup(this);
  }
  virtual BooleanExp* replace( const char* name_, BooleanExp& exp ) {
    if (0 == strcmp(name_, name)) {
      return exp.copy();
    } else {
      return new VariableExp(name);
    }
  }
  virtual BooleanExp* copy() const {
    return new VariableExp(name);
  }
  VariableExp(const VariableExp&) = delete; // rule of three
  VariableExp& operator=(const VariableExp&) = delete;
private:
  char* name;
};

class AndExp : public BooleanExp {
public:
  AndExp(BooleanExp* op1, BooleanExp* op2)
    :operand1(nullptr), operand2(nullptr) {
    operand1 = op1;
    operand2 = op2;
  }
  virtual ~AndExp() = default;
  virtual bool evaluate(Context& aContext) {
    return operand1->evaluate(aContext) && operand2->evaluate(aContext);
  }
  virtual BooleanExp* replace(const char* name_, BooleanExp& exp) {
    return new AndExp(
        operand1->replace(name_, exp),
        operand2->replace(name_, exp)
      );
  }
  virtual BooleanExp* copy() const {
    return new AndExp(operand1->copy(), operand2->copy());
  }
  AndExp(const AndExp&) = delete; // rule of three
  AndExp& operator=(const AndExp&) = delete;
private:
  BooleanExp* operand1;
  BooleanExp* operand2;
};

int main() {
  BooleanExp* expression;
  Context context;
  VariableExp* x = new VariableExp("X");
  VariableExp* y = new VariableExp("Y");
  expression = new AndExp(x, y);

  context.assign(x, false);
  context.assign(y, true);
  bool result = expression->evaluate(context);
  std::cout << result << '\n';

  context.assign(x, true);
  context.assign(y, true);
  result = expression->evaluate(context);
  std::cout << result << '\n';
}

The program output is:

0
1

See also

[edit]

References

[edit]
  1. ^ Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. ISBN 0-201-63361-2.
  2. ^ Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley. pp. 243ff. ISBN 0-201-63361-2.{{cite book}}: CS1 maint: multiple names: authors list (link)
  3. ^ "The Interpreter design pattern - Problem, Solution, and Applicability". w3sDesign.com. Retrieved 2017-08-12.
  4. ^ "The Interpreter design pattern - Structure and Collaboration". w3sDesign.com. Retrieved 2017-08-12.
[edit]