Completed
Push — master ( 8e5fc7...2c2d1f )
by Nico
01:28
created

Parser::parse()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 39
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 39
ccs 30
cts 30
cp 1
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 31
nc 8
nop 1
crap 8
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 230
    protected $userDefinedFunctions = [];
70
71 230
    public function __construct(TokenizerInterface $tokenizer, Expressions\Factory $expressionFactory)
72 230
    {
73 230
        $this->tokenizer = $tokenizer;
74
        $this->expressionFactory = $expressionFactory;
75
    }
76
77
    /**
78 224
     * @throws Exceptions\ParserException
79
     */
80 224
    public function parse(string $rule) : string
81 224
    {
82 224
        $this->output = '';
83 224
        $this->operator = null;
84
        $this->values = null;
85 224
        $this->operatorRequired = false;
86 186
87 186
        foreach (new AST($this->tokenizer->tokenize($rule), $this) as $token) {
88 182
            switch ($token->getGroup()) {
89 182
                case Constants::GROUP_VALUE:
90 186
                    $this->assignVariableValueFromToken($token);
91 36
                    break;
92 36
                case Constants::GROUP_LOGICAL:
93 186
                    $this->assignLogicalToken($token);
94 22
                    continue 2;
95 20
                case Constants::GROUP_PARENTHESES:
96 186
                    $this->assignParentheses($token);
97 182
                    continue 2;
98 180
                case Constants::GROUP_OPERATOR:
99 184
                    $this->assignOperator($token);
100 184
                    continue 2;
101 182
                case Constants::GROUP_COMMENT:
102
                case Constants::GROUP_SPACE:
103 6
                    continue 2;
104 6
                default:
105 6
                    throw new Exceptions\ParserException(sprintf(
106 6
                        'Unknown token "%s" at position %d on line %d',
107 6
                        $token->getValue(),
108
                        $token->getPosition(),
109
                        $token->getLine()
110
                    ));
111 182
            }
112
113
            $this->parseExpression();
114 164
        }
115 158
116
        $this->assertSyntaxSeemsOkay();
117
        return $this->output;
118 224
    }
119
120 224
    public function assignVariables(array $variables)
121 224
    {
122
        $this->variables = $variables;
123
    }
124
125
    /**
126
     * @param Tokens\BaseToken $token
127 182
     * @throws Exceptions\ParserException
128
     */
129 182
    protected function assignVariableValueFromToken(BaseToken $token)
130 2
    {
131 2
        if ($this->operatorRequired) {
132 2
            throw new Exceptions\ParserException(sprintf(
133 2
                'Missing operator at position %d on line %d',
134
                $token->getPosition(),
135
                $token->getLine()
136
            ));
137 182
        }
138 182
139
        $this->operatorRequired = !$this->operatorRequired;
140 182
        $this->incompleteCondition = false;
141 182
142
        if (!isset($this->values)) {
143 178
            $this->values = [$token->getValue()];
144
        } else {
145 182
            $this->values[] = $token->getValue();
146
        }
147
    }
148
149
    /**
150 22
     * @throws Exceptions\ParserException
151
     */
152 22
    protected function assignParentheses(BaseToken $token)
153
    {
154 22
        $tokenValue = $token->getValue();
155 20
156 4
        if ($tokenValue === '(') {
157 4
            if ($this->operatorRequired) {
158 4
                throw new Exceptions\ParserException(sprintf(
159 4
                    'Unexpected token "(" at position %d on line %d',
160
                    $token->getPosition(),
161
                    $token->getLine()
162
                ));
163 20
            }
164
165 20
            $this->openParenthesis++;
166 2
        } else {
167 2
            if ($this->openParenthesis < 1) {
168 2
                throw new Exceptions\ParserException(sprintf(
169 2
                    'Missing opening parenthesis at position %d on line %d',
170
                    $token->getPosition(),
171
                    $token->getLine()
172
                ));
173 18
            }
174
175
            $this->closedParenthesis++;
176 20
        }
177 20
178
        $this->output .= $tokenValue;
179
    }
180
181
    /**
182 36
     * @throws Exceptions\ParserException
183
     */
184 36
    protected function assignLogicalToken(BaseToken $token)
185 2
    {
186 2
        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
                $token->getPosition(),
191
                $token->getLine()
192
            ));
193 36
        }
194 36
195 36
        $this->output .= $token->getValue();
196 36
        $this->incompleteCondition = true;
197
        $this->operatorRequired = false;
198
    }
199
200
    /**
201 182
     * @throws Exceptions\ParserException
202
     */
203 182
    protected function assignOperator(BaseToken $token)
204 2
    {
205 2
        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
                $token->getPosition(),
210 182
                $token->getLine()
211 2
            ));
212 2
        } 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
                $token->getPosition(),
217
                $token->getLine()
218
            ));
219 180
        }
220 180
221 180
        $this->operator = $token->getValue();
222
        $this->operatorRequired = false;
223
    }
224
225
    /**
226 182
     * @throws Exceptions\ExpressionFactoryException
227
     */
228 182
    protected function parseExpression()
229 182
    {
230
        if (!isset($this->operator) || count($this->values) <> 2) {
231
            return;
232 178
        }
233 178
234 178
        $this->operatorRequired = true;
235
        $expression = $this->expressionFactory->createFromOperator($this->operator);
236 176
        $this->output .= (int) $expression->evaluate($this->values[0], $this->values[1]);
237 176
238
        unset($this->operator, $this->values);
239
    }
240
241
    /**
242 164
     * @throws Exceptions\ParserException
243
     */
244 164
    protected function assertSyntaxSeemsOkay()
245 2
    {
246 2
        if ($this->incompleteCondition) {
247
            throw new Exceptions\ParserException(
248 162
                'Incomplete and/or condition'
249 2
            );
250 2
        } elseif ($this->openParenthesis > $this->closedParenthesis) {
251
            throw new Exceptions\ParserException(
252 160
                'Missing closing parenthesis'
253 2
            );
254 2
        } elseif (isset($this->operator) || (isset($this->values) && count($this->values) > 0)) {
255
            throw new Exceptions\ParserException(
256
                'Incomplete expression'
257 158
            );
258
        }
259 2
    }
260
261 2
    public function registerFunctionClass(string $className)
262 2
    {
263
        /** @var CallableUserFunction $class */
264 2
        $class = new $className();
265
266 2
        if (!$class instanceof CallableUserFunction) {
267 2
            throw new InvalidArgumentException(
268
                sprintf(
269
                    "%s must be an instance of %s",
270
                    get_class($className),
271
                    CallableUserFunction::class
272
                )
273 4
            );
274
        }
275 4
276 2
        $this->registerFunction($class->getName(), function () use ($class) {
277 4
            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
        });
279
    }
280
281
    public function registerFunction(string $name, Closure $callback)
282
    {
283
        $this->userDefinedFunctions[$name] = $callback;
284
    }
285
286
    public function registerToken(string $token, string $regex, int $priority = 10)
287
    {
288
        $this->tokenizer->registerToken($token, $regex, $priority);
289
    }
290
291
    /**
292
     * @param string $name
293
     * @return Closure|null
294
     */
295
    public function getFunction(string $name)
296
    {
297
        return isset($this->userDefinedFunctions[$name])
298
            ? $this->userDefinedFunctions[$name]
299
            : null;
300
    }
301
}
302