Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
152 / 152 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
Lexer | |
100.00% |
152 / 152 |
|
100.00% |
4 / 4 |
59 | |
100.00% |
1 / 1 |
getTokenSyntax | |
100.00% |
50 / 50 |
|
100.00% |
1 / 1 |
25 | |||
getCatchablePatterns | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
getNonCatchablePatterns | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getType | |
100.00% |
89 / 89 |
|
100.00% |
1 / 1 |
32 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Symftony\Xpression; |
6 | |
7 | use Doctrine\Common\Lexer\AbstractLexer; |
8 | use Symftony\Xpression\Exception\Lexer\UnknownTokenTypeException; |
9 | |
10 | class 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 | } |