Calculator::setVariableResolver()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the fubhy/math-php package.
5
 *
6
 * (c) Sebastian Siemssen <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Fubhy\Math;
13
14
use Fubhy\Math\Exception\IncorrectExpressionException;
15
use Fubhy\Math\Exception\UnknownVariableException;
16
use Fubhy\Math\Token\FunctionToken;
17
use Fubhy\Math\Token\NumberToken;
18
use Fubhy\Math\Token\Operator\OperatorTokenInterface;
19
use Fubhy\Math\Token\VariableToken;
20
use Moontoast\Math\BigNumber;
21
22
/**
23
 * Parser for mathematical expressions.
24
 *
25
 * @author Sebastian Siemssen <[email protected]>
26
 */
27
class Calculator
28
{
29
    /**
30
     * Static cache of token streams in reverse polish (postfix) notation.
31
     *
32
     * @var array
33
     */
34
    protected $tokenCache = [];
35
36
    /**
37
     * Optional VariableResolverInterface to resolve variables value at runtime
38
     *
39
     * @var VariableResolverInterface
40
     */
41
    protected $variableResolver;
42
43
    /**
44
     * Constructs a new Calculator object.
45
     *
46
     * @param array $constants
47
     * @param array $functions
48
     * @param array $operators
49
     */
50
    public function __construct(array $constants = null, array $functions = null, array $operators = null)
51
    {
52
        $this->lexer = new Lexer();
0 ignored issues
show
Bug introduced by
The property lexer does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
53
54
        $constants = $constants ?: static::getDefaultConstants();
55
        $functions = $functions ?: static::getDefaultFunctions();
56
        $operators = $operators ?: static::getDefaultOperators();
57
58
        foreach ($constants as $constant) {
59
            $this->lexer->addConstant($constant[0], $constant[1]);
60
        }
61
62
        foreach ($functions as $function) {
63
            $this->lexer->addFunction($function[0], $function[1], $function[2]);
64
        }
65
66
        foreach ($operators as $operator) {
67
            $this->lexer->addOperator($operator[0], $operator[1]);
68
        }
69
70
    }
71
72
    /**
73
     * Calculates the result of a mathematical expression.
74
     *
75
     * @param string $expression
76
     *     The mathematical expression.
77
     * @param array $variables
78
     *     A list of numerical values keyed by their variable names.
79
     *
80
     * @return mixed
81
     *     The result of the mathematical expression.
82
     *
83
     * @throws \Fubhy\Math\Exception\IncorrectExpressionException
84
     * @throws \Fubhy\Math\Exception\UnknownVariableException
85
     */
86
    public function calculate($expression, $variables = array())
87
    {
88
        $hash = md5($expression);
89
        if (!isset($this->tokenCache[$hash])) {
90
            $this->tokenCache[$hash] = $this->lexer->postfix($this->lexer->tokenize($expression));
91
        }
92
93
        $stack = [];
94
        $tokens = $this->tokenCache[$hash];
95
        foreach ($tokens as $token) {
96
            if ($token instanceof NumberToken) {
97
                array_push($stack, $token);
98
            } elseif ($token instanceof VariableToken) {
99
                $identifier = $token->getValue();
100
101
                if (!isset($variables[$identifier]) && $this->variableResolver) {
102
                    $variableValue = $this->variableResolver->resolveVariable($identifier);
103
                    if ($variableValue !== null) {
104
                        $variables[$identifier] = $variableValue;
105
                    }
106
                }
107
108
                if (!isset($variables[$identifier])) {
109
                    throw new UnknownVariableException(
110
                      'Could not find value for variable '.$identifier.' at offset '.$token->getOffset()
111
                    );
112
                }
113
114
                array_push($stack, new NumberToken($token->getOffset(), $variables[$identifier]));
115
            } elseif ($token instanceof OperatorTokenInterface || $token instanceof FunctionToken) {
116
                array_push($stack, $token->execute($stack));
117
            }
118
        }
119
120
        $result = array_pop($stack);
121
        if (!empty($stack)) {
122
            throw new IncorrectExpressionException();
123
        }
124
125
        return $result->getValue();
126
    }
127
128
    /**
129
     * Returns the default list of operators.
130
     *
131
     * @return array
132
     *   The default list of operators.
133
     */
134
    public static function getDefaultOperators()
135
    {
136
        return [
137
          ['plus', 'Fubhy\Math\Token\Operator\PlusToken'],
138
          ['minus', 'Fubhy\Math\Token\Operator\MinusToken'],
139
          ['multiply', 'Fubhy\Math\Token\Operator\MultiplyToken'],
140
          ['division', 'Fubhy\Math\Token\Operator\DivisionToken'],
141
          ['modulus', 'Fubhy\Math\Token\Operator\ModulusToken'],
142
          ['power', 'Fubhy\Math\Token\Operator\PowerToken'],
143
        ];
144
    }
145
146
    /**
147
     * Returns the default list of functions.
148
     *
149
     * @return array
150
     *   The default list of functions.
151
     */
152
    public static function getDefaultFunctions()
153
    {
154
        return [
155
          [
156
            'abs',
157
            function ($number) {
158
                return (new BigNumber($number))
159
                  ->abs()
160
                  ->getValue();
161
            },
162
            1,
163
          ],
164
          [
165
            'ceil',
166
            function ($number) {
167
                return (new BigNumber($number))
168
                  ->ceil()
169
                  ->getValue();
170
            },
171
            1,
172
          ],
173
          [
174
            'floor',
175
            function ($number) {
176
                return (new BigNumber($number))
177
                  ->floor()
178
                  ->getValue();
179
            },
180
            1,
181
          ],
182
          [
183
            'powmod',
184
            function ($number, $pow, $mod) {
185
                return (new BigNumber($number))
186
                  ->powMod($pow, $mod)
187
                  ->getValue();
188
            },
189
            3,
190
          ],
191
          [
192
            'round',
193
            function ($number) {
194
                return (new BigNumber($number))
195
                  ->round()
196
                  ->getValue();
197
            },
198
            1,
199
          ],
200
          [
201
            'signum',
202
            function ($number) {
203
                return (new BigNumber($number))
204
                  ->signum();
205
            },
206
            1,
207
          ],
208
          [
209
            'sqrt',
210
            function ($number) {
211
                return (new BigNumber($number))
212
                  ->sqrt()
213
                  ->getValue();
214
            },
215
            1,
216
          ],
217
        ];
218
    }
219
220
    /**
221
     * Returns the default list of constants.
222
     *
223
     * @return array
224
     *   The default list of constants.
225
     */
226
    public static function getDefaultConstants()
227
    {
228
        return [
229
          ['pi', pi()],
230
          ['e', exp(1)],
231
        ];
232
    }
233
234
    /**
235
     * @param VariableResolverInterface $variableResolver
236
     */
237
    public function setVariableResolver($variableResolver)
238
    {
239
        $this->variableResolver = $variableResolver;
240
    }
241
242
}
243