Calc::math()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6.0208

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
ccs 11
cts 12
cp 0.9167
rs 8.8571
cc 6
eloc 12
nc 6
nop 3
crap 6.0208
1
<?php
2
3
/**
4
 * Calc.php
5
 *
6
 * @date 28.03.2015 0:54:24
7
 * @copyright Sklyarov Alexey <[email protected]>
8
 */
9
10
namespace Sufir\Calc;
11
12
use Closure;
13
use InvalidArgumentException;
14
use ReflectionObject;
15
use RuntimeException;
16
use SplStack;
17
use Sufir\Calc\Token;
18
use Sufir\Calc\Token\FunctionToken;
19
use Sufir\Calc\Token\NumberToken;
20
use Sufir\Calc\Token\VariableToken;
21
22
/**
23
 * Calc
24
 *
25
 * Description of Calc
26
 *
27
 * @author Sklyarov Alexey <[email protected]>
28
 * @package Sufir\Calc
29
 */
30
final class Calc
31
{
32
    private $functions = [];
33
34
    private $variables = [];
35
36
    /**
37
     * @param integer $scale
38
     */
39 18
    public function __construct($scale = 5)
40
    {
41 18
        bcscale($scale);
42 18
    }
43
44
    /**
45
     *
46
     * @param Token $tokens
47
     * @param array $variables
48
     * @return string
49
     * @throws RuntimeException
50
     */
51 15
    public function evaluate(array $tokens, array $variables = [])
52
    {
53 15
        $stack = new SplStack;
54
55 15
        $this->defineVars($variables);
56
57 15
        foreach ($tokens as $token) {
58 15
            if ($token->isNumber()) {
59 15
                $stack->push($token);
60
61 15
            } elseif ($token->isVariable()) {
62 5
                if (!isset($this->variables[$token->getValue()])) {
63 1
                    throw new RuntimeException(
64 1
                        "Undefined variable: «{$token}»"
65 1
                    );
66
                }
67
68 4
                $stack->push(new NumberToken($this->variables[$token->getValue()]));
69
70 15
            } elseif ($token->isOperator()) {
71 12
                if ($stack->count() > 1) {
72 12
                    $second = $stack->pop();
73 12
                    $first = $stack->pop();
74
75 12
                    $result = self::math($token->getValue(), $first->getValue(), $second->getValue());
76
77 12
                    $stack->push(new NumberToken($result));
78 12
                } else {
79 1
                    throw new RuntimeException(
80 1
                        "Not enough operands for a binary operator: «{$token}»"
81 1
                    );
82
                }
83
84 15
            } elseif ($token->isFunction()) {
85 10
                if (!isset($this->functions[$token->getValue()])) {
86 1
                    throw new RuntimeException("Undefined function: «{$token}()»");
87
                }
88
89 9
                $countOfArguments = $this->functions[$token->getValue()]['args'];
90
91 9
                if ($stack->count() < $countOfArguments) {
92 1
                    throw new RuntimeException(
93 1
                        "Wrong argenents for function «{$token}()»:"
94 1
                        . "defined {$stack->count()} expected {$countOfArguments}"
95 1
                    );
96
                }
97
98 8
                $arguments = [];
99 8
                for ($idx = 0; $idx < $countOfArguments; $idx++) {
100 5
                    $arguments[] = $stack->pop()->getValue();
101 5
                }
102 8
                $arguments = array_reverse($arguments);
103
104 8
                $result = call_user_func_array($this->functions[$token->getValue()]['func'], $arguments);
105
106 8
                if (!is_numeric($result)) {
107 1
                    throw new RuntimeException(
108 1
                        "Wrong result type of «{$token->getValue()}()» function, expected integer or float"
109 1
                    );
110
                }
111
112 7
                $stack->push(new NumberToken($result));
113 7
            }
114 15
        }
115
116 10
        return $stack->top()->getValue();
117
    }
118
119
    /**
120
     * Register new function.
121
     *
122
     * @param string $name
123
     * @param Closure $callable
124
     * @return \Sufir\Calc\Calc
125
     */
126 10
    public function registerFunction($name, Closure $callable)
127
    {
128 10
        $name = rtrim($name, " \t\n\r\0\x0B\(\)");
129 10
        if (!FunctionToken::validate($name)) {
130 1
            throw new InvalidArgumentException("Wrong function name «{$name}»");
131
        }
132
133 9
        $objReflector = new ReflectionObject($callable);
134 9
        $reflector = $objReflector->getMethod('__invoke');
135 9
        $parameters = $reflector->getParameters();
136
137 9
        $this->functions[$name] = array(
138 9
            'args' => count($parameters),
139 9
            'func' => $callable,
140
        );
141
142 9
        return $this;
143
    }
144
145
    /**
146
     * Define new variable.
147
     *
148
     * @param string $name
149
     * @param integer $value
150
     * @return \Sufir\Calc\Calc
151
     * @throws RuntimeException
152
     */
153 6
    public function defineVar($name, $value)
154
    {
155 6
        $name = '$' . ltrim($name, " \t\n\r\0\x0B\$");
156 6
        if (!VariableToken::validate($name)) {
157 1
            throw new InvalidArgumentException("Wrong variable name «{$name}»");
158
        }
159
160 5
        if (!is_numeric($value)) {
161 1
            throw new InvalidArgumentException(
162 1
                "Wrong type of «{$name}», expected integer or float"
163 1
            );
164
        }
165
166 4
        $this->variables[$name] = $value;
167
168 4
        return $this;
169
    }
170
171
    /**
172
     * Define new variables.
173
     * <br>
174
     * Array format ['varName' => 'varValue', ...]
175
     *
176
     * @param array $variables
177
     * @return \Sufir\Calc\Calc
178
     */
179 15
    public function defineVars(array $variables = [])
180
    {
181 15
        foreach ($variables as $name => $value) {
182 4
            $this->defineVar($name, $value);
183 15
        }
184
185 15
        return $this;
186
    }
187
188
    /**
189
     * @param string $operator
190
     * @param integer|float $firstOperand
191
     * @param integer|float $secondOperand
192
     * @return string|null
193
     */
194 12
    private static function math($operator, $firstOperand, $secondOperand)
195
    {
196
        switch ($operator) {
197 12
            case '^':
198 4
                return bcpow($firstOperand, $secondOperand);
199 12
            case '*':
200 9
                return bcmul($firstOperand, $secondOperand);
201 5
            case '/':
202 3
                return bcdiv($firstOperand, $secondOperand);
203 5
            case '+':
204 5
                return bcadd($firstOperand, $secondOperand);
205 4
            case '-':
206 4
                return bcsub($firstOperand, $secondOperand);
207
        }
208
    }
209
}
210