1 | <?php |
||
15 | class Parser |
||
16 | { |
||
17 | /** |
||
18 | * @var array |
||
19 | */ |
||
20 | public $variables = []; |
||
21 | |||
22 | /** |
||
23 | * @var null|mixed[] |
||
24 | */ |
||
25 | protected $values = null; |
||
26 | |||
27 | /** |
||
28 | * @var null|string |
||
29 | */ |
||
30 | protected $operator = null; |
||
31 | |||
32 | /** |
||
33 | * @var string |
||
34 | */ |
||
35 | protected $output = ''; |
||
36 | |||
37 | /** |
||
38 | * @var bool |
||
39 | */ |
||
40 | protected $operatorRequired = false; |
||
41 | |||
42 | /** |
||
43 | * @var bool |
||
44 | */ |
||
45 | protected $incompleteCondition = false; |
||
46 | |||
47 | /** |
||
48 | * @var int |
||
49 | */ |
||
50 | protected $openParenthesis = 0; |
||
51 | |||
52 | /** |
||
53 | * @var int |
||
54 | */ |
||
55 | protected $closedParenthesis = 0; |
||
56 | |||
57 | /** |
||
58 | * @var TokenizerInterface |
||
59 | */ |
||
60 | protected $tokenizer; |
||
61 | |||
62 | /** |
||
63 | * @var Expressions\Factory |
||
64 | */ |
||
65 | protected $expressionFactory; |
||
66 | |||
67 | protected $userDefinedFunctions = []; |
||
68 | |||
69 | public function __construct(TokenizerInterface $tokenizer, Expressions\Factory $expressionFactory) |
||
74 | 224 | ||
75 | 224 | /** |
|
76 | * @throws Exceptions\ParserException |
||
77 | */ |
||
78 | public function parse(string $rule) : string |
||
117 | |||
118 | 158 | public function assignVariables(array $variables) |
|
122 | |||
123 | /** |
||
124 | * @param string $class |
||
125 | 218 | * @param string $regex |
|
126 | * @param int $priority |
||
127 | 218 | */ |
|
128 | 218 | public function registerToken($class, $regex, $priority = null) |
|
129 | { |
||
130 | $this->tokenizer->registerToken($class, $regex, $priority); |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * @param Tokens\BaseToken $token |
||
135 | 2 | * @throws Exceptions\ParserException |
|
136 | */ |
||
137 | 2 | protected function assignVariableValueFromToken(BaseToken $token) |
|
138 | 2 | { |
|
139 | if ($this->operatorRequired) { |
||
140 | throw new Exceptions\ParserException(sprintf( |
||
141 | 'Missing operator at position %d on line %d', |
||
142 | $token->getPosition(), |
||
143 | $token->getLine() |
||
144 | 176 | )); |
|
145 | } |
||
146 | 176 | ||
147 | 2 | $this->operatorRequired = !$this->operatorRequired; |
|
148 | 2 | $this->incompleteCondition = false; |
|
149 | 2 | ||
150 | 2 | if (!isset($this->values)) { |
|
151 | 2 | $this->values = [$token->getValue()]; |
|
152 | } else { |
||
153 | $this->values[] = $token->getValue(); |
||
154 | 176 | } |
|
155 | 176 | } |
|
156 | |||
157 | 176 | /** |
|
158 | 176 | * @throws Exceptions\ParserException |
|
159 | 176 | */ |
|
160 | 172 | protected function assignParentheses(BaseToken $token) |
|
161 | { |
||
162 | 176 | $tokenValue = $token->getValue(); |
|
163 | |||
164 | if ($tokenValue === '(') { |
||
165 | if ($this->operatorRequired) { |
||
166 | throw new Exceptions\ParserException(sprintf( |
||
167 | 'Unexpected token "(" at position %d on line %d', |
||
168 | 22 | $token->getPosition(), |
|
169 | $token->getLine() |
||
170 | 22 | )); |
|
171 | } |
||
172 | 22 | ||
173 | 20 | $this->openParenthesis++; |
|
174 | 4 | } else { |
|
175 | 4 | if ($this->openParenthesis < 1) { |
|
176 | 4 | throw new Exceptions\ParserException(sprintf( |
|
177 | 4 | 'Missing opening parenthesis at position %d on line %d', |
|
178 | 4 | $token->getPosition(), |
|
179 | $token->getLine() |
||
180 | )); |
||
181 | 20 | } |
|
182 | 20 | ||
183 | 20 | $this->closedParenthesis++; |
|
184 | 2 | } |
|
185 | 2 | ||
186 | 2 | $this->output .= $tokenValue; |
|
187 | 2 | } |
|
188 | 2 | ||
189 | /** |
||
190 | * @throws Exceptions\ParserException |
||
191 | 18 | */ |
|
192 | protected function assignLogicalToken(BaseToken $token) |
||
193 | { |
||
194 | 20 | if (!$this->operatorRequired) { |
|
195 | 20 | throw new Exceptions\ParserException(sprintf( |
|
196 | 'Unexpected "%s" at position %d on line %d', |
||
197 | $token->getOriginalValue(), |
||
198 | $token->getPosition(), |
||
199 | $token->getLine() |
||
200 | )); |
||
201 | 36 | } |
|
202 | |||
203 | 36 | $this->output .= $token->getValue(); |
|
204 | 2 | $this->incompleteCondition = true; |
|
205 | 2 | $this->operatorRequired = false; |
|
206 | 2 | } |
|
207 | 2 | ||
208 | 2 | /** |
|
209 | 2 | * @throws Exceptions\ParserException |
|
210 | */ |
||
211 | protected function assignOperator(BaseToken $token) |
||
212 | 36 | { |
|
213 | 36 | if (isset($this->operator)) { |
|
214 | 36 | throw new Exceptions\ParserException(sprintf( |
|
215 | 36 | 'Unexpected "%s" at position %d on line %d', |
|
216 | $token->getOriginalValue(), |
||
217 | $token->getPosition(), |
||
218 | $token->getLine() |
||
219 | )); |
||
220 | } elseif (!isset($this->values)) { |
||
221 | 176 | throw new Exceptions\ParserException(sprintf( |
|
222 | 'Incomplete expression for token "%s" at position %d on line %d', |
||
223 | 176 | $token->getOriginalValue(), |
|
224 | 2 | $token->getPosition(), |
|
225 | 2 | $token->getLine() |
|
226 | 2 | )); |
|
227 | 2 | } |
|
228 | 2 | ||
229 | 2 | $this->operator = $token->getValue(); |
|
230 | 176 | $this->operatorRequired = false; |
|
231 | 2 | } |
|
232 | 2 | ||
233 | 2 | /** |
|
234 | 2 | * @throws Exceptions\ExpressionFactoryException |
|
235 | 2 | */ |
|
236 | 2 | protected function parseExpression() |
|
237 | { |
||
238 | if (!isset($this->operator) || count($this->values) <> 2) { |
||
239 | 174 | return; |
|
240 | 174 | } |
|
241 | 174 | ||
242 | $this->operatorRequired = true; |
||
243 | $expression = $this->expressionFactory->createFromOperator($this->operator); |
||
244 | $this->output .= (int) $expression->evaluate($this->values[0], $this->values[1]); |
||
245 | |||
246 | 176 | unset($this->operator, $this->values); |
|
247 | } |
||
248 | 176 | ||
249 | 176 | /** |
|
250 | * @throws Exceptions\ParserException |
||
251 | */ |
||
252 | 172 | protected function assertSyntaxSeemsOkay() |
|
253 | 172 | { |
|
254 | 172 | if ($this->incompleteCondition) { |
|
255 | throw new Exceptions\ParserException( |
||
256 | 170 | 'Incomplete and/or condition' |
|
257 | 170 | ); |
|
258 | } elseif ($this->openParenthesis > $this->closedParenthesis) { |
||
259 | throw new Exceptions\ParserException( |
||
260 | 'Missing closing parenthesis' |
||
261 | ); |
||
262 | 158 | } elseif (isset($this->operator) || (isset($this->values) && count($this->values) > 0)) { |
|
263 | throw new Exceptions\ParserException( |
||
264 | 158 | 'Incomplete expression' |
|
265 | 2 | ); |
|
266 | } |
||
267 | 2 | } |
|
268 | 156 | ||
269 | 2 | public function registerFunction(string $name, Closure $callback) |
|
270 | { |
||
271 | 2 | $this->userDefinedFunctions[$name] = $callback; |
|
272 | 154 | } |
|
273 | 2 | ||
274 | public function registerToken(string $token, string $regex, int $priority = 10) |
||
278 | |||
279 | /** |
||
280 | * @param string $name |
||
281 | * @return Closure|null |
||
282 | */ |
||
283 | public function getFunction(string $name) |
||
284 | { |
||
285 | return isset($this->userDefinedFunctions[$name]) |
||
289 | } |
||
290 |