Completed
Push — master ( bb08e4...b9bb4c )
by Nico
01:40
created

Parser::evaluateExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @license     http://opensource.org/licenses/mit-license.php MIT
7
 * @link        https://github.com/nicoSWD
8
 * @author      Nicolas Oelgart <[email protected]>
9
 */
10
namespace nicoSWD\Rule\Parser;
11
12
use Closure;
13
use nicoSWD\Rule\Compiler\CompilerInterface;
14
use nicoSWD\Rule\Compiler\CompilerFactoryInterface;
15
use nicoSWD\Rule\Expression\ExpressionFactoryInterface;
16
use nicoSWD\Rule\TokenStream\AST;
17
use nicoSWD\Rule\TokenStream\Token\BaseToken;
18
use nicoSWD\Rule\TokenStream\Token\TokenType;
19
20
class Parser
21
{
22
    /** @var AST */
23
    private $ast;
24
    /** @var ExpressionFactoryInterface */
25
    private $expressionFactory;
26
    /** @var CompilerFactoryInterface */
27
    private $compilerFactory;
28
    /** @var BaseToken|null */
29
    private $operator;
30
    /** @var mixed[] */
31
    private $values = [];
32
33 220
    public function __construct(
34
        AST $ast,
35
        ExpressionFactoryInterface $expressionFactory,
36
        CompilerFactoryInterface $compilerFactory
37
    ) {
38 220
        $this->ast = $ast;
39 220
        $this->expressionFactory = $expressionFactory;
40 220
        $this->compilerFactory = $compilerFactory;
41 220
    }
42
43 220
    public function parse(string $rule): string
44
    {
45 220
        $compiler = $this->compilerFactory->create();
46 220
        $this->resetState();
47
48 220
        foreach ($this->ast->getStream($rule) as $token) {
49 186
            $handler = $this->getHandlerForType($token->getType());
50 186
            $handler($token, $compiler);
51
52 184
            if ($this->expressionCanBeEvaluated()) {
53 184
                $this->evaluateExpression($compiler);
54
            }
55
        }
56
57 162
        return $compiler->getCompiledRule();
58
    }
59
60 186
    private function getHandlerForType(int $tokenType): Closure
61
    {
62
        $handlers = [
63 186
            TokenType::VALUE       => $this->handleValueToken(),
64 186
            TokenType::INT_VALUE   => $this->handleValueToken(),
65 186
            TokenType::OPERATOR    => $this->handleOperatorToken(),
66 186
            TokenType::LOGICAL     => $this->handleLogicalToken(),
67 186
            TokenType::PARENTHESIS => $this->handleParenthesisToken(),
68 186
            TokenType::SPACE       => $this->handleDummyToken(),
69 186
            TokenType::COMMENT     => $this->handleDummyToken(),
70 186
            TokenType::UNKNOWN     => $this->handleUnknownToken(),
71
        ];
72
73 186
        return $handlers[$tokenType] ?? $handlers[TokenType::UNKNOWN];
74
    }
75
76 176
    private function evaluateExpression(CompilerInterface $compiler)
77
    {
78 176
        $expression = $this->expressionFactory->createFromOperator($this->operator);
0 ignored issues
show
Bug introduced by
It seems like $this->operator can be null; however, createFromOperator() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
79
80 176
        $compiler->addBoolean(
81 176
            $expression->evaluate(...$this->values)
0 ignored issues
show
Bug introduced by
The call to evaluate() misses a required argument $rightValue.

This check looks for function calls that miss required arguments.

Loading history...
82
        );
83
84 174
        $this->resetState();
85 174
    }
86
87 184
    private function expressionCanBeEvaluated(): bool
88
    {
89 184
        return isset($this->operator) && count($this->values) === 2;
90
    }
91
92
    private function handleValueToken(): Closure
93
    {
94 186
        return function (BaseToken $token) {
95 180
            $this->values[] = $token->getValue();
96 186
        };
97
    }
98
99
    private function handleOperatorToken(): Closure
100
    {
101 186
        return function (BaseToken $token) {
102 180
            if (isset($this->operator)) {
103 2
                throw Exception\ParserException::unexpectedToken($token);
104 180
            } elseif (empty($this->values)) {
105 2
                throw Exception\ParserException::incompleteExpression($token);
106
            }
107
108 178
            $this->operator = $token;
109 186
        };
110
    }
111
112
    private function handleLogicalToken(): Closure
113
    {
114 186
        return function (BaseToken $token, CompilerInterface $compiler) {
115 38
            $compiler->addLogical($token);
116 186
        };
117
    }
118
119
    private function handleParenthesisToken(): Closure
120
    {
121 186
        return function (BaseToken $token, CompilerInterface $compiler) {
122 24
            $compiler->addParentheses($token);
123 186
        };
124
    }
125
126
    private function handleUnknownToken(): Closure
127
    {
128 186
        return function (BaseToken $token) {
129 6
            throw Exception\ParserException::unknownToken($token);
130 186
        };
131
    }
132
133
    private function handleDummyToken(): Closure
134
    {
135 186
        return function () {
136
            // Do nothing
137 186
        };
138
    }
139
140 220
    private function resetState()
141
    {
142 220
        $this->operator = null;
143 220
        $this->values = [];
144 220
    }
145
}
146