Completed
Push — master ( 4ecd47...a202dc )
by Alexey
02:24
created

Calc   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 98.75%

Importance

Changes 6
Bugs 3 Features 0
Metric Value
wmc 24
c 6
b 3
f 0
lcom 1
cbo 3
dl 0
loc 165
ccs 79
cts 80
cp 0.9875
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
C evaluate() 0 67 12
A registerFunction() 0 18 2
A defineVar() 0 17 3
B math() 0 15 6
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 Sufir\Calc\Token;
13
use SplStack;
14
use Closure;
15
use ReflectionObject;
16
use RuntimeException;
17
use Sufir\Calc\Token\NumberToken;
18
use Sufir\Calc\Token\FunctionToken;
19
use Sufir\Calc\Token\VariableToken;
20
use InvalidArgumentException;
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
    //protected $allowedPhpFunctions = false;
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
37
38
    /**
39
     * @param integer $scale
40
     */
41 17
    public function __construct($scale = 5)
42
    {
43 17
        bcscale($scale);
44 17
    }
45
46
    /**
47
     *
48
     * @param Token $tokens
49
     * @param array $variables
50
     * @return string
51
     * @throws RuntimeException
52
     */
53 14
    public function evaluate(array $tokens, array $variables = [])
0 ignored issues
show
Unused Code introduced by
The parameter $variables is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
54
    {
55 14
        $stack = new SplStack;
56
57
        //var_dump($tokens);
58
59 14
        foreach ($tokens as $token) {
60 14
            if ($token->isNumber()) {
61 14
                $stack->push($token);
62
63 14
            } elseif ($token->isVariable()) {
64 4
                if (!isset($this->variables[$token->getValue()])) {
65 1
                    throw new RuntimeException(
66 1
                        "Undefined variable: «{$token}»"
67 1
                    );
68
                }
69
70 3
                $stack->push(new NumberToken($this->variables[$token->getValue()]));
71
72 14
            } elseif ($token->isOperator()) {
73 12
                if ($stack->count() > 1) {
74 12
                    $second = $stack->pop();
75 12
                    $first = $stack->pop();
76
77 12
                    $result = self::math($token->getValue(), $first->getValue(), $second->getValue());
78
79 12
                    $stack->push(new NumberToken($result));
80 12
                } else {
81 1
                    throw new RuntimeException(
82 1
                        "Not enough operands for a binary operator: «{$token}»"
83 1
                    );
84
                }
85
86 14
            } elseif ($token->isFunction()) {
87 9
                if (!isset($this->functions[$token->getValue()])) {
88 1
                    throw new RuntimeException("Undefined function: «{$token}()»");
89
                }
90
91 8
                $countOfArguments = $this->functions[$token->getValue()]['args'];
92
93 8
                if ($stack->count() < $countOfArguments) {
94 1
                    throw new RuntimeException(
95 1
                        "Wrong argenents for function «{$token}()»:"
96 1
                        . "defined {$stack->count()} expected {$countOfArguments}"
97 1
                    );
98
                }
99
100 7
                $arguments = [];
101 7
                for ($idx = 0; $idx < $countOfArguments; $idx++) {
102 4
                    $arguments[] = $stack->pop()->getValue();
103 4
                }
104 7
                $arguments = array_reverse($arguments);
105
106 7
                $result = call_user_func_array($this->functions[$token->getValue()]['func'], $arguments);
107
108 7
                if (!is_numeric($result)) {
109 1
                    throw new RuntimeException(
110 1
                        "Wrong result type of «{$token->getValue()}()» function, expected integer or float"
111 1
                    );
112
                }
113
114 6
                $stack->push(new NumberToken($result));
115 6
            }
116 14
        }
117
118 9
        return $stack->top()->getValue();
119
    }
120
121
    /**
122
     * Register new function.
123
     *
124
     * @param string $name
125
     * @param Closure $callable
126
     * @return \Sufir\Calc\Calc
127
     */
128 9
    public function registerFunction($name, Closure $callable)
129
    {
130 9
        $name = rtrim($name, " \t\n\r\0\x0B\(\)");
131 9
        if (!FunctionToken::validate($name)) {
132 1
            throw new InvalidArgumentException("Wrong function name «{$name}»");
133
        }
134
135 8
        $objReflector = new ReflectionObject($callable);
136 8
        $reflector = $objReflector->getMethod('__invoke');
137 8
        $parameters = $reflector->getParameters();
138
139 8
        $this->functions[$name] = array(
140 8
            'args' => count($parameters),
141 8
            'func' => $callable,
142
        );
143
144 8
        return $this;
145
    }
146
147
    /**
148
     * Define new variable.
149
     *
150
     * @param string $name
151
     * @param integer $value
152
     * @return \Sufir\Calc\Calc
153
     * @throws RuntimeException
154
     */
155 5
    public function defineVar($name, $value)
156
    {
157 5
        $name = '$' . ltrim($name, " \t\n\r\0\x0B\$");
158 5
        if (!VariableToken::validate($name)) {
159 1
            throw new InvalidArgumentException("Wrong variable name «{$name}»");
160
        }
161
162 4
        if (!is_numeric($value)) {
163 1
            throw new InvalidArgumentException(
164 1
                "Wrong type of «{$name}», expected integer or float"
165 1
            );
166
        }
167
168 3
        $this->variables[$name] = $value;
169
170 3
        return $this;
171
    }
172
173
    /**
174
     * @param string $operator
175
     * @param integer|float $firstOperand
176
     * @param integer|float $secondOperand
177
     * @return int
178
     */
179 12
    private static function math($operator, $firstOperand, $secondOperand)
180
    {
181
        switch ($operator) {
182 12
            case '^':
183 4
                return bcpow($firstOperand, $secondOperand);
184 12
            case '*':
185 9
                return bcmul($firstOperand, $secondOperand);
186 5
            case '/':
187 3
                return bcdiv($firstOperand, $secondOperand);
188 5
            case '+':
189 5
                return bcadd($firstOperand, $secondOperand);
190 4
            case '-':
191 4
                return bcsub($firstOperand, $secondOperand);
192
        }
193
    }
194
}
195