Completed
Push — master ( ab6622...658a52 )
by Nico
02:02
created

Parser::assignOperator()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 16
cts 16
cp 1
rs 9.3142
c 0
b 0
f 0
cc 3
eloc 15
nc 3
nop 1
crap 3
1
<?php
2
3
/**
4
 * @license     http://opensource.org/licenses/mit-license.php MIT
5
 * @link        https://github.com/nicoSWD
6
 * @author      Nicolas Oelgart <[email protected]>
7
 */
8
declare(strict_types=1);
9
10
namespace nicoSWD\Rules;
11
12
use Closure;
13
use InvalidArgumentException;
14
use nicoSWD\Rules\Core\CallableUserFunction;
15
use nicoSWD\Rules\Tokens\BaseToken;
16
17
class Parser
18
{
19
    /**
20
     * @var array
21
     */
22
    public $variables = [];
23
24
    /**
25
     * @var null|mixed[]
26
     */
27
    protected $values = null;
28
29
    /**
30
     * @var null|string
31
     */
32
    protected $operator =  null;
33
34
    /**
35
     * @var string
36
     */
37
    protected $output = '';
38
39
    /**
40
     * @var bool
41
     */
42
    protected $operatorRequired = false;
43
44
    /**
45
     * @var bool
46
     */
47
    protected $incompleteCondition = false;
48
49
    /**
50
     * @var int
51
     */
52
    protected $openParenthesis = 0;
53
54
    /**
55
     * @var int
56
     */
57
    protected $closedParenthesis = 0;
58
59
    /**
60
     * @var TokenizerInterface
61
     */
62
    protected $tokenizer;
63
64
    /**
65
     * @var Expressions\Factory
66
     */
67
    protected $expressionFactory;
68
69
    protected $userDefinedFunctions = [];
70
71 232
    public function __construct(TokenizerInterface $tokenizer, Expressions\Factory $expressionFactory)
72
    {
73 232
        $this->tokenizer = $tokenizer;
74 232
        $this->expressionFactory = $expressionFactory;
75 232
    }
76
77
    /**
78
     * @throws Exceptions\ParserException
79
     */
80 226
    public function parse(string $rule) : string
81
    {
82 226
        $this->output = '';
83 226
        $this->operator = null;
84 226
        $this->values = null;
85 226
        $this->operatorRequired = false;
86
87 226
        foreach (new AST($this->tokenizer->tokenize($rule), $this) as $token) {
88 188
            switch ($token->getGroup()) {
89 188
                case Constants::GROUP_VALUE:
90 184
                    $this->assignVariableValueFromToken($token);
91 184
                    break;
92 188
                case Constants::GROUP_LOGICAL:
93 36
                    $this->assignLogicalToken($token);
94 36
                    continue 2;
95 188
                case Constants::GROUP_PARENTHESES:
96 22
                    $this->assignParentheses($token);
97 20
                    continue 2;
98 188
                case Constants::GROUP_OPERATOR:
99 184
                    $this->assignOperator($token);
100 182
                    continue 2;
101 186
                case Constants::GROUP_COMMENT:
102 186
                case Constants::GROUP_SPACE:
103 184
                    continue 2;
104
                default:
105 6
                    throw new Exceptions\ParserException(sprintf(
106 6
                        'Unknown token "%s" at position %d on line %d',
107 6
                        $token->getValue(),
108 6
                        $token->getPosition(),
109 6
                        $token->getLine()
110
                    ));
111
            }
112
113 184
            $this->parseExpression();
114
        }
115
116 166
        $this->assertSyntaxSeemsOkay();
117 160
        return $this->output;
118
    }
119
120 226
    public function assignVariables(array $variables)
121
    {
122 226
        $this->variables = $variables;
123 226
    }
124
125
    /**
126
     * @param Tokens\BaseToken $token
127
     * @throws Exceptions\ParserException
128
     */
129 184
    protected function assignVariableValueFromToken(BaseToken $token)
130
    {
131 184
        if ($this->operatorRequired) {
132 2
            throw new Exceptions\ParserException(sprintf(
133 2
                'Missing operator at position %d on line %d',
134 2
                $token->getPosition(),
135 2
                $token->getLine()
136
            ));
137
        }
138
139 184
        $this->operatorRequired = !$this->operatorRequired;
140 184
        $this->incompleteCondition = false;
141
142 184
        if (!isset($this->values)) {
143 184
            $this->values = [$token->getValue()];
144
        } else {
145 180
            $this->values[] = $token->getValue();
146
        }
147 184
    }
148
149
    /**
150
     * @throws Exceptions\ParserException
151
     */
152 22
    protected function assignParentheses(BaseToken $token)
153
    {
154 22
        $tokenValue = $token->getValue();
155
156 22
        if ($tokenValue === '(') {
157 20
            if ($this->operatorRequired) {
158 4
                throw new Exceptions\ParserException(sprintf(
159 4
                    'Unexpected token "(" at position %d on line %d',
160 4
                    $token->getPosition(),
161 4
                    $token->getLine()
162
                ));
163
            }
164
165 20
            $this->openParenthesis++;
166
        } else {
167 20
            if ($this->openParenthesis < 1) {
168 2
                throw new Exceptions\ParserException(sprintf(
169 2
                    'Missing opening parenthesis at position %d on line %d',
170 2
                    $token->getPosition(),
171 2
                    $token->getLine()
172
                ));
173
            }
174
175 18
            $this->closedParenthesis++;
176
        }
177
178 20
        $this->output .= $tokenValue;
179 20
    }
180
181
    /**
182
     * @throws Exceptions\ParserException
183
     */
184 36
    protected function assignLogicalToken(BaseToken $token)
185
    {
186 36
        if (!$this->operatorRequired) {
187 2
            throw new Exceptions\ParserException(sprintf(
188 2
                'Unexpected "%s" at position %d on line %d',
189 2
                $token->getOriginalValue(),
190 2
                $token->getPosition(),
191 2
                $token->getLine()
192
            ));
193
        }
194
195 36
        $this->output .= $token->getValue();
196 36
        $this->incompleteCondition = true;
197 36
        $this->operatorRequired = false;
198 36
    }
199
200
    /**
201
     * @throws Exceptions\ParserException
202
     */
203 184
    protected function assignOperator(BaseToken $token)
204
    {
205 184
        if (isset($this->operator)) {
206 2
            throw new Exceptions\ParserException(sprintf(
207 2
                'Unexpected "%s" at position %d on line %d',
208 2
                $token->getOriginalValue(),
209 2
                $token->getPosition(),
210 2
                $token->getLine()
211
            ));
212 184
        } elseif (!isset($this->values)) {
213 2
            throw new Exceptions\ParserException(sprintf(
214 2
                'Incomplete expression for token "%s" at position %d on line %d',
215 2
                $token->getOriginalValue(),
216 2
                $token->getPosition(),
217 2
                $token->getLine()
218
            ));
219
        }
220
221 182
        $this->operator = $token->getValue();
222 182
        $this->operatorRequired = false;
223 182
    }
224
225
    /**
226
     * @throws Exceptions\ExpressionFactoryException
227
     */
228 184
    protected function parseExpression()
229
    {
230 184
        if (!isset($this->operator) || count($this->values) <> 2) {
231 184
            return;
232
        }
233
234 180
        $this->operatorRequired = true;
235 180
        $expression = $this->expressionFactory->createFromOperator($this->operator);
236 180
        $this->output .= (int) $expression->evaluate($this->values[0], $this->values[1]);
237
238 178
        unset($this->operator, $this->values);
239 178
    }
240
241
    /**
242
     * @throws Exceptions\ParserException
243
     */
244 166
    protected function assertSyntaxSeemsOkay()
245
    {
246 166
        if ($this->incompleteCondition) {
247 2
            throw new Exceptions\ParserException(
248 2
                'Incomplete and/or condition'
249
            );
250 164
        } elseif ($this->openParenthesis > $this->closedParenthesis) {
251 2
            throw new Exceptions\ParserException(
252 2
                'Missing closing parenthesis'
253
            );
254 162
        } elseif (isset($this->operator) || (isset($this->values) && count($this->values) > 0)) {
255 2
            throw new Exceptions\ParserException(
256 2
                'Incomplete expression'
257
            );
258
        }
259 160
    }
260
261 4
    public function registerFunctionClass(string $className)
262
    {
263
        /** @var CallableUserFunction $class */
264 4
        $class = new $className();
265
266 4
        if (!$class instanceof CallableUserFunction) {
267 2
            throw new InvalidArgumentException(
268 2
                sprintf(
269 2
                    "%s must be an instance of %s",
270 2
                    $className,
271 2
                    CallableUserFunction::class
272
                )
273
            );
274
        }
275
276 2
        $this->registerFunction($class->getName(), function () use ($class) {
277 2
            return $class->call(...func_get_args());
0 ignored issues
show
Documentation introduced by
func_get_args() is of type array, but the function expects a object<nicoSWD\Rules\Tokens\BaseToken>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
278 2
        });
279 2
    }
280
281 4
    public function registerFunction(string $name, Closure $callback)
282
    {
283 4
        $this->userDefinedFunctions[$name] = $callback;
284 4
    }
285
286 2
    public function registerToken(string $token, string $regex, int $priority = 10)
287
    {
288 2
        $this->tokenizer->registerToken($token, $regex, $priority);
289 2
    }
290
291
    /**
292
     * @param string $name
293
     * @return Closure|null
294
     */
295 6
    public function getFunction(string $name)
296
    {
297 6
        return isset($this->userDefinedFunctions[$name])
298 4
            ? $this->userDefinedFunctions[$name]
299 6
            : null;
300
    }
301
}
302