SPARQLParser::xLimitOrOffsetClause()   A
last analyzed

Complexity

Conditions 6
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6.3949

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 8
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 14
ccs 7
cts 9
cp 0.7778
crap 6.3949
rs 9.2222
1
<?php
2
3
/**
4
 * This file is part of the sweetrdf/InMemoryStoreSqlite package and licensed under
5
 * the terms of the GPL-2 license.
6
 *
7
 * (c) Konrad Abicht <[email protected]>
8
 * (c) Benjamin Nowack
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace sweetrdf\InMemoryStoreSqlite\Parser;
15
16
use sweetrdf\InMemoryStoreSqlite\Log\Logger;
17
use sweetrdf\InMemoryStoreSqlite\NamespaceHelper;
18
use sweetrdf\InMemoryStoreSqlite\StringReader;
19
20
use function sweetrdf\InMemoryStoreSqlite\calcBase;
21
22
class SPARQLParser extends TurtleParser
23
{
24
    /**
25
     * @var array<mixed>
26
     */
27
    public array $bnode_pattern_index;
28
29 98
    public function __construct(Logger $logger, NamespaceHelper $namespaceHelper, StringReader $stringReader)
30
    {
31 98
        parent::__construct($logger, $namespaceHelper, $stringReader);
32
33 98
        $this->bnode_prefix = 'arc'.substr(md5(uniqid(rand())), 0, 4).'b';
34 98
        $this->bnode_id = 0;
35 98
        $this->bnode_pattern_index = ['patterns' => [], 'bnodes' => []];
36
    }
37
38 98
    public function parse(string $q, string $path = ''): void
39
    {
40 98
        $this->base = $path ? calcBase($path) : NamespaceHelper::BASE_NAMESPACE;
41 98
        $this->r = [
42 98
            'base' => '',
43 98
            'vars' => [],
44 98
            'prefixes' => [],
45 98
        ];
46 98
        $this->unparsed_code = $q;
47 98
        list($r, $v) = $this->xQuery($q);
48 98
        if ($r) {
49 97
            $this->r['query'] = $r;
50 97
            $this->unparsed_code = trim($v);
51 4
        } elseif (!$this->logger->hasEntries('error') && !$this->unparsed_code) {
52
            $this->logger->error('Query not properly closed');
53
        }
54 98
        $this->r['prefixes'] = $this->namespaceHelper->getNamespaces();
55 98
        $this->r['base'] = $this->base;
56
        /* remove trailing comments */
57 98
        while (preg_match('/^\s*(\#[^\xd\xa]*)(.*)$/si', $this->unparsed_code, $m)) {
58
            $this->unparsed_code = $m[2];
59
        }
60 98
        if ($this->unparsed_code && !$this->logger->hasEntries('error')) {
61
            $rest = preg_replace('/[\x0a|\x0d]/i', ' ', substr($this->unparsed_code, 0, 30));
62
            $msg = trim($rest) ? 'Could not properly handle "'.$rest.'"' : 'Syntax error, probably an incomplete pattern';
63
            $this->logger->error($msg);
64
        }
65
    }
66
67
    /* 1 */
68
69
    protected function xQuery($v)
70
    {
71
        list($r, $v) = $this->xPrologue($v);
72
        foreach (['Select', 'Construct', 'Describe', 'Ask'] as $type) {
73
            $m = 'x'.$type.'Query';
74
            if ((list($r, $v) = $this->$m($v)) && $r) {
75
                return [$r, $v];
76
            }
77
        }
78
79
        return [0, $v];
80
    }
81
82
    /* 2 */
83
84 98
    protected function xPrologue($v)
85
    {
86 98
        $r = 0;
87 98
        if ((list($sub_r, $v) = $this->xBaseDecl($v)) && $sub_r) {
88
            $this->base = $sub_r;
89
            $r = 1;
90
        }
91 98
        while ((list($sub_r, $v) = $this->xPrefixDecl($v)) && $sub_r) {
92 20
            $this->namespaceHelper->setPrefix($sub_r['prefix'], $sub_r['uri']);
93 20
            $r = 1;
94
        }
95
96 98
        return [$r, $v];
97
    }
98
99
    /* 5.. */
100
101 98
    protected function xSelectQuery($v)
102
    {
103 98
        if ($sub_r = $this->x('SELECT\s+', $v)) {
104 91
            $r = [
105 91
                'type' => 'select',
106 91
                'result_vars' => [],
107 91
                'dataset' => [],
108 91
            ];
109 91
            $all_vars = 0;
110 91
            $sub_v = $sub_r[1];
111
            /* distinct, reduced */
112 91
            if ($sub_r = $this->x('(DISTINCT|REDUCED)\s+', $sub_v)) {
113 1
                $r[strtolower($sub_r[1])] = 1;
114 1
                $sub_v = $sub_r[2];
115
            }
116
            /* result vars */
117 91
            if ($sub_r = $this->x('\*\s+', $sub_v)) {
118 48
                $all_vars = 1;
119 48
                $sub_v = $sub_r[1];
120
            } else {
121 45
                while ((list($sub_r, $sub_v) = $this->xResultVar($sub_v)) && $sub_r) {
122 43
                    $r['result_vars'][] = $sub_r;
123
                }
124
            }
125 91
            if (!$all_vars && !\count($r['result_vars'])) {
126 2
                $this->logger->error('No result bindings specified.');
127
            }
128
            /* dataset */
129 91
            while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) {
130 29
                $r['dataset'][] = $sub_r;
131
            }
132
            /* where */
133 91
            if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) {
134 88
                $r['pattern'] = $sub_r;
135
            } else {
136 3
                return [0, $v];
137
            }
138
            /* solution modifier */
139 88
            if ((list($sub_r, $sub_v) = $this->xSolutionModifier($sub_v)) && $sub_r) {
140 14
                $r = array_merge($r, $sub_r);
141
            }
142
            /* all vars */
143 88
            if ($all_vars) {
144 47
                foreach ($this->r['vars'] as $var) {
145 47
                    $r['result_vars'][] = ['var' => $var, 'aggregate' => 0, 'alias' => ''];
146
                }
147 47
                if (!$r['result_vars']) {
148
                    $r['result_vars'][] = '*';
149
                }
150
            }
151
152 88
            return [$r, $sub_v];
153
        }
154
155 92
        return [0, $v];
156
    }
157
158
    protected function xResultVar($v)
159
    {
160
        return $this->xVar($v);
161
    }
162
163
    /* 6.. */
164
165 92
    protected function xConstructQuery($v)
166
    {
167 92
        if ($sub_r = $this->x('CONSTRUCT\s*', $v)) {
168 3
            $r = [
169 3
                'type' => 'construct',
170 3
                'dataset' => [],
171 3
            ];
172 3
            $sub_v = $sub_r[1];
173
            /* construct template */
174 3
            if ((list($sub_r, $sub_v) = $this->xConstructTemplate($sub_v)) && \is_array($sub_r)) {
175 3
                $r['construct_triples'] = $sub_r;
176
            } else {
177
                $this->logger->error('Construct Template not found');
178
179
                return [0, $v];
180
            }
181
            /* dataset */
182 3
            while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) {
183
                $r['dataset'][] = $sub_r;
184
            }
185
            /* where */
186 3
            if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) {
187 3
                $r['pattern'] = $sub_r;
188
            } else {
189
                return [0, $v];
190
            }
191
            /* solution modifier */
192 3
            if ((list($sub_r, $sub_v) = $this->xSolutionModifier($sub_v)) && $sub_r) {
193
                $r = array_merge($r, $sub_r);
194
            }
195
196 3
            return [$r, $sub_v];
197
        }
198
199 92
        return [0, $v];
200
    }
201
202
    /* 7.. */
203
204 92
    protected function xDescribeQuery($v)
205
    {
206 92
        if ($sub_r = $this->x('DESCRIBE\s+', $v)) {
207 3
            $r = [
208 3
                'type' => 'describe',
209 3
                'result_vars' => [],
210 3
                'result_uris' => [],
211 3
                'dataset' => [],
212 3
            ];
213 3
            $sub_v = $sub_r[1];
214 3
            $all_vars = 0;
215
            /* result vars/uris */
216 3
            if ($sub_r = $this->x('\*\s+', $sub_v)) {
217 1
                $all_vars = 1;
218 1
                $sub_v = $sub_r[1];
219
            } else {
220
                do {
221 2
                    $proceed = 0;
222 2
                    if ((list($sub_r, $sub_v) = $this->xResultVar($sub_v)) && $sub_r) {
223 1
                        $r['result_vars'][] = $sub_r;
224 1
                        $proceed = 1;
225
                    }
226 2
                    if ((list($sub_r, $sub_v) = $this->xIRIref($sub_v)) && $sub_r) {
227 1
                        $r['result_uris'][] = $sub_r;
228 1
                        $proceed = 1;
229
                    }
230 2
                } while ($proceed);
231
            }
232 3
            if (!$all_vars && !\count($r['result_vars']) && !\count($r['result_uris'])) {
233
                $this->logger->error('No result bindings specified.');
234
            }
235
            /* dataset */
236 3
            while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) {
237
                $r['dataset'][] = $sub_r;
238
            }
239
            /* where */
240 3
            if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) {
241 2
                $r['pattern'] = $sub_r;
242
            }
243
            /* solution modifier */
244 3
            if ((list($sub_r, $sub_v) = $this->xSolutionModifier($sub_v)) && $sub_r) {
245
                $r = array_merge($r, $sub_r);
246
            }
247
            /* all vars */
248 3
            if ($all_vars) {
249 1
                foreach ($this->r['vars'] as $var) {
250 1
                    $r['result_vars'][] = ['var' => $var, 'aggregate' => 0, 'alias' => ''];
251
                }
252
            }
253
254 3
            return [$r, $sub_v];
255
        }
256
257 92
        return [0, $v];
258
    }
259
260
    /* 8.. */
261
262 92
    protected function xAskQuery($v)
263
    {
264 92
        if ($sub_r = $this->x('ASK\s+', $v)) {
265 3
            $r = [
266 3
                'type' => 'ask',
267 3
                'dataset' => [],
268 3
            ];
269 3
            $sub_v = $sub_r[1];
270
            /* dataset */
271 3
            while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) {
272 2
                $r['dataset'][] = $sub_r;
273
            }
274
            /* where */
275 3
            if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) {
276 3
                $r['pattern'] = $sub_r;
277
278 3
                return [$r, $sub_v];
279
            } else {
280
                $this->logger->error('Missing or invalid WHERE clause.');
281
            }
282
        }
283
284 92
        return [0, $v];
285
    }
286
287
    /* 9, 10, 11, 12 */
288
289 97
    protected function xDatasetClause($v)
290
    {
291 97
        if ($r = $this->x('FROM(\s+NAMED)?\s+', $v)) {
292 31
            $named = $r[1] ? 1 : 0;
293 31
            if ((list($r, $sub_v) = $this->xIRIref($r[2])) && $r) {
294 31
                return [['graph' => $r, 'named' => $named], $sub_v];
295
            }
296
        }
297
298 97
        return [0, $v];
299
    }
300
301
    /* 13 */
302
303 97
    protected function xWhereClause($v)
304
    {
305 97
        if ($r = $this->x('(WHERE)?', $v)) {
306 97
            $v = $r[2];
307
        }
308 97
        if ((list($r, $v) = $this->xGroupGraphPattern($v)) && $r) {
309 94
            return [$r, $v];
310
        }
311
312 91
        return [0, $v];
313
    }
314
315
    /* 14, 15 */
316
317
    protected function xSolutionModifier($v)
318
    {
319
        $r = [];
320
        if ((list($sub_r, $sub_v) = $this->xOrderClause($v)) && $sub_r) {
321
            $r['order_infos'] = $sub_r;
322
        }
323
        while ((list($sub_r, $sub_v) = $this->xLimitOrOffsetClause($sub_v)) && $sub_r) {
324
            $r = array_merge($r, $sub_r);
325
        }
326
327
        return ($v == $sub_v) ? [0, $v] : [$r, $sub_v];
328
    }
329
330
    /* 18, 19 */
331
332 97
    protected function xLimitOrOffsetClause($v)
333
    {
334 97
        if ($sub_r = $this->x('(LIMIT|OFFSET)', $v)) {
335 3
            $key = strtolower($sub_r[1]);
336 3
            $sub_v = $sub_r[2];
337 3
            if ((list($sub_r, $sub_v) = $this->xINTEGER($sub_v)) && (false !== $sub_r)) {
338 3
                return [[$key => $sub_r], $sub_v];
339
            }
340
            if ((list($sub_r, $sub_v) = $this->xPlaceholder($sub_v)) && (false !== $sub_r)) {
341
                return [[$key => $sub_r], $sub_v];
342
            }
343
        }
344
345 97
        return [0, $v];
346
    }
347
348
    /* 16 */
349
350 97
    protected function xOrderClause($v)
351
    {
352 97
        if ($sub_r = $this->x('ORDER BY\s+', $v)) {
353 5
            $sub_v = $sub_r[1];
354 5
            $r = [];
355 5
            while ((list($sub_r, $sub_v) = $this->xOrderCondition($sub_v)) && $sub_r) {
356 4
                $r[] = $sub_r;
357
            }
358 5
            if (\count($r)) {
359 4
                return [$r, $sub_v];
360
            } else {
361 1
                $this->logger->error('No order conditions specified.');
362
            }
363
        }
364
365 97
        return [0, $v];
366
    }
367
368
    /* 17, 27 */
369
370 5
    protected function xOrderCondition($v)
371
    {
372 5
        if ($sub_r = $this->x('(ASC|DESC)', $v)) {
373 4
            $dir = strtolower($sub_r[1]);
374 4
            $sub_v = $sub_r[2];
375 4
            if ((list($sub_r, $sub_v) = $this->xBrackettedExpression($sub_v)) && $sub_r) {
376 4
                $sub_r['direction'] = $dir;
377
378 4
                return [$sub_r, $sub_v];
379
            }
380 5
        } elseif ((list($sub_r, $sub_v) = $this->xVar($v)) && $sub_r) {
381
            $sub_r['direction'] = 'asc';
382
383
            return [$sub_r, $sub_v];
384 5
        } elseif ((list($sub_r, $sub_v) = $this->xBrackettedExpression($v)) && $sub_r) {
385
            return [$sub_r, $sub_v];
386 5
        } elseif ((list($sub_r, $sub_v) = $this->xBuiltInCall($v)) && $sub_r) {
387
            $sub_r['direction'] = 'asc';
388
389
            return [$sub_r, $sub_v];
390 5
        } elseif ((list($sub_r, $sub_v) = $this->xFunctionCall($v)) && $sub_r) {
391
            $sub_r['direction'] = 'asc';
392
393
            return [$sub_r, $sub_v];
394
        }
395
396 5
        return [0, $v];
397
    }
398
399
    /* 20 */
400
401 97
    protected function xGroupGraphPattern($v)
402
    {
403 97
        $pattern_id = substr(md5(uniqid(rand())), 0, 4);
404 97
        if ($sub_r = $this->x('\{', $v)) {
405 95
            $r = ['type' => 'group', 'patterns' => []];
406 95
            $sub_v = $sub_r[1];
407 95
            if ((list($sub_r, $sub_v) = $this->xTriplesBlock($sub_v)) && $sub_r) {
408 94
                $this->indexBnodes($sub_r, $pattern_id);
409 94
                $r['patterns'][] = ['type' => 'triples', 'patterns' => $sub_r];
410
            }
411
            do {
412 95
                $proceed = 0;
413 95
                if ((list($sub_r, $sub_v) = $this->xGraphPatternNotTriples($sub_v)) && $sub_r) {
414 3
                    $r['patterns'][] = $sub_r;
415 3
                    $pattern_id = substr(md5(uniqid(rand())), 0, 4);
416 3
                    $proceed = 1;
417 95
                } elseif ((list($sub_r, $sub_v) = $this->xFilter($sub_v)) && $sub_r) {
418 23
                    $r['patterns'][] = ['type' => 'filter', 'constraint' => $sub_r];
419 23
                    $proceed = 1;
420
                }
421 95
                if ($sub_r = $this->x('\.', $sub_v)) {
422
                    $sub_v = $sub_r[1];
423
                }
424 95
                if ((list($sub_r, $sub_v) = $this->xTriplesBlock($sub_v)) && $sub_r) {
425
                    $this->indexBnodes($sub_r, $pattern_id);
426
                    $r['patterns'][] = ['type' => 'triples', 'patterns' => $sub_r];
427
                    $proceed = 1;
428
                }
429 95
                if ((list($sub_r, $sub_v) = $this->xPlaceholder($sub_v)) && $sub_r) {
430
                    $r['patterns'][] = $sub_r;
431
                    $proceed = 1;
432
                }
433 95
            } while ($proceed);
434 95
            if ($sub_r = $this->x('\}', $sub_v)) {
435 94
                $sub_v = $sub_r[1];
436
437 94
                return [$r, $sub_v];
438
            }
439 1
            $rest = preg_replace('/[\x0a|\x0d]/i', ' ', substr($sub_v, 0, 30));
440 1
            $this->logger->error('Incomplete or invalid Group Graph pattern. Could not handle "'.$rest.'"');
441
        }
442
443 97
        return [0, $v];
444
    }
445
446 94
    protected function indexBnodes($triples, $pattern_id)
447
    {
448 94
        $index_id = \count($this->bnode_pattern_index['patterns']);
0 ignored issues
show
Unused Code introduced by
The assignment to $index_id is dead and can be removed.
Loading history...
449 94
        $index_id = $pattern_id;
450 94
        $this->bnode_pattern_index['patterns'][] = $triples;
451 94
        foreach ($triples as $t) {
452 94
            foreach (['s', 'p', 'o'] as $term) {
453 94
                if ('bnode' == $t[$term.'_type']) {
454
                    $val = $t[$term];
455
                    if (isset($this->bnode_pattern_index['bnodes'][$val]) && ($this->bnode_pattern_index['bnodes'][$val] != $index_id)) {
456
                        $this->logger->error('Re-used bnode label "'.$val.'" across graph patterns');
457
                    } else {
458
                        $this->bnode_pattern_index['bnodes'][$val] = $index_id;
459
                    }
460
                }
461
            }
462
        }
463
    }
464
465
    /* 22.., 25.. */
466
467 95
    protected function xGraphPatternNotTriples($v)
468
    {
469 95
        if ((list($sub_r, $sub_v) = $this->xOptionalGraphPattern($v)) && $sub_r) {
470 2
            return [$sub_r, $sub_v];
471
        }
472 95
        if ((list($sub_r, $sub_v) = $this->xGraphGraphPattern($v)) && $sub_r) {
473
            return [$sub_r, $sub_v];
474
        }
475 95
        $r = ['type' => 'union', 'patterns' => []];
476 95
        $sub_v = $v;
477
        do {
478 95
            $proceed = 0;
479 95
            if ((list($sub_r, $sub_v) = $this->xGroupGraphPattern($sub_v)) && $sub_r) {
480 1
                $r['patterns'][] = $sub_r;
481 1
                if ($sub_r = $this->x('UNION', $sub_v)) {
482 1
                    $sub_v = $sub_r[1];
483 1
                    $proceed = 1;
484
                }
485
            }
486 95
        } while ($proceed);
487 95
        $pc = \count($r['patterns']);
488 95
        if (1 == $pc) {
489
            return [$r['patterns'][0], $sub_v];
490 95
        } elseif ($pc > 1) {
491 1
            return [$r, $sub_v];
492
        }
493
494 95
        return [0, $v];
495
    }
496
497
    /* 23 */
498
499 95
    protected function xOptionalGraphPattern($v)
500
    {
501 95
        if ($sub_r = $this->x('OPTIONAL', $v)) {
502 2
            $sub_v = $sub_r[1];
503 2
            if ((list($sub_r, $sub_v) = $this->xGroupGraphPattern($sub_v)) && $sub_r) {
504 2
                return [['type' => 'optional', 'patterns' => $sub_r['patterns']], $sub_v];
505
            }
506
            $this->logger->error('Missing or invalid Group Graph Pattern after OPTIONAL');
507
        }
508
509 95
        return [0, $v];
510
    }
511
512
    /* 24.. */
513
514 95
    protected function xGraphGraphPattern($v)
515
    {
516 95
        if ($sub_r = $this->x('GRAPH', $v)) {
517
            $sub_v = $sub_r[1];
518
            $r = ['type' => 'graph', 'var' => '', 'uri' => '', 'patterns' => []];
519
            if ((list($sub_r, $sub_v) = $this->xVar($sub_v)) && $sub_r) {
520
                $r['var'] = $sub_r;
521
            } elseif ((list($sub_r, $sub_v) = $this->xIRIref($sub_v)) && $sub_r) {
522
                $r['uri'] = $sub_r;
523
            }
524
            if ($r['var'] || $r['uri']) {
525
                if ((list($sub_r, $sub_v) = $this->xGroupGraphPattern($sub_v)) && $sub_r) {
526
                    $r['patterns'][] = $sub_r;
527
528
                    return [$r, $sub_v];
529
                }
530
                $this->logger->error('Missing or invalid Graph Pattern');
531
            }
532
        }
533
534 95
        return [0, $v];
535
    }
536
537
    /* 26.., 27.. */
538
539 95
    protected function xFilter($v)
540
    {
541 95
        if ($r = $this->x('FILTER', $v)) {
542 23
            $sub_v = $r[1];
543 23
            if ((list($r, $sub_v) = $this->xBrackettedExpression($sub_v)) && $r) {
544 19
                return [$r, $sub_v];
545
            }
546 4
            if ((list($r, $sub_v) = $this->xBuiltInCall($sub_v)) && $r) {
547 4
                return [$r, $sub_v];
548
            }
549
            if ((list($r, $sub_v) = $this->xFunctionCall($sub_v)) && $r) {
550
                return [$r, $sub_v];
551
            }
552
            $this->logger->error('Incomplete FILTER');
553
        }
554
555 95
        return [0, $v];
556
    }
557
558
    /* 28.. */
559
560 5
    protected function xFunctionCall($v)
561
    {
562 5
        if ((list($r, $sub_v) = $this->xIRIref($v)) && $r) {
563
            if ((list($sub_r, $sub_v) = $this->xArgList($sub_v)) && $sub_r) {
564
                return [['type' => 'function_call', 'uri' => $r, 'args' => $sub_r], $sub_v];
565
            }
566
        }
567
568 5
        return [0, $v];
569
    }
570
571
    /* 29 */
572
573 18
    protected function xArgList($v)
574
    {
575 18
        $r = [];
576 18
        $sub_v = $v;
577 18
        $closed = 0;
578 18
        if ($sub_r = $this->x('\(', $sub_v)) {
579 18
            $sub_v = $sub_r[1];
580
            do {
581 18
                $proceed = 0;
582 18
                if ((list($sub_r, $sub_v) = $this->xExpression($sub_v)) && $sub_r) {
583 18
                    $r[] = $sub_r;
584 18
                    if ($sub_r = $this->x('\,', $sub_v)) {
585 4
                        $sub_v = $sub_r[1];
586 4
                        $proceed = 1;
587
                    }
588
                }
589 18
                if ($sub_r = $this->x('\)', $sub_v)) {
590 18
                    $sub_v = $sub_r[1];
591 18
                    $closed = 1;
592 18
                    $proceed = 0;
593
                }
594 18
            } while ($proceed);
595
        }
596
597 18
        return $closed ? [$r, $sub_v] : [0, $v];
598
    }
599
600
    /* 30, 31 */
601
602 92
    protected function xConstructTemplate($v)
603
    {
604 92
        if ($sub_r = $this->x('\{', $v)) {
605 92
            $r = [];
606 92
            if ((list($sub_r, $sub_v) = $this->xTriplesBlock($sub_r[1])) && \is_array($sub_r)) {
607 92
                $r = $sub_r;
608
            }
609 92
            if ($sub_r = $this->x('\}', $sub_v)) {
610 91
                return [$r, $sub_r[1]];
611
            }
612
        }
613
614 5
        return [0, $v];
615
    }
616
617
    /* 46, 47 */
618
619 27
    protected function xExpression($v)
620
    {
621 27
        if ((list($sub_r, $sub_v) = $this->xConditionalAndExpression($v)) && $sub_r) {
622 27
            $r = ['type' => 'expression', 'sub_type' => 'or', 'patterns' => [$sub_r]];
623
            do {
624 27
                $proceed = 0;
625 27
                if ($sub_r = $this->x('\|\|', $sub_v)) {
626
                    $sub_v = $sub_r[1];
627
                    if ((list($sub_r, $sub_v) = $this->xConditionalAndExpression($sub_v)) && $sub_r) {
628
                        $r['patterns'][] = $sub_r;
629
                        $proceed = 1;
630
                    }
631
                }
632 27
            } while ($proceed);
633
634 27
            return 1 == \count($r['patterns']) ? [$r['patterns'][0], $sub_v] : [$r, $sub_v];
635
        }
636
637
        return [0, $v];
638
    }
639
640
    /* 48.., 49.. */
641
642 27
    protected function xConditionalAndExpression($v)
643
    {
644 27
        if ((list($sub_r, $sub_v) = $this->xRelationalExpression($v)) && $sub_r) {
645 27
            $r = ['type' => 'expression', 'sub_type' => 'and', 'patterns' => [$sub_r]];
646
            do {
647 27
                $proceed = 0;
648 27
                if ($sub_r = $this->x('\&\&', $sub_v)) {
649 1
                    $sub_v = $sub_r[1];
650 1
                    if ((list($sub_r, $sub_v) = $this->xRelationalExpression($sub_v)) && $sub_r) {
651 1
                        $r['patterns'][] = $sub_r;
652 1
                        $proceed = 1;
653
                    }
654
                }
655 27
            } while ($proceed);
656
657 27
            return 1 == \count($r['patterns']) ? [$r['patterns'][0], $sub_v] : [$r, $sub_v];
658
        }
659
660
        return [0, $v];
661
    }
662
663
    /* 50, 51 */
664
665 27
    protected function xRelationalExpression($v)
666
    {
667 27
        if ((list($sub_r, $sub_v) = $this->xAdditiveExpression($v)) && $sub_r) {
668 27
            $r = ['type' => 'expression', 'sub_type' => 'relational', 'patterns' => [$sub_r]];
669
            do {
670 27
                $proceed = 0;
671
                /* don't mistake '<' + uriref with '<'-operator ("longest token" rule) */
672 27
                if ((list($sub_r, $sub_v) = $this->xIRI_REF($sub_v)) && $sub_r) {
673
                    $this->logger->error('Expected operator, found IRIref: "'.$sub_r.'".');
674
                }
675 27
                if ($sub_r = $this->x('(\!\=|\=\=|\=|\<\=|\>\=|\<|\>)', $sub_v)) {
676 9
                    $op = $sub_r[1];
677 9
                    $sub_v = $sub_r[2];
678 9
                    $r['operator'] = $op;
679 9
                    if ((list($sub_r, $sub_v) = $this->xAdditiveExpression($sub_v)) && $sub_r) {
680
                        //$sub_r['operator'] = $op;
681 9
                        $r['patterns'][] = $sub_r;
682 9
                        $proceed = 1;
683
                    }
684
                }
685 27
            } while ($proceed);
686
687 27
            return 1 == \count($r['patterns']) ? [$r['patterns'][0], $sub_v] : [$r, $sub_v];
688
        }
689
690
        return [0, $v];
691
    }
692
693
    /* 52 */
694
695 27
    protected function xAdditiveExpression($v)
696
    {
697 27
        if ((list($sub_r, $sub_v) = $this->xMultiplicativeExpression($v)) && $sub_r) {
698 27
            $r = ['type' => 'expression', 'sub_type' => 'additive', 'patterns' => [$sub_r]];
699
            do {
700 27
                $proceed = 0;
701 27
                if ($sub_r = $this->x('(\+|\-)', $sub_v)) {
702
                    $op = $sub_r[1];
703
                    $sub_v = $sub_r[2];
704
                    if ((list($sub_r, $sub_v) = $this->xMultiplicativeExpression($sub_v)) && $sub_r) {
705
                        $sub_r['operator'] = $op;
706
                        $r['patterns'][] = $sub_r;
707
                        $proceed = 1;
708
                    } elseif ((list($sub_r, $sub_v) = $this->xNumericLiteral($sub_v)) && $sub_r) {
709
                        $r['patterns'][] = ['type' => 'numeric', 'operator' => $op, 'value' => $sub_r];
710
                        $proceed = 1;
711
                    }
712
                }
713 27
            } while ($proceed);
714
            //return array($r, $sub_v);
715 27
            return 1 == \count($r['patterns']) ? [$r['patterns'][0], $sub_v] : [$r, $sub_v];
716
        }
717
718
        return [0, $v];
719
    }
720
721
    /* 53 */
722
723 27
    protected function xMultiplicativeExpression($v)
724
    {
725 27
        if ((list($sub_r, $sub_v) = $this->xUnaryExpression($v)) && $sub_r) {
726 27
            $r = ['type' => 'expression', 'sub_type' => 'multiplicative', 'patterns' => [$sub_r]];
727
            do {
728 27
                $proceed = 0;
729 27
                if ($sub_r = $this->x('(\*|\/)', $sub_v)) {
730
                    $op = $sub_r[1];
731
                    $sub_v = $sub_r[2];
732
                    if ((list($sub_r, $sub_v) = $this->xUnaryExpression($sub_v)) && $sub_r) {
733
                        $sub_r['operator'] = $op;
734
                        $r['patterns'][] = $sub_r;
735
                        $proceed = 1;
736
                    }
737
                }
738 27
            } while ($proceed);
739
740 27
            return 1 == \count($r['patterns']) ? [$r['patterns'][0], $sub_v] : [$r, $sub_v];
741
        }
742
743
        return [0, $v];
744
    }
745
746
    /* 54 */
747
748 27
    protected function xUnaryExpression($v)
749
    {
750 27
        $sub_v = $v;
751 27
        $op = '';
752 27
        if ($sub_r = $this->x('(\!|\+|\-)', $sub_v)) {
753
            $op = $sub_r[1];
754
            $sub_v = $sub_r[2];
755
        }
756 27
        if ((list($sub_r, $sub_v) = $this->xPrimaryExpression($sub_v)) && $sub_r) {
757 27
            if (!\is_array($sub_r)) {
758
                $sub_r = ['type' => 'unary', 'expression' => $sub_r];
759 27
            } elseif ($sub_op = $sub_r['operator'] ?? '') {
760
                $ops = ['!!' => '', '++' => '+', '--' => '+', '+-' => '-', '-+' => '-'];
761
                $op = isset($ops[$op.$sub_op]) ? $ops[$op.$sub_op] : $op.$sub_op;
762
            }
763 27
            $sub_r['operator'] = $op;
764
765 27
            return [$sub_r, $sub_v];
766
        }
767
768
        return [0, $v];
769
    }
770
771
    /* 55 */
772
773 27
    protected function xPrimaryExpression($v)
774
    {
775 27
        foreach (['BrackettedExpression', 'BuiltInCall', 'IRIrefOrFunction', 'RDFLiteral', 'NumericLiteral', 'BooleanLiteral', 'Var', 'Placeholder'] as $type) {
776 27
            $m = 'x'.$type;
777 27
            if ((list($sub_r, $sub_v) = $this->$m($v)) && $sub_r) {
778 27
                return [$sub_r, $sub_v];
779
            }
780
        }
781
782
        return [0, $v];
783
    }
784
785
    /* 56 */
786
787 28
    protected function xBrackettedExpression($v)
788
    {
789 28
        if ($r = $this->x('\(', $v)) {
790 23
            if ((list($r, $sub_v) = $this->xExpression($r[1])) && $r) {
791 23
                if ($sub_r = $this->x('\)', $sub_v)) {
792 23
                    return [$r, $sub_r[1]];
793
                }
794
            }
795
        }
796
797 28
        return [0, $v];
798
    }
799
800
    /* 57.., 58.. */
801
802 28
    protected function xBuiltInCall($v)
803
    {
804 28
        if ($sub_r = $this->x('(str|lang|langmatches|datatype|bound|sameterm|isiri|isuri|isblank|isliteral|regex)\s*\(', $v)) {
805 18
            $r = ['type' => 'built_in_call', 'call' => strtolower($sub_r[1])];
806 18
            if ((list($sub_r, $sub_v) = $this->xArgList('('.$sub_r[2])) && \is_array($sub_r)) {
807 18
                $r['args'] = $sub_r;
808
809 18
                return [$r, $sub_v];
810
            }
811
        }
812
813 28
        return [0, $v];
814
    }
815
816
    /* 59.. */
817
818 27
    protected function xIRIrefOrFunction($v)
819
    {
820 27
        if ((list($r, $v) = $this->xIRIref($v)) && $r) {
821 1
            if ((list($sub_r, $sub_v) = $this->xArgList($v)) && \is_array($sub_r)) {
822
                return [['type' => 'function', 'uri' => $r, 'args' => $sub_r], $sub_v];
823
            }
824
825 1
            return [['type' => 'uri', 'uri' => $r], $sub_v];
826
        }
827
    }
828
829
    /* 70.. @@sync with TurtleParser */
830
831 98
    protected function xIRI_REF($v)
832
    {
833 98
        if (($r = $this->x('\<(\$\{[^\>]*\})\>', $v)) && ($sub_r = $this->xPlaceholder($r[1]))) {
0 ignored issues
show
Unused Code introduced by
The assignment to $sub_r is dead and can be removed.
Loading history...
834
            return [$r[1], $r[2]];
835 98
        } elseif ($r = $this->x('\<([^\<\>\s\"\|\^`]*)\>', $v)) {
836 96
            return [$r[1] ? $r[1] : true, $r[2]];
837
        }
838 98
        /* allow reserved chars in obvious IRIs */ elseif ($r = $this->x('\<(https?\:[^\s][^\<\>]*)\>', $v)) {
839
            return [$r[1] ? $r[1] : true, $r[2]];
840
        }
841
842 98
        return [0, $v];
843
    }
844
}
845