1.3. Interpreter

1.3.1. Intent

According to the Gang of Four, the Interpreter pattern is defined like that: “Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language” (Design Patterns: Elements of Reusable Object-Oriented Software, 2013, p. 243).

1.3.2. When to use it?

The Interpreter pattern should be used when a language has to be interpreted. It is better if the grammar used is pretty simple and if the efficiency is not a major concern.

1.3.3. Diagram

Created using PhpStorm and yFiles.

Class diagram of the Interpreter pattern

1.3.4. Implementation

ExpressionInterface.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php

namespace Phpatterns\Behavioral\Interpreter;

interface ExpressionInterface
{
    /**
     * @param array $context
     * @return int
     */
    public function interpret(array $context);
}

Multiply.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

namespace Phpatterns\Behavioral\Interpreter\Expression;

use Phpatterns\Behavioral\Interpreter;

class Multiply implements Interpreter\ExpressionInterface
{
    /** @var Interpreter\ExpressionInterface */
    private $left;

    /** @var Interpreter\ExpressionInterface */
    private $right;

    public function __construct(Interpreter\ExpressionInterface $left, Interpreter\ExpressionInterface $right)
    {
        $this->left = $left;
        $this->right = $right;
    }

    public function interpret(array $context)
    {
        return $this->left->interpret($context) * $this->right->interpret($context);
    }
}

Plus.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

namespace Phpatterns\Behavioral\Interpreter\Expression;

use Phpatterns\Behavioral\Interpreter;

class Plus implements Interpreter\ExpressionInterface
{
    /** @var Interpreter\ExpressionInterface */
    private $left;

    /** @var Interpreter\ExpressionInterface */
    private $right;

    public function __construct(Interpreter\ExpressionInterface $left, Interpreter\ExpressionInterface $right)
    {
        $this->left = $left;
        $this->right = $right;
    }

    public function interpret(array $context)
    {
        return $this->left->interpret($context) + $this->right->interpret($context);
    }
}

Number.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

namespace Phpatterns\Behavioral\Interpreter\Expression;

use Phpatterns\Behavioral\Interpreter;

class Number implements Interpreter\ExpressionInterface
{
    /** @var int */
    private $value;

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

    public function interpret(array $context)
    {
        return $this->value;
    }
}

Variable.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

namespace Phpatterns\Behavioral\Interpreter\Expression;

use Phpatterns\Behavioral\Interpreter;

class Variable implements Interpreter\ExpressionInterface
{
    /** @var string */
    private $name;

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

    public function interpret(array $context)
    {
        if (isset($context[$this->name])) {
            return $context[$this->name]->interpret($context);
        }

        throw new \Exception('Missing context value!');
    }
}

Parser.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

namespace Phpatterns\Behavioral\Interpreter;

use Phpatterns\Behavioral\Interpreter\Expression;

class Parser
{
    /** @var ExpressionInterface */
    private $expressionTree;

    /**
     * @param string $strExpression
     */
    public function __construct($strExpression)
    {
        $expressionStack = [];
        foreach (explode(' ', $strExpression) as $part) {
            if ('*' === $part) {
                $right = array_pop($expressionStack);
                $left = array_pop($expressionStack);
                array_push($expressionStack, new Expression\Multiply($left, $right));
            } elseif ('+' === $part) {
                $right = array_pop($expressionStack);
                $left = array_pop($expressionStack);
                array_push($expressionStack, new Expression\Plus($left, $right));
            } elseif (is_numeric($part)) {
                array_push($expressionStack, new Expression\Number($part));
            } else {
                array_push($expressionStack, new Expression\Variable($part));
            }
        }

        $this->expressionTree = array_pop($expressionStack);
    }

    public function interpret(array $context)
    {
        return $this->expressionTree->interpret($context);
    }
}

1.3.5. Tests

InterpreterTest.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php

namespace Test\Phpatterns\Behavioral\Interpreter;

use Phpatterns\Behavioral\Interpreter;
use Phpatterns\Behavioral\Interpreter\Expression;

class InterpreterTest extends \PHPUnit_Framework_TestCase
{
    public function testInterpreterMechanismWithContext()
    {
        $firstContext = [
            'x' => new Expression\Number(9),
            'y' => new Expression\Number(3)
        ];
        $this->assertSame(
            30,
            (new Interpreter\Parser('3 x * y +'))->interpret($firstContext)
        );

        $secondContext = [
            'w' => new Expression\Number(10),
            'x' => new Expression\Number(3),
            'y' => new Expression\Number(7),
            'z' => new Expression\Number(4)
        ];
        $this->assertSame(
            628,
            (new Interpreter\Parser('w 5 x * * y + z *'))->interpret($secondContext)
        );
    }

    public function testInterpreterMechanismWithEmptyContext()
    {
        $this->assertSame(
            22,
            (new Interpreter\Parser('3 5 * 7 +'))->interpret([])
        );

        $this->assertSame(
            2600,
            (new Interpreter\Parser('3 5 2 * * 100 + 20 *'))->interpret([])
        );
    }

    public function testInterpreterMechanismWithMissingContext()
    {
        $this->setExpectedException('Exception', 'Missing context value!');
        (new Interpreter\Parser('3 x * y +'))->interpret(['x' => new Expression\Number(9)]);
    }
}