Executor::defaultFunctions()   C
last analyzed

Complexity

Conditions 15
Paths 1

Size

Total Lines 66
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 45
c 1
b 0
f 0
dl 0
loc 66
rs 5.9166
cc 15
nc 1
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Platine Expression
5
 *
6
 * Platine Expression is an expression parser, evaluator with support of custom
7
 * operators and functions
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Expression
12
 * Copyright (c) Alexander Kiryukhin
13
 *
14
 * Permission is hereby granted, free of charge, to any person obtaining a copy
15
 * of this software and associated documentation files (the "Software"), to deal
16
 * in the Software without restriction, including without limitation the rights
17
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
 * copies of the Software, and to permit persons to whom the Software is
19
 * furnished to do so, subject to the following conditions:
20
 *
21
 * The above copyright notice and this permission notice shall be included in all
22
 * copies or substantial portions of the Software.
23
 *
24
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
 * SOFTWARE.
31
 */
32
33
/**
34
 * @file Executor.php
35
 *
36
 * The Executor class
37
 *
38
 *  @package    Platine\Expression
39
 *  @author Platine Developers Team
40
 *  @copyright  Copyright (c) 2020
41
 *  @license    http://opensource.org/licenses/MIT  MIT License
42
 *  @link   https://www.platine-php.com
43
 *  @version 1.0.0
44
 *  @filesource
45
 */
46
declare(strict_types=1);
47
48
namespace Platine\Expression;
49
50
use InvalidArgumentException;
51
use Platine\Expression\Exception\DivisionByZeroException;
52
use Platine\Expression\Exception\UnknownVariableException;
53
54
/**
55
 * @class Executor
56
 * @package Platine\Expression
57
 */
58
class Executor
59
{
60
    /**
61
     * The variable list
62
     * @var array<string, mixed>
63
     */
64
    protected array $variables = [];
65
66
    /**
67
     * The callable that will be called if variable not found
68
     * @var callable|null
69
     */
70
    protected $variableNotFoundHandler = null;
71
72
    /**
73
     * The callable that will be called for variable validation
74
     * @var callable|null
75
     */
76
    protected $variableValidationHandler = null;
77
78
    /**
79
     * The list of operators
80
     * @var array<string, Operator>
81
     */
82
    protected array $operators = [];
83
84
    /**
85
     * The list of functions
86
     * @var array<string, CustomFunction>
87
     */
88
    protected array $functions = [];
89
90
    /**
91
     * The list of cache
92
     * @var array<string, Token[]>
93
     */
94
    protected array $caches = [];
95
96
    /**
97
     * Create new instance
98
     */
99
    public function __construct()
100
    {
101
        $this->addDefaults();
102
    }
103
104
    /**
105
     * When do clone of this object
106
     */
107
    public function __clone()
108
    {
109
        $this->addDefaults();
110
    }
111
112
    /**
113
     * Execute the expression and return the result
114
     * @param string $expression
115
     * @param bool $cache
116
     * @return mixed
117
     */
118
    public function execute(string $expression, bool $cache = true): mixed
119
    {
120
        $cacheKey = $expression;
121
        if (!array_key_exists($cacheKey, $this->caches)) {
122
            $tokens = (new Tokenizer($expression, $this->operators))
123
                       ->tokenize()
124
                       ->buildReversePolishNotation();
125
126
            if ($cache) {
127
                $this->caches[$cacheKey] = $tokens;
128
            }
129
        } else {
130
            $tokens = $this->caches[$cacheKey];
131
        }
132
133
        $calculator = new Calculator($this->functions, $this->operators);
134
135
        return $calculator->calculate(
136
            $tokens,
137
            $this->variables,
138
            $this->variableNotFoundHandler
139
        );
140
    }
141
142
    /**
143
     * Add new operator
144
     * @param Operator $operator
145
     * @return $this
146
     */
147
    public function addOperator(Operator $operator): self
148
    {
149
        $this->operators[$operator->getOperator()] = $operator;
150
        return $this;
151
    }
152
153
    /**
154
     * Add new function
155
     * @param string $name
156
     * @param callable $function
157
     * @return $this
158
     */
159
    public function addFunction(string $name, callable $function): self
160
    {
161
        $this->functions[$name] = new CustomFunction($name, $function);
162
        return $this;
163
    }
164
165
    /**
166
     * Return the list of variables
167
     * @return array<string, int|float>
168
     */
169
    public function getVariables(): array
170
    {
171
        return $this->variables;
172
    }
173
174
    /**
175
     * Return the value for the given variable name
176
     * @param string $name
177
     * @return mixed
178
     */
179
    public function getVariable(string $name): mixed
180
    {
181
        if (! array_key_exists($name, $this->variables)) {
182
            if ($this->variableNotFoundHandler !== null) {
183
                return call_user_func($this->variableNotFoundHandler, $name);
184
            }
185
186
            throw new UnknownVariableException(sprintf(
187
                'Unknown variable [%s]',
188
                $name
189
            ));
190
        }
191
192
        return $this->variables[$name];
193
    }
194
195
    /**
196
     * Set the variable to be used later
197
     * @param string $name
198
     * @param mixed $value
199
     * @return $this
200
     */
201
    public function setVariable(string $name, mixed $value): self
202
    {
203
        if ($this->variableValidationHandler !== null) {
204
            call_user_func($this->variableValidationHandler, $name, $value);
205
        }
206
        $this->variables[$name] = $value;
207
208
        return $this;
209
    }
210
211
    /**
212
     * Set the variables using array
213
     * @param array<string, mixed> $variables
214
     * @param bool $clear whether to clear all existing variables
215
     * @return $this
216
     */
217
    public function setVariables(array $variables, bool $clear = true): self
218
    {
219
        if ($clear) {
220
            $this->clearVariables();
221
        }
222
223
        foreach ($variables as $name => $value) {
224
            $this->setVariable($name, $value);
225
        }
226
227
        return $this;
228
    }
229
230
    /**
231
     * Check whether the given variable exists
232
     * @param string $name
233
     * @return bool
234
     */
235
    public function variableExist(string $name): bool
236
    {
237
        return array_key_exists($name, $this->variables);
238
    }
239
240
    /**
241
     * Remove the given variable
242
     * @param string $name
243
     * @return $this
244
     */
245
    public function removeVariable(string $name): self
246
    {
247
        unset($this->variables[$name]);
248
249
        return $this;
250
    }
251
252
    /**
253
     * Remove the given operator
254
     * @param string $name
255
     * @return $this
256
     */
257
    public function removeOperator(string $name): self
258
    {
259
        unset($this->operators[$name]);
260
261
        return $this;
262
    }
263
264
    /**
265
     * Clear all variables
266
     * @return $this
267
     */
268
    public function clearVariables(): self
269
    {
270
        $this->variables = [];
271
        $this->variableNotFoundHandler = null;
272
273
        return $this;
274
    }
275
276
    /**
277
     * Set the callable to be used for variable not found
278
     * @param callable $handler
279
     * @return $this
280
     */
281
    public function setVariableNotFoundHandler(callable $handler): self
282
    {
283
        $this->variableNotFoundHandler = $handler;
284
        return $this;
285
    }
286
287
    /**
288
     * Set the callable to be used for variable validation
289
     * @param callable $handler
290
     * @return $this
291
     */
292
    public function setVariableValidationHandler(callable $handler): self
293
    {
294
        $this->variableValidationHandler = $handler;
295
        return $this;
296
    }
297
298
    /**
299
     * Return the variable not found handler
300
     * @return callable|null
301
     */
302
    public function getVariableNotFoundHandler(): ?callable
303
    {
304
        return $this->variableNotFoundHandler;
305
    }
306
307
    /**
308
     * Return the variable validation handler
309
     * @return callable|null
310
     */
311
    public function getVariableValidationHandler(): ?callable
312
    {
313
        return $this->variableValidationHandler;
314
    }
315
316
    /**
317
     * Return the list of caches
318
     * @return array<string, Token[]>
319
     */
320
    public function getCaches(): array
321
    {
322
        return $this->caches;
323
    }
324
325
326
    /**
327
     * Return the list of operators
328
     * @return array<string, Operator>
329
     */
330
    public function getOperators(): array
331
    {
332
        return $this->operators;
333
    }
334
335
    /**
336
     * Return the list of functions
337
     * @return array<string, CustomFunction>
338
     */
339
    public function getFunctions(): array
340
    {
341
        return $this->functions;
342
    }
343
344
345
    /**
346
     * Add the default values like variables, operators, functions
347
     * @return $this
348
     */
349
    protected function addDefaults(): self
350
    {
351
        foreach ($this->defaultOperators() as $name => $operator) {
352
            [$callable, $priority, $isRightAssociative] = $operator;
353
            $this->addOperator(new Operator($name, $isRightAssociative, $priority, $callable));
354
        }
355
356
        foreach ($this->defaultFunctions() as $name => $callable) {
357
            $this->addFunction($name, $callable);
358
        }
359
360
        $this->variables = $this->defaultVariables();
361
362
        return $this;
363
    }
364
365
    /**
366
     * Return the list of default operators
367
     * @return array<string, array{callable, int, bool}>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, array{callable, int, bool}> at position 6 could not be parsed: Expected ':' at position 6, but found 'callable'.
Loading history...
368
     */
369
    protected function defaultOperators(): array
370
    {
371
        return [
372
            '+' => [static fn($a, $b) => $a + $b, 170, false],
373
            '-' => [static fn($a, $b) => $a - $b, 170, false],
374
            // unary positive token
375
            'uPos' => [static fn($a) => $a, 200, false],
376
            // unary minus token
377
            'uNeg' => [static fn($a) => 0 - $a, 200, false],
378
            '*' => [static fn($a, $b) => $a * $b, 180, false],
379
            '/' => [
380
                static function ($a, $b) {
381
                    if ($b == 0) {
382
                        throw new DivisionByZeroException();
383
                    }
384
385
                    return $a / $b;
386
                },
387
                180,
388
                false
389
            ],
390
            '^' => [static fn($a, $b) => pow($a, $b), 220, true],
391
            '%' => [static fn($a, $b) => $a % $b, 180, false],
392
            '&&' => [static fn($a, $b) => $a && $b, 100, false],
393
            '||' => [static fn($a, $b) => $a || $b, 90, false],
394
            '==' => [static fn($a, $b) => is_string($a) || is_string($b) ? strcmp($a, $b) == 0 : $a == $b, 140, false],
395
            '!=' => [static fn($a, $b) => is_string($a) || is_string($b) ? strcmp($a, $b) != 0 : $a != $b, 140, false],
396
            '>=' => [static fn($a, $b) => $a >= $b, 150, false],
397
            '>' => [static fn($a, $b) => $a > $b, 150, false],
398
            '<=' => [static fn($a, $b) => $a <= $b, 150, false],
399
            '<' => [static fn($a, $b) => $a < $b, 150, false],
400
            '!' => [static fn($a) => ! $a, 190, false],
401
        ];
402
    }
403
404
    /**
405
     * Return the list of default functions
406
     * @return array<string, callable>
407
     */
408
    protected function defaultFunctions(): array
409
    {
410
        return [
411
            'abs' => static function ($arg) {
412
                if ((int) $arg == $arg) {
413
                    return abs(intval($arg));
414
                }
415
                return abs(floatval($arg));
416
            },
417
            'array' => static fn(...$args) => $args,
418
            'avg' => static function ($arg1, ...$args) {
419
                if (is_array($arg1)) {
420
                    if (count($arg1) === 0) {
421
                        throw new InvalidArgumentException('Array must contains at least one element');
422
                    }
423
424
                    return array_sum($arg1) / count($arg1);
425
                }
426
427
                $args = [$arg1, ...array_values($args)];
428
                return array_sum($args) / count($args);
429
            },
430
            'ceil' => static function ($arg) {
431
                if ((int) $arg == $arg) {
432
                    return ceil(intval($arg));
433
                }
434
                return ceil(floatval($arg));
435
            },
436
            'floor' => static function ($arg) {
437
                if ((int) $arg == $arg) {
438
                    return floor(intval($arg));
439
                }
440
                return floor(floatval($arg));
441
            },
442
            'exp' => static fn($arg) => exp(floatval($arg)),
443
            'ln' => static fn($arg) => log(floatval($arg)),
444
            'lg' => static fn($arg) => log10($arg),
445
            'log' => static fn($arg) => log(floatval($arg)),
446
            'log10' => static fn($arg) => log10(floatval($arg)),
447
            'log1p' => static fn($arg) => log1p(floatval($arg)),
448
            'fmod' => static fn($arg1, $arg2) => fmod(floatval($arg1), floatval($arg2)),
449
            'sqrt' => static fn($arg) => sqrt(floatval($arg)),
450
            'hypot' => static fn($arg1, $arg2) => hypot(floatval($arg1), floatval($arg2)),
451
            'intdiv' => static fn($arg1, $arg2) => intdiv(intval($arg1), intval($arg2)),
452
            'max' => static function ($arg1, ...$args) {
453
                if (is_array($arg1) && count($arg1) === 0) {
454
                    throw new InvalidArgumentException('Array must contains at least one element');
455
                }
456
457
                return max(is_array($arg1) && count($arg1) > 0 ? $arg1 : [$arg1, ...array_values($args)]);
458
            },
459
            'min' => static function ($arg1, ...$args) {
460
                if (is_array($arg1) && count($arg1) === 0) {
461
                    throw new InvalidArgumentException('Array must contains at least one element');
462
                }
463
464
                return min(is_array($arg1) && count($arg1) > 0  ? $arg1 : [$arg1, ...array_values($args)]);
465
            },
466
            'pow' => static fn($arg1, $arg2) => $arg1 ** $arg2,
467
            'round' => static function ($arg, int $precision = 0) {
468
                if ((int) $arg == $arg) {
469
                    return round(intval($arg), intval($precision));
470
                }
471
                return round(floatval($arg), intval($precision));
472
            },
473
            'pi' => static fn() => M_PI,
474
        ];
475
    }
476
477
    /**
478
     * Return the default variables
479
     * @return array<string, mixed>
480
     */
481
    protected function defaultVariables(): array
482
    {
483
        return [
484
          'pi' => 3.14159265359,
485
          'e' => 2.71828182846
486
        ];
487
    }
488
}
489