ExpressionParser::generateComparisonExpression()   F
last analyzed

Complexity

Conditions 20
Paths 416

Size

Total Lines 128
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 65
nc 416
nop 4
dl 0
loc 128
rs 0.8111
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace POData\UriProcessor\QueryProcessor\ExpressionParser;
6
7
use POData\Common\Messages;
8
use POData\Common\NotImplementedException;
9
use POData\Common\ODataConstants;
10
use POData\Common\ODataException;
11
use POData\Providers\Metadata\ResourcePropertyKind;
12
use POData\Providers\Metadata\ResourceType;
13
use POData\Providers\Metadata\Type\Binary;
14
use POData\Providers\Metadata\Type\Boolean;
15
use POData\Providers\Metadata\Type\DateTime;
16
use POData\Providers\Metadata\Type\Decimal;
17
use POData\Providers\Metadata\Type\Double;
18
use POData\Providers\Metadata\Type\Guid;
19
use POData\Providers\Metadata\Type\Int32;
20
use POData\Providers\Metadata\Type\Int64;
21
use POData\Providers\Metadata\Type\IType;
22
use POData\Providers\Metadata\Type\Null1;
23
use POData\Providers\Metadata\Type\Single;
24
use POData\Providers\Metadata\Type\StringType;
25
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\AbstractExpression;
26
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ArithmeticExpression;
27
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ConstantExpression;
28
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ExpressionType;
29
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\FunctionCallExpression;
30
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\LogicalExpression;
31
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\PropertyAccessExpression;
32
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\RelationalExpression;
33
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\UnaryExpression;
34
use POData\UriProcessor\QueryProcessor\FunctionDescription;
35
use ReflectionException;
36
37
/**
38
 * Class ExpressionParser.
39
 */
40
class ExpressionParser
41
{
42
    const RECURSION_LIMIT = 200;
43
44
    /**
45
     * The Lexical analyzer.
46
     *
47
     * @var ExpressionLexer
48
     */
49
    protected $lexer;
50
51
    /**
52
     * The current recursion depth.
53
     *
54
     * @var int
55
     */
56
    private $recursionDepth;
57
58
    /**
59
     * The ResourceType on which $filter condition needs to be applied.
60
     *
61
     * @var ResourceType
62
     */
63
    private $resourceType;
64
65
    /**
66
     * @var bool
67
     */
68
    private $isPHPExpressionProvider;
69
70
    /**
71
     * True if the filter expression contains level 2 property access, for example
72
     * Customers?$filter=Address/LineNumber eq 12
73
     * Customer?$filter=Order/OrderID gt 1234
74
     * False otherwise.
75
     *
76
     * @var bool
77
     */
78
    private $hasLevel2PropertyInTheExpression;
79
80
    /**
81
     * Construct a new instance of ExpressionParser.
82
     *
83
     * @param string       $text                    The expression to parse
84
     * @param ResourceType $resourceType            The resource type of the resource targeted by the resource path
85
     * @param bool         $isPHPExpressionProvider
86
     *
87
     * TODO Expression parser should not depend on the fact that end user is implementing IExpressionProvider or not
88
     * @throws ODataException
89
     */
90
    public function __construct(string $text, ResourceType $resourceType, bool $isPHPExpressionProvider)
91
    {
92
        $this->lexer                            = new ExpressionLexer($text);
93
        $this->resourceType                     = $resourceType;
94
        $this->isPHPExpressionProvider          = $isPHPExpressionProvider;
95
        $this->hasLevel2PropertyInTheExpression = false;
96
    }
97
98
    /**
99
     * Checks whether the expression contains level 2 property access.
100
     *
101
     * @return bool
102
     */
103
    public function hasLevel2Property(): bool
104
    {
105
        return $this->hasLevel2PropertyInTheExpression;
106
    }
107
108
    /**
109
     * Resets parser with new expression string.
110
     *
111
     * @param  string         $text Reset the expression to parse
112
     * @throws ODataException
113
     */
114
    public function resetParser(string $text): void
115
    {
116
        $this->lexer          = new ExpressionLexer($text);
117
        $this->recursionDepth = 0;
118
    }
119
120
    /**
121
     * Parse the expression in filter option.
122
     *
123
     * @throws ODataException
124
     * @throws ReflectionException
125
     * @throws NotImplementedException
126
     * @return AbstractExpression
127
     */
128
    public function parseFilter(): AbstractExpression
129
    {
130
        return $this->parseExpression();
131
    }
132
133
    /**
134
     * Start parsing the expression.
135
     *
136
     * @throws ODataException
137
     * @throws ReflectionException
138
     * @throws NotImplementedException
139
     * @return AbstractExpression
140
     */
141
    private function parseExpression(): AbstractExpression
142
    {
143
        $this->recurseEnter();
144
        $expr = $this->parseLogicalOr();
145
        $this->recurseLeave();
146
147
        return $expr;
148
    }
149
150
    /**
151
     * Increment recursion count and throw error if beyond limit.
152
     *
153
     *
154
     * @throws ODataException If max recursion limit hits
155
     */
156
    private function recurseEnter(): void
157
    {
158
        ++$this->recursionDepth;
159
        if ($this->recursionDepth == self::RECURSION_LIMIT) {
160
            throw ODataException::createSyntaxError('Recursion limit reached.');
161
        }
162
    }
163
164
    /**
165
     * Parse logical or (or).
166
     *
167
     * @throws ODataException
168
     * @throws ReflectionException
169
     * @throws NotImplementedException
170
     * @return AbstractExpression
171
     */
172
    private function parseLogicalOr(): AbstractExpression
173
    {
174
        $this->recurseEnter();
175
        $left = $this->parseLogicalAnd();
176
        while ($this->tokenIdentifierIs(/* @scrutinizer ignore-type */ ODataConstants::KEYWORD_OR)) {
177
            $logicalOpToken = clone $this->getCurrentToken();
178
            $this->getLexer()->nextToken();
179
            $right = $this->parseLogicalAnd();
180
            FunctionDescription::verifyLogicalOpArguments($logicalOpToken, $left, $right);
181
            $left = new LogicalExpression(
182
                $left,
183
                $right,
184
                ExpressionType::OR_LOGICAL()
185
            );
186
        }
187
188
        $this->recurseLeave();
189
190
        return $left;
191
    }
192
193
    /**
194
     * Parse logical and (and).
195
     *
196
     * @throws ODataException
197
     * @throws ReflectionException
198
     * @throws NotImplementedException
199
     * @return AbstractExpression
200
     */
201
    private function parseLogicalAnd(): AbstractExpression
202
    {
203
        $this->recurseEnter();
204
        $left = $this->parseComparison();
205
        while ($this->tokenIdentifierIs(/* @scrutinizer ignore-type */ ODataConstants::KEYWORD_AND)) {
206
            $logicalOpToken = clone $this->getCurrentToken();
207
            $this->getLexer()->nextToken();
208
            $right = $this->parseComparison();
209
            FunctionDescription::verifyLogicalOpArguments($logicalOpToken, $left, $right);
210
            $left = new LogicalExpression($left, $right, ExpressionType::AND_LOGICAL());
211
        }
212
213
        $this->recurseLeave();
214
215
        return $left;
216
    }
217
218
    /**
219
     * Parse comparison operation (eq, ne, gt, ge, lt, le).
220
     *
221
     * @throws ODataException
222
     * @throws ReflectionException
223
     * @throws NotImplementedException
224
     * @return AbstractExpression
225
     */
226
    private function parseComparison(): AbstractExpression
227
    {
228
        $this->recurseEnter();
229
        $left = $this->parseAdditive();
230
        while ($this->getCurrentToken()->isComparisonOperator()) {
231
            $comparisonToken = clone $this->getCurrentToken();
232
            $this->getLexer()->nextToken();
233
            $right = $this->parseAdditive();
234
            $left  = self::generateComparisonExpression(
235
                $left,
236
                $right,
237
                $comparisonToken,
238
                $this->isPHPExpressionProvider
239
            );
240
        }
241
242
        $this->recurseLeave();
243
244
        return $left;
245
    }
246
247
    /**
248
     * Parse additive operation (add, sub).
249
     *
250
     * @throws ODataException
251
     * @throws ReflectionException
252
     * @throws NotImplementedException
253
     * @return AbstractExpression
254
     */
255
    private function parseAdditive(): AbstractExpression
256
    {
257
        $this->recurseEnter();
258
        $left = $this->parseMultiplicative();
259
        while ($this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ ODataConstants::KEYWORD_ADD)
260
            || $this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ ODataConstants::KEYWORD_SUB)) {
261
            $additiveToken = clone $this->getCurrentToken();
262
            $this->getLexer()->nextToken();
263
            $right        = $this->parseMultiplicative();
264
            $opReturnType = FunctionDescription::verifyAndPromoteArithmeticOpArguments($additiveToken, $left, $right);
265
            if ($additiveToken->identifierIs(/* @scrutinizer ignore-type */ ODataConstants::KEYWORD_ADD)) {
266
                $left = new ArithmeticExpression($left, $right, ExpressionType::ADD(), $opReturnType);
267
            } else {
268
                $left = new ArithmeticExpression($left, $right, ExpressionType::SUBTRACT(), $opReturnType);
269
            }
270
        }
271
272
        $this->recurseLeave();
273
274
        return $left;
275
    }
276
277
    /**
278
     * Parse multiplicative operators (mul, div, mod).
279
     *
280
     * @throws ODataException
281
     * @throws ReflectionException
282
     * @throws NotImplementedException
283
     * @return AbstractExpression
284
     */
285
    private function parseMultiplicative(): AbstractExpression
286
    {
287
        $this->recurseEnter();
288
        $left = $this->parseUnary();
289
        while ($this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ ODataConstants::KEYWORD_MULTIPLY)
290
            || $this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ ODataConstants::KEYWORD_DIVIDE)
291
            || $this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ ODataConstants::KEYWORD_MODULO)
292
        ) {
293
            $multiplyToken = clone $this->getCurrentToken();
294
            $this->getLexer()->nextToken();
295
            $right        = $this->parseUnary();
296
            $opReturnType = FunctionDescription::verifyAndPromoteArithmeticOpArguments(
297
                $multiplyToken,
298
                $left,
299
                $right
300
            );
301
            if ($multiplyToken->identifierIs(/* @scrutinizer ignore-type */ ODataConstants::KEYWORD_MULTIPLY)) {
302
                $left = new ArithmeticExpression($left, $right, ExpressionType::MULTIPLY(), $opReturnType);
303
            } elseif ($multiplyToken->identifierIs(/* @scrutinizer ignore-type */ ODataConstants::KEYWORD_DIVIDE)) {
304
                $left = new ArithmeticExpression($left, $right, ExpressionType::DIVIDE(), $opReturnType);
305
            } else {
306
                $left = new ArithmeticExpression($left, $right, ExpressionType::MODULO(), $opReturnType);
307
            }
308
        }
309
310
        $this->recurseLeave();
311
312
        return $left;
313
    }
314
315
    /**
316
     * Parse unary operator (- ,not).
317
     *
318
     * @throws ODataException
319
     * @throws ReflectionException
320
     * @throws NotImplementedException
321
     * @return AbstractExpression
322
     */
323
    private function parseUnary(): AbstractExpression
324
    {
325
        $this->recurseEnter();
326
327
        if ($this->getCurrentToken()->getId() == ExpressionTokenId::MINUS()
328
            || $this->getCurrentToken()->identifierIs(/* @scrutinizer ignore-type */ ODataConstants::KEYWORD_NOT)
329
        ) {
330
            $op = clone $this->getCurrentToken();
331
            $this->getLexer()->nextToken();
332
            if ($op->getId() == ExpressionTokenId::MINUS()
333
                && (ExpressionLexer::isNumeric($this->getCurrentToken()->getId()))
334
            ) {
335
                $numberLiteral           = $this->getCurrentToken();
336
                $numberLiteral->Text     = '-' . $numberLiteral->Text;
337
                $numberLiteral->Position = $op->Position;
338
                $this->setCurrentToken($numberLiteral);
339
                $this->recurseLeave();
340
341
                return $this->parsePrimary();
342
            }
343
344
            $expr = $this->parsePrimary();
345
            FunctionDescription::validateUnaryOpArguments($op, $expr);
346
            if ($op->getId() == ExpressionTokenId::MINUS()) {
347
                $expr = new UnaryExpression($expr, ExpressionType::NEGATE(), $expr->getType());
348
            } else {
349
                $expr = new UnaryExpression($expr, ExpressionType::NOT_LOGICAL(), new Boolean());
350
            }
351
352
            $this->recurseLeave();
353
354
            return $expr;
355
        }
356
357
        $this->recurseLeave();
358
359
        return $this->parsePrimary();
360
    }
361
362
    /**
363
     * Get the current token from lexer.
364
     *
365
     * @return ExpressionToken
366
     */
367
    private function getCurrentToken(): ExpressionToken
368
    {
369
        return $this->getLexer()->getCurrentToken();
370
    }
371
372
    /**
373
     * Retrieve current lexer instance.
374
     *
375
     * @return ExpressionLexer
376
     */
377
    public function getLexer(): ExpressionLexer
378
    {
379
        return $this->lexer;
380
    }
381
382
    /**
383
     * Set the current token in lexer.
384
     *
385
     * @param ExpressionToken $token The token to set as current token
386
     */
387
    private function setCurrentToken(ExpressionToken $token): void
388
    {
389
        $this->getLexer()->setCurrentToken($token);
390
    }
391
392
    /**
393
     * Decrement recursion count.
394
     */
395
    private function recurseLeave(): void
396
    {
397
        --$this->recursionDepth;
398
    }
399
400
    /**
401
     * Start parsing the primary.
402
     *
403
     * @throws ODataException
404
     * @throws ReflectionException
405
     * @throws NotImplementedException
406
     * @return AbstractExpression
407
     */
408
    private function parsePrimary(): AbstractExpression
409
    {
410
        $this->recurseEnter();
411
        $expr = $this->parsePrimaryStart();
412
        while (true) {
413
            if ($this->getCurrentToken()->getId() == ExpressionTokenId::SLASH()) {
414
                $this->getLexer()->nextToken();
415
                $expr = $this->parsePropertyAccess($expr);
416
            } else {
417
                break;
418
            }
419
        }
420
421
        $this->recurseLeave();
422
423
        return $expr;
424
    }
425
426
    /**
427
     * Parse primary tokens [literals, identifiers (e.g. function call), open param for sub expressions].
428
     *
429
     * @throws NotImplementedException
430
     * @throws ReflectionException
431
     * @throws ODataException
432
     * @return AbstractExpression
433
     */
434
    private function parsePrimaryStart(): AbstractExpression
435
    {
436
        switch ($this->getLexer()->getCurrentToken()->getId()) {
437
            case ExpressionTokenId::BOOLEAN_LITERAL():
438
                return $this->parseTypedLiteral(new Boolean());
439
            case ExpressionTokenId::DATETIME_LITERAL():
440
                return $this->parseTypedLiteral(new DateTime());
441
            case ExpressionTokenId::DECIMAL_LITERAL():
442
                return $this->parseTypedLiteral(new Decimal());
443
            case ExpressionTokenId::NULL_LITERAL():
444
                return $this->parseNullLiteral();
445
            case ExpressionTokenId::IDENTIFIER():
446
                return $this->parseIdentifier();
447
            case ExpressionTokenId::STRING_LITERAL():
448
                return $this->parseTypedLiteral(new StringType());
449
            case ExpressionTokenId::INT64_LITERAL():
450
                return $this->parseTypedLiteral(new Int64());
451
            case ExpressionTokenId::INTEGER_LITERAL():
452
                return $this->parseTypedLiteral(new Int32());
453
            case ExpressionTokenId::DOUBLE_LITERAL():
454
                return $this->parseTypedLiteral(new Double());
455
            case ExpressionTokenId::SINGLE_LITERAL():
456
                return $this->parseTypedLiteral(new Single());
457
            case ExpressionTokenId::GUID_LITERAL():
458
                return $this->parseTypedLiteral(new Guid());
459
            case ExpressionTokenId::BINARY_LITERAL():
460
                throw new NotImplementedException(
461
                    'Support for binary is not implemented'
462
                );
463
            //return $this->parseTypedLiteral(new Binary());
464
            case ExpressionTokenId::OPENPARAM():
465
                return $this->parseParenExpression();
466
            default:
467
                throw ODataException::createSyntaxError('Expression expected.');
468
        }
469
    }
470
471
    /**
472
     * Parse primitive type literal.
473
     *
474
     * @param IType $targetType Expected type of the current literal
475
     *
476
     * @throws ODataException
477
     * @return ConstantExpression
478
     */
479
    private function parseTypedLiteral(IType $targetType): ConstantExpression
480
    {
481
        $literal = $this->getLexer()->getCurrentToken()->Text;
482
        $outVal  = null;
483
        if (!$targetType->validate($literal, $outVal)) {
484
            throw ODataException::createSyntaxError(
485
                Messages::expressionParserUnrecognizedLiteral(
486
                    $targetType->getFullTypeName(),
487
                    $literal,
488
                    $this->getLexer()->getCurrentToken()->Position
489
                )
490
            );
491
        }
492
493
        $result = new ConstantExpression($outVal, $targetType);
494
        $this->getLexer()->nextToken();
495
496
        return $result;
497
    }
498
499
    /**
500
     * Parse null literal.
501
     *
502
     * @throws ODataException
503
     * @return ConstantExpression
504
     */
505
    private function parseNullLiteral(): ConstantExpression
506
    {
507
        $this->getLexer()->nextToken();
508
509
        return new ConstantExpression(null, new Null1());
510
    }
511
512
    /**
513
     * Parse an identifier.
514
     *
515
     * @throws NotImplementedException
516
     * @throws ReflectionException
517
     * @throws ODataException
518
     * @return FunctionCallExpression|PropertyAccessExpression
519
     */
520
    private function parseIdentifier(): AbstractExpression
521
    {
522
        $this->validateToken(ExpressionTokenId::IDENTIFIER());
523
524
        // An open paren here would indicate calling a method
525
        $identifierIsFunction = $this->getLexer()->peekNextToken()->getId() == ExpressionTokenId::OPENPARAM();
526
        if ($identifierIsFunction) {
527
            return $this->parseIdentifierAsFunction();
528
        } else {
529
            return $this->parsePropertyAccess(null);
530
        }
531
    }
532
533
    /**
534
     * Validate the current token.
535
     *
536
     * @param ExpressionTokenId $expressionTokenId Token to check
537
     *                                             with current token
538
     *
539
     * @throws ODataException
540
     */
541
    private function validateToken(ExpressionTokenId $expressionTokenId): void
542
    {
543
        if ($this->getCurrentToken()->getId() != $expressionTokenId) {
544
            throw ODataException::createSyntaxError('Syntax error.');
545
        }
546
    }
547
548
    /**
549
     * Try to parse an identifier which is followed by an open bracket as an astoria URI function call.
550
     *
551
     * @throws NotImplementedException
552
     * @throws ReflectionException
553
     * @throws ODataException
554
     * @return FunctionCallExpression
555
     */
556
    private function parseIdentifierAsFunction(): FunctionCallExpression
557
    {
558
        $functionToken = clone $this->getCurrentToken();
559
        $functions     = FunctionDescription::verifyFunctionExists($functionToken);
560
        $this->getLexer()->nextToken();
561
        $paramExpressions = $this->parseArgumentList();
562
        $function         = FunctionDescription::verifyFunctionCallOpArguments(
563
            $functions,
564
            $paramExpressions,
565
            $functionToken
566
        );
567
568
        return new FunctionCallExpression($function, $paramExpressions);
569
    }
570
571
    /**
572
     * Start parsing argument list of a function-call.
573
     *
574
     * @throws NotImplementedException
575
     * @throws ReflectionException
576
     * @throws ODataException
577
     * @return array<AbstractExpression>
578
     */
579
    private function parseArgumentList(): array
580
    {
581
        if ($this->getCurrentToken()->getId() != ExpressionTokenId::OPENPARAM()) {
582
            throw ODataException::createSyntaxError('Open parenthesis expected.');
583
        }
584
585
        $this->getLexer()->nextToken();
586
        $args = $this->getCurrentToken()->getId() != ExpressionTokenId::CLOSEPARAM()
587
            ? $this->parseArguments() : [];
588
        if ($this->getCurrentToken()->getId() != ExpressionTokenId::CLOSEPARAM()) {
589
            throw ODataException::createSyntaxError('Close parenthesis expected.');
590
        }
591
592
        $this->getLexer()->nextToken();
593
594
        return $args;
595
    }
596
597
    /**
598
     * Parse arguments of a function-call.
599
     *
600
     * @throws NotImplementedException
601
     * @throws ReflectionException
602
     * @throws ODataException
603
     * @return array<AbstractExpression>
604
     */
605
    private function parseArguments(): array
606
    {
607
        $argList = [];
608
        while (true) {
609
            $argList[] = $this->parseExpression();
610
            if ($this->getCurrentToken()->getId() != ExpressionTokenId::COMMA()) {
611
                break;
612
            }
613
614
            $this->getLexer()->nextToken();
615
        }
616
617
        return $argList;
618
    }
619
620
    /**
621
     * Parse a property access.
622
     *
623
     * @param PropertyAccessExpression|null $parentExpression Parent expression
624
     *
625
     * @throws ReflectionException
626
     * @throws ODataException
627
     * @return PropertyAccessExpression
628
     */
629
    private function parsePropertyAccess(PropertyAccessExpression $parentExpression = null): PropertyAccessExpression
630
    {
631
        $identifier = $this->getCurrentToken()->getIdentifier();
632
        if (null === $parentExpression) {
633
            $parentResourceType = $this->resourceType;
634
        } else {
635
            $parentResourceType                     = $parentExpression->getResourceType();
636
            $this->hasLevel2PropertyInTheExpression = true;
637
        }
638
639
        $resourceProperty = $parentResourceType->resolveProperty($identifier);
640
        if (null === $resourceProperty) {
641
            throw ODataException::createSyntaxError(
642
                Messages::expressionLexerNoPropertyInType(
643
                    $identifier,
644
                    $parentResourceType->getFullName(),
645
                    $this->getCurrentToken()->Position
646
                )
647
            );
648
        }
649
650
        if (($resourceProperty->getKind()) == ResourcePropertyKind::RESOURCESET_REFERENCE()) {
651
            throw ODataException::createSyntaxError(
652
                Messages::expressionParserEntityCollectionNotAllowedInFilter(
653
                    $resourceProperty->getName(),
654
                    $parentResourceType->getFullName(),
655
                    $this->getCurrentToken()->Position
656
                )
657
            );
658
        }
659
660
        $exp = new PropertyAccessExpression($resourceProperty, $parentExpression);
661
        $this->getLexer()->nextToken();
662
663
        return $exp;
664
    }
665
666
    /**
667
     * Parse Sub expression.
668
     *
669
     * @throws NotImplementedException
670
     * @throws ReflectionException
671
     * @throws ODataException
672
     * @return AbstractExpression
673
     */
674
    private function parseParenExpression(): AbstractExpression
675
    {
676
        if ($this->getCurrentToken()->getId() != ExpressionTokenId::OPENPARAM()) {
677
            throw ODataException::createSyntaxError('Open parenthesis expected.');
678
        }
679
680
        $this->getLexer()->nextToken();
681
        $expr = $this->parseExpression();
682
        if ($this->getCurrentToken()->getId() != ExpressionTokenId::CLOSEPARAM()) {
683
            throw ODataException::createSyntaxError('Close parenthesis expected.');
684
        }
685
686
        $this->getLexer()->nextToken();
687
688
        return $expr;
689
    }
690
691
    /**
692
     * Generates Comparison Expression.
693
     *
694
     * @param AbstractExpression $left                    The LHS expression
695
     * @param AbstractExpression $right                   The RHS expression
696
     * @param ExpressionToken    $expressionToken         The comparison expression token
697
     * @param bool               $isPHPExpressionProvider
698
     *
699
     * @throws ODataException
700
     * @return FunctionCallExpression|UnaryExpression|RelationalExpression
701
     */
702
    private static function generateComparisonExpression(
703
        AbstractExpression $left,
704
        AbstractExpression $right,
705
        ExpressionToken $expressionToken,
706
        bool $isPHPExpressionProvider
707
    ): AbstractExpression {
708
        FunctionDescription::verifyRelationalOpArguments($expressionToken, $left, $right);
709
710
        //We need special handling for comparison of following types:
711
        //1. EdmString
712
        //2. DateTime
713
        //3. Guid
714
        //4. Binary
715
        //Will make these comparison as function calls, which will
716
        // be converted to language specific function call by expression
717
        // provider
718
        $string = new StringType();
719
        if ($left->typeIs($string) && $right->typeIs($string)) {
720
            $strcmpFunctions = FunctionDescription::stringComparisonFunctions();
721
            $left            = new FunctionCallExpression($strcmpFunctions[0], [$left, $right]);
722
            $right           = new ConstantExpression(0, new Int32());
723
        }
724
725
        $dateTime = new DateTime();
726
        if ($left->typeIs($dateTime) && $right->typeIs($dateTime)) {
727
            $dateTimeCmpFunctions = FunctionDescription::dateTimeComparisonFunctions();
728
            $left                 = new FunctionCallExpression($dateTimeCmpFunctions[0], [$left, $right]);
729
            $right                = new ConstantExpression(0, new Int32());
730
        }
731
732
        $guid = new Guid();
733
        if ($left->typeIs($guid) && $right->typeIs($guid)) {
734
            $guidEqualityFunctions = FunctionDescription::guidEqualityFunctions();
735
            $left                  = new FunctionCallExpression($guidEqualityFunctions[0], [$left, $right]);
736
            $right                 = new ConstantExpression(true, new Boolean());
737
        }
738
739
        $binary = new Binary();
740
        if ($left->typeIs($binary) && $right->typeIs($binary)) {
741
            $binaryEqualityFunctions = FunctionDescription::binaryEqualityFunctions();
742
            $left                    = new FunctionCallExpression($binaryEqualityFunctions[0], [$left, $right]);
743
            $right                   = new ConstantExpression(true, new Boolean());
744
        }
745
746
        $null = new Null1();
747
        if ($left->typeIs($null) || $right->typeIs($null)) {
748
            // If the end user is responsible for implementing IExpressionProvider
749
            // then the sub-tree for a nullability check would be:
750
751
            //          RelationalExpression(EQ/NE)
752
            //                    |
753
            //               ------------
754
            //               |           |
755
            //               |           |
756
            //            CustomerID    NULL
757
758
            // Otherwise (In case of default PHPExpressionProvider):
759
760
            //  CustomerID eq null
761
            //  ==================
762
763
            //              FunctionCallExpression(is_null)
764
            //                       |
765
            //                       |- Signature => bool (typeof(CustomerID))
766
            //                       |- args => {CustomerID}
767
768
            //  CustomerID ne null
769
            //  ==================
770
771
            //              UnaryExpression (not)
772
            //                       |
773
            //              FunctionCallExpression(is_null)
774
            //                       |
775
            //                       |- Signature => bool (typeof(CustomerID))
776
            //                       |- args => {CustomerID}
777
778
            if ($isPHPExpressionProvider) {
779
                $arg                       = $left->typeIs($null) ? $right : $left;
780
                $isNullFunctionDescription = new FunctionDescription('is_null', new Boolean(), [$arg->getType()]);
781
                switch ($expressionToken->Text) {
782
                    case ODataConstants::KEYWORD_EQUAL:
783
                        return new FunctionCallExpression($isNullFunctionDescription, [$arg]);
784
                    case ODataConstants::KEYWORD_NOT_EQUAL:
785
                        return new UnaryExpression(
786
                            new FunctionCallExpression($isNullFunctionDescription, [$arg]),
787
                            ExpressionType::NOT_LOGICAL(),
788
                            new Boolean()
789
                        );
790
                }
791
            }
792
        }
793
794
        switch ($expressionToken->Text) {
795
            case ODataConstants::KEYWORD_EQUAL:
796
                return new RelationalExpression(
797
                    $left,
798
                    $right,
799
                    ExpressionType::EQUAL()
800
                );
801
            case ODataConstants::KEYWORD_NOT_EQUAL:
802
                return new RelationalExpression(
803
                    $left,
804
                    $right,
805
                    ExpressionType::NOTEQUAL()
806
                );
807
            case ODataConstants::KEYWORD_GREATERTHAN:
808
                return new RelationalExpression(
809
                    $left,
810
                    $right,
811
                    ExpressionType::GREATERTHAN()
812
                );
813
            case ODataConstants::KEYWORD_GREATERTHAN_OR_EQUAL:
814
                return new RelationalExpression(
815
                    $left,
816
                    $right,
817
                    ExpressionType::GREATERTHAN_OR_EQUAL()
818
                );
819
            case ODataConstants::KEYWORD_LESSTHAN:
820
                return new RelationalExpression(
821
                    $left,
822
                    $right,
823
                    ExpressionType::LESSTHAN()
824
                );
825
            default:
826
                return new RelationalExpression(
827
                    $left,
828
                    $right,
829
                    ExpressionType::LESSTHAN_OR_EQUAL()
830
                );
831
        }
832
    }
833
834
    /**
835
     * Check the current token is of a specific kind.
836
     * TODO: Figure out why rest of code is passing a string instead of an object.
837
     *
838
     * @param ExpressionTokenId $expressionTokenId Token to check with current token
839
     *
840
     * @return bool
841
     */
842
    private function tokenIdentifierIs($expressionTokenId): bool
843
    {
844
        return $this->getCurrentToken()->identifierIs($expressionTokenId);
845
    }
846
}
847