Completed
Pull Request — master (#4)
by Nico
02:34
created

Parser   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 275
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 6

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 36
lcom 3
cbo 6
dl 0
loc 275
ccs 127
cts 127
cp 1
rs 8.8
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
C parse() 0 39 8
A assignVariables() 0 4 1
A registerToken() 0 4 1
A assignVariableValueFromToken() 0 19 3
B assignParentheses() 0 28 4
A assignLogicalToken() 0 15 2
A assignOperator() 0 21 3
A parseExpression() 0 12 3
B assertSyntaxSeemsOkay() 0 16 6
A registerFunction() 0 4 1
A getFunction() 0 6 2
1
<?php
2
3
/**
4
 * @license     http://opensource.org/licenses/mit-license.php MIT
5
 * @link        https://github.com/nicoSWD
6
 * @author      Nicolas Oelgart <[email protected]>
7
 */
8
declare(strict_types=1);
9
10
namespace nicoSWD\Rules;
11
12
use Closure;
13
use nicoSWD\Rules\Tokens\BaseToken;
14
15
class Parser
16
{
17
    /**
18
     * @var array
19
     */
20
    public $variables = [];
21
22
    /**
23
     * @var null|mixed[]
24
     */
25
    protected $values = null;
26
27
    /**
28
     * @var null|string
29
     */
30
    protected $operator =  null;
31
32
    /**
33
     * @var string
34
     */
35
    protected $output = '';
36
37
    /**
38
     * @var bool
39
     */
40
    protected $operatorRequired = false;
41
42
    /**
43
     * @var bool
44
     */
45
    protected $incompleteCondition = false;
46
47
    /**
48
     * @var int
49
     */
50
    protected $openParenthesis = 0;
51
52
    /**
53
     * @var int
54
     */
55
    protected $closedParenthesis = 0;
56
57
    /**
58
     * @var TokenizerInterface
59
     */
60
    protected $tokenizer;
61
62
    /**
63
     * @var Expressions\Factory
64
     */
65
    protected $expressionFactory;
66
67
    protected $userDefinedFunctions = [];
68
69
    public function __construct(TokenizerInterface $tokenizer, Expressions\Factory $expressionFactory)
70
    {
71 224
        $this->tokenizer = $tokenizer;
72
        $this->expressionFactory = $expressionFactory;
73 224
    }
74 224
75 224
    /**
76
     * @throws Exceptions\ParserException
77
     */
78
    public function parse(string $rule) : string
79
    {
80
        $this->output = '';
81
        $this->operator = null;
82 218
        $this->values = null;
83
        $this->operatorRequired = false;
84 218
85 218
        foreach (new AST($this->tokenizer->tokenize($rule), $this) as $token) {
86 218
            switch ($token->getGroup()) {
87 218
                case Constants::GROUP_VALUE:
88
                    $this->assignVariableValueFromToken($token);
89 218
                    break;
90 180
                case Constants::GROUP_LOGICAL:
91 180
                    $this->assignLogicalToken($token);
92 176
                    continue 2;
93 176
                case Constants::GROUP_PARENTHESES:
94 180
                    $this->assignParentheses($token);
95 36
                    continue 2;
96 36
                case Constants::GROUP_OPERATOR:
97 180
                    $this->assignOperator($token);
98 22
                    continue 2;
99 32
                case Constants::GROUP_COMMENT:
100 180
                case Constants::GROUP_SPACE:
101 176
                    continue 2;
102 174
                default:
103 178
                    throw new Exceptions\ParserException(sprintf(
104 178
                        'Unknown token "%s" at position %d on line %d',
105 176
                        $token->getValue(),
106 6
                        $token->getPosition(),
107 6
                        $token->getLine()
108 6
                    ));
109 6
            }
110 6
111 6
            $this->parseExpression();
112 6
        }
113 176
114
        $this->assertSyntaxSeemsOkay();
115 176
        return $this->output;
116 178
    }
117
118 158
    public function assignVariables(array $variables)
119 152
    {
120
        $this->variables = $variables;
121
    }
122
123
    /**
124
     * @param string $class
125 218
     * @param string $regex
126
     * @param int    $priority
127 218
     */
128 218
    public function registerToken($class, $regex, $priority = null)
129
    {
130
        $this->tokenizer->registerToken($class, $regex, $priority);
131
    }
132
133
    /**
134
     * @param Tokens\BaseToken $token
135 2
     * @throws Exceptions\ParserException
136
     */
137 2
    protected function assignVariableValueFromToken(BaseToken $token)
138 2
    {
139
        if ($this->operatorRequired) {
140
            throw new Exceptions\ParserException(sprintf(
141
                'Missing operator at position %d on line %d',
142
                $token->getPosition(),
143
                $token->getLine()
144 176
            ));
145
        }
146 176
147 2
        $this->operatorRequired = !$this->operatorRequired;
148 2
        $this->incompleteCondition = false;
149 2
150 2
        if (!isset($this->values)) {
151 2
            $this->values = [$token->getValue()];
152
        } else {
153
            $this->values[] = $token->getValue();
154 176
        }
155 176
    }
156
157 176
    /**
158 176
     * @throws Exceptions\ParserException
159 176
     */
160 172
    protected function assignParentheses(BaseToken $token)
161
    {
162 176
        $tokenValue = $token->getValue();
163
164
        if ($tokenValue === '(') {
165
            if ($this->operatorRequired) {
166
                throw new Exceptions\ParserException(sprintf(
167
                    'Unexpected token "(" at position %d on line %d',
168 22
                    $token->getPosition(),
169
                    $token->getLine()
170 22
                ));
171
            }
172 22
173 20
            $this->openParenthesis++;
174 4
        } else {
175 4
            if ($this->openParenthesis < 1) {
176 4
                throw new Exceptions\ParserException(sprintf(
177 4
                    'Missing opening parenthesis at position %d on line %d',
178 4
                    $token->getPosition(),
179
                    $token->getLine()
180
                ));
181 20
            }
182 20
183 20
            $this->closedParenthesis++;
184 2
        }
185 2
186 2
        $this->output .= $tokenValue;
187 2
    }
188 2
189
    /**
190
     * @throws Exceptions\ParserException
191 18
     */
192
    protected function assignLogicalToken(BaseToken $token)
193
    {
194 20
        if (!$this->operatorRequired) {
195 20
            throw new Exceptions\ParserException(sprintf(
196
                'Unexpected "%s" at position %d on line %d',
197
                $token->getOriginalValue(),
198
                $token->getPosition(),
199
                $token->getLine()
200
            ));
201 36
        }
202
203 36
        $this->output .= $token->getValue();
204 2
        $this->incompleteCondition = true;
205 2
        $this->operatorRequired = false;
206 2
    }
207 2
208 2
    /**
209 2
     * @throws Exceptions\ParserException
210
     */
211
    protected function assignOperator(BaseToken $token)
212 36
    {
213 36
        if (isset($this->operator)) {
214 36
            throw new Exceptions\ParserException(sprintf(
215 36
                'Unexpected "%s" at position %d on line %d',
216
                $token->getOriginalValue(),
217
                $token->getPosition(),
218
                $token->getLine()
219
            ));
220
        } elseif (!isset($this->values)) {
221 176
            throw new Exceptions\ParserException(sprintf(
222
                'Incomplete expression for token "%s" at position %d on line %d',
223 176
                $token->getOriginalValue(),
224 2
                $token->getPosition(),
225 2
                $token->getLine()
226 2
            ));
227 2
        }
228 2
229 2
        $this->operator = $token->getValue();
230 176
        $this->operatorRequired = false;
231 2
    }
232 2
233 2
    /**
234 2
     * @throws Exceptions\ExpressionFactoryException
235 2
     */
236 2
    protected function parseExpression()
237
    {
238
        if (!isset($this->operator) || count($this->values) <> 2) {
239 174
            return;
240 174
        }
241 174
242
        $this->operatorRequired = true;
243
        $expression = $this->expressionFactory->createFromOperator($this->operator);
244
        $this->output .= (int) $expression->evaluate($this->values[0], $this->values[1]);
245
246 176
        unset($this->operator, $this->values);
247
    }
248 176
249 176
    /**
250
     * @throws Exceptions\ParserException
251
     */
252 172
    protected function assertSyntaxSeemsOkay()
253 172
    {
254 172
        if ($this->incompleteCondition) {
255
            throw new Exceptions\ParserException(
256 170
                'Incomplete and/or condition'
257 170
            );
258
        } elseif ($this->openParenthesis > $this->closedParenthesis) {
259
            throw new Exceptions\ParserException(
260
                'Missing closing parenthesis'
261
            );
262 158
        } elseif (isset($this->operator) || (isset($this->values) && count($this->values) > 0)) {
263
            throw new Exceptions\ParserException(
264 158
                'Incomplete expression'
265 2
            );
266
        }
267 2
    }
268 156
269 2
    public function registerFunction(string $name, Closure $callback)
270
    {
271 2
        $this->userDefinedFunctions[$name] = $callback;
272 154
    }
273 2
274
    public function registerToken(string $token, string $regex, int $priority = 10)
275 2
    {
276
        $this->tokenizer->registerToken($token, $regex, $priority);
277 152
    }
278
279
    /**
280
     * @param string $name
281
     * @return Closure|null
282
     */
283
    public function getFunction(string $name)
284
    {
285
        return isset($this->userDefinedFunctions[$name])
286
            ? $this->userDefinedFunctions[$name]
287
            : null;
288
    }
289
}
290