1
|
|
|
<?php
|
2
|
|
|
|
3
|
|
|
namespace POData\UriProcessor\QueryProcessor\ExpressionParser;
|
4
|
|
|
|
5
|
|
|
use POData\Common\Messages;
|
6
|
|
|
use POData\Common\ODataException;
|
7
|
|
|
use POData\Common\ODataConstants;
|
8
|
|
|
use POData\Providers\Metadata\Type\Char;
|
9
|
|
|
|
10
|
|
|
/**
|
11
|
|
|
* Class ExpressionLexer
|
12
|
|
|
*
|
13
|
|
|
* Lexical analyzer for Astoria URI expression parsing
|
14
|
|
|
* Literals Representation
|
15
|
|
|
* --------------------------------------------------------------------
|
16
|
|
|
* Null null
|
17
|
|
|
* Boolean true | false
|
18
|
|
|
* Int32 (digit+)
|
19
|
|
|
* Int64 (digit+)(L|l)
|
20
|
|
|
* Decimal (digit+ ['.' digit+])(M|m)
|
21
|
|
|
* Float (Single) (digit+ ['.' digit+][e|E [+|-] digit+)(f|F)
|
22
|
|
|
* Double (digit+ ['.' digit+][e|E [+|-] digit+)
|
23
|
|
|
* String "'" .* "'"
|
24
|
|
|
* DateTime datetime"'"dddd-dd-dd[T|' ']dd:mm[ss[.fffffff]]"'"
|
25
|
|
|
* Binary (binary|X)'digit*'
|
26
|
|
|
* GUID guid'digit*
|
27
|
|
|
*
|
28
|
|
|
* @package POData\UriProcessor\QueryProcessor\ExpressionParser
|
29
|
|
|
*/
|
30
|
|
|
class ExpressionLexer
|
31
|
|
|
{
|
32
|
|
|
/**
|
33
|
|
|
* Suffix for single literals
|
34
|
|
|
*
|
35
|
|
|
* @var char
|
36
|
|
|
*/
|
37
|
|
|
const SINGLE_SUFFIX_LOWER = 'f';
|
38
|
|
|
|
39
|
|
|
/**
|
40
|
|
|
* Suffix for single literals
|
41
|
|
|
*
|
42
|
|
|
* @var char
|
43
|
|
|
*/
|
44
|
|
|
const SINGLE_SUFFIX_UPPER = 'F';
|
45
|
|
|
|
46
|
|
|
/**
|
47
|
|
|
* Text being parsed
|
48
|
|
|
*
|
49
|
|
|
* @var string
|
50
|
|
|
*/
|
51
|
|
|
private $_text;
|
52
|
|
|
|
53
|
|
|
/**
|
54
|
|
|
* Length of text being parsed
|
55
|
|
|
*
|
56
|
|
|
* @var int
|
57
|
|
|
*/
|
58
|
|
|
private $_textLen;
|
59
|
|
|
|
60
|
|
|
/**
|
61
|
|
|
* Position on text being parsed
|
62
|
|
|
*
|
63
|
|
|
* @var int
|
64
|
|
|
*/
|
65
|
|
|
private $_textPos;
|
66
|
|
|
|
67
|
|
|
/**
|
68
|
|
|
* Character being processed
|
69
|
|
|
*
|
70
|
|
|
* @var char
|
71
|
|
|
*/
|
72
|
|
|
private $_ch;
|
73
|
|
|
|
74
|
|
|
/**
|
75
|
|
|
* ExpressionToken being processed
|
76
|
|
|
*
|
77
|
|
|
* @var ExpressionToken
|
78
|
|
|
*/
|
79
|
|
|
private $_token;
|
80
|
|
|
|
81
|
|
|
/**
|
82
|
|
|
* Initialize a new instance of ExpressionLexer
|
83
|
|
|
*
|
84
|
|
|
* @param string $expression Expression to parse
|
85
|
|
|
*/
|
86
|
148 |
|
public function __construct($expression)
|
87
|
|
|
{
|
88
|
148 |
|
$this->_text = $expression;
|
89
|
148 |
|
$this->_textLen = strlen($this->_text);
|
90
|
148 |
|
$this->_token = new ExpressionToken();
|
91
|
148 |
|
$this->_setTextPos(0);
|
92
|
148 |
|
$this->nextToken();
|
93
|
|
|
}
|
94
|
|
|
|
95
|
|
|
/**
|
96
|
|
|
* To get the expression token being processed
|
97
|
|
|
*
|
98
|
|
|
* @return ExpressionToken
|
99
|
|
|
*/
|
100
|
148 |
|
public function getCurrentToken()
|
101
|
|
|
{
|
102
|
148 |
|
return $this->_token;
|
103
|
|
|
}
|
104
|
|
|
|
105
|
|
|
/**
|
106
|
|
|
* To set the token being processed
|
107
|
|
|
*
|
108
|
|
|
* @param ExpressionToken $token The expression token to set as current
|
109
|
|
|
*
|
110
|
|
|
* @return void
|
111
|
|
|
*/
|
112
|
|
|
public function setCurrentToken($token)
|
113
|
|
|
{
|
114
|
|
|
$this->_token = $token;
|
115
|
|
|
}
|
116
|
|
|
|
117
|
|
|
/**
|
118
|
|
|
* To get the text being parsed
|
119
|
|
|
*
|
120
|
|
|
* @return string
|
121
|
|
|
*/
|
122
|
|
|
public function getExpressionText()
|
123
|
|
|
{
|
124
|
|
|
return $this->_text;
|
125
|
|
|
}
|
126
|
|
|
|
127
|
|
|
/**
|
128
|
|
|
* Position of the current token in the text being parsed
|
129
|
|
|
*
|
130
|
|
|
* @return int
|
131
|
|
|
*/
|
132
|
|
|
public function getPosition()
|
133
|
|
|
{
|
134
|
|
|
return $this->_token->Position;
|
135
|
|
|
}
|
136
|
|
|
|
137
|
|
|
/**
|
138
|
|
|
* Whether the specified token identifier is a numeric literal
|
139
|
|
|
*
|
140
|
|
|
* @param ExpressionTokenId $id Token identifier to check
|
141
|
|
|
*
|
142
|
|
|
* @return bool true if it's a numeric literal; false otherwise
|
143
|
|
|
*/
|
144
|
41 |
|
public static function isNumeric($id)
|
145
|
|
|
{
|
146
|
41 |
|
return
|
147
|
41 |
|
$id == ExpressionTokenId::INTEGER_LITERAL
|
148
|
41 |
|
|| $id == ExpressionTokenId::DECIMAL_LITERAL
|
149
|
41 |
|
|| $id == ExpressionTokenId::DOUBLE_LITERAL
|
150
|
41 |
|
|| $id == ExpressionTokenId::INT64_LITERAL
|
151
|
41 |
|
|| $id == ExpressionTokenId::SINGLE_LITERAL;
|
152
|
|
|
}
|
153
|
|
|
|
154
|
|
|
/**
|
155
|
|
|
* Reads the next token, skipping whitespace as necessary.
|
156
|
|
|
*
|
157
|
|
|
* @return void
|
158
|
|
|
*/
|
159
|
148 |
|
public function nextToken()
|
160
|
|
|
{
|
161
|
|
|
|
162
|
148 |
|
while (Char::isWhiteSpace($this->_ch)) {
|
163
|
107 |
|
$this->_nextChar();
|
164
|
|
|
}
|
165
|
|
|
|
166
|
148 |
|
$t = null;
|
167
|
148 |
|
$tokenPos = $this->_textPos;
|
168
|
148 |
|
$rest = substr($this->_text, $tokenPos);
|
169
|
148 |
|
$matches_array = [];
|
170
|
148 |
|
preg_match('/^\(\s*(?<string_array>(\'[^\']*\'(\s*\,\s*\'[^\']*\')*)|(?<int_array>\d+(\s*\,\s*\d+)*))\s*\)/', $rest, $matches_array);
|
171
|
148 |
|
if ($matches_array) {
|
172
|
|
|
$t = ExpressionTokenId::ARRAY_LITERAL;
|
173
|
|
|
$this->_setTextPos($this->_textPos + strlen($matches_array[0]));
|
174
|
|
|
} else {
|
175
|
148 |
|
switch ($this->_ch) {
|
176
|
148 |
|
case '(':
|
177
|
40 |
|
$this->_nextChar();
|
178
|
40 |
|
$t = ExpressionTokenId::OPENPARAM;
|
179
|
40 |
|
break;
|
180
|
148 |
|
case ')':
|
181
|
40 |
|
$this->_nextChar();
|
182
|
40 |
|
$t = ExpressionTokenId::CLOSEPARAM;
|
183
|
40 |
|
break;
|
184
|
148 |
|
case ',':
|
185
|
74 |
|
$this->_nextChar();
|
186
|
74 |
|
$t = ExpressionTokenId::COMMA;
|
187
|
74 |
|
break;
|
188
|
148 |
|
case '-':
|
189
|
5 |
|
$hasNext = $this->_textPos + 1 < $this->_textLen;
|
190
|
5 |
|
if ($hasNext && Char::isDigit($this->_text[$this->_textPos + 1])) {
|
191
|
4 |
|
$this->_nextChar();
|
192
|
4 |
|
$t = $this->_parseFromDigit();
|
193
|
4 |
|
if (self::isNumeric($t)) {
|
194
|
4 |
|
break;
|
195
|
|
|
}
|
196
|
|
|
|
197
|
|
|
$this->_setTextPos($tokenPos);
|
198
|
4 |
|
} else if ($hasNext && $this->_text[$tokenPos + 1] == 'I') {
|
199
|
1 |
|
$this->_nextChar();
|
200
|
1 |
|
$this->_parseIdentifier();
|
201
|
1 |
|
$currentIdentifier = substr($this->_text, $tokenPos + 1, $this->_textPos - $tokenPos - 1);
|
202
|
|
|
|
203
|
1 |
|
if (self::_isInfinityLiteralDouble($currentIdentifier)) {
|
204
|
1 |
|
$t = ExpressionTokenId::DOUBLE_LITERAL;
|
205
|
1 |
|
break;
|
206
|
1 |
|
} else if (self::_isInfinityLiteralSingle($currentIdentifier)) {
|
207
|
1 |
|
$t = ExpressionTokenId::SINGLE_LITERAL;
|
208
|
1 |
|
break;
|
209
|
|
|
}
|
210
|
|
|
|
211
|
|
|
// If it looked like '-INF' but wasn't we'll rewind and fall
|
212
|
|
|
// through to a simple '-' token.
|
213
|
|
|
$this->_setTextPos($tokenPos);
|
214
|
|
|
}
|
215
|
|
|
|
216
|
4 |
|
$this->_nextChar();
|
217
|
4 |
|
$t = ExpressionTokenId::MINUS;
|
218
|
4 |
|
break;
|
219
|
148 |
|
case '=':
|
220
|
18 |
|
$this->_nextChar();
|
221
|
18 |
|
$t = ExpressionTokenId::EQUAL;
|
222
|
18 |
|
break;
|
223
|
148 |
|
case '/':
|
224
|
24 |
|
$this->_nextChar();
|
225
|
24 |
|
$t = ExpressionTokenId::SLASH;
|
226
|
24 |
|
break;
|
227
|
148 |
|
case '?':
|
228
|
|
|
$this->_nextChar();
|
229
|
|
|
$t = ExpressionTokenId::QUESTION;
|
230
|
|
|
break;
|
231
|
148 |
|
case '.':
|
232
|
|
|
$this->_nextChar();
|
233
|
|
|
$t = ExpressionTokenId::DOT;
|
234
|
|
|
break;
|
235
|
148 |
|
case '\'':
|
236
|
66 |
|
$quote = $this->_ch;
|
237
|
|
|
do {
|
238
|
66 |
|
$this->_nextChar();
|
239
|
66 |
|
while ($this->_textPos < $this->_textLen && $this->_ch != $quote) {
|
240
|
66 |
|
$this->_nextChar();
|
241
|
|
|
}
|
242
|
|
|
|
243
|
66 |
|
if ($this->_textPos == $this->_textLen) {
|
244
|
1 |
|
$this->_parseError(
|
245
|
1 |
|
Messages::expressionLexerUnterminatedStringLiteral(
|
246
|
1 |
|
$this->_textPos, $this->_text
|
247
|
1 |
|
)
|
248
|
1 |
|
);
|
249
|
|
|
}
|
250
|
|
|
|
251
|
66 |
|
$this->_nextChar();
|
252
|
66 |
|
} while ($this->_ch == $quote);
|
253
|
66 |
|
$t = ExpressionTokenId::STRING_LITERAL;
|
254
|
66 |
|
break;
|
255
|
148 |
|
case '*':
|
256
|
5 |
|
$this->_nextChar();
|
257
|
5 |
|
$t = ExpressionTokenId::STAR;
|
258
|
5 |
|
break;
|
259
|
|
|
default:
|
260
|
147 |
|
if (Char::isLetter($this->_ch) || $this->_ch == '_') {
|
261
|
135 |
|
$this->_parseIdentifier();
|
262
|
135 |
|
$t = ExpressionTokenId::IDENTIFIER;
|
263
|
135 |
|
break;
|
264
|
|
|
}
|
265
|
|
|
|
266
|
142 |
|
if (Char::isDigit($this->_ch)) {
|
267
|
66 |
|
$t = $this->_parseFromDigit();
|
268
|
66 |
|
break;
|
269
|
|
|
}
|
270
|
|
|
|
271
|
139 |
|
if ($this->_textPos == $this->_textLen) {
|
272
|
138 |
|
$t = ExpressionTokenId::END;
|
273
|
138 |
|
break;
|
274
|
|
|
}
|
275
|
|
|
|
276
|
2 |
|
$this->_parseError(
|
277
|
2 |
|
Messages::expressionLexerInvalidCharacter(
|
278
|
2 |
|
$this->_ch, $this->_textPos
|
|
|
|
|
279
|
2 |
|
)
|
280
|
2 |
|
);
|
281
|
|
|
}
|
282
|
|
|
}
|
283
|
|
|
|
284
|
148 |
|
$this->_token->Id = $t;
|
|
|
|
|
285
|
148 |
|
$this->_token->Text = substr($this->_text, $tokenPos, $this->_textPos - $tokenPos);
|
286
|
148 |
|
$this->_token->Position = $tokenPos;
|
287
|
|
|
|
288
|
|
|
// Handle type-prefixed literals such as binary, datetime or guid.
|
289
|
148 |
|
$this->_handleTypePrefixedLiterals();
|
290
|
|
|
|
291
|
|
|
// Handle keywords.
|
292
|
148 |
|
if ($this->_token->Id == ExpressionTokenId::IDENTIFIER) {
|
293
|
134 |
|
if (self::_isInfinityOrNaNDouble($this->_token->Text)) {
|
294
|
1 |
|
$this->_token->Id = ExpressionTokenId::DOUBLE_LITERAL;
|
295
|
134 |
|
} else if (self::_isInfinityOrNanSingle($this->_token->Text)) {
|
296
|
1 |
|
$this->_token->Id = ExpressionTokenId::SINGLE_LITERAL;
|
297
|
134 |
|
} else if ($this->_token->Text == ODataConstants::KEYWORD_TRUE
|
298
|
134 |
|
|| $this->_token->Text == ODataConstants::KEYWORD_FALSE
|
299
|
|
|
) {
|
300
|
8 |
|
$this->_token->Id = ExpressionTokenId::BOOLEAN_LITERAL;
|
301
|
134 |
|
} else if ($this->_token->Text == ODataConstants::KEYWORD_NULL) {
|
302
|
11 |
|
$this->_token->Id = ExpressionTokenId::NULL_LITERAL;
|
303
|
|
|
}
|
304
|
|
|
}
|
305
|
|
|
}
|
306
|
|
|
|
307
|
|
|
/**
|
308
|
|
|
* Returns the next token without advancing the lexer to next token
|
309
|
|
|
*
|
310
|
|
|
* @return ExpressionToken
|
311
|
|
|
*/
|
312
|
56 |
|
public function peekNextToken()
|
313
|
|
|
{
|
314
|
56 |
|
$savedTextPos = $this->_textPos;
|
315
|
56 |
|
$savedChar = $this->_ch;
|
316
|
56 |
|
$savedToken = clone $this->_token;
|
317
|
56 |
|
$this->nextToken();
|
318
|
56 |
|
$result = clone $this->_token;
|
319
|
56 |
|
$this->_textPos = $savedTextPos;
|
320
|
56 |
|
$this->_ch = $savedChar;
|
321
|
56 |
|
$this->_token->Id = $savedToken->Id;
|
322
|
56 |
|
$this->_token->Position = $savedToken->Position;
|
323
|
56 |
|
$this->_token->Text = $savedToken->Text;
|
324
|
56 |
|
return $result;
|
325
|
|
|
}
|
326
|
|
|
|
327
|
|
|
/**
|
328
|
|
|
* Validates the current token is of the specified kind
|
329
|
|
|
*
|
330
|
|
|
* @param ExpressionTokenId $tokenId Expected token kind
|
331
|
|
|
*
|
332
|
|
|
* @return void
|
333
|
|
|
*
|
334
|
|
|
* @throws ODataException if current token is not of the
|
335
|
|
|
* specified kind.
|
336
|
|
|
*/
|
337
|
98 |
|
public function validateToken($tokenId)
|
338
|
|
|
{
|
339
|
98 |
|
if ($this->_token->Id != $tokenId) {
|
340
|
4 |
|
$this->_parseError(
|
341
|
4 |
|
Messages::expressionLexerSyntaxError(
|
342
|
4 |
|
$this->_textPos
|
343
|
4 |
|
)
|
344
|
4 |
|
);
|
345
|
|
|
}
|
346
|
|
|
}
|
347
|
|
|
|
348
|
|
|
/**
|
349
|
|
|
* Starting from an identifier, reads alternate sequence of dots and identifiers
|
350
|
|
|
* and returns the text for it
|
351
|
|
|
*
|
352
|
|
|
* @return string The dotted identifier starting at the current identifier
|
353
|
|
|
*/
|
354
|
97 |
|
public function readDottedIdentifier()
|
355
|
|
|
{
|
356
|
97 |
|
$this->validateToken(ExpressionTokenId::IDENTIFIER);
|
|
|
|
|
357
|
96 |
|
$identifier = $this->_token->Text;
|
358
|
96 |
|
$this->nextToken();
|
359
|
96 |
|
while ($this->_token->Id == ExpressionTokenId::DOT) {
|
360
|
|
|
$this->nextToken();
|
361
|
|
|
$this->validateToken(ExpressionTokenId::IDENTIFIER);
|
362
|
|
|
$identifier = $identifier . '.' . $this->_token->Text;
|
363
|
|
|
$this->nextToken();
|
364
|
|
|
}
|
365
|
|
|
|
366
|
96 |
|
return $identifier;
|
367
|
|
|
}
|
368
|
|
|
|
369
|
|
|
/**
|
370
|
|
|
* Check if the parameter ($tokenText) is INF or NaN
|
371
|
|
|
*
|
372
|
|
|
* @param string $tokenText Text to look in
|
373
|
|
|
*
|
374
|
|
|
* @return boolean true if match found, false otherwise
|
375
|
|
|
*/
|
376
|
134 |
|
private static function _isInfinityOrNaNDouble($tokenText)
|
377
|
|
|
{
|
378
|
134 |
|
if (strlen($tokenText) == 3) {
|
379
|
28 |
|
if ($tokenText[0] == 'I') {
|
380
|
1 |
|
return self::_isInfinityLiteralDouble($tokenText);
|
381
|
28 |
|
} else if ($tokenText[0] == 'N') {
|
382
|
1 |
|
return strncmp($tokenText, ODataConstants::XML_NAN_LITERAL, 3) == 0;
|
383
|
|
|
}
|
384
|
|
|
}
|
385
|
|
|
|
386
|
134 |
|
return false;
|
387
|
|
|
}
|
388
|
|
|
|
389
|
|
|
/**
|
390
|
|
|
* Check if the parameter ($text) is INF
|
391
|
|
|
*
|
392
|
|
|
* @param string $text Text to look in
|
393
|
|
|
*
|
394
|
|
|
* @return boolean true if match found, false otherwise
|
395
|
|
|
*/
|
396
|
2 |
|
private static function _isInfinityLiteralDouble($text)
|
397
|
|
|
{
|
398
|
2 |
|
return strcmp($text, ODataConstants::XML_INFINITY_LITERAL) == 0;
|
399
|
|
|
}
|
400
|
|
|
|
401
|
|
|
/**
|
402
|
|
|
* Checks if the parameter ($tokenText) is INFf/INFF or NaNf/NaNF.
|
403
|
|
|
*
|
404
|
|
|
* @param string $tokenText Input token
|
405
|
|
|
*
|
406
|
|
|
* @return bool true if match found, false otherwise
|
407
|
|
|
*/
|
408
|
134 |
|
private static function _isInfinityOrNanSingle($tokenText)
|
409
|
|
|
{
|
410
|
134 |
|
if (strlen($tokenText) == 4) {
|
411
|
27 |
|
if ($tokenText[0] == 'I') {
|
412
|
1 |
|
return self::_isInfinityLiteralSingle($tokenText);
|
413
|
27 |
|
} else if ($tokenText[0] == 'N') {
|
414
|
1 |
|
return ($tokenText[3] == ExpressionLexer::SINGLE_SUFFIX_LOWER
|
415
|
1 |
|
|| $tokenText[3] == ExpressionLexer::SINGLE_SUFFIX_UPPER)
|
416
|
1 |
|
&& strncmp($tokenText, ODataConstants::XML_NAN_LITERAL, 3) == 0;
|
417
|
|
|
}
|
418
|
|
|
}
|
419
|
|
|
|
420
|
134 |
|
return false;
|
421
|
|
|
}
|
422
|
|
|
|
423
|
|
|
/**
|
424
|
|
|
* Checks whether parameter ($text) EQUALS to 'INFf' or 'INFF' at position
|
425
|
|
|
*
|
426
|
|
|
* @param string $text Text to look in
|
427
|
|
|
*
|
428
|
|
|
* @return bool true if the substring is equal using an ordinal comparison;
|
429
|
|
|
* false otherwise
|
430
|
|
|
*/
|
431
|
2 |
|
private static function _isInfinityLiteralSingle($text)
|
432
|
|
|
{
|
433
|
2 |
|
return strlen($text) == 4
|
434
|
2 |
|
&& ($text[3] == ExpressionLexer::SINGLE_SUFFIX_LOWER
|
435
|
2 |
|
|| $text[3] == ExpressionLexer::SINGLE_SUFFIX_UPPER)
|
436
|
2 |
|
&& strncmp($text, ODataConstants::XML_INFINITY_LITERAL, 3) == 0;
|
437
|
|
|
}
|
438
|
|
|
|
439
|
|
|
/**
|
440
|
|
|
* Handles the literals that are prefixed by types.
|
441
|
|
|
* This method modified the token field as necessary.
|
442
|
|
|
*
|
443
|
|
|
* @return void
|
444
|
|
|
*
|
445
|
|
|
* @throws ODataException
|
446
|
|
|
*/
|
447
|
148 |
|
private function _handleTypePrefixedLiterals()
|
448
|
|
|
{
|
449
|
148 |
|
$id = $this->_token->Id;
|
450
|
148 |
|
if ($id != ExpressionTokenId::IDENTIFIER) {
|
|
|
|
|
451
|
144 |
|
return;
|
452
|
|
|
}
|
453
|
|
|
|
454
|
135 |
|
$quoteFollows = $this->_ch == '\'';
|
455
|
135 |
|
if (!$quoteFollows) {
|
456
|
134 |
|
return;
|
457
|
|
|
}
|
458
|
|
|
|
459
|
30 |
|
$tokenText = $this->_token->Text;
|
460
|
|
|
|
461
|
30 |
|
if (strcasecmp('datetime', $tokenText) == 0) {
|
462
|
12 |
|
$id = ExpressionTokenId::DATETIME_LITERAL;
|
463
|
20 |
|
} else if (strcasecmp('guid', $tokenText) == 0) {
|
464
|
20 |
|
$id = ExpressionTokenId::GUID_LITERAL;
|
465
|
1 |
|
} else if (strcasecmp('binary', $tokenText) == 0
|
466
|
1 |
|
|| strcasecmp('X', $tokenText) == 0
|
467
|
1 |
|
|| strcasecmp('x', $tokenText) == 0
|
468
|
|
|
) {
|
469
|
1 |
|
$id = ExpressionTokenId::BINARY_LITERAL;
|
470
|
|
|
} else {
|
471
|
|
|
return;
|
472
|
|
|
}
|
473
|
|
|
|
474
|
30 |
|
$tokenPos = $this->_token->Position;
|
475
|
|
|
do {
|
476
|
30 |
|
$this->_nextChar();
|
477
|
30 |
|
} while ($this->_ch != '\0' && $this->_ch != '\'');
|
478
|
|
|
|
479
|
30 |
|
if ($this->_ch == '\0') {
|
480
|
1 |
|
$this->_parseError(
|
481
|
1 |
|
Messages::expressionLexerUnterminatedStringLiteral(
|
482
|
1 |
|
$this->_textPos, $this->_text
|
483
|
1 |
|
)
|
484
|
1 |
|
);
|
485
|
|
|
}
|
486
|
|
|
|
487
|
30 |
|
$this->_nextChar();
|
488
|
30 |
|
$this->_token->Id = $id;
|
489
|
30 |
|
$this->_token->Text
|
490
|
30 |
|
= substr($this->_text, $tokenPos, $this->_textPos - $tokenPos);
|
491
|
|
|
}
|
492
|
|
|
|
493
|
|
|
/**
|
494
|
|
|
* Parses a token that starts with a digit
|
495
|
|
|
*
|
496
|
|
|
* @return ExpressionTokenId The kind of token recognized.
|
497
|
|
|
*/
|
498
|
69 |
|
private function _parseFromDigit()
|
499
|
|
|
{
|
500
|
69 |
|
$result = null;
|
501
|
69 |
|
$startChar = $this->_ch;
|
502
|
69 |
|
$this->_nextChar();
|
503
|
69 |
|
if ($startChar == '0' && $this->_ch == 'x' || $this->_ch == 'X') {
|
|
|
|
|
504
|
|
|
$result = ExpressionTokenId::BINARY_LITERAL;
|
505
|
|
|
do {
|
506
|
|
|
$this->_nextChar();
|
507
|
|
|
} while (ctype_xdigit($this->_ch));
|
508
|
|
|
} else {
|
509
|
69 |
|
$result = ExpressionTokenId::INTEGER_LITERAL;
|
510
|
69 |
|
while (Char::isDigit($this->_ch)) {
|
|
|
|
|
511
|
53 |
|
$this->_nextChar();
|
512
|
|
|
}
|
513
|
|
|
|
514
|
69 |
|
if ($this->_ch == '.') {
|
515
|
14 |
|
$result = ExpressionTokenId::DOUBLE_LITERAL;
|
516
|
14 |
|
$this->_nextChar();
|
517
|
14 |
|
$this->_validateDigit();
|
518
|
|
|
|
519
|
|
|
do {
|
520
|
14 |
|
$this->_nextChar();
|
521
|
14 |
|
} while (Char::isDigit($this->_ch));
|
522
|
|
|
}
|
523
|
|
|
|
524
|
69 |
|
if ($this->_ch == 'E' || $this->_ch == 'e') {
|
525
|
2 |
|
$result = ExpressionTokenId::DOUBLE_LITERAL;
|
526
|
2 |
|
$this->_nextChar();
|
527
|
2 |
|
if ($this->_ch == '+' || $this->_ch == '-') {
|
528
|
1 |
|
$this->_nextChar();
|
529
|
|
|
}
|
530
|
|
|
|
531
|
2 |
|
$this->_validateDigit();
|
532
|
|
|
do {
|
533
|
2 |
|
$this->_nextChar();
|
534
|
2 |
|
} while (Char::isDigit($this->_ch));
|
535
|
|
|
}
|
536
|
|
|
|
537
|
69 |
|
if ($this->_ch == 'M' || $this->_ch == 'm') {
|
538
|
4 |
|
$result = ExpressionTokenId::DECIMAL_LITERAL;
|
539
|
4 |
|
$this->_nextChar();
|
540
|
69 |
|
} else if ($this->_ch == 'd' || $this->_ch == 'D') {
|
541
|
2 |
|
$result = ExpressionTokenId::DOUBLE_LITERAL;
|
542
|
2 |
|
$this->_nextChar();
|
543
|
69 |
|
} else if ($this->_ch == 'L' || $this->_ch == 'l') {
|
544
|
3 |
|
$result = ExpressionTokenId::INT64_LITERAL;
|
545
|
3 |
|
$this->_nextChar();
|
546
|
69 |
|
} else if ($this->_ch == 'f' || $this->_ch == 'F') {
|
547
|
5 |
|
$result = ExpressionTokenId::SINGLE_LITERAL;
|
548
|
5 |
|
$this->_nextChar();
|
549
|
|
|
}
|
550
|
|
|
}
|
551
|
|
|
|
552
|
69 |
|
return $result;
|
|
|
|
|
553
|
|
|
}
|
554
|
|
|
|
555
|
|
|
/**
|
556
|
|
|
* Parses an identifier by advancing the current character.
|
557
|
|
|
*
|
558
|
|
|
* @return void
|
559
|
|
|
*/
|
560
|
135 |
|
private function _parseIdentifier()
|
561
|
|
|
{
|
562
|
|
|
do {
|
563
|
135 |
|
$this->_nextChar();
|
564
|
135 |
|
} while (Char::isLetterOrDigit($this->_ch) || $this->_ch == '_');
|
|
|
|
|
565
|
|
|
}
|
566
|
|
|
|
567
|
|
|
/**
|
568
|
|
|
* Advance to next character.
|
569
|
|
|
*
|
570
|
|
|
* @return void
|
571
|
|
|
*/
|
572
|
148 |
|
private function _nextChar()
|
573
|
|
|
{
|
574
|
148 |
|
if ($this->_textPos < $this->_textLen) {
|
575
|
148 |
|
$this->_textPos++;
|
576
|
|
|
}
|
577
|
|
|
|
578
|
148 |
|
$this->_ch
|
579
|
148 |
|
= $this->_textPos < $this->_textLen
|
|
|
|
|
580
|
148 |
|
? $this->_text[$this->_textPos] : '\0';
|
581
|
|
|
}
|
582
|
|
|
|
583
|
|
|
/**
|
584
|
|
|
* Set the text position.
|
585
|
|
|
*
|
586
|
|
|
* @param int $pos Value to position.
|
587
|
|
|
*
|
588
|
|
|
* @return void
|
589
|
|
|
*/
|
590
|
148 |
|
private function _setTextPos($pos)
|
591
|
|
|
{
|
592
|
148 |
|
$this->_textPos = $pos;
|
593
|
148 |
|
$this->_ch
|
594
|
148 |
|
= $this->_textPos < $this->_textLen
|
|
|
|
|
595
|
148 |
|
? $this->_text[$this->_textPos] : '\0';
|
596
|
|
|
}
|
597
|
|
|
|
598
|
|
|
/**
|
599
|
|
|
* Validate current character is a digit.
|
600
|
|
|
*
|
601
|
|
|
* @return void
|
602
|
|
|
*/
|
603
|
14 |
|
private function _validateDigit()
|
604
|
|
|
{
|
605
|
14 |
|
if (!Char::isDigit($this->_ch)) {
|
606
|
1 |
|
$this->_parseError(
|
607
|
1 |
|
Messages::expressionLexerDigitExpected(
|
608
|
1 |
|
$this->_textPos
|
609
|
1 |
|
)
|
610
|
1 |
|
);
|
611
|
|
|
}
|
612
|
|
|
}
|
613
|
|
|
|
614
|
|
|
/**
|
615
|
|
|
* Throws parser error.
|
616
|
|
|
*
|
617
|
|
|
* @param string $message The error message.
|
618
|
|
|
*
|
619
|
|
|
* @return void
|
620
|
|
|
*
|
621
|
|
|
* @throws ODataException
|
622
|
|
|
*/
|
623
|
9 |
|
private function _parseError($message)
|
624
|
|
|
{
|
625
|
9 |
|
throw ODataException::createSyntaxError($message);
|
626
|
|
|
}
|
627
|
|
|
}
|
628
|
|
|
|