Math::tokenize()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 1
eloc 8
nc 1
nop 1
1
<?php
2
/*
3
    Copyright (c) 2016 Michal Čihař <[email protected]>
4
5
    This file is part of SimpleMath.
6
7
    This program is free software; you can redistribute it and/or modify
8
    it under the terms of the GNU General Public License as published by
9
    the Free Software Foundation; either version 2 of the License, or
10
    (at your option) any later version.
11
12
    This program is distributed in the hope that it will be useful,
13
    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
    GNU General Public License for more details.
16
17
    You should have received a copy of the GNU General Public License along
18
    with this program; if not, write to the Free Software Foundation, Inc.,
19
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
*/
21
namespace SimpleMath;
22
23
24
/**
25
 * Core class of the library performing parsing and calculations.
26
 */
27
class Math {
28
29
    protected $variables = array();
30
    protected $stack = null;
31
32
    /**
33
     * Parses and executes given formula
34
     *
35
     * @param string $string Formula to process
36
     *
37
     * @return int
38
     */
39
    public function evaluate($string) {
40
        $this->parse($string);
41
        return $this->run();
42
    }
43
44
    /**
45
     * Parses given formula. The parsed formula is stored in
46
     * the class.
47
     *
48
     * @param string $string Formula to process
49
     *
50
     * @return void
51
     */
52
    public function parse($string) {
53
        $tokens = $this->tokenize($string);
54
        $output = new Stack();
55
        $operators = new Stack();
56
        foreach ($tokens as $token) {
57
            $expression = Expression::factory($token);
58
            if ($expression->isOperator()) {
59
                $this->parseOperator($expression, $output, $operators);
60
            } elseif ($expression->isParenthesis()) {
61
                $this->parseParenthesis($expression, $output, $operators);
62
            } else {
63
                $output->push($expression);
64
            }
65
        }
66
        while (($operator = $operators->pop())) {
67
            if ($operator->isParenthesis()) {
68
                throw new \RuntimeException('Mismatched Parenthesis');
69
            }
70
            $output->push($operator);
71
        }
72
        $this->stack = $output;
73
    }
74
75
    /**
76
     * Registers variable for use withing calculation
77
     *
78
     * @param string $name  Name of variable
79
     * @param int    $value Value of variable
80
     *
81
     * @return void
82
     */
83
    public function registerVariable($name, $value) {
84
        $this->variables[$name] = $value;
85
    }
86
87
    /**
88
     * Executes currently parsed formula with current variables
89
     *
90
     * @return int
91
     */
92
    public function run() {
93
        $stack = clone $this->stack;
94
        while (($operator = $stack->pop()) && $operator->isOperator()) {
95
            $value = $operator->operate($stack, $this->variables);
96
            if (!is_null($value)) {
97
                $stack->push(Expression::factory($value));
98
            }
99
        }
100
        return $operator->render();
101
    }
102
103
    protected function parseParenthesis(Expression $expression, Stack $output, Stack $operators) {
104
        if ($expression->isOpen()) {
105
            $operators->push($expression);
106
        } else {
107
            $clean = false;
108
            while (($end = $operators->pop())) {
109
                if ($end->isParenthesis()) {
110
                    $clean = true;
111
                    break;
112
                } else {
113
                    $output->push($end);
114
                }
115
            }
116
            if (!$clean) {
117
                throw new \RuntimeException('Mismatched Parenthesis');
118
            }
119
        }
120
    }
121
122
    protected function parseOperator(Expression $expression, Stack $output, Stack $operators) {
123
        $end = $operators->poke();
124
        if (!$end) {
125
            $operators->push($expression);
126
        } elseif ($end->isOperator()) {
127
            do {
128
                if ($expression->isLeftAssoc() && $expression->getPrecidence() <= $end->getPrecidence()) {
129
                    $output->push($operators->pop());
130
                } elseif (!$expression->isLeftAssoc() && $expression->getPrecidence() < $end->getPrecidence()) {
131
                    $output->push($operators->pop());
132
                } else {
133
                    break;
134
                }
135
            } while (($end = $operators->poke()) && $end->isOperator());
136
            $operators->push($expression);
137
        } else {
138
            $operators->push($expression);
139
        }
140
    }
141
142
    protected function tokenize($string) {
143
        $parts = preg_split(
144
            '(([0-9]*\.?[0-9]+|\+|-|\(|\)|\*|%|\/|:|\?|==|<=|>=|<|>|!=|&&|\|\|)|\s+)',
145
            $string,
146
            null,
147
            PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
148
        );
149
        $parts = array_map('trim', $parts);
150
        return $parts;
151
    }
152
153
}
154