Completed
Push — master ( 827422...e8808c )
by Nico
9s
created

Parser::getFunction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
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 230
    public function __construct(TokenizerInterface $tokenizer, Expressions\Factory $expressionFactory)
70
    {
71 230
        $this->tokenizer = $tokenizer;
72 230
        $this->expressionFactory = $expressionFactory;
73 230
    }
74
75
    /**
76
     * @throws Exceptions\ParserException
77
     */
78 224
    public function parse(string $rule) : string
79
    {
80 224
        $this->output = '';
81 224
        $this->operator = null;
82 224
        $this->values = null;
83 224
        $this->operatorRequired = false;
84
85 224
        foreach (new AST($this->tokenizer->tokenize($rule), $this) as $token) {
86 186
            switch ($token->getGroup()) {
87 186
                case Constants::GROUP_VALUE:
88 182
                    $this->assignVariableValueFromToken($token);
89 182
                    break;
90 186
                case Constants::GROUP_LOGICAL:
91 36
                    $this->assignLogicalToken($token);
92 36
                    continue 2;
93 186
                case Constants::GROUP_PARENTHESES:
94 22
                    $this->assignParentheses($token);
95 20
                    continue 2;
96 186
                case Constants::GROUP_OPERATOR:
97 182
                    $this->assignOperator($token);
98 180
                    continue 2;
99 184
                case Constants::GROUP_COMMENT:
100 184
                case Constants::GROUP_SPACE:
101 182
                    continue 2;
102
                default:
103 6
                    throw new Exceptions\ParserException(sprintf(
104 6
                        'Unknown token "%s" at position %d on line %d',
105 6
                        $token->getValue(),
106 6
                        $token->getPosition(),
107 6
                        $token->getLine()
108
                    ));
109
            }
110
111 182
            $this->parseExpression();
112
        }
113
114 164
        $this->assertSyntaxSeemsOkay();
115 158
        return $this->output;
116
    }
117
118 224
    public function assignVariables(array $variables)
119
    {
120 224
        $this->variables = $variables;
121 224
    }
122
123
    /**
124
     * @param Tokens\BaseToken $token
125
     * @throws Exceptions\ParserException
126
     */
127 182
    protected function assignVariableValueFromToken(BaseToken $token)
128
    {
129 182
        if ($this->operatorRequired) {
130 2
            throw new Exceptions\ParserException(sprintf(
131 2
                'Missing operator at position %d on line %d',
132 2
                $token->getPosition(),
133 2
                $token->getLine()
134
            ));
135
        }
136
137 182
        $this->operatorRequired = !$this->operatorRequired;
138 182
        $this->incompleteCondition = false;
139
140 182
        if (!isset($this->values)) {
141 182
            $this->values = [$token->getValue()];
142
        } else {
143 178
            $this->values[] = $token->getValue();
144
        }
145 182
    }
146
147
    /**
148
     * @throws Exceptions\ParserException
149
     */
150 22
    protected function assignParentheses(BaseToken $token)
151
    {
152 22
        $tokenValue = $token->getValue();
153
154 22
        if ($tokenValue === '(') {
155 20
            if ($this->operatorRequired) {
156 4
                throw new Exceptions\ParserException(sprintf(
157 4
                    'Unexpected token "(" at position %d on line %d',
158 4
                    $token->getPosition(),
159 4
                    $token->getLine()
160
                ));
161
            }
162
163 20
            $this->openParenthesis++;
164
        } else {
165 20
            if ($this->openParenthesis < 1) {
166 2
                throw new Exceptions\ParserException(sprintf(
167 2
                    'Missing opening parenthesis at position %d on line %d',
168 2
                    $token->getPosition(),
169 2
                    $token->getLine()
170
                ));
171
            }
172
173 18
            $this->closedParenthesis++;
174
        }
175
176 20
        $this->output .= $tokenValue;
177 20
    }
178
179
    /**
180
     * @throws Exceptions\ParserException
181
     */
182 36
    protected function assignLogicalToken(BaseToken $token)
183
    {
184 36
        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 2
                $token->getLine()
190
            ));
191
        }
192
193 36
        $this->output .= $token->getValue();
194 36
        $this->incompleteCondition = true;
195 36
        $this->operatorRequired = false;
196 36
    }
197
198
    /**
199
     * @throws Exceptions\ParserException
200
     */
201 182
    protected function assignOperator(BaseToken $token)
202
    {
203 182
        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
            ));
210 182
        } elseif (!isset($this->values)) {
211 2
            throw new Exceptions\ParserException(sprintf(
212 2
                'Incomplete expression for token "%s" at position %d on line %d',
213 2
                $token->getOriginalValue(),
214 2
                $token->getPosition(),
215 2
                $token->getLine()
216
            ));
217
        }
218
219 180
        $this->operator = $token->getValue();
220 180
        $this->operatorRequired = false;
221 180
    }
222
223
    /**
224
     * @throws Exceptions\ExpressionFactoryException
225
     */
226 182
    protected function parseExpression()
227
    {
228 182
        if (!isset($this->operator) || count($this->values) <> 2) {
229 182
            return;
230
        }
231
232 178
        $this->operatorRequired = true;
233 178
        $expression = $this->expressionFactory->createFromOperator($this->operator);
234 178
        $this->output .= (int) $expression->evaluate($this->values[0], $this->values[1]);
235
236 176
        unset($this->operator, $this->values);
237 176
    }
238
239
    /**
240
     * @throws Exceptions\ParserException
241
     */
242 164
    protected function assertSyntaxSeemsOkay()
243
    {
244 164
        if ($this->incompleteCondition) {
245 2
            throw new Exceptions\ParserException(
246 2
                'Incomplete and/or condition'
247
            );
248 162
        } elseif ($this->openParenthesis > $this->closedParenthesis) {
249 2
            throw new Exceptions\ParserException(
250 2
                'Missing closing parenthesis'
251
            );
252 160
        } elseif (isset($this->operator) || (isset($this->values) && count($this->values) > 0)) {
253 2
            throw new Exceptions\ParserException(
254 2
                'Incomplete expression'
255
            );
256
        }
257 158
    }
258
259 2
    public function registerFunction(string $name, Closure $callback)
260
    {
261 2
        $this->userDefinedFunctions[$name] = $callback;
262 2
    }
263
264 2
    public function registerToken(string $token, string $regex, int $priority = 10)
265
    {
266 2
        $this->tokenizer->registerToken($token, $regex, $priority);
267 2
    }
268
269
    /**
270
     * @param string $name
271
     * @return Closure|null
272
     */
273 4
    public function getFunction(string $name)
274
    {
275 4
        return isset($this->userDefinedFunctions[$name])
276 2
            ? $this->userDefinedFunctions[$name]
277 4
            : null;
278
    }
279
}
280