Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
152 / 152
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
Lexer
100.00% covered (success)
100.00%
152 / 152
100.00% covered (success)
100.00%
4 / 4
59
100.00% covered (success)
100.00%
1 / 1
 getTokenSyntax
100.00% covered (success)
100.00%
50 / 50
100.00% covered (success)
100.00%
1 / 1
25
 getCatchablePatterns
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 getNonCatchablePatterns
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getType
100.00% covered (success)
100.00%
89 / 89
100.00% covered (success)
100.00%
1 / 1
32
1<?php
2
3declare(strict_types=1);
4
5namespace Symftony\Xpression;
6
7use Doctrine\Common\Lexer\AbstractLexer;
8use Symftony\Xpression\Exception\Lexer\UnknownTokenTypeException;
9
10class Lexer extends AbstractLexer
11{
12    public const T_NONE = 0;
13    public const T_ALL = 2 ** 24 - 1;
14
15    // Punctuation
16    public const T_COMMA = 2 ** 0;
17
18    // Operand
19    public const T_OPERAND = 2 ** 1 + 2 ** 2 + 2 ** 3 + 2 ** 4;
20    public const T_INTEGER = 2 ** 1;
21    public const T_STRING = 2 ** 2;
22    public const T_INPUT_PARAMETER = 2 ** 3;
23    public const T_FLOAT = 2 ** 4;
24
25    // Comparison operator
26    public const T_COMPARISON = 2 ** 5 + 2 ** 6 + 2 ** 7 + 2 ** 8 + 2 ** 9 + 2 ** 10 + 2 ** 18 + 2 ** 19 + 2 ** 21 + 2 ** 22;
27    public const T_EQUALS = 2 ** 5;
28    public const T_NOT_EQUALS = 2 ** 6;
29    public const T_GREATER_THAN = 2 ** 7;
30    public const T_GREATER_THAN_EQUALS = 2 ** 8;
31    public const T_LOWER_THAN = 2 ** 9;
32    public const T_LOWER_THAN_EQUALS = 2 ** 10;
33
34    // Composite operator
35    public const T_COMPOSITE = 2 ** 11 + 2 ** 12 + 2 ** 13 + 2 ** 14 + 2 ** 15;
36    public const T_AND = 2 ** 11;
37    public const T_NOT_AND = 2 ** 12;
38    public const T_OR = 2 ** 13;
39    public const T_NOT_OR = 2 ** 14;
40    public const T_XOR = 2 ** 15;
41
42    // Brace
43    public const T_OPEN_PARENTHESIS = 2 ** 16;
44    public const T_CLOSE_PARENTHESIS = 2 ** 17;
45    public const T_OPEN_SQUARE_BRACKET = 2 ** 18;
46    public const T_NOT_OPEN_SQUARE_BRACKET = 2 ** 19;
47    public const T_CLOSE_SQUARE_BRACKET = 2 ** 20;
48    public const T_DOUBLE_OPEN_CURLY_BRACKET = 2 ** 21;
49    public const T_NOT_DOUBLE_OPEN_CURLY_BRACKET = 2 ** 22;
50    public const T_DOUBLE_CLOSE_CURLY_BRACKET = 2 ** 23;
51
52    /**
53     * @return string[]
54     */
55    public static function getTokenSyntax(mixed $tokenType): array
56    {
57        $tokenSyntax = [];
58        // Punctuation
59        if ($tokenType & self::T_COMMA) {
60            $tokenSyntax[] = ',';
61        }
62
63        // Recognize numeric values
64        if ($tokenType & self::T_FLOAT) {
65            $tokenSyntax[] = 'simple float';
66        }
67        if ($tokenType & self::T_INTEGER) {
68            $tokenSyntax[] = 'simple integer';
69        }
70        if ($tokenType & self::T_INPUT_PARAMETER) {
71            $tokenSyntax[] = '/[a-z_][a-z0-9_]*/';
72        }
73
74        // Recognize quoted strings
75        if ($tokenType & self::T_STRING) {
76            $tokenSyntax[] = '"value" or \'value\'';
77        }
78
79        // Comparison operator
80        if ($tokenType & self::T_EQUALS) {
81            $tokenSyntax[] = '=';
82        }
83        if ($tokenType & self::T_NOT_EQUALS) {
84            $tokenSyntax[] = '≠ or !=';
85        }
86        if ($tokenType & self::T_GREATER_THAN) {
87            $tokenSyntax[] = '>';
88        }
89        if ($tokenType & self::T_GREATER_THAN_EQUALS) {
90            $tokenSyntax[] = '≥ or >=';
91        }
92        if ($tokenType & self::T_LOWER_THAN) {
93            $tokenSyntax[] = '<';
94        }
95        if ($tokenType & self::T_LOWER_THAN_EQUALS) {
96            $tokenSyntax[] = '≤ or <=';
97        }
98
99        // Composite operator
100        if ($tokenType & self::T_AND) {
101            $tokenSyntax[] = '&';
102        }
103        if ($tokenType & self::T_NOT_AND) {
104            $tokenSyntax[] = '!&';
105        }
106        if ($tokenType & self::T_OR) {
107            $tokenSyntax[] = '|';
108        }
109        if ($tokenType & self::T_NOT_OR) {
110            $tokenSyntax[] = '!|';
111        }
112        if ($tokenType & self::T_XOR) {
113            $tokenSyntax[] = '⊕ or ^|';
114        }
115
116        // Brace
117        if ($tokenType & self::T_OPEN_PARENTHESIS) {
118            $tokenSyntax[] = '(';
119        }
120        if ($tokenType & self::T_CLOSE_PARENTHESIS) {
121            $tokenSyntax[] = ')';
122        }
123        if ($tokenType & self::T_OPEN_SQUARE_BRACKET) {
124            $tokenSyntax[] = '[';
125        }
126        if ($tokenType & self::T_NOT_OPEN_SQUARE_BRACKET) {
127            $tokenSyntax[] = '![';
128        }
129        if ($tokenType & self::T_CLOSE_SQUARE_BRACKET) {
130            $tokenSyntax[] = ']';
131        }
132        if ($tokenType & self::T_DOUBLE_OPEN_CURLY_BRACKET) {
133            $tokenSyntax[] = '{{';
134        }
135        if ($tokenType & self::T_NOT_DOUBLE_OPEN_CURLY_BRACKET) {
136            $tokenSyntax[] = '!{{';
137        }
138        if ($tokenType & self::T_DOUBLE_CLOSE_CURLY_BRACKET) {
139            $tokenSyntax[] = '}}';
140        }
141
142        return $tokenSyntax;
143    }
144
145    /**
146     * @return array
147     */
148    protected function getCatchablePatterns()
149    {
150        return [
151            "'(?:[^']|'')*'", // quoted strings
152            '"(?:[^"]|"")*"', // quoted strings
153            '\^\||⊕|!&|&|!\||\|', // Composite operator
154            '≤|≥|≠|<=|>=|!=|<|>|=|\[|!\[|\]|!{{|{{|}}', // Comparison operator
155            '[a-z_][a-z0-9_\.\-]*', // identifier or qualified name
156            '(?:[+-]?[0-9]*(?:[\.][0-9]+)*)', // numbers
157//            '(?:[+-]?(?:(?:(?:[0-9]+|(?:[0-9]*[\.][0-9]+)|(?:[0-9]+[\.][0-9]*))[eE][+-]?[0-9]+)|(?:[0-9]*[\.][0-9]+)|(?:[0-9]+[\.][0-9]*)))', // number extended all float (.5 / 1.5 / -1.2e3)
158        ];
159    }
160
161    /**
162     * @return array
163     */
164    protected function getNonCatchablePatterns()
165    {
166        return [
167            '\s+',
168            '(.)',
169        ];
170    }
171
172    /**
173     * @param string $value
174     *
175     * @return int
176     *
177     * @throws UnknownTokenTypeException
178     */
179    protected function getType(&$value)
180    {
181        switch (true) {
182            // Punctuation
183            case ',' === $value[0]:
184                $type = self::T_COMMA;
185
186                break;
187
188            // Recognize numeric values
189            case is_numeric($value):
190                if (str_contains($value, '.') || false !== stripos($value, 'e')) {
191                    $value = (float)$value;
192                    $type = self::T_FLOAT;
193
194                    break;
195                }
196
197                $value = (int)$value;
198                $type = self::T_INTEGER;
199
200                break;
201
202            // Recognize quoted strings
203            case '"' === $value[0]:
204                $value = str_replace('""', '"', substr($value, 1, \strlen($value) - 2));
205
206                $type = self::T_STRING;
207
208                break;
209
210            case "'" === $value[0]:
211                $value = str_replace("''", "'", substr($value, 1, \strlen($value) - 2));
212
213                $type = self::T_STRING;
214
215                break;
216
217            case preg_match('/[a-z_][a-z0-9_]*/i', $value):
218                $type = self::T_INPUT_PARAMETER;
219
220                break;
221
222            // Comparison operator
223            case '=' === $value:
224                $type = self::T_EQUALS;
225
226                break;
227
228            case '≠' === $value:
229            case '!=' === $value:
230                $value = '≠';
231                $type = self::T_NOT_EQUALS;
232
233                break;
234
235            case '>' === $value:
236                $type = self::T_GREATER_THAN;
237
238                break;
239
240            case '>=' === $value:
241            case '≥' === $value:
242                $value = '≥';
243                $type = self::T_GREATER_THAN_EQUALS;
244
245                break;
246
247            case '<' === $value:
248                $type = self::T_LOWER_THAN;
249
250                break;
251
252            case '<=' === $value:
253            case '≤' === $value:
254                $value = '≤';
255                $type = self::T_LOWER_THAN_EQUALS;
256
257                break;
258
259            // Composite operator
260            case '&' === $value:
261                $type = self::T_AND;
262
263                break;
264
265            case '!&' === $value:
266                $type = self::T_NOT_AND;
267
268                break;
269
270            case '|' === $value:
271                $type = self::T_OR;
272
273                break;
274
275            case '!|' === $value:
276                $type = self::T_NOT_OR;
277
278                break;
279
280            case '^|' === $value:
281            case '⊕' === $value:
282                $value = '⊕';
283                $type = self::T_XOR;
284
285                break;
286
287            // Brace
288            case '(' === $value:
289                $type = self::T_OPEN_PARENTHESIS;
290
291                break;
292
293            case ')' === $value:
294                $type = self::T_CLOSE_PARENTHESIS;
295
296                break;
297
298            case '[' === $value:
299                $type = self::T_OPEN_SQUARE_BRACKET;
300
301                break;
302
303            case '![' === $value:
304                $type = self::T_NOT_OPEN_SQUARE_BRACKET;
305
306                break;
307
308            case ']' === $value:
309                $type = self::T_CLOSE_SQUARE_BRACKET;
310
311                break;
312
313            case '{{' === $value:
314                $type = self::T_DOUBLE_OPEN_CURLY_BRACKET;
315
316                break;
317
318            case '!{{' === $value:
319                $type = self::T_NOT_DOUBLE_OPEN_CURLY_BRACKET;
320
321                break;
322
323            case '}}' === $value:
324                $type = self::T_DOUBLE_CLOSE_CURLY_BRACKET;
325
326                break;
327
328            // Default
329            default:
330                throw new UnknownTokenTypeException($value);
331        }
332
333        return $type;
334    }
335}