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

Parser::getFunction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 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 Tokens\BaseToken $token
125 218
     * @throws Exceptions\ParserException
126
     */
127 218
    protected function assignVariableValueFromToken(BaseToken $token)
128 218
    {
129
        if ($this->operatorRequired) {
130
            throw new Exceptions\ParserException(sprintf(
131
                'Missing operator at position %d on line %d',
132
                $token->getPosition(),
133
                $token->getLine()
134
            ));
135 2
        }
136
137 2
        $this->operatorRequired = !$this->operatorRequired;
138 2
        $this->incompleteCondition = false;
139
140
        if (!isset($this->values)) {
141
            $this->values = [$token->getValue()];
142
        } else {
143
            $this->values[] = $token->getValue();
144 176
        }
145
    }
146 176
147 2
    /**
148 2
     * @throws Exceptions\ParserException
149 2
     */
150 2
    protected function assignParentheses(BaseToken $token)
151 2
    {
152
        $tokenValue = $token->getValue();
153
154 176
        if ($tokenValue === '(') {
155 176
            if ($this->operatorRequired) {
156
                throw new Exceptions\ParserException(sprintf(
157 176
                    'Unexpected token "(" at position %d on line %d',
158 176
                    $token->getPosition(),
159 176
                    $token->getLine()
160 172
                ));
161
            }
162 176
163
            $this->openParenthesis++;
164
        } else {
165
            if ($this->openParenthesis < 1) {
166
                throw new Exceptions\ParserException(sprintf(
167
                    'Missing opening parenthesis at position %d on line %d',
168 22
                    $token->getPosition(),
169
                    $token->getLine()
170 22
                ));
171
            }
172 22
173 20
            $this->closedParenthesis++;
174 4
        }
175 4
176 4
        $this->output .= $tokenValue;
177 4
    }
178 4
179
    /**
180
     * @throws Exceptions\ParserException
181 20
     */
182 20
    protected function assignLogicalToken(BaseToken $token)
183 20
    {
184 2
        if (!$this->operatorRequired) {
185 2
            throw new Exceptions\ParserException(sprintf(
186 2
                'Unexpected "%s" at position %d on line %d',
187 2
                $token->getOriginalValue(),
188 2
                $token->getPosition(),
189
                $token->getLine()
190
            ));
191 18
        }
192
193
        $this->output .= $token->getValue();
194 20
        $this->incompleteCondition = true;
195 20
        $this->operatorRequired = false;
196
    }
197
198
    /**
199
     * @throws Exceptions\ParserException
200
     */
201 36
    protected function assignOperator(BaseToken $token)
202
    {
203 36
        if (isset($this->operator)) {
204 2
            throw new Exceptions\ParserException(sprintf(
205 2
                'Unexpected "%s" at position %d on line %d',
206 2
                $token->getOriginalValue(),
207 2
                $token->getPosition(),
208 2
                $token->getLine()
209 2
            ));
210
        } elseif (!isset($this->values)) {
211
            throw new Exceptions\ParserException(sprintf(
212 36
                'Incomplete expression for token "%s" at position %d on line %d',
213 36
                $token->getOriginalValue(),
214 36
                $token->getPosition(),
215 36
                $token->getLine()
216
            ));
217
        }
218
219
        $this->operator = $token->getValue();
220
        $this->operatorRequired = false;
221 176
    }
222
223 176
    /**
224 2
     * @throws Exceptions\ExpressionFactoryException
225 2
     */
226 2
    protected function parseExpression()
227 2
    {
228 2
        if (!isset($this->operator) || count($this->values) <> 2) {
229 2
            return;
230 176
        }
231 2
232 2
        $this->operatorRequired = true;
233 2
        $expression = $this->expressionFactory->createFromOperator($this->operator);
234 2
        $this->output .= (int) $expression->evaluate($this->values[0], $this->values[1]);
235 2
236 2
        unset($this->operator, $this->values);
237
    }
238
239 174
    /**
240 174
     * @throws Exceptions\ParserException
241 174
     */
242
    protected function assertSyntaxSeemsOkay()
243
    {
244
        if ($this->incompleteCondition) {
245
            throw new Exceptions\ParserException(
246 176
                'Incomplete and/or condition'
247
            );
248 176
        } elseif ($this->openParenthesis > $this->closedParenthesis) {
249 176
            throw new Exceptions\ParserException(
250
                'Missing closing parenthesis'
251
            );
252 172
        } elseif (isset($this->operator) || (isset($this->values) && count($this->values) > 0)) {
253 172
            throw new Exceptions\ParserException(
254 172
                'Incomplete expression'
255
            );
256 170
        }
257 170
    }
258
259
    public function registerFunction(string $name, Closure $callback)
260
    {
261
        $this->userDefinedFunctions[$name] = $callback;
262 158
    }
263
264 158
    public function registerToken(string $token, string $regex, int $priority = 10)
265 2
    {
266
        $this->tokenizer->registerToken($token, $regex, $priority);
267 2
    }
268 156
269 2
    /**
270
     * @param string $name
271 2
     * @return Closure|null
272 154
     */
273 2
    public function getFunction(string $name)
274
    {
275 2
        return isset($this->userDefinedFunctions[$name])
276
            ? $this->userDefinedFunctions[$name]
277 152
            : null;
278
    }
279
}
280