File Coverage

blib/lib/AtteanX/Parser/SPARQL.pm
Criterion Covered Total %
statement 1710 2438 70.1
branch 519 904 57.4
condition 75 173 43.3
subroutine 189 205 92.2
pod 11 12 91.6
total 2504 3732 67.1


line stmt bran cond sub pod time code
1 12     12   540600 use v5.14;
  12         46  
2 12     12   66 use warnings;
  12         32  
  12         633  
3              
4             =head1 NAME
5              
6             AtteanX::Parser::SPARQL - SPARQL 1.1 Parser.
7              
8             =head1 VERSION
9              
10             This document describes AtteanX::Parser::SPARQL version 0.032.
11              
12             =head1 SYNOPSIS
13              
14             use AtteanX::Parser::SPARQL;
15             my $algbrea = AtteanX::Parser::SPARQL->parse($sparql);
16             # or:
17             my $parser = AtteanX::Parser::SPARQL->new();
18             my ($algebra) = $parser->parse_list_from_bytes($sparql);
19            
20             # or to allow parsing of SPARQL 1.1 Updates:
21            
22             my $algbrea = AtteanX::Parser::SPARQL->parse_update($sparql);
23             # or:
24             my $parser = AtteanX::Parser::SPARQL->new(update => 1);
25             my ($algebra) = $parser->parse_list_from_bytes($sparql);
26            
27             =head1 DESCRIPTION
28              
29             This module implements a recursive-descent parser for SPARQL 1.1 using the
30             L<AtteanX::Parser::SPARQLLex> tokenizer. Successful parsing results in an
31             object whose type is one of: L<Attean::Algebra::Query>,
32             L<Attean::Algebra::Update>, or L<Attean::Algebra::Sequence>.
33              
34             =head1 ROLES
35              
36             This class consumes L<Attean::API::Parser>, L<Attean::API::AtOnceParser>, and
37             L<Attean::API::AbbreviatingParser>.
38              
39             =head1 ATTRIBUTES
40              
41             =over 4
42              
43             =item C<< canonical_media_type >>
44              
45             =item C<< media_types >>
46              
47             =item C<< file_extensions >>
48              
49             =item C<< handled_type >>
50              
51             =item C<< lexer >>
52              
53             =item C<< args >>
54              
55             =item C<< build >>
56              
57             =item C<< update >>
58              
59             =item C<< namespaces >>
60              
61             =item C<< baseURI >>
62              
63             =item C<< filters >>
64              
65             =back
66              
67             =head1 METHODS
68              
69             =over 4
70              
71             =cut
72              
73              
74             use strict;
75 12     12   68 use warnings;
  12         28  
  12         253  
76 12     12   70 no warnings 'redefine';
  12         37  
  12         345  
77 12     12   58 use Carp qw(cluck confess croak);
  12         25  
  12         485  
78 12     12   70  
  12         41  
  12         745  
79             use Attean;
80 12     12   64 use Data::Dumper;
  12         32  
  12         88  
81 12     12   89 use URI::NamespaceMap;
  12         28  
  12         581  
82 12     12   85 use List::MoreUtils qw(zip);
  12         51  
  12         497  
83 12     12   72 use AtteanX::Parser::SPARQLLex;
  12         24  
  12         186  
84 12     12   16705 use AtteanX::SPARQL::Constants;
  12         36  
  12         523  
85 12     12   82 use Types::Standard qw(InstanceOf HashRef ArrayRef Bool Str Int);
  12         24  
  12         2123  
86 12     12   86 use Scalar::Util qw(blessed looks_like_number reftype refaddr);
  12         29  
  12         74  
87 12     12   11858  
  12         27  
  12         610  
88             ######################################################################
89              
90             use Moo;
91 12     12   94  
  12         28  
  12         62  
92             has 'lexer' => (is => 'rw', isa => InstanceOf['AtteanX::Parser::SPARQLLex::Iterator']);
93             has 'args' => (is => 'ro', isa => HashRef);
94             has 'build' => (is => 'rw', isa => HashRef);
95             has 'update' => (is => 'rw', isa => Bool);
96             has 'baseURI' => (is => 'rw');
97             has '_stack' => (is => 'rw', isa => ArrayRef);
98             has 'filters' => (is => 'rw', isa => ArrayRef);
99             has 'counter' => (is => 'rw', isa => Int, default => 0);
100             has '_pattern_container_stack' => (is => 'rw', isa => ArrayRef);
101              
102              
103 8     8 1 35  
104             return [qw(application/sparql-query application/sparql-update)];
105 1     1 1 671 }
106              
107             state $ITEM_TYPE = Type::Tiny::Role->new(role => 'Attean::API::Algebra');
108 3     3 1 12 return $ITEM_TYPE;
109             }
110              
111             with 'Attean::API::AtOnceParser', 'Attean::API::Parser', 'Attean::API::AbbreviatingParser';
112 5     5 1 508 with 'MooX::Log::Any';
113 5         251  
114             my $class = shift;
115             my %args = @_;
116             my $ns = delete $args{namespaces} // URI::NamespaceMap->new();
117             my %a = (args => \%args, namespaces => $ns);
118             if (my $handler = delete $args{handler}) {
119             $a{handler} = $handler;
120 53     53 0 61535 }
121 53         152 return \%a;
122 53   66     1164 }
123 53         58418  
124 53 100       211 ################################################################################
125 1         6  
126             my $self = shift;
127 53         924 my $l = shift;
128             $l->add_regex_rule( qr/RANK/, KEYWORD, sub { return uc(shift) } );
129             return $l;
130             }
131              
132             =item C<< parse ( $sparql ) >>
133 52     52   117  
134 52         103 Parse the C<< $sparql >> query string and return the resulting
135 52     1   470 L<Attean::API::Algebra> object.
  1         4  
136 52         137  
137             =cut
138              
139             my $self = shift;
140             my $parser = ref($self) ? $self : $self->new();
141             my ($algebra) = $parser->parse_list_from_bytes(@_);
142             return $algebra;
143             }
144              
145             =item C<< parse_update ( $sparql ) >>
146              
147 28     28 1 31324 Parse the C<< $sparql >> update string and return the resulting
148 28 100       468 L<Attean::API::Algebra> object.
149 28         1324  
150 26         285 =cut
151              
152             my $self = shift;
153             my $parser = ref($self) ? $self : $self->new();
154             $parser->update(1);
155             my ($algebra) = $parser->parse_list_from_bytes(@_);
156             return $algebra;
157             }
158              
159             =item C<< parse_list_from_io( $fh ) >>
160              
161 14     14 1 1068 =cut
162 14 50       403  
163 14         1267 my $self = shift;
164 14         467 my $p = AtteanX::Parser::SPARQLLex->new();
165 13         209 my $l = $self->_configure_lexer( $p->parse_iter_from_io(@_) );
166             $self->lexer($l);
167             $self->baseURI($self->{args}{base});
168             my $q = $self->_parse();
169             return unless (ref($q));
170             my $a = $q->{triples}[0];
171             return unless (ref($a));
172             return $a;
173 2     2 1 4 }
174 2         38  
175 2         81 =item C<< parse_list_from_bytes( $bytes ) >>
176 2         40  
177 2         65 =cut
178 2         6  
179 2 50       8 my $self = shift;
180 2         12 my $p = AtteanX::Parser::SPARQLLex->new();
181 2 50       8 my $l = $self->_configure_lexer( $p->parse_iter_from_bytes(@_) );
182 2         12 $self->lexer($l);
183             $self->baseURI($self->{args}{base});
184             my $q = $self->_parse();
185             return unless (ref($q));
186             my $a = $q->{triples}[0];
187             return unless (ref($a));
188             return $a;
189             }
190 45     45 1 983  
191 45         812 =item C<< parse_nodes ( $string ) >>
192 45         1769  
193 45         949 Returns a list of L<Attean::API::Term> or L<Attean::API::Variable> objects,
194 45         1626 parsed in SPARQL syntax from the supplied C<< $string >>. Parsing is ended
195 45         187 either upon seeing a DOT, or reaching the end of the string.
196 42 50       147  
197 42         97 =cut
198 42 50       127  
199 42         286 my $self = shift;
200             my $p = AtteanX::Parser::SPARQLLex->new();
201             my $bytes = shift;
202             my %args = @_;
203             my $commas = $args{'commas'} || 0;
204             my $l = $self->_configure_lexer( $p->parse_iter_from_bytes($bytes) );
205             $self->lexer($l);
206             $self->baseURI($self->{args}{base});
207             $self->build({base => $self->baseURI});
208            
209             my @nodes;
210             while ($self->_peek_token) {
211 5     5 1 61 if ($self->_Verb_test) {
212 5         95 $self->_Verb;
213 5         208 } else {
214 5         17 $self->_GraphNode;
215 5   50     29 }
216 5         25
217 5         108 if ($commas) {
218 5         172 $self->_optional_token(COMMA);
219 5         97 }
220            
221 5         121 push(@nodes, splice(@{ $self->{_stack} }));
222 5         17 if ($self->_test_token(DOT)) {
223 15 100       47 $self->log->notice('DOT seen in string, stopping here');
224 13         36 last;
225             }
226 2         8 }
227            
228             return @nodes;
229 15 50       582 }
230 0         0  
231             my $self = shift;
232            
233 15         57 unless ($self->update) {
  15         35  
234 15 50       32 my $t = $self->lexer->peek;
235 0         0 unless (defined($t)) {
236 0         0 confess "No query string found to parse";
237             }
238             }
239              
240 5         52 $self->_stack([]);
241             $self->filters([]);
242             $self->_pattern_container_stack([]);
243             my $triples = $self->_push_pattern_container();
244 47     47   104 my $build = { sources => [], triples => $triples };
245             $self->build($build);
246 47 100       778 if ($self->baseURI) {
247 33         974 $build->{base} = $self->baseURI;
248 33 50       114 }
249 0         0  
250             $self->_RW_Query();
251             delete $build->{star};
252             my $data = $build;
253 47         969 return $data;
254 47         1895 }
255 47         1734  
256 47         1195 ################################################################################
257 47         173  
258 47         780  
259 47 100       1248 # [1] Query ::= Prologue ( SelectQuery | ConstructQuery | DescribeQuery | AskQuery | LoadUpdate )
260 1         14 my $self = shift;
261             $self->_Prologue;
262              
263 47         171 my $read_query = 0;
264 44         123 my $update = 0;
265 44         117 while (1) {
266 44         111 if ($self->_optional_token(KEYWORD, 'SELECT')) {
267             $self->_SelectQuery();
268             $read_query++;
269             } elsif ($self->_optional_token(KEYWORD, 'CONSTRUCT')) {
270             $self->_ConstructQuery();
271             $read_query++;
272             } elsif ($self->_optional_token(KEYWORD, 'DESCRIBE')) {
273             $self->_DescribeQuery();
274 47     47   96 $read_query++;
275 47         186 } elsif ($self->_optional_token(KEYWORD, 'ASK')) {
276             $self->_AskQuery();
277 47         102 $read_query++;
278 47         99 } elsif ($self->_test_token(KEYWORD, 'CREATE')) {
279 47         84 unless ($self->update) {
280 49 100       127 croak "CREATE GRAPH update forbidden in read-only queries";
    100          
    100          
    100          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    100          
    50          
281 28         115 }
282 27         47 $update++;
283             $self->_CreateGraph();
284 3         16 } elsif ($self->_test_token(KEYWORD, 'DROP')) {
285 3         9 unless ($self->update) {
286             croak "DROP GRAPH update forbidden in read-only queries";
287 1         5 }
288 1         3 $update++;
289             $self->_DropGraph();
290 4         22 } elsif ($self->_test_token(KEYWORD, 'LOAD')) {
291 4         11 unless ($self->update) {
292             croak "LOAD update forbidden in read-only queries"
293 0 0       0 }
294 0         0 $update++;
295             $self->_LoadUpdate();
296 0         0 } elsif ($self->_test_token(KEYWORD, 'CLEAR')) {
297 0         0 unless ($self->update) {
298             croak "CLEAR GRAPH update forbidden in read-only queries";
299 0 0       0 }
300 0         0 $update++;
301             $self->_ClearGraphUpdate();
302 0         0 } elsif ($self->_test_token(KEYWORD, qr/^(WITH|INSERT|DELETE)/)) {
303 0         0 unless ($self->update) {
304             croak "INSERT/DELETE update forbidden in read-only queries";
305 0 0       0 }
306 0         0 $update++;
307             my ($graph);
308 0         0 if ($self->_optional_token(KEYWORD, 'WITH')) {
309 0         0 $self->{build}{custom_update_dataset} = 1;
310             $self->_IRIref;
311 0 0       0 ($graph) = splice( @{ $self->{_stack} } );
312 0         0 }
313             if ($self->_optional_token(KEYWORD, 'INSERT')) {
314 0         0 if ($self->_optional_token(KEYWORD, 'DATA')) {
315 0         0 unless ($self->update) {
316             croak "INSERT DATA update forbidden in read-only queries";
317 9 50       145 }
318 0         0 $self->_InsertDataUpdate();
319             } else {
320 9         63 $self->_InsertUpdate($graph);
321 9         22 }
322 9 50       28 } elsif ($self->_optional_token(KEYWORD, 'DELETE')) {
323 0         0 if ($self->_optional_token(KEYWORD, 'DATA')) {
324 0         0 unless ($self->update) {
325 0         0 croak "DELETE DATA update forbidden in read-only queries";
  0         0  
326             }
327 9 100       32 $self->_DeleteDataUpdate();
    50          
328 3 100       14 } else {
329 1 50       19 $self->_DeleteUpdate($graph);
330 0         0 }
331             }
332 1         9 } elsif ($self->_test_token(KEYWORD, 'COPY')) {
333             $update++;
334 2         11 $self->_AddCopyMoveUpdate('COPY');
335             } elsif ($self->_test_token(KEYWORD, 'MOVE')) {
336             $update++;
337 6 100       17 $self->_AddCopyMoveUpdate('MOVE');
338 1 50       16 } elsif ($self->_test_token(KEYWORD, 'ADD')) {
339 0         0 $update++;
340             $self->_AddCopyMoveUpdate('ADD');
341 1         13 } elsif ($self->_test_token(SEMICOLON)) {
342             $self->_expected_token(SEMICOLON);
343 5         29 next if ($self->_Query_test);
344             last;
345             } else {
346             if ($self->update and not $self->_peek_token) {
347 0         0 last;
348 0         0 }
349            
350 0         0 my $t = $self->_peek_token;
351 0         0 return $self->_token_error($t, 'Expected query type');
352             }
353 3         5  
354 3         10 last if ($read_query);
355             if ($self->_optional_token(SEMICOLON)) {
356 0         0 if ($self->_Query_test) {
357 0 0       0 next;
358 0         0 }
359             }
360 1 50 33     16 last;
361 0         0 }
362             my $count = scalar(@{ $self->{build}{triples} });
363            
364 1         9 my $t = $self->_peek_token;
365 1         8 if ($t) {
366             my $type = AtteanX::SPARQL::Constants::decrypt_constant($t->type);
367             croak "Syntax error: Remaining input after query: $type " . Dumper($t->args);
368 46 100       270 }
369 11 100       34  
370 2 50       9 if ($count == 0 or $count > 1) {
371 2         8 my @patterns = splice(@{ $self->{build}{triples} });
372             my %seen;
373             foreach my $p (@patterns) {
374 9         22 my @blanks = $p->blank_nodes;
375             foreach my $b (@blanks) {
376 44         80 if ($seen{$b->value}++) {
  44         131  
377             croak "Cannot re-use a blank node label in multiple update operations in a single request";
378 44         129 }
379 44 50       165 }
380 0         0 }
381 0         0 my $pattern = Attean::Algebra::Sequence->new( children => \@patterns );
382             $self->_check_duplicate_blanks($pattern);
383             $self->{build}{triples} = [ $pattern ];
384 44 100 66     261 }
385 2         7
  2         9  
386 2         5 my %dataset;
387 2         7 foreach my $s (@{ $self->{build}{sources} }) {
388 4         51 my ($iri, $group) = @$s;
389 4         9 if ($group eq 'NAMED') {
390 0 0       0 push(@{ $dataset{named} }, $iri );
391 0         0 } else {
392             push(@{ $dataset{default} }, $iri );
393             }
394             }
395 2         25  
396 2         12 my $algebra = $self->{build}{triples}[0];
397 2         6
398             if ($update) {
399             $self->{build}{triples}[0] = Attean::Algebra::Update->new( children => [$algebra] );
400 44         95 } else {
401 44         71 $self->{build}{triples}[0] = Attean::Algebra::Query->new( children => [$algebra], dataset => \%dataset );
  44         158  
402 8         18 }
403 8 100       23 }
404 4         7  
  4         17  
405             my $self = shift;
406 4         7 return ($self->_test_token(KEYWORD, qr/^(SELECT|CONSTRUCT|DESCRIBE|ASK|LOAD|CLEAR|DROP|ADD|MOVE|COPY|CREATE|INSERT|DELETE|WITH)/i));
  4         14  
407             }
408              
409             # [2] Prologue ::= BaseDecl? PrefixDecl*
410 44         117 # [3] BaseDecl ::= 'BASE' IRI_REF
411             # [4] PrefixDecl ::= 'PREFIX' PNAME_NS IRI_REF
412 44 100       131 my $self = shift;
413 9         177  
414             my $base;
415 35         644 my @base;
416             if ($self->_optional_token(KEYWORD, 'BASE')) {
417             my $iriref = $self->_expected_token(IRI);
418             my $iri = $iriref->value;
419             $base = $self->new_iri( value => $iri );
420 2     2   7 @base = $base;
421 2         11 $self->{base} = $base;
422             }
423              
424             my %namespaces;
425             while ($self->_optional_token(KEYWORD, 'PREFIX')) {
426             my $prefix = $self->_expected_token(PREFIXNAME);
427             my @args = @{ $prefix->args };
428 47     47   83 if (scalar(@args) > 1) {
429             croak "Syntax error: PREFIX namespace used a full PNAME_LN, not a PNAME_NS";
430 47         96 }
431             my $ns = substr($prefix->value, 0, length($prefix->value) - 1);
432 47 50       173 my $iriref = $self->_expected_token(IRI);
433 0         0 my $iri = $iriref->value;
434 0         0 if (@base) {
435 0         0 my $r = $self->new_iri( value => $iri, base => shift(@base) );
436 0         0 $iri = $r->value;
437 0         0 }
438             $namespaces{ $ns } = $iri;
439             $self->namespaces->add_mapping($ns, $iri);
440 47         102 }
441 47         142  
442 15         75 $self->{build}{namespaces} = \%namespaces;
443 15         53 $self->{build}{base} = $base if (defined($base));
  15         52  
444 15 50       51  
445 0         0 # push(@data, (base => $base)) if (defined($base));
446             # return @data;
447 15         43 }
448 15         41  
449 15         68 my $self = shift;
450 15 50       45 $self->_expected_token(LBRACE);
451 0         0 local($self->{__data_pattern}) = 1;
452 0         0 my @triples = $self->_ModifyTemplate();
453             $self->_expected_token(RBRACE);
454 15         41  
455 15         85 my $insert = Attean::Algebra::Modify->new(insert => \@triples);
456             $self->_add_patterns( $insert );
457             $self->{build}{method} = 'UPDATE';
458 47         149 }
459 47 50       176  
460             my $self = shift;
461             $self->_expected_token(LBRACE);
462             local($self->{__data_pattern}) = 1;
463             local($self->{__no_bnodes}) = "DELETE DATA block";
464             my @triples = $self->_ModifyTemplate();
465             $self->_expected_token(RBRACE);
466 1     1   3
467 1         4 my $delete = Attean::Algebra::Modify->new(delete => \@triples);
468 1         4 $self->_add_patterns( $delete );
469 1         4 $self->{build}{method} = 'UPDATE';
470 1         4 }
471              
472 1         19 my $self = shift;
473 1         4 my $graph = shift;
474 1         5 $self->_expected_token(LBRACE);
475             my @triples = $self->_ModifyTemplate();
476             $self->_expected_token(RBRACE);
477            
478 1     1   3 if ($graph) {
479 1         6 @triples = map { $_->as_quad_pattern($graph) } @triples;
480 1         4 }
481 1         3  
482 1         5 my %dataset;
483 1         6 while ($self->_optional_token(KEYWORD, 'USING')) {
484             $self->{build}{custom_update_dataset} = 1;
485 1         25 my $named = 0;
486 1         5 if ($self->_optional_token(KEYWORD, 'NAMED')) {
487 1         6 $named = 1;
488             }
489             $self->_IRIref;
490             my ($iri) = splice( @{ $self->{_stack} } );
491 2     2   5 if ($named) {
492 2         4 $dataset{named}{$iri->value} = $iri;
493 2         9 } else {
494 2         13 push(@{ $dataset{default} }, $iri );
495 2         7 }
496             }
497 2 50       7  
498 0         0 $self->_expected_token(KEYWORD, 'WHERE');
  0         0  
499             if ($graph) {
500             $self->_GroupGraphPattern;
501 2         8 my $ggp = $self->_remove_pattern;
502 2         7 $ggp = Attean::Algebra::Graph->new( children => [$ggp], graph => $graph );
503 0         0 $self->_add_patterns( $ggp );
504 0         0 } else {
505 0 0       0 $self->_GroupGraphPattern;
506 0         0 }
507              
508 0         0 my $ggp = $self->_remove_pattern;
509 0         0  
  0         0  
510 0 0       0 my @triples_with_fresh_bnodes = $self->_statements_with_fresh_bnodes(@triples);
511 0         0 my $insert = Attean::Algebra::Modify->new( children => [$ggp], insert => \@triples_with_fresh_bnodes, dataset => \%dataset );
512             $self->_add_patterns( $insert );
513 0         0 $self->{build}{method} = 'UPDATE';
  0         0  
514             }
515              
516             my $self = shift;
517 2         8 my @triples = @_;
518 1 50       4
519 0         0 my %fresh_blank_map;
520 0         0 my @triples_with_fresh_bnodes;
521 0         0 foreach my $t (@triples) {
522 0         0 my @pos = ref($t)->variables;
523             if ($t->has_blanks) {
524 1         4 my @terms;
525             foreach my $term ($t->values) {
526             if ($term->does('Attean::API::Blank')) {
527 1         5 if (my $b = $fresh_blank_map{$term->value}) {
528             push(@terms, $b);
529 1         5 } else {
530 1         12 my $id = $self->counter;
531 1         4 $self->counter($id+1);
532 1         5 my $name = ".b-$id";
533             my $b = Attean::Blank->new($name);
534             push(@terms, $b);
535             $fresh_blank_map{$term->value} = $b;
536 1     1   2 }
537 1         3 } else {
538             push(@terms, $term);
539 1         2 }
540             }
541 1         3 push(@triples_with_fresh_bnodes, ref($t)->new(zip @pos, @terms));
542 1         5 } else {
543 1 50       5 push(@triples_with_fresh_bnodes, $t);
544 0         0 }
545 0         0 }
546 0 0       0 return @triples_with_fresh_bnodes;
547 0 0       0 }
548 0         0  
549             my $self = shift;
550 0         0 my $graph = shift;
551 0         0
552 0         0 my %dataset;
553 0         0 if ($self->_optional_token(KEYWORD, 'WHERE')) {
554 0         0 if ($graph) {
555 0         0 croak "Syntax error: WITH clause cannot be used with DELETE WHERE operations";
556             }
557             $self->_expected_token(LBRACE);
558 0         0 my @st = $self->_ModifyTemplate();
559             $self->_expected_token(RBRACE);
560             my @patterns;
561 0         0 my @triples;
562             my @quads;
563 1         3 my @blanks = grep { $_->does('Attean::API::Blank') } map { $_->values } @st;
564             if (scalar(@blanks) > 0) {
565             croak "Cannot use blank nodes in a DELETE pattern";
566 1         3 }
567             foreach my $s (@st) {
568             if ($s->does('Attean::API::QuadPattern')) {
569             push(@quads, $s);
570 5     5   13 my $tp = $s->as_triple_pattern;
571 5         11 my $bgp = Attean::Algebra::BGP->new( triples => [$tp] );
572             push(@patterns, Attean::Algebra::Graph->new( graph => $s->graph, children => [$bgp] ));
573 5         10 } else {
574 5 50       14 push(@triples, $s);
575 0 0       0 }
576 0         0 }
577             push(@patterns, Attean::Algebra::BGP->new( triples => \@triples ));
578 0         0 my $ggp = Attean::Algebra::Join->new( children => \@patterns );
579 0         0 my $update = Attean::Algebra::Modify->new( children => [$ggp], delete => [@st]);
580 0         0 $self->_add_patterns( $update );
581 0         0 $self->{build}{method} = 'UPDATE';
582             return;
583 0         0 } else {
584 0         0 my @delete_triples;
  0         0  
  0         0  
585 0 0       0 {
586 0         0 local($self->{__no_bnodes}) = "DELETE block";
587             $self->_expected_token(LBRACE);
588 0         0 @delete_triples = $self->_ModifyTemplate( $graph );
589 0 0       0 $self->_expected_token(RBRACE);
590 0         0 }
591 0         0
592 0         0 my @insert_triples;
593 0         0 if ($self->_optional_token(KEYWORD, 'INSERT')) {
594             $self->_expected_token(LBRACE);
595 0         0 @insert_triples = $self->_ModifyTemplate( $graph );
596             @insert_triples = $self->_statements_with_fresh_bnodes(@insert_triples);
597             $self->_expected_token(RBRACE);
598 0         0 }
599 0         0
600 0         0 if ($graph) {
601 0         0 @insert_triples = map { $_->does('Attean::API::QuadPattern') ? $_ : $_->as_quad_pattern($graph) } @insert_triples;
602 0         0 @delete_triples = map { $_->does('Attean::API::QuadPattern') ? $_ : $_->as_quad_pattern($graph) } @delete_triples;
603 0         0 }
604              
605 5         14 while ($self->_optional_token(KEYWORD, 'USING')) {
606             $self->{build}{custom_update_dataset} = 1;
607 5         11 my $named = 0;
  5         23  
608 5         20 if ($self->_optional_token(KEYWORD, 'NAMED')) {
609 5         25 $named = 1;
610 5         17 }
611             $self->_IRIref;
612             my ($iri) = splice( @{ $self->{_stack} } );
613 5         12 if ($named) {
614 5 50       12 $dataset{named}{$iri->value} = $iri;
615 0         0 } else {
616 0         0 push(@{ $dataset{default} }, $iri );
617 0         0 }
618 0         0 }
619            
620             $self->_expected_token(KEYWORD, 'WHERE');
621 5 50       17
622 0 0       0 if ($graph) {
  0         0  
623 0 0       0 $self->_GroupGraphPattern;
  0         0  
624             delete $self->{__no_bnodes};
625             my $ggp = $self->_remove_pattern;
626 5         13 $ggp = Attean::Algebra::Graph->new( children => [$ggp], graph => $graph );
627 9         35 $self->_add_patterns( $ggp );
628 9         18 } else {
629 9 100       20 $self->_GroupGraphPattern;
630 3         4 delete $self->{__no_bnodes};
631             }
632 9         36  
633 9         31 my $ggp = $self->_remove_pattern;
  9         20  
634 9 100       24  
635 3         16 my %args = (children => [$ggp], dataset => \%dataset);
636             if (scalar(@insert_triples)) {
637 6         11 $args{insert} = \@insert_triples;
  6         21  
638             }
639             if (scalar(@delete_triples)) {
640             $args{delete} = \@delete_triples;
641 5         27 my @blanks = grep { $_->does('Attean::API::Blank') } map { $_->values } @delete_triples;
642             if (scalar(@blanks) > 0) {
643 5 50       19 croak "Cannot use blank nodes in a DELETE pattern";
644 0         0 }
645 0         0 }
646 0         0 my $update = Attean::Algebra::Modify->new( %args );
647 0         0 $self->_add_patterns( $update );
648 0         0 $self->{build}{method} = 'UPDATE';
649             }
650 5         23 }
651 5         19  
652             my $self = shift;
653             return 1 if ($self->_TriplesBlock_test);
654 5         23 return 1 if ($self->_test_token(KEYWORD, 'GRAPH'));
655             return 0;
656 5         21 }
657 5 50       17  
658 0         0 my $self = shift;
659             my $graph = shift;
660 5 50       13
661 5         14 my @triples;
662 5         17 while ($self->_ModifyTemplate_test) {
  15         216  
  5         25  
663 5 50       118 push(@triples, $self->__ModifyTemplate( $graph ));
664 0         0 }
665            
666             return @triples;
667 5         118 }
668 5         23  
669 5         26 my $self = shift;
670             my $graph = shift;
671             local($self->{_modify_template}) = 1;
672             if ($self->_TriplesBlock_test) {
673             $self->_push_pattern_container;
674 18     18   33 $self->_TriplesBlock;
675 18 100       53 (my $cont, undef) = $self->_pop_pattern_container; # ignore hints in a modify template
676 9 50       36 my ($bgp) = @{ $cont };
677 9         36 my @triples = @{ $bgp->triples };
678             if ($graph) {
679             @triples = map { $_->as_quad_pattern($graph) } @triples;
680             }
681 9     9   23
682 9         16 return @triples;
683             } else {
684 9         18 $self->_GraphGraphPattern;
685 9         35
686 9         51 {
687             my (@d) = splice(@{ $self->{_stack} });
688             $self->__handle_GraphPatternNotTriples( @d );
689 9         33 }
690            
691             my $data = $self->_remove_pattern;
692             my $graph = $data->graph;
693 9     9   20 my @bgps = $data->subpatterns_of_type('Attean::Algebra::BGP');
694 9         23 my @triples = map { $_->as_quad_pattern($graph) } map { @{ $_->triples } } @bgps;
695 9         34 return @triples;
696 9 50       32 }
697 9         32 }
698 9         34  
699 9         30 my $self = shift;
700 9         21 $self->_expected_token(KEYWORD, 'LOAD');
  9         48  
701 9         22 my $silent = $self->_optional_token(KEYWORD, 'SILENT') ? 1 : 0;
  9         41  
702 9 50       31 $self->_IRIref;
703 0         0 my ($iri) = splice( @{ $self->{_stack} } );
  0         0  
704             if ($self->_optional_token(KEYWORD, 'INTO')) {
705             $self->_expected_token(KEYWORD, 'GRAPH');
706 9         55 $self->_IRIref;
707             my ($graph) = splice( @{ $self->{_stack} } );
708 0         0 my $pat = Attean::Algebra::Load->new( silent => $silent, url => $iri, graph => $graph );
709             $self->_add_patterns( $pat );
710             } else {
711 0         0 my $pat = Attean::Algebra::Load->new( silent => $silent, url => $iri );
  0         0  
  0         0  
712 0         0 $self->_add_patterns( $pat );
713             }
714             $self->{build}{method} = 'LOAD';
715 0         0 }
716 0         0  
717 0         0 my $self = shift;
718 0         0 $self->_expected_token(KEYWORD, 'CREATE');
  0         0  
  0         0  
  0         0  
719 0         0 my $silent = $self->_optional_token(KEYWORD, 'SILENT') ? 1 : 0;
720             $self->_expected_token(KEYWORD, 'GRAPH');
721             $self->_IRIref;
722             my ($graph) = splice( @{ $self->{_stack} } );
723             my $pat = Attean::Algebra::Create->new( silent => $silent, graph => $graph );
724 0     0   0 $self->_add_patterns( $pat );
725 0         0 $self->{build}{method} = 'CREATE';
726 0 0       0 }
727 0         0  
728 0         0 my $self = shift;
  0         0  
729 0 0       0 $self->_expected_token(KEYWORD, 'CLEAR');
730 0         0 my $silent = $self->_optional_token(KEYWORD, 'SILENT') ? 1 : 0;
731 0         0 if ($self->_optional_token(KEYWORD, 'GRAPH')) {
732 0         0 $self->_IRIref;
  0         0  
733 0         0 my ($graph) = splice( @{ $self->{_stack} } );
734 0         0 my $pat = Attean::Algebra::Clear->new(silent => $silent, target => 'GRAPH', graph => $graph);
735             $self->_add_patterns( $pat );
736 0         0 } elsif ($self->_optional_token(KEYWORD, 'DEFAULT')) {
737 0         0 my $pat = Attean::Algebra::Clear->new(silent => $silent, target => 'DEFAULT');
738             $self->_add_patterns( $pat );
739 0         0 } elsif ($self->_optional_token(KEYWORD, 'NAMED')) {
740             my $pat = Attean::Algebra::Clear->new(silent => $silent, target => 'NAMED');
741             $self->_add_patterns( $pat );
742             } elsif ($self->_optional_token(KEYWORD, 'ALL')) {
743 0     0   0 my $pat = Attean::Algebra::Clear->new(silent => $silent, target => 'ALL');
744 0         0 $self->_add_patterns( $pat );
745 0 0       0 }
746 0         0 $self->{build}{method} = 'CLEAR';
747 0         0 }
748 0         0  
  0         0  
749 0         0 my $self = shift;
750 0         0 $self->_expected_token(KEYWORD, 'DROP');
751 0         0 my $silent = $self->_optional_token(KEYWORD, 'SILENT') ? 1 : 0;
752             if ($self->_optional_token(KEYWORD, 'GRAPH')) {
753             $self->_IRIref;
754             my ($graph) = splice( @{ $self->{_stack} } );
755 0     0   0 my $pat = Attean::Algebra::Clear->new(drop => 1, silent => $silent, target => 'GRAPH', graph => $graph);
756 0         0 $self->_add_patterns( $pat );
757 0 0       0 } elsif ($self->_optional_token(KEYWORD, 'DEFAULT')) {
758 0 0       0 my $pat = Attean::Algebra::Clear->new(drop => 1, silent => $silent, target => 'DEFAULT');
    0          
    0          
    0          
759 0         0 $self->_add_patterns( $pat );
760 0         0 } elsif ($self->_optional_token(KEYWORD, 'NAMED')) {
  0         0  
761 0         0 my $pat = Attean::Algebra::Clear->new(drop => 1, silent => $silent, target => 'NAMED');
762 0         0 $self->_add_patterns( $pat );
763             } elsif ($self->_optional_token(KEYWORD, 'ALL')) {
764 0         0 my $pat = Attean::Algebra::Clear->new(drop => 1, silent => $silent, target => 'ALL');
765 0         0 $self->_add_patterns( $pat );
766             }
767 0         0 $self->{build}{method} = 'CLEAR';
768 0         0 }
769              
770 0         0 my $self = shift;
771 0         0 if ($self->_optional_token(KEYWORD, 'DEFAULT')) {
772             return;
773 0         0 } else {
774             $self->_optional_token(KEYWORD, 'GRAPH');
775             $self->_IRIref;
776             my ($g) = splice( @{ $self->{_stack} } );
777 0     0   0 return $g;
778 0         0 }
779 0 0       0 }
780 0 0       0  
    0          
    0          
    0          
781 0         0 my $self = shift;
782 0         0 my $op = shift;
  0         0  
783 0         0 $self->_expected_token(KEYWORD, $op);
784 0         0 my $silent = $self->_optional_token(KEYWORD, 'SILENT') ? 1 : 0;
785            
786 0         0 my %args = (silent => $silent);
787 0         0 if ($op eq 'COPY') {
788             $args{drop_destination} =1;
789 0         0 } elsif ($op eq 'MOVE') {
790 0         0 $args{drop_destination} = 1;
791             $args{drop_source} = 1;
792 0         0 }
793 0         0 if (my $from = $self->__graph()) {
794             $args{source} = $from;
795 0         0 }
796             $self->_expected_token(KEYWORD, 'TO');
797             if (my $to = $self->__graph()) {
798             $args{destination} = $to;
799 6     6   10 }
800 6 100       12 my $pattern = Attean::Algebra::Add->new( %args );
801 3         11 $self->_add_patterns( $pattern );
802             $self->{build}{method} = 'UPDATE';
803 3         10 }
804 3         11  
805 3         9 # [5] SelectQuery ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( Var+ | '*' ) DatasetClause* WhereClause SolutionModifier
  3         7  
806 3         11 my $self = shift;
807             if ($self->_optional_token(KEYWORD, qr/^(DISTINCT)/)) {
808             $self->{build}{options}{distinct} = 1;
809             } elsif ($self->_optional_token(KEYWORD, qr/^(REDUCED)/)) {
810             $self->{build}{options}{distinct} = 2;
811 3     3   6 }
812 3         4
813 3         8 my ($star, $exprs, $vars) = $self->__SelectVars;
814 3 50       6 my @exprs = @$exprs;
815            
816 3         8 $self->_DatasetClause();
817 3 50       10
    50          
818 0         0 $self->_WhereClause;
819             $self->_SolutionModifier($vars);
820 0         0
821 0         0 if ($self->_optional_token(KEYWORD, 'VALUES')) {
822             my @vars;
823 3 50       9 # $self->_Var;
824 3         7 # push( @vars, splice(@{ $self->{_stack} }));
825             my $parens = 0;
826 3         7 if ($self->_optional_token(NIL)) {
827 3 50       9 $parens = 1;
828 0         0 } else {
829             if ($self->_optional_token(LPAREN)) {
830 3         46 $parens = 1;
831 3         13 }
832 3         12 while ($self->_test_token(VAR)) {
833             $self->_Var;
834             push( @vars, splice(@{ $self->{_stack} }));
835             }
836             if ($parens) {
837 28     28   72 $self->_expected_token(RPAREN);
838 28 50       126 }
    100          
839 0         0 }
840            
841 1         4 my $count = scalar(@vars);
842             if (not($parens) and $count == 0) {
843             croak "Syntax error: Expected VAR in inline data declaration";
844 28         163 } elsif (not($parens) and $count > 1) {
845 28         74 croak "Syntax error: Inline data declaration can only have one variable when parens are omitted";
846             }
847 28         111
848             my $short = (not($parens) and $count == 1);
849 28         104 $self->_expected_token(LBRACE);
850 28         98 if ($self->_optional_token(NIL)) {
851            
852 28 100       83 } else {
853 1         4 if (not($short) or ($short and $self->_test_token(LPAREN))) {
854             while ($self->_test_token(LPAREN)) {
855             my $terms = $self->_Binding($count);
856 1         2 push( @{ $self->{build}{bindings}{terms} }, $terms );
857 1 50       3 }
858 0         0 } else {
859             while ($self->_BindingValue_test) {
860 1 50       4 $self->_BindingValue;
861 1         2 my ($term) = splice(@{ $self->{_stack} });
862             push( @{ $self->{build}{bindings}{terms} }, [$term] );
863 1         4 }
864 1         4 }
865 1         2 }
  1         3  
866            
867 1 50       4 $self->_expected_token(RBRACE);
868 1         3  
869             my $bindings = delete $self->{build}{bindings};
870             my @rows = @{ $bindings->{terms} || [] };
871             my @vbs;
872 1         2 foreach my $r (@rows) {
873 1 50 33     8 my %d;
    50 33        
874 0         0 foreach my $i (0 .. $#{ $r }) {
875             if (blessed($r->[$i])) {
876 0         0 $d{ $vars[$i]->value } = $r->[$i];
877             }
878             }
879 1   33     4 my $r = Attean::Result->new(bindings => \%d);
880 1         3 push(@vbs, $r);
881 1 50       3 }
882             my $table = Attean::Algebra::Table->new( variables => \@vars, rows => \@vbs );
883             my $pattern = pop(@{ $self->{build}{triples} });
884 1 50 0     5 push(@{ $self->{build}{triples} }, $self->_new_join($pattern, $table));
      33        
885 1         4 }
886 2         7
887 2         4 my %projected = map { $_ => 1 } $self->__solution_modifiers( $star, @exprs );
  2         9  
888             delete $self->{build}{options};
889             $self->{build}{method} = 'SELECT';
890 0         0 }
891 0         0  
892 0         0 my $self = shift;
  0         0  
893 0         0 my $star = 0;
  0         0  
894             my @vars;
895             my $count = 0;
896             my @exprs;
897             while ($self->_test_token(STAR) or $self->__SelectVar_test) {
898 1         4 if ($self->_test_token(STAR)) {
899             $self->{build}{star}++;
900 1         3 $self->_expected_token(STAR);
901 1 50       2 $star = 1;
  1         5  
902 1         2 $count++;
903 1         19 last;
904 2         4 } else {
905 2         5 my @s = $self->__SelectVar;
  2         6  
906 2 50       9 if (scalar(@s) > 1) {
907 2         10 my ($var, $expr) = @s;
908             push(@exprs, $var->value, $expr);
909             } else {
910 2         25 my $var = $s[0];
911 2         50 push(@exprs, $var->value, $var);
912             }
913 1         9 push(@vars, shift(@s));
914 1         3 $count++;
  1         3  
915 1         2 }
  1         7  
916             }
917            
918 28         142 my %seen;
  45         128  
919 27         73 foreach my $v (@vars) {
920 27         100 if ($v->does('Attean::API::Variable')) {
921             my $name = $v->value;
922             if ($seen{ $name }++) {
923             croak "Syntax error: Repeated variable ($name) used in projection list";
924 29     29   65 }
925 29         60 }
926 29         54 }
927 29         67
928 29         48 $self->{build}{variables} = \@vars;
929 29   100     80 if ($count == 0) {
930 32 100       89 croak "Syntax error: No select variable or expression specified";
931 25         120 }
932 25         88 return $star, \@exprs, \@vars;
933 25         61 }
934 25         43  
935 25         49 my $self = shift;
936             $self->_expected_token(LPAREN);
937 7         22 $self->_Expression;
938 7 100       23 my ($expr) = splice(@{ $self->{_stack} });
939 1         2 $self->_expected_token(KEYWORD, 'AS');
940 1         4 $self->_Var;
941             my ($var) = splice(@{ $self->{_stack} });
942 6         9 $self->_expected_token(RPAREN);
943 6         24
944             return ($var, $expr);
945 7         17 }
946 7         16  
947             my $self = shift;
948             local($self->{__aggregate_call_ok}) = 1;
949             # return 1 if $self->_BuiltInCall_test;
950 29         56 return 1 if $self->_test_token(LPAREN);
951 29         77 return $self->_test_token(VAR);
952 7 50       26 }
953 7         119  
954 7 50       30 my $self = shift;
955 0         0 local($self->{__aggregate_call_ok}) = 1;
956             if ($self->_test_token(LPAREN)) {
957             my ($var, $expr) = $self->_BrackettedAliasExpression;
958             return ($var, $expr);
959             } else {
960 29         84 $self->_Var;
961 29 50       93 my ($var) = splice(@{ $self->{_stack} });
962 0         0 return $var;
963             }
964 29         116 }
965              
966             # [6] ConstructQuery ::= 'CONSTRUCT' ConstructTemplate DatasetClause* WhereClause SolutionModifier
967             my $self = shift;
968 3     3   6 my $shortcut = 1;
969 3         9 if ($self->_test_token(LBRACE)) {
970 3         14 $shortcut = 0;
971 3         5 $self->_ConstructTemplate;
  3         10  
972 3         11 }
973 3         11 $self->_DatasetClause();
974 3         7 if ($shortcut) {
  3         9  
975 3         13 $self->_TriplesWhereClause;
976             } else {
977 3         9 $self->_WhereClause;
978             }
979            
980             $self->_SolutionModifier();
981 11     11   18
982 11         29 my $pattern = $self->{build}{triples}[0];
983             my $triples = delete $self->{build}{construct_triples};
984 11 100       23 if (blessed($triples) and $triples->isa('Attean::Algebra::BGP')) {
985 10         23 $triples = $triples->triples;
986             }
987             # my @triples;
988             # warn $triples;
989 7     7   13 # foreach my $t (@{ $triples // [] }) {
990 7         18 # if ($t->isa('Attean::Algebra::BGP')) {
991 7 100       18 # push(@triples, @{ $t->triples });
992 1         4 # } else {
993 1         4 # push(@triples, $t);
994             # }
995 6         32 # }
996 6         14 my $construct = Attean::Algebra::Construct->new( children => [$pattern], triples => $triples );
  6         19  
997 6         19 $self->{build}{triples}[0] = $construct;
998             $self->{build}{method} = 'CONSTRUCT';
999             }
1000              
1001             # [7] DescribeQuery ::= 'DESCRIBE' ( VarOrIRIref+ | '*' ) DatasetClause* WhereClause? SolutionModifier
1002             my $self = shift;
1003 3     3   8
1004 3         8 my $star = 0;
1005 3 50       10 if ($self->_optional_token(STAR)) {
1006 3         7 $star = 1;
1007 3         14 $self->{build}{variables} = ['*'];
1008             } else {
1009 3         14 $self->_VarOrIRIref;
1010 3 50       15 while ($self->_VarOrIRIref_test) {
1011 0         0 $self->_VarOrIRIref;
1012             }
1013 3         10 $self->{build}{variables} = [ splice(@{ $self->{_stack} }) ];
1014             }
1015            
1016 3         13 $self->_DatasetClause();
1017            
1018 3         9 if ($self->_WhereClause_test) {
1019 3         9 $self->_WhereClause;
1020 3 50 33     17 } else {
1021 0         0 my $pattern = Attean::Algebra::BGP->new();
1022             $self->_add_patterns( $pattern );
1023             }
1024            
1025             $self->_SolutionModifier();
1026             $self->{build}{method} = 'DESCRIBE';
1027              
1028             my $pattern = $self->{build}{triples}[0];
1029             my $terms = $star ? [map { Attean::Variable->new($_) } $pattern->in_scope_variables] : $self->{build}{variables};
1030             $self->{build}{triples}[0] = Attean::Algebra::Describe->new( terms => $terms, children => [$pattern] );
1031             }
1032 3         50  
1033 3         10 # [8] AskQuery ::= 'ASK' DatasetClause* WhereClause
1034 3         10 my $self = shift;
1035            
1036             $self->_DatasetClause();
1037            
1038             $self->_WhereClause;
1039 1     1   3
1040             $self->{build}{variables} = [];
1041 1         2 $self->{build}{method} = 'ASK';
1042 1 50       4
1043 0         0
1044 0         0 my $pattern = $self->{build}{triples}[0];
1045             $self->{build}{triples}[0] = Attean::Algebra::Ask->new( children => [$pattern] );
1046 1         5 }
1047 1         5  
1048 0         0 # sub _DatasetClause_test {
1049             # my $self = shift;
1050 1         3 # return $self->_test_token(KEYWORD, 'FROM');
  1         6  
1051             # }
1052              
1053 1         5 # [9] DatasetClause ::= 'FROM' ( DefaultGraphClause | NamedGraphClause )
1054             my $self = shift;
1055 1 50       6
1056 1         5 # my @dataset;
1057             $self->{build}{sources} = [];
1058 0         0 while ($self->_optional_token(KEYWORD, 'FROM')) {
1059 0         0 if ($self->_test_token(KEYWORD, 'NAMED')) {
1060             $self->_NamedGraphClause;
1061             } else {
1062 1         5 $self->_DefaultGraphClause;
1063 1         4 }
1064             }
1065 1         3 }
1066 1 50       4  
  0         0  
1067 1         25 # [10] DefaultGraphClause ::= SourceSelector
1068             my $self = shift;
1069             $self->_SourceSelector;
1070             my ($source) = splice(@{ $self->{_stack} });
1071             push( @{ $self->{build}{sources} }, [$source, 'DEFAULT'] );
1072 4     4   9 }
1073              
1074 4         16 # [11] NamedGraphClause ::= 'NAMED' SourceSelector
1075             my $self = shift;
1076 4         22 $self->_expected_token(KEYWORD, 'NAMED');
1077             $self->_SourceSelector;
1078 4         13 my ($source) = splice(@{ $self->{_stack} });
1079 4         10 push( @{ $self->{build}{sources} }, [$source, 'NAMED'] );
1080             }
1081              
1082 4         9 # [12] SourceSelector ::= IRIref
1083 4         103 my $self = shift;
1084             $self->_IRIref;
1085             }
1086              
1087             # [13] WhereClause ::= 'WHERE'? GroupGraphPattern
1088             my $self = shift;
1089             return 1 if ($self->_test_token(KEYWORD, 'WHERE'));
1090             return 1 if ($self->_test_token(LBRACE));
1091             return 0;
1092             }
1093 36     36   69 my $self = shift;
1094             $self->_optional_token(KEYWORD, 'WHERE');
1095             $self->_GroupGraphPattern;
1096 36         95
1097 36         94 my $ggp = $self->_peek_pattern;
1098 8 100       33 $self->_check_duplicate_blanks($ggp);
1099 4         18 }
1100              
1101 4         30 my $self = shift;
1102             my $p = shift;
1103             # warn 'TODO: $ggp->_check_duplicate_blanks'; # XXXXXXXX
1104             # my @children = @{ $ggp->children };
1105             # my %seen;
1106             # foreach my $c (@{ $ggp->children }) {
1107             # my @blanks = $c->blank_nodes;
1108 4     4   9 # foreach my $b (@blanks) {
1109 4         14 # my $id = $b->value;
1110 4         16 # if ($seen{ $id }++) {
  4         15  
1111 4         11 # warn $ggp->as_string;
  4         19  
1112             # croak "Same blank node identifier ($id) used in more than one BasicGraphPattern.";
1113             # }
1114             # }
1115             # }
1116 4     4   11 return 1;
1117 4         19 }
1118 4         18  
1119 4         17 my $self = shift;
  4         14  
1120 4         10 $self->_push_pattern_container;
  4         22  
1121            
1122             $self->_expected_token(KEYWORD, 'WHERE');
1123             $self->_expected_token(LBRACE);
1124             if ($self->_TriplesBlock_test) {
1125 8     8   25 $self->_TriplesBlock;
1126 8         23 }
1127             $self->_expected_token(RBRACE);
1128            
1129             my ($cont, $hints) = $self->_pop_pattern_container;
1130             $self->{build}{construct_triples} = $cont->[0];
1131 1     1   3
1132 1 50       4 my $pattern = $self->_new_join(@$cont);
1133 0 0       0 $pattern->hints($hints);
1134 0         0 $self->_add_patterns( $pattern );
1135             }
1136              
1137 37     37   82 # sub _Binding_test {
1138 37         104 # my $self = shift;
1139 37         171 # return $self->_test_token(LPAREN);
1140             # }
1141 37         175  
1142 37         123 my $self = shift;
1143             my $count = shift;
1144            
1145             $self->_expected_token(LPAREN);
1146 39     39   67
1147 39         79 my @terms;
1148             foreach my $i (1..$count) {
1149             unless ($self->_BindingValue_test) {
1150             my $found = $i-1;
1151             croak "Syntax error: Expected $count BindingValues but only found $found";
1152             }
1153             $self->_BindingValue;
1154             push( @terms, splice(@{ $self->{_stack} }));
1155             }
1156             $self->_expected_token(RPAREN);
1157             return \@terms;
1158             }
1159              
1160             my $self = shift;
1161 39         77 return 1 if ($self->_IRIref_test);
1162             return 1 if ($self->_test_token(KEYWORD, 'UNDEF'));
1163             return 1 if ($self->_test_literal_token);
1164             return 1 if ($self->_IRIref_test);
1165 0     0   0 return 1 if ($self->_test_token(BNODE));
1166 0         0 return 1 if ($self->_test_token(NIL));
1167             return 1 if ($self->_test_token(LTLT));
1168 0         0 return 0;
1169 0         0 }
1170 0 0       0  
1171 0         0 my $self = shift;
1172             if ($self->_optional_token(KEYWORD, 'UNDEF')) {
1173 0         0 push(@{ $self->{_stack} }, undef);
1174             } elsif ($self->_test_token(LTLT)) {
1175 0         0 $self->_QuotedTriple();
1176 0         0 } else {
1177             $self->_GraphTerm;
1178 0         0 }
1179 0         0 }
1180 0         0  
1181             # [20] GroupCondition ::= ( BuiltInCall | FunctionCall | '(' Expression ( 'AS' Var )? ')' | Var )
1182             my $self = shift;
1183             return 1 if ($self->_BuiltInCall_test);
1184             return 1 if ($self->_IRIref_test);
1185             return 1 if ($self->_test_token(LPAREN));
1186             return 1 if ($self->_test_token(VAR));
1187             return 0;
1188             }
1189 2     2   4  
1190 2         3 my $self = shift;
1191             if ($self->_optional_token(LPAREN)) {
1192 2         6 $self->_Expression;
1193             my ($expr) = splice(@{ $self->{_stack} });
1194 2         3 if ($self->_optional_token(KEYWORD, 'AS')) {
1195 2         5 $self->_Var;
1196 2 50       5 my ($var) = splice(@{ $self->{_stack} });
1197 0         0 push(@{ $self->{build}{__group_vars} }, [$var, $expr]);
1198 0         0 my $vexpr = Attean::ValueExpression->new( value => $var );
1199             $self->_add_stack( $vexpr );
1200 2         7 } else {
1201 2         3 $self->_add_stack( $expr );
  2         7  
1202             }
1203 2         5 $self->_expected_token(RPAREN);
1204 2         4
1205             } elsif ($self->_IRIref_test) {
1206             $self->_FunctionCall;
1207             } elsif ($self->_BuiltInCall_test) {
1208 6     6   9 $self->_BuiltInCall;
1209 6 100       15 } else {
1210 4 50       22 $self->_Var;
1211 4 100       11 my $var = pop(@{ $self->{_stack} });
1212 1 50       5 my $expr = Attean::ValueExpression->new(value => $var);
1213 1 50       12 $self->_add_stack($expr);
1214 1 50       3 }
1215 1 50       9 }
1216 1         3  
1217             # [14] SolutionModifier ::= OrderClause? LimitOffsetClauses?
1218             my $self = shift;
1219             my $vars = shift // [];
1220 5     5   10
1221 5 50       11 if ($self->_test_token(KEYWORD, 'GROUP')) {
    50          
1222 0         0 $self->_GroupClause($vars);
  0         0  
1223             }
1224 0         0
1225             if ($self->_test_token(KEYWORD, 'RANK')) {
1226 5         15 $self->_RankClause;
1227             }
1228            
1229             if ($self->_test_token(KEYWORD, 'HAVING')) {
1230             $self->_HavingClause;
1231             }
1232 2     2   4
1233 2 50       5 if ($self->_OrderClause_test) {
1234 2 50       9 $self->_OrderClause;
1235 2 50       9 }
1236 2 50       7
1237 2         9 if ($self->_LimitOffsetClauses_test) {
1238             $self->_LimitOffsetClauses;
1239             }
1240             }
1241 2     2   4  
1242 2 50       6 my $self = shift;
    50          
    50          
1243 0         0 my $vars = shift;
1244 0         0 $self->_expected_token(KEYWORD, 'GROUP');
  0         0  
1245 0 0       0 $self->_expected_token(KEYWORD, 'BY');
1246 0         0
1247 0         0 if ($self->{build}{star}) {
  0         0  
1248 0         0 croak "Syntax error: SELECT * cannot be used with aggregate grouping";
  0         0  
1249 0         0 }
1250 0         0
1251             $self->{build}{__aggregate} ||= {};
1252 0         0 my @vars;
1253             $self->__GroupByVar;
1254 0         0 my ($v) = splice(@{ $self->{_stack} });
1255             push( @vars, $v );
1256             while ($self->__GroupByVar_test) {
1257 0         0 $self->__GroupByVar;
1258             my ($v) = splice(@{ $self->{_stack} });
1259 0         0 push( @vars, $v );
1260             }
1261 2         11  
1262 2         4 my %seen;
  2         7  
1263 2         40 foreach my $v (@vars) {
1264 2         9 my $var = $v->value;
1265             if ($var->does('Attean::API::Variable')) {
1266             my $name = $var->value;
1267             $seen{ $name }++;
1268             }
1269             }
1270 33     33   62
1271 33   100     108 # warn 'TODO: verify that projection only includes aggregates and grouping variables'; # XXXXX
1272             # foreach my $v (@$vars) {
1273 33 100       81 # if ($v->does('Attean::API::Variable')) {
1274 2         15 # my $name = $v->value;
1275             # unless ($seen{ $name }) {
1276             # croak "Syntax error: Variable used in projection but not present in aggregate grouping ($name)";
1277 33 100       98 # # throw ::Error::ParseError -text => "Syntax error: Variable used in projection but not present in aggregate grouping ($name)";
1278 1         14 # }
1279             # }
1280             # }
1281 33 100       93
1282 1         6 $self->{build}{__group_by} = \@vars;
1283             }
1284              
1285 33 100       128 my $self = shift;
1286 1         4 $self->_expected_token(KEYWORD, 'RANK');
1287             $self->_expected_token(LPAREN);
1288             $self->_OrderCondition;
1289 33 100       122 my @order;
1290 2         8 push(@order, splice(@{ $self->{_stack} }));
1291             while ($self->_OrderCondition_test) {
1292             $self->_OrderCondition;
1293             push(@order, splice(@{ $self->{_stack} }));
1294             }
1295 2     2   14 $self->_expected_token(RPAREN);
1296 2         5 $self->_expected_token(KEYWORD, 'AS');
1297 2         6 $self->_Var;
1298 2         8 my ($var) = splice(@{ $self->{_stack} });
1299            
1300 2 50       15 my @exprs;
1301 0         0 my %ascending;
1302             foreach my $o (@order) {
1303             my ($dir, $expr) = @$o;
1304 2   100     9 push(@exprs, $expr);
1305 2         5 $ascending{ $expr->value->value } = ($dir eq 'ASC') ? 1 : 0; # TODO: support ranking by complex expressions, not just variables
1306 2         9 }
1307 2         9 my $r = Attean::AggregateExpression->new(
  2         6  
1308 2         5 distinct => 0,
1309 2         8 operator => 'RANK',
1310 0         0 children => \@exprs,
1311 0         0 scalar_vars => {
  0         0  
1312 0         0 ascending => \%ascending,
1313             },
1314             variable => $var,
1315 2         4 );
1316 2         5
1317 2         7 $self->{build}{__aggregate}{ $var->value } = [ $var, $r ];
1318 2 50       9 }
1319 2         40  
1320 2         8 my $self = shift;
1321             $self->_expected_token(KEYWORD, 'HAVING');
1322             $self->{build}{__aggregate} ||= {};
1323             local($self->{__aggregate_call_ok}) = 1;
1324             $self->_Constraint;
1325             my ($expr) = splice(@{ $self->{_stack} });
1326             $self->{build}{__having} = $expr;
1327             }
1328              
1329             # [15] LimitOffsetClauses ::= ( LimitClause OffsetClause? | OffsetClause LimitClause? )
1330             my $self = shift;
1331             return 1 if ($self->_test_token(KEYWORD, 'LIMIT'));
1332             return 1 if ($self->_test_token(KEYWORD, 'OFFSET'));
1333             return 0;
1334             }
1335 2         8  
1336             my $self = shift;
1337             if ($self->_LimitClause_test) {
1338             $self->_LimitClause;
1339 1     1   3 if ($self->_OffsetClause_test) {
1340 1         3 $self->_OffsetClause;
1341 1         6 }
1342 1         9 } else {
1343 1         2 $self->_OffsetClause;
1344 1         3 if ($self->_LimitClause_test) {
  1         3  
1345 1         4 $self->_LimitClause;
1346 0         0 }
1347 0         0 }
  0         0  
1348             }
1349 1         4  
1350 1         4 # [16] OrderClause ::= 'ORDER' 'BY' OrderCondition+
1351 1         3 my $self = shift;
1352 1         5 return 1 if ($self->_test_token(KEYWORD, 'ORDER'));
  1         4  
1353             return 0;
1354 1         3 }
1355              
1356 1         3 my $self = shift;
1357 1         3 $self->_expected_token(KEYWORD, 'ORDER');
1358 1         2 $self->_expected_token(KEYWORD, 'BY');
1359 1 50       8 my @order;
1360             $self->{build}{__aggregate} ||= {};
1361 1         10 local($self->{__aggregate_call_ok}) = 1;
1362             $self->_OrderCondition;
1363             push(@order, splice(@{ $self->{_stack} }));
1364             while ($self->_OrderCondition_test) {
1365             $self->_OrderCondition;
1366             push(@order, splice(@{ $self->{_stack} }));
1367             }
1368             $self->{build}{options}{orderby} = \@order;
1369             }
1370              
1371 1         20 # [17] OrderCondition ::= ( ( 'ASC' | 'DESC' ) BrackettedExpression ) | ( Constraint | Var )
1372             my $self = shift;
1373             return 1 if ($self->_test_token(KEYWORD, 'ASC'));
1374             return 1 if ($self->_test_token(KEYWORD, 'DESC'));
1375 1     1   3 return 1 if ($self->_test_token(VAR));
1376 1         3 return 1 if $self->_Constraint_test;
1377 1   50     15 return 0;
1378 1         3 }
1379 1         4  
1380 1         9 my $self = shift;
  1         3  
1381 1         4 my $dir = 'ASC';
1382             if (my $t = $self->_optional_token(KEYWORD, qr/^(ASC|DESC)/)) {
1383             $dir = $t->value;
1384             $self->_BrackettedExpression;
1385             } elsif ($self->_test_token(VAR)) {
1386 33     33   68 $self->_Var;
1387 33 100       81 my $var = pop(@{ $self->{_stack} });
1388 32 100       91 my $expr = Attean::ValueExpression->new(value => $var);
1389 31         89 $self->_add_stack($expr);
1390             } else {
1391             $self->_Constraint;
1392             }
1393 2     2   5 my ($expr) = splice(@{ $self->{_stack} });
1394 2 100       8 $self->_add_stack( [ $dir, $expr ] );
1395 1         3 }
1396 1 50       4  
1397 1         5 # [18] LimitClause ::= 'LIMIT' INTEGER
1398             my $self = shift;
1399             return ($self->_test_token(KEYWORD, 'LIMIT'));
1400 1         5 }
1401 1 50       225  
1402 0         0 my $self = shift;
1403             $self->_expected_token(KEYWORD, 'LIMIT');
1404             my $t = $self->_expected_token(INTEGER);
1405             $self->{build}{options}{limit} = $t->value;
1406             }
1407              
1408             # [19] OffsetClause ::= 'OFFSET' INTEGER
1409 33     33   59 my $self = shift;
1410 33 100       98 return ($self->_test_token(KEYWORD, 'OFFSET'));
1411 32         139 }
1412              
1413             my $self = shift;
1414             $self->_expected_token(KEYWORD, 'OFFSET');
1415 1     1   2 my $t = $self->_expected_token(INTEGER);
1416 1         4 $self->{build}{options}{offset} = $t->value;
1417 1         4 }
1418 1         2  
1419 1   50     7 # [20] GroupGraphPattern ::= '{' TriplesBlock? ( ( GraphPatternNotTriples | Filter ) '.'? TriplesBlock? )* '}'
1420 1         3 my $self = shift;
1421 1         5
1422 1         2 $self->_expected_token(LBRACE);
  1         2  
1423 1         4
1424 0         0 if ($self->_SubSelect_test) {
1425 0         0 $self->_SubSelect;
  0         0  
1426             } else {
1427 1         4 $self->_GroupGraphPatternSub;
1428             }
1429              
1430             $self->_expected_token(RBRACE);
1431             }
1432 2     2   4  
1433 2 50       6 my $self = shift;
1434 2 50       15 $self->_push_pattern_container;
1435 2 50       13
1436 2 50       8 my $got_pattern = 0;
1437 2         6 my $need_dot = 0;
1438             if ($self->_TriplesBlock_test) {
1439             $need_dot = 1;
1440             $got_pattern++;
1441 2     2   4 $self->_TriplesBlock;
1442 2         6 }
1443 2 100       14
    50          
1444 1         6 while (not $self->_test_token(RBRACE)) {
1445 1         8 my $cur = $self->_peek_token;
1446             if ($self->_GraphPatternNotTriples_test) {
1447 1         4 $need_dot = 0;
1448 1         2 $got_pattern++;
  1         3  
1449 1         22 $self->_GraphPatternNotTriples;
1450 1         4 my (@data) = splice(@{ $self->{_stack} });
1451             $self->__handle_GraphPatternNotTriples( @data );
1452 0         0 } elsif ($self->_test_token(KEYWORD, 'FILTER')) {
1453             $got_pattern++;
1454 2         6 $need_dot = 0;
  2         6  
1455 2         8 $self->_Filter;
1456             }
1457            
1458             if ($need_dot or $self->_test_token(DOT)) {
1459             $self->_expected_token(DOT);
1460 3     3   6 if ($got_pattern) {
1461 3         9 $need_dot = 0;
1462             $got_pattern = 0;
1463             } else {
1464             croak "Syntax error: Extra dot found without preceding pattern";
1465 1     1   2 }
1466 1         4 }
1467 1         4
1468 1         4 if ($self->_TriplesBlock_test) {
1469             my $peek = $self->_peek_pattern;
1470             if (blessed($peek) and $peek->isa('Attean::Algebra::BGP')) {
1471             $self->_TriplesBlock;
1472             my $rhs = $self->_remove_pattern;
1473 1     1   2 my $lhs = $self->_remove_pattern;
1474 1         3 if ($rhs->isa('Attean::Algebra::BGP')) {
1475             my $merged = $self->__new_bgp( map { @{ $_->triples } } ($lhs, $rhs) );
1476             $self->_add_patterns( $merged );
1477             } else {
1478 2     2   4 my $merged = $self->_new_join($lhs, $rhs);
1479 2         7 $self->_add_patterns( $merged );
1480 2         6 }
1481 2         6 } else {
1482             $self->_TriplesBlock;
1483             }
1484             }
1485              
1486 54     54   98 my $t = $self->_peek_token;
1487             last if (refaddr($t) == refaddr($cur));
1488 54         160 }
1489             my ($cont, $hints) = $self->_pop_pattern_container;
1490 54 100       242  
1491 1         7 my @filters = splice(@{ $self->{filters} });
1492             my @patterns;
1493 53         244 my $pattern = $self->_new_join(@$cont);
1494             $pattern->hints($hints);
1495             if (@filters) {
1496 54         158 while (my $f = shift @filters) {
1497             $pattern = Attean::Algebra::Filter->new( children => [$pattern], expression => $f );
1498             }
1499             }
1500 53     53   93 $self->_add_patterns( $pattern );
1501 53         169 }
1502              
1503 53         105 my $self = shift;
1504 53         91 my $data = shift;
1505 53 100       174 return unless ($data);
1506 41         92 my ($class, @args) = @$data;
1507 41         64 if ($class =~ /^Attean::Algebra::(LeftJoin|Minus)$/) {
1508 41         126 my ($cont, $hints) = $self->_pop_pattern_container;
1509             my $ggp = $self->_new_join(@$cont);
1510             $ggp->hints($hints);
1511 53         171 $self->_push_pattern_container;
1512 17         67 # my $ggp = $self->_remove_pattern();
1513 17 100       66 unless ($ggp) {
    50          
1514 13         26 $ggp = Attean::Algebra::BGP->new();
1515 13         27 }
1516 13         49
1517 13         24 my $opt = $class->new( children => [$ggp, @args] );
  13         38  
1518 13         46 $self->_add_patterns( $opt );
1519             } elsif ($class eq 'Attean::Algebra::Table') {
1520 4         7 my ($table) = @args;
1521 4         8 $self->_add_patterns( $table );
1522 4         13 } elsif ($class eq 'Attean::Algebra::Extend') {
1523             my ($cont, $hints) = $self->_pop_pattern_container;
1524             my $ggp = $self->_new_join(@$cont);
1525 17 50 33     86 $ggp->hints($hints);
1526 0         0 $self->_push_pattern_container;
1527 0 0       0 # my $ggp = $self->_remove_pattern();
1528 0         0 unless ($ggp) {
1529 0         0 $ggp = Attean::Algebra::BGP->new();
1530             }
1531 0         0 my ($var, $expr) = @args;
1532             my %in_scope = map { $_ => 1 } $ggp->in_scope_variables;
1533             if (exists $in_scope{ $var->value }) {
1534             croak "Syntax error: BIND used with variable already in scope";
1535 17 100       59 }
1536 1         3 my $bind = Attean::Algebra::Extend->new( children => [$ggp], variable => $var, expression => $expr );
1537 1 50 33     6 $self->_add_patterns( $bind );
1538 0         0 } elsif ($class eq 'Attean::Algebra::Service') {
1539 0         0 my ($endpoint, $pattern, $silent) = @args;
1540 0         0 if ($endpoint->does('Attean::API::Variable')) {
1541 0 0       0 # SERVICE ?var
1542 0         0 croak "SERVICE ?var not implemented";
  0         0  
  0         0  
1543 0         0 } else {
1544             # SERVICE <endpoint>
1545 0         0 # no-op
1546 0         0 my $service = Attean::Algebra::Service->new( children => [$pattern], endpoint => $endpoint, silent => $silent );
1547             $self->_add_patterns( $service );
1548             }
1549 1         3 } elsif ($class =~ /Attean::Algebra::(Union|Graph|Join)$/) {
1550             # no-op
1551             } else {
1552             croak 'Unrecognized GraphPattern: ' . $class;
1553 17         46 }
1554 17 50       130 }
1555              
1556 53         169 my $self = shift;
1557             return $self->_test_token(KEYWORD, 'SELECT');
1558 53         90 }
  53         131  
1559 53         91  
1560 53         149 my $self = shift;
1561 53         875 my $pattern;
1562 53 100       1598 {
1563 4         14 local($self->{namespaces}) = $self->{namespaces};
1564 4         73 local($self->{_stack}) = [];
1565             local($self->{filters}) = [];
1566             local($self->{_pattern_container_stack}) = [];
1567 53         170  
1568             my $triples = $self->_push_pattern_container();
1569             local($self->{build}) = { triples => $triples};
1570             if ($self->{baseURI}) {
1571 13     13   24 $self->{build}{base} = $self->{baseURI};
1572 13         21 }
1573 13 100       40
1574 12         34 $self->_expected_token(KEYWORD, 'SELECT');
1575 12 100       152 if (my $t = $self->_optional_token(KEYWORD, qr/^(DISTINCT|REDUCED)/)) {
    50          
    100          
    100          
    50          
1576 2         7 my $mod = $t->value;
1577 2         6 $self->{build}{options}{lc($mod)} = 1;
1578 2         36 }
1579 2         53
1580             my ($star, $exprs, $vars) = $self->__SelectVars;
1581 2 50       6 my @exprs = @$exprs;
1582 0         0
1583             $self->_WhereClause;
1584             $self->_SolutionModifier($vars);
1585 2         21
1586 2         9 if ($self->{build}{options}{orderby}) {
1587             my $order = delete $self->{build}{options}{orderby};
1588 0         0 my $pattern = pop(@{ $self->{build}{triples} });
1589 0         0
1590             my @order = @$order;
1591 2         7 my @cmps;
1592 2         6 foreach my $o (@order) {
1593 2         33 my ($dir, $expr) = @$o;
1594 2         58 my $asc = ($dir eq 'ASC');
1595             push(@cmps, Attean::Algebra::Comparator->new(ascending => $asc, expression => $expr));
1596 2 50       7 }
1597 0         0 my $sort = Attean::Algebra::OrderBy->new( children => [$pattern], comparators => \@cmps );
1598             push(@{ $self->{build}{triples} }, $sort);
1599 2         5 }
1600 2         8
  1         54  
1601 2 50       51 if ($self->_optional_token(KEYWORD, 'VALUES')) {
1602 0         0 my @vars;
1603             my $parens = 0;
1604 2         21 if ($self->_optional_token(LPAREN)) {
1605 2         11 $parens = 1;
1606             }
1607 2         7 while ($self->_test_token(VAR)) {
1608 2 50       9 $self->_Var;
1609             push( @vars, splice(@{ $self->{_stack} }));
1610 0         0 }
1611             if ($parens) {
1612             $self->_expected_token(RPAREN);
1613             }
1614 2         96 my $count = scalar(@vars);
1615 2         11 if (not($parens) and $count == 0) {
1616             croak "Syntax error: Expected VAR in inline data declaration";
1617             } elsif (not($parens) and $count > 1) {
1618             croak "Syntax error: Inline data declaration can only have one variable when parens are omitted";
1619             }
1620 0         0
1621             my $short = (not($parens) and $count == 1);
1622             $self->_expected_token(LBRACE);
1623             if (not($short) or ($short and $self->_test_token(LPAREN))) {
1624             while ($self->_test_token(LPAREN)) {
1625 54     54   104 my $terms = $self->_Binding($count);
1626 54         150 push( @{ $self->{build}{bindings}{terms} }, $terms );
1627             }
1628             } else {
1629             while ($self->_BindingValue_test) {
1630 1     1   2 $self->_BindingValue;
1631 1         3 my ($term) = splice(@{ $self->{_stack} });
1632             push( @{ $self->{build}{bindings}{terms} }, [$term] );
1633 1         2 }
  1         3  
1634 1         3 }
1635 1         3
1636 1         3 $self->_expected_token(RBRACE);
1637             $self->{build}{bindings}{vars} = \@vars;
1638 1         3
1639 1         3 my $bindings = delete $self->{build}{bindings};
1640 1 50       5 my @rows = @{ $bindings->{terms} };
1641 0         0 my @vbs;
1642             foreach my $r (@rows) {
1643             my %d;
1644 1         4 foreach my $i (0 .. $#{ $r }) {
1645 1 50       5 if (blessed($r->[$i])) {
1646 0         0 $d{ $vars[$i]->value } = $r->[$i];
1647 0         0 }
1648             }
1649             my $r = Attean::Result->new(bindings => \%d);
1650 1         4 push(@vbs, $r);
1651 1         3 }
1652             my $table = Attean::Algebra::Table->new( variables => \@vars, rows => \@vbs );
1653 1         5 my $pattern = pop(@{ $self->{build}{triples} });
1654 1         4 push(@{ $self->{build}{triples} }, $self->_new_join($pattern, $table));
1655             }
1656 1 50       4
1657 0         0 $self->__solution_modifiers( $star, @exprs );
1658 0         0
  0         0  
1659             delete $self->{build}{options};
1660 0         0 my $data = delete $self->{build};
1661 0         0 $pattern = $data->{triples}[0];
1662 0         0 $pattern = Attean::Algebra::Query->new( children => [$pattern], subquery => 1 );
1663 0         0 }
1664 0         0
1665 0         0 $self->_add_patterns( $pattern );
1666             }
1667 0         0  
1668 0         0 # [21] TriplesBlock ::= TriplesSameSubject ( '.' TriplesBlock? )?
  0         0  
1669             my $self = shift;
1670             # VarOrTerm | TriplesNode -> (Var | GraphTerm) | (Collection | BlankNodePropertyList) -> Var | IRIref | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL | Collection | BlankNodePropertyList
1671 1 50       3 # but since a triple can't start with a literal, this is reduced to:
1672 0         0 # Var | IRIref | BlankNode | NIL
1673 0         0 return 1 if ($self->_test_token(VAR));
1674 0 0       0 return 1 if ($self->_test_token(NIL));
1675 0         0 return 1 if ($self->_test_token(ANON));
1676             return 1 if ($self->_test_token(BNODE));
1677 0         0 return 1 if ($self->_test_token(LPAREN));
1678 0         0 return 1 if ($self->_test_token(LBRACKET));
1679 0         0 return 1 if ($self->_test_token(LTLT));
  0         0  
1680             return 1 if ($self->_IRIref_test);
1681 0 0       0 return 1 if ($self->_test_literal_token);
1682 0         0 return 0;
1683             }
1684 0         0  
1685 0 0 0     0 my $self = shift;
    0 0        
1686 0         0 return 1 if ($self->_test_token(STRING1D));
1687             return 1 if ($self->_test_token(STRING3D));
1688 0         0 return 1 if ($self->_test_token(STRING1S));
1689             return 1 if ($self->_test_token(STRING3S));
1690             return 1 if ($self->_test_token(DECIMAL));
1691 0   0     0 return 1 if ($self->_test_token(DOUBLE));
1692 0         0 return 1 if ($self->_test_token(INTEGER));
1693 0 0 0     0 return 1 if ($self->_test_token(BOOLEAN));
      0        
1694 0         0 return 0;
1695 0         0 }
1696 0         0  
  0         0  
1697              
1698             my $self = shift;
1699 0         0 $self->_push_pattern_container;
1700 0         0 $self->__TriplesBlock;
1701 0         0 my ($triples, $hints) = $self->_pop_pattern_container;
  0         0  
1702 0         0 my $bgp = $self->__new_bgp( @$triples );
  0         0  
1703             $bgp->hints($hints);
1704             $self->_add_patterns( $bgp );
1705             }
1706 0         0  
1707 0         0 ## this one (with two underscores) doesn't pop patterns off the stack and make a BGP.
1708             ## instead, things are left on the stack so we can recurse without doing the wrong thing.
1709 0         0 ## the one with one underscore (_TriplesBlock) will pop everything off and make the BGP.
1710 0         0 my $self = shift;
  0         0  
1711 0         0 my $got_dot = 0;
1712 0         0 TRIPLESBLOCKLOOP:
1713 0         0 $self->_TriplesSameSubjectPath;
1714 0         0 while ($self->_test_token(DOT)) {
  0         0  
1715 0 0       0 if ($got_dot) {
1716 0         0 croak "Syntax error: found extra DOT after TriplesBlock";
1717             }
1718             $self->_expected_token(DOT);
1719 0         0 $got_dot++;
1720 0         0 if ($self->_TriplesBlock_test) {
1721             $got_dot = 0;
1722 0         0 goto TRIPLESBLOCKLOOP;
1723 0         0 }
  0         0  
1724 0         0 }
  0         0  
1725             }
1726              
1727 1         5 # [22] GraphPatternNotTriples ::= OptionalGraphPattern | GroupOrUnionGraphPattern | GraphGraphPattern
1728             my $self = shift;
1729 1         3 return 1 if ($self->_test_token(LBRACE));
1730 1         2 my $t = $self->_peek_token;
1731 1         2 return unless ($t);
1732 1         10 return 0 unless ($t->type == KEYWORD);
1733             return ($t->value =~ qr/^(VALUES|BIND|SERVICE|MINUS|OPTIONAL|GRAPH|HINT)$/i);
1734             }
1735 1         5  
1736             my $self = shift;
1737             if ($self->_test_token(KEYWORD, 'VALUES')) {
1738             $self->_InlineDataClause;
1739             } elsif ($self->_test_token(KEYWORD, 'SERVICE')) {
1740 109     109   177 $self->_ServiceGraphPattern;
1741             } elsif ($self->_test_token(KEYWORD, 'MINUS')) {
1742             $self->_MinusGraphPattern;
1743             } elsif ($self->_test_token(KEYWORD, 'BIND')) {
1744 109 100       217 $self->_Bind;
1745 59 50       161 } elsif ($self->_test_token(KEYWORD, 'HINT')) {
1746 59 50       139 $self->_Hint;
1747 59 50       154 } elsif ($self->_test_token(KEYWORD, 'OPTIONAL')) {
1748 59 100       153 $self->_OptionalGraphPattern;
1749 58 50       138 } elsif ($self->_test_token(LBRACE)) {
1750 58 100       149 $self->_GroupOrUnionGraphPattern;
1751 56 100       171 } else {
1752 46 50       144 $self->_GraphGraphPattern;
1753 46         143 }
1754             }
1755              
1756             my $self = shift;
1757 84     84   140 $self->_expected_token(KEYWORD, 'VALUES');
1758 84 100       166 my @vars;
1759 74 50       155
1760 74 100       193 my $parens = 0;
1761 73 100       171 if ($self->_optional_token(LPAREN)) {
1762 72 50       160 $parens = 1;
1763 72 50       169 }
1764 72 50       135 while ($self->_test_token(VAR)) {
1765 72 50       143 $self->_Var;
1766 72         184 push( @vars, splice(@{ $self->{_stack} }));
1767             }
1768             if ($parens) {
1769             $self->_expected_token(RPAREN);
1770             }
1771 51     51   85
1772 51         140 my $count = scalar(@vars);
1773 51         159 if (not($parens) and $count == 0) {
1774 51         182 croak "Syntax error: Expected VAR in inline data declaration";
1775 51         172 } elsif (not($parens) and $count > 1) {
1776 51         830 croak "Syntax error: Inline data declaration can only have one variable when parens are omitted";
1777 51         1683 }
1778            
1779             my $short = (not($parens) and $count == 1);
1780             $self->_expected_token(LBRACE);
1781             my @rows;
1782             if (not($short) or ($short and $self->_test_token(LPAREN))) {
1783             # { (term) (term) }
1784 51     51   80 while ($self->_test_token(LPAREN)) {
1785 51         86 my $terms = $self->_Binding($count);
1786 51         167 push( @rows, $terms );
1787             }
1788 51         153 } else {
1789 9 50       37 # { term term }
1790 0         0 while ($self->_BindingValue_test) {
1791             $self->_BindingValue;
1792 9         32 my ($term) = splice(@{ $self->{_stack} });
1793 9         29 push( @rows, [$term] );
1794 9 50       31 }
1795 0         0 }
1796 0         0
1797             $self->_expected_token(RBRACE);
1798            
1799             my @vbs;
1800             foreach my $row (@rows) {
1801             my %d;
1802             # Turn triple patterns into ground triples.
1803 17     17   31 @d{ map { $_->value } @vars } = map { $_->does('Attean::API::TriplePattern') ? $_->as_triple : $_ } @$row;
1804 17 100       38 my $result = Attean::Result->new(bindings => \%d);
1805 14         38 push(@vbs, $result);
1806 14 50       40 }
1807 14 50       57 my $table = Attean::Algebra::Table->new( variables => \@vars, rows => \@vbs );
1808 14         61 $self->_add_stack( ['Attean::Algebra::Table', $table] );
1809            
1810             }
1811              
1812 13     13   23 my $self = shift;
1813 13 50       30 $self->_expected_token(KEYWORD, 'BIND');
    100          
    100          
    100          
    100          
    100          
    100          
1814 0         0 my ($var, $expr) = $self->_BrackettedAliasExpression;
1815             $self->_add_stack( ['Attean::Algebra::Extend', $var, $expr] );
1816 2         10 }
1817              
1818 1         3 my $self = shift;
1819             $self->_expected_token(KEYWORD, 'HINT');
1820 2         10 my $terms = $self->_HintTerms();
1821             $self->_add_hint($terms);
1822 1         6 }
1823              
1824 1         5 my $self = shift;
1825            
1826 3         13 $self->_expected_token(LPAREN);
1827            
1828 3         18 my @terms;
1829             while ($self->_BindingValue_test) {
1830             $self->_BindingValue;
1831             push(@terms, splice(@{ $self->{_stack} }));
1832             }
1833 0     0   0 $self->_expected_token(RPAREN);
1834 0         0 return \@terms;
1835 0         0 }
1836              
1837 0         0 my $self = shift;
1838 0 0       0 $self->_expected_token(KEYWORD, 'SERVICE');
1839 0         0 my $silent = $self->_optional_token(KEYWORD, 'SILENT') ? 1 : 0;
1840             $self->__close_bgp_with_filters;
1841 0         0 if ($self->_test_token(VAR)) {
1842 0         0 $self->_Var;
1843 0         0 } else {
  0         0  
1844             $self->_IRIref;
1845 0 0       0 }
1846 0         0 my ($endpoint) = splice( @{ $self->{_stack} } );
1847             $self->_GroupGraphPattern;
1848             my $ggp = $self->_remove_pattern;
1849 0         0
1850 0 0 0     0 my $opt = ['Attean::Algebra::Service', $endpoint, $ggp, ($silent ? 1 : 0)];
    0 0        
1851 0         0 $self->_add_stack( $opt );
1852             }
1853 0         0  
1854             # [23] OptionalGraphPattern ::= 'OPTIONAL' GroupGraphPattern
1855             # sub _OptionalGraphPattern_test {
1856 0   0     0 # my $self = shift;
1857 0         0 # return $self->_test_token(KEYWORD, 'OPTIONAL');
1858 0         0 # }
1859 0 0 0     0  
      0        
1860             my $self = shift;
1861 0         0 my @filters = splice(@{ $self->{filters} });
1862 0         0 if (@filters) {
1863 0         0 my ($cont, $hints) = $self->_pop_pattern_container;
1864             my $ggp = $self->_new_join(@$cont);
1865             $ggp->hints($hints);
1866             $self->_push_pattern_container;
1867 0         0 # my $ggp = $self->_remove_pattern();
1868 0         0 unless ($ggp) {
1869 0         0 $ggp = Attean::Algebra::BGP->new();
  0         0  
1870 0         0 }
1871             while (my $f = shift @filters) {
1872             $ggp = Attean::Algebra::Filter->new( children => [$ggp], expression => $f );
1873             }
1874 0         0 $self->_add_patterns($ggp);
1875             }
1876 0         0 }
1877 0         0  
1878 0         0 my $self = shift;
1879             $self->_expected_token(KEYWORD, 'OPTIONAL');
1880 0 0       0 $self->__close_bgp_with_filters;
  0         0  
  0         0  
1881 0         0
1882 0         0 $self->_GroupGraphPattern;
1883             my $ggp = $self->_remove_pattern;
1884 0         0 my $opt = ['Attean::Algebra::LeftJoin', $ggp];
1885 0         0 $self->_add_stack( $opt );
1886             }
1887              
1888             my $self = shift;
1889             $self->_expected_token(KEYWORD, 'MINUS');
1890 2     2   4 $self->__close_bgp_with_filters;
1891 2         7
1892 2         8 $self->_GroupGraphPattern;
1893 2         9 my $ggp = $self->_remove_pattern;
1894             my $opt = ['Attean::Algebra::Minus', $ggp];
1895             $self->_add_stack( $opt );
1896             }
1897 1     1   2  
1898 1         4 # [24] GraphGraphPattern ::= 'GRAPH' VarOrIRIref GroupGraphPattern
1899 1         5 my $self = shift;
1900 1         4 if ($self->{__data_pattern}) {
1901             if ($self->{__graph_nesting_level}++) {
1902             croak "Syntax error: Nested named GRAPH blocks not allowed in data template.";
1903             }
1904 1     1   2 }
1905            
1906 1         3 $self->_expected_token(KEYWORD, 'GRAPH');
1907             $self->_VarOrIRIref;
1908 1         2 my ($graph) = splice(@{ $self->{_stack} });
1909 1         4 if ($graph->does('Attean::API::IRI')) {
1910 3         17 $self->_GroupGraphPattern;
1911 3         5 } else {
  3         8  
1912             $self->_GroupGraphPattern;
1913 1         4 }
1914 1         12  
1915             if ($self->{__data_pattern}) {
1916             $self->{__graph_nesting_level}--;
1917             }
1918 2     2   6
1919 2         8 my $ggp = $self->_remove_pattern;
1920 2 50       7 my $pattern = Attean::Algebra::Graph->new( children => [$ggp], graph => $graph );
1921 2         10 $self->_add_patterns( $pattern );
1922 2 50       6 $self->_add_stack( [ 'Attean::Algebra::Graph' ] );
1923 0         0 }
1924              
1925 2         9 # [25] GroupOrUnionGraphPattern ::= GroupGraphPattern ( 'UNION' GroupGraphPattern )*
1926             # sub _GroupOrUnionGraphPattern_test {
1927 2         8 # my $self = shift;
  2         7  
1928 2         9 # return $self->_test_token(LBRACE);
1929 2         9 # }
1930              
1931 2 50       10 my $self = shift;
1932 2         7 $self->_GroupGraphPattern;
1933             my $ggp = $self->_remove_pattern;
1934             if ($self->_test_token(KEYWORD, 'UNION')) {
1935             while ($self->_optional_token(KEYWORD, 'UNION')) {
1936             $self->_GroupGraphPattern;
1937             my $rhs = $self->_remove_pattern;
1938             $ggp = Attean::Algebra::Union->new( children => [$ggp, $rhs] );
1939             }
1940             $self->_add_patterns( $ggp );
1941             $self->_add_stack( [ 'Attean::Algebra::Union' ] );
1942 4     4   9 } else {
1943 4         9 $self->_add_patterns( $ggp );
  4         12  
1944 4 50       15 $self->_add_stack( [ 'Attean::Algebra::Join' ] );
1945 0         0 }
1946 0         0 }
1947 0         0  
1948 0         0 # [26] Filter ::= 'FILTER' Constraint
1949             my $self = shift;
1950 0 0       0 $self->_expected_token(KEYWORD, 'FILTER');
1951 0         0 $self->_Constraint;
1952             my ($expr) = splice(@{ $self->{_stack} });
1953 0         0 $self->_add_filter( $expr );
1954 0         0 }
1955              
1956 0         0 # [27] Constraint ::= BrackettedExpression | BuiltInCall | FunctionCall
1957             my $self = shift;
1958             return 1 if ($self->_test_token(LPAREN));
1959             return 1 if $self->_BuiltInCall_test;
1960             return 1 if $self->_IRIref_test;
1961 1     1   2 return 0;
1962 1         3 }
1963 1         9  
1964             my $self = shift;
1965 1         5 if ($self->_test_token(LPAREN)) {
1966 1         4 $self->_BrackettedExpression();
1967 1         3 } elsif ($self->_BuiltInCall_test) {
1968 1         2 $self->_BuiltInCall();
1969             } else {
1970             $self->_FunctionCall();
1971             }
1972 1     1   4 }
1973 1         3  
1974 1         5 # [28] FunctionCall ::= IRIref ArgList
1975             # sub _FunctionCall_test {
1976 1         3 # my $self = shift;
1977 1         3 # return $self->_IRIref_test;
1978 1         3 # }
1979 1         4  
1980             my $self = shift;
1981             $self->_IRIref;
1982             my ($iri) = splice(@{ $self->{_stack} });
1983             if (my $func = Attean->get_global_aggregate($iri)) {
1984 3     3   9
1985 3 50       12 }
1986 0 0       0
1987 0         0 my @args = $self->_ArgList;
1988              
1989             if ($iri->value =~ m<^http://www[.]w3[.]org/2001/XMLSchema#(?:integer|decimal|float|double|boolean|string|dateTime)$>) {
1990             my $expr = Attean::CastExpression->new( children => \@args, datatype => $iri );
1991 3         14 $self->_add_stack( $expr );
1992 3         17 } else {
1993 3         8 my $func = Attean::ValueExpression->new( value => $iri );
  3         9  
1994 3 100       21 my $expr = $self->new_function_expression( 'INVOKE', $func, @args );
1995 2         59 $self->_add_stack( $expr );
1996             }
1997 1         45 }
1998              
1999             # [29] ArgList ::= ( NIL | '(' Expression ( ',' Expression )* ')' )
2000 3 50       13 my $self = shift;
2001 0         0 return 1 if $self->_test_token(NIL);
2002             return $self->_test_token(LPAREN);
2003             }
2004 3         12  
2005 3         56 my $self = shift;
2006 3         54 if ($self->_optional_token(NIL)) {
2007 3         13 return;
2008             } else {
2009             $self->_expected_token(LPAREN);
2010             my @args;
2011             unless ($self->_test_token(RPAREN)) {
2012             $self->_Expression;
2013             push( @args, splice(@{ $self->{_stack} }) );
2014             while ($self->_optional_token(COMMA)) {
2015             $self->_Expression;
2016             push( @args, splice(@{ $self->{_stack} }) );
2017 3     3   7 }
2018 3         11 }
2019 3         9 $self->_expected_token(RPAREN);
2020 3 100       9 return @args;
2021 1         4 }
2022 1         4 }
2023 1         3  
2024 1         10 # [30] ConstructTemplate ::= '{' ConstructTriples? '}'
2025             my $self = shift;
2026 1         4 $self->_push_pattern_container;
2027 1         4 $self->_expected_token(LBRACE);
2028            
2029 2         9 if ($self->_ConstructTriples_test) {
2030 2         8 $self->_ConstructTriples;
2031             }
2032              
2033             $self->_expected_token(RBRACE);
2034             (my $cont, undef) = $self->_pop_pattern_container; # ignore hints in a construct template
2035             $self->{build}{construct_triples} = $cont;
2036 4     4   7 }
2037 4         11  
2038 4         15 # [31] ConstructTriples ::= TriplesSameSubject ( '.' ConstructTriples? )?
2039 4         10 my $self = shift;
  4         12  
2040 4         12 return $self->_TriplesBlock_test;
2041             }
2042              
2043             my $self = shift;
2044             $self->_TriplesSameSubject;
2045 2     2   6 while ($self->_optional_token(DOT)) {
2046 2 50       5 if ($self->_ConstructTriples_test) {
2047 2 50       8 $self->_TriplesSameSubject;
2048 2 50       9 }
2049 2         6 }
2050             }
2051              
2052             # [32] TriplesSameSubject ::= VarOrTerm PropertyListNotEmpty | TriplesNode PropertyList
2053 5     5   7 my $self = shift;
2054 5 100       14 my @triples;
    50          
2055 4         24 if ($self->_TriplesNode_test) {
2056             $self->_TriplesNode;
2057 0         0 my ($s) = splice(@{ $self->{_stack} });
2058             $self->_PropertyList;
2059 1         10 my @list = splice(@{ $self->{_stack} });
2060             foreach my $data (@list) {
2061             push(@triples, $self->__new_statement( $s, @$data ));
2062             }
2063             } else {
2064             $self->_VarOrTermOrQuotedTP;
2065             my ($s) = splice(@{ $self->{_stack} });
2066              
2067             $self->_PropertyListNotEmpty;
2068             my (@list) = splice(@{ $self->{_stack} });
2069             foreach my $data (@list) {
2070 1     1   2 push(@triples, $self->__new_statement( $s, @$data ));
2071 1         4 }
2072 1         2 }
  1         3  
2073 1 50       6
2074             $self->_add_patterns( @triples );
2075             # return @triples;
2076             }
2077 1         3  
2078             # TriplesSameSubjectPath ::= VarOrTerm PropertyListNotEmptyPath | TriplesNode PropertyListPath
2079 1 50       10 my $self = shift;
2080 0         0 my @triples;
2081 0         0 if ($self->_TriplesNode_test) {
2082             $self->_TriplesNode;
2083 1         18 my ($s) = splice(@{ $self->{_stack} });
2084 1         7 $self->_PropertyListPath;
2085 1         19 my @list = splice(@{ $self->{_stack} });
2086             foreach my $data (@list) {
2087             push(@triples, $self->__new_statement( $s, @$data ));
2088             }
2089             } else {
2090             $self->_VarOrTermOrQuotedTP;
2091 1     1   3 my ($s) = splice(@{ $self->{_stack} });
2092 1 50       3 $self->_PropertyListNotEmptyPath;
2093 1         13 my (@list) = splice(@{ $self->{_stack} });
2094             foreach my $data (@list) {
2095             push(@triples, $self->__new_statement( $s, @$data ));
2096             }
2097 2     2   4 }
2098 2 50       6 $self->_add_patterns( @triples );
2099 0         0 # return @triples;
2100             }
2101 2         14  
2102 2         5 # [33] PropertyListNotEmpty ::= Verb ObjectList ( ';' ( Verb ObjectList )? )*
2103 2 50       3 my $self = shift;
2104 2         8 $self->_Verb;
2105 2         5 my ($v) = splice(@{ $self->{_stack} });
  2         3  
2106 2         6 $self->_ObjectList;
2107 0         0 my @l = splice(@{ $self->{_stack} });
2108 0         0 my @props = map { [$v, $_] } @l;
  0         0  
2109             while ($self->_optional_token(SEMICOLON)) {
2110             if ($self->_Verb_test) {
2111 2         7 $self->_Verb;
2112 2         9 my ($v) = splice(@{ $self->{_stack} });
2113             $self->_ObjectList;
2114             my @l = splice(@{ $self->{_stack} });
2115             push(@props, map { [$v, $_] } @l);
2116             }
2117             }
2118 3     3   7 $self->_add_stack( @props );
2119 3         11 }
2120 3         13  
2121             # [34] PropertyList ::= PropertyListNotEmpty?
2122 3 50       16 my $self = shift;
2123 3         15 if ($self->_Verb_test) {
2124             $self->_PropertyListNotEmpty;
2125             }
2126 3         12 }
2127 3         9  
2128 3         11 # [33] PropertyListNotEmptyPath ::= (VerbPath | VerbSimple) ObjectList ( ';' ( (VerbPath | VerbSimple) ObjectList )? )*
2129             my $self = shift;
2130             if ($self->_VerbPath_test) {
2131             $self->_VerbPath;
2132             } else {
2133 3     3   9 $self->_VerbSimple;
2134 3         11 }
2135             my ($v) = splice(@{ $self->{_stack} });
2136             $self->_ObjectList;
2137             my @l = splice(@{ $self->{_stack} });
2138 3     3   8 my @props = map { [$v, $_] } @l;
2139 3         14 while ($self->_optional_token(SEMICOLON)) {
2140 3         12 if ($self->_VerbPath_test or $self->_test_token(VAR)) {
2141 0 0       0 if ($self->_VerbPath_test) {
2142 0         0 $self->_VerbPath;
2143             } else {
2144             $self->_VerbSimple;
2145             }
2146             my ($v) = splice(@{ $self->{_stack} });
2147             $self->_ObjectList;
2148             my @l = splice(@{ $self->{_stack} });
2149 3     3   7 push(@props, map { [$v, $_] } @l);
2150 3         6 }
2151 3 100       11 }
2152 1         4 $self->_add_stack( @props );
2153 1         3 }
  1         6  
2154 1         4  
2155 1         6 # [34] PropertyListPath ::= PropertyListNotEmptyPath?
  1         4  
2156 1         3 my $self = shift;
2157 0         0 if ($self->_Verb_test) {
2158             $self->_PropertyListNotEmptyPath;
2159             }
2160 2         9 }
2161 2         4  
  2         7  
2162             # [35] ObjectList ::= Object ( ',' Object )*
2163 2         11 my $self = shift;
2164 2         7
  2         7  
2165 2         7 my @list;
2166 2         11 $self->_Object;
2167             push(@list, splice(@{ $self->{_stack} }));
2168            
2169             while ($self->_optional_token(COMMA)) {
2170 3         15 $self->_Object;
2171             push(@list, splice(@{ $self->{_stack} }));
2172             }
2173             $self->_add_stack( @list );
2174             }
2175              
2176 51     51   82 # [36] Object ::= GraphNode
2177 51         86 my $self = shift;
2178 51 50       155 $self->_GraphNode;
2179 0         0 if ($self->_optional_token(LANNOT)) {
2180 0         0 ######################## TODO: SPARQL-star annotation syntax
  0         0  
2181 0         0 my ($s) = splice(@{ $self->{_stack} });
2182 0         0 $self->_PropertyListNotEmptyPath;
  0         0  
2183 0         0 my (@list) = splice(@{ $self->{_stack} });
2184 0         0 my $obj = AtteanX::Parser::SPARQL::ObjectWrapper->new( value => $s, annotations => \@list);
2185             $self->_add_stack($obj);
2186             ########################
2187 51         175 $self->_expected_token(RANNOT)
2188 51         117 }
  51         125  
2189 51         181 }
2190 51         86  
  51         104  
2191 51         116 # [37] Verb ::= VarOrIRIref | 'a'
2192 59         186 my $self = shift;
2193             return 1 if ($self->_test_token(A));
2194             return 1 if ($self->_test_token(VAR));
2195 51         186 return 1 if ($self->_IRIref_test);
2196             return 0;
2197             }
2198              
2199             my $self = shift;
2200             if ($self->_optional_token(A)) {
2201 2     2   4 my $type = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', lazy => 1);
2202 2         9 $self->_add_stack( $type );
2203 2         6 } else {
  2         7  
2204 2         9 $self->_VarOrIRIref;
2205 2         5 }
  2         8  
2206 2         7 }
  2         8  
2207 2         9  
2208 0 0       0 # VerbSimple ::= Var
2209 0         0 # sub _VerbSimple_test {
2210 0         0 # my $self = shift;
  0         0  
2211 0         0 # return ($self->_test_token(VAR));
2212 0         0 # }
  0         0  
2213 0         0  
  0         0  
2214             my $self = shift;
2215             $self->_Var;
2216 2         9 }
2217              
2218             # VerbPath ::= Path
2219             my $self = shift;
2220             return 1 if ($self->_IRIref_test);
2221 1     1   2 return 1 if ($self->_test_token(HAT));
2222 1 50       3 return 1 if ($self->_test_token(OR));
2223 0         0 return 1 if ($self->_test_token(BANG));
2224             return 1 if ($self->_test_token(LPAREN));
2225             return 1 if ($self->_test_token(A));
2226             return 0;
2227             }
2228              
2229 53     53   90 my $self = shift;
2230 53 100       161 $self->_Path
2231 25         72 }
2232              
2233 28         87 # [74] Path ::= PathAlternative
2234             my $self = shift;
2235 53         115 $self->_PathAlternative;
  53         130  
2236 53         208 }
2237 53         96  
  53         144  
2238 53         153 ################################################################################
  53         183  
2239 53         135  
2240 9 50 33     39 # [75] PathAlternative ::= PathSequence ( '|' PathSequence )*
2241 9 50       31 my $self = shift;
2242 9         34 $self->_PathSequence;
2243             while ($self->_optional_token(OR)) {
2244 0         0 my ($lhs) = splice(@{ $self->{_stack} });
2245             # $self->_PathOneInPropertyClass;
2246 9         18 $self->_PathSequence;
  9         24  
2247 9         29 my ($rhs) = splice(@{ $self->{_stack} });
2248 9         24 $self->_add_stack( ['PATH', '|', $lhs, $rhs] );
  9         31  
2249 9         26 }
  9         52  
2250             }
2251              
2252 53         145 # [76] PathSequence ::= PathEltOrInverse ( '/' PathEltOrInverse | '^' PathElt )*
2253             my $self = shift;
2254             $self->_PathEltOrInverse;
2255             while ($self->_test_token(SLASH) or $self->_test_token(HAT)) {
2256             my $op;
2257 0     0   0 my ($lhs) = splice(@{ $self->{_stack} });
2258 0 0       0 if ($self->_optional_token(SLASH)) {
2259 0         0 $op = '/';
2260             $self->_PathEltOrInverse;
2261             } else {
2262             $op = '^';
2263             $self->_expected_token(HAT);
2264             $self->_PathElt;
2265 64     64   107 }
2266             my ($rhs) = splice(@{ $self->{_stack} });
2267 64         108 $self->_add_stack( ['PATH', $op, $lhs, $rhs] );
2268 64         184 }
2269 64         117 }
  64         169  
2270              
2271 64         178 # [77] PathElt ::= PathPrimary PathMod?
2272 0         0 my $self = shift;
2273 0         0 $self->_PathPrimary;
  0         0  
2274             # $self->__consume_ws_opt;
2275 64         186 if ($self->_PathMod_test) {
2276             my @path = splice(@{ $self->{_stack} });
2277             $self->_PathMod;
2278             my ($mod) = splice(@{ $self->{_stack} });
2279             if (defined($mod)) {
2280 64     64   99 $self->_add_stack( ['PATH', $mod, @path] );
2281 64         188 } else {
2282 64 100       232 # this might happen if we descend into _PathMod by mistaking a + as
2283             # a path modifier, but _PathMod figures out it's actually part of a
2284 2         4 # signed numeric object that follows the path
  2         5  
2285 2         10 $self->_add_stack( @path );
2286 2         4 }
  2         5  
2287 2         30 }
2288 2         1515 }
2289              
2290 2         7 # [78] PathEltOrInverse ::= PathElt | '^' PathElt
2291             my $self = shift;
2292             if ($self->_optional_token(HAT)) {
2293             $self->_PathElt;
2294             my @props = splice(@{ $self->{_stack} });
2295             $self->_add_stack( [ 'PATH', '^', @props ] );
2296 16     16   27 } else {
2297 16 100       35 $self->_PathElt;
2298 14 100       39 }
2299 13 100       37 }
2300 3         10  
2301             # [79] PathMod ::= ( '*' | '?' | '+' | '{' ( Integer ( ',' ( '}' | Integer '}' ) | '}' ) ) )
2302             my $self = shift;
2303             return 1 if ($self->_test_token(STAR));
2304 19     19   31 return 1 if ($self->_test_token(QUESTION));
2305 19 100       49 return 1 if ($self->_test_token(PLUS));
2306 6         110 return 1 if ($self->_test_token(LBRACE));
2307 6         469 return 0;
2308             }
2309 13         51  
2310             my $self = shift;
2311             if ($self->_test_token(STAR) or $self->_test_token(QUESTION) or $self->_test_token(PLUS)) {
2312             my $t = $self->_next_token;
2313             my $op;
2314             if ($t->type == STAR) {
2315             $op = '*';
2316             } elsif ($t->type == QUESTION) {
2317             $op = '?';
2318             } else {
2319             $op = '+';
2320 28     28   59 }
2321 28         69 $self->_add_stack($op);
2322             ### path repetition range syntax :path{n,m}; removed from 1.1 Query 2LC
2323             # } else {
2324             # $self->_eat(qr/{/);
2325             # $self->__consume_ws_opt;
2326 71     71   109 # my $value = 0;
2327 71 100       177 # if ($self->_test(qr/}/)) {
2328 35 50       101 # throw ::Error::ParseError -text => "Syntax error: Empty Path Modifier";
2329 35 50       99 # }
2330 35 50       96 # if ($self->_test($r_INTEGER)) {
2331 35 50       98 # $value = $self->_eat( $r_INTEGER );
2332 35 100       116 # $self->__consume_ws_opt;
2333 28         98 # }
2334             # if ($self->_test(qr/,/)) {
2335             # $self->_eat(qr/,/);
2336             # $self->__consume_ws_opt;
2337 34     34   56 # if ($self->_test(qr/}/)) {
2338 34         72 # $self->_eat(qr/}/);
2339             # $self->_add_stack( "$value-" );
2340             # } else {
2341             # my $end = $self->_eat( $r_INTEGER );
2342             # $self->__consume_ws_opt;
2343 34     34   54 # $self->_eat(qr/}/);
2344 34         72 # $self->_add_stack( "$value-$end" );
2345             # }
2346             # } else {
2347             # $self->_eat(qr/}/);
2348             # $self->_add_stack( "$value" );
2349             # }
2350             }
2351 34     34   54 }
2352 34         117  
2353 34         94 # [80] PathPrimary ::= ( IRIref | 'a' | '!' PathNegatedPropertyClass | '(' Path ')' )
2354 0         0 my $self = shift;
  0         0  
2355             if ($self->_IRIref_test) {
2356 0         0 $self->_IRIref;
2357 0         0 } elsif ($self->_optional_token(A)) {
  0         0  
2358 0         0 my $type = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', lazy => 1);
2359             $self->_add_stack( $type );
2360             } elsif ($self->_optional_token(BANG)) {
2361             $self->_PathNegatedPropertyClass;
2362             my (@path) = splice(@{ $self->{_stack} });
2363             $self->_add_stack( ['PATH', '!', @path] );
2364 34     34   60 } else {
2365 34         96 $self->_expected_token(LPAREN);
2366 34   66     74 $self->_Path;
2367 1         2 $self->_expected_token(RPAREN);
2368 1         2 }
  1         2  
2369 1 50       3 }
2370 1         2  
2371 1         3 # [81] PathNegatedPropertyClass ::= ( PathOneInPropertyClass | '(' ( PathOneInPropertyClass ( '|' PathOneInPropertyClass )* )? ')' )
2372             my $self = shift;
2373 0         0 if ($self->_optional_token(LPAREN)) {
2374 0         0
2375 0         0 my @nodes;
2376             if ($self->_PathOneInPropertyClass_test) {
2377 1         3 $self->_PathOneInPropertyClass;
  1         4  
2378 1         3 push(@nodes, splice(@{ $self->{_stack} }));
2379             while ($self->_optional_token(OR)) {
2380             $self->_PathOneInPropertyClass;
2381             push(@nodes, splice(@{ $self->{_stack} }));
2382             # $self->_add_stack( ['PATH', '|', $lhs, $rhs] );
2383             }
2384 35     35   59 }
2385 35         102 $self->_expected_token(RPAREN);
2386             $self->_add_stack( @nodes );
2387 35 100       133 } else {
2388 1         2 $self->_PathOneInPropertyClass;
  1         3  
2389 1         4 }
2390 1         2 }
  1         3  
2391 1 50       2  
2392 1         4 # [82] PathOneInPropertyClass ::= IRIref | 'a'
2393             my $self = shift;
2394             return 1 if $self->_IRIref_test;
2395             return 1 if ($self->_test_token(A));
2396             return 1 if ($self->_test_token(HAT));
2397 0         0 return 0;
2398             }
2399              
2400             my $self = shift;
2401             my $rev = 0;
2402             if ($self->_optional_token(HAT)) {
2403             $rev = 1;
2404 35     35   53 }
2405 35 50       80 if ($self->_optional_token(A)) {
2406 0         0 my $type = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', lazy => 1);
2407 0         0 if ($rev) {
  0         0  
2408 0         0 $self->_add_stack( [ 'PATH', '^', $type ] );
2409             } else {
2410 35         105 $self->_add_stack( $type );
2411             }
2412             } else {
2413             $self->_IRIref;
2414             if ($rev) {
2415             my ($path) = splice(@{ $self->{_stack} });
2416 35     35   58 $self->_add_stack( [ 'PATH', '^', $path ] );
2417 35 100       78 }
2418 34 50       104 }
2419 34 50       79 }
2420 34 50       69  
2421 34         107 ################################################################################
2422              
2423             # [38] TriplesNode ::= Collection | BlankNodePropertyList
2424             my $self = shift;
2425 1     1   2 return 1 if $self->_test_token(LPAREN);
2426 1 50 33     3 return 1 if $self->_test_token(LBRACKET);
      33        
2427 1         3 return 0;
2428 1         3 }
2429 1 50       4  
    0          
2430 1         2 my $self = shift;
2431             if ($self->_test_token(LPAREN)) {
2432 0         0 $self->_Collection;
2433             } else {
2434 0         0 $self->_BlankNodePropertyList;
2435             }
2436 1         3 }
2437              
2438             # [39] BlankNodePropertyList ::= '[' PropertyListNotEmpty ']'
2439             my $self = shift;
2440             if (my $where = $self->{__no_bnodes}) {
2441             croak "Syntax error: Blank nodes not allowed in $where";
2442             }
2443             $self->_expected_token(LBRACKET);
2444             # $self->_PropertyListNotEmpty;
2445             $self->_PropertyListNotEmptyPath;
2446             $self->_expected_token(RBRACKET);
2447            
2448             my @props = splice(@{ $self->{_stack} });
2449             my $subj = Attean::Blank->new();
2450             my @triples = map { $self->__new_statement( $subj, @$_ ) } @props;
2451             $self->_add_patterns( @triples );
2452             $self->_add_stack( $subj );
2453             }
2454              
2455             # [40] Collection ::= '(' GraphNode+ ')'
2456             my $self = shift;
2457             $self->_expected_token(LPAREN);
2458             $self->_GraphNode;
2459             my @nodes;
2460             push(@nodes, splice(@{ $self->{_stack} }));
2461            
2462             while ($self->_GraphNode_test) {
2463             $self->_GraphNode;
2464             push(@nodes, splice(@{ $self->{_stack} }));
2465             }
2466            
2467             $self->_expected_token(RPAREN);
2468            
2469             my $subj = Attean::Blank->new();
2470 35     35   368 my $cur = $subj;
2471 35 100       75 my $last;
    50          
    0          
2472 29         86  
2473             my $first = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', lazy => 1);
2474 6         106 my $rest = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', lazy => 1);
2475 6         454 my $nil = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', lazy => 1);
2476              
2477 0         0
2478 0         0 my @triples;
  0         0  
2479 0         0 foreach my $node (@nodes) {
2480             push(@triples, $self->__new_statement( $cur, $first, $node ) );
2481 0         0 my $new = Attean::Blank->new();
2482 0         0 push(@triples, $self->__new_statement( $cur, $rest, $new ) );
2483 0         0 $last = $cur;
2484             $cur = $new;
2485             }
2486             pop(@triples);
2487             push(@triples, $self->__new_statement( $last, $rest, $nil ));
2488             $self->_add_patterns( @triples );
2489 0     0   0
2490 0 0       0 $self->_add_stack( $subj );
2491             }
2492 0         0  
2493 0 0       0 # [41] GraphNode ::= VarOrTerm | TriplesNode
2494 0         0 my $self = shift;
2495 0         0 # VarOrTerm | TriplesNode -> (Var | GraphTerm) | (Collection | BlankNodePropertyList) -> Var | IRIref | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL | Collection | BlankNodePropertyList
  0         0  
2496 0         0 # but since a triple can't start with a literal, this is reduced to:
2497 0         0 # Var | IRIref | BlankNode | NIL
2498 0         0 return 1 if ($self->_test_token(VAR));
  0         0  
2499             return 1 if ($self->_IRIref_test);
2500             return 1 if ($self->_test_token(BNODE));
2501             return 1 if ($self->_test_token(LBRACKET));
2502 0         0 return 1 if ($self->_test_token(LPAREN));
2503 0         0 return 1 if ($self->_test_token(ANON));
2504             return 1 if ($self->_test_token(NIL));
2505 0         0 return 1 if ($self->_test_token(LTLT));
2506             return 0;
2507             }
2508              
2509             my $self = shift;
2510             if ($self->_TriplesNode_test) {
2511 0     0   0 $self->_TriplesNode;
2512 0 0       0 } else {
2513 0 0       0 $self->_VarOrTermOrQuotedTP;
2514 0 0       0 }
2515 0         0 }
2516              
2517             # [42] VarOrTerm ::= Var | GraphTerm
2518             # sub _VarOrTerm_test {
2519 0     0   0 # my $self = shift;
2520 0         0 # return 1 if ($self->_peek_token(VAR));
2521 0 0       0 # return 1 if ($self->_IRIref_test);
2522 0         0 # return 1 if ($self->_peek_token(BOOLEAN));
2523             # return 1 if ($self->_test_literal_token);
2524 0 0       0 # return 1 if ($self->_peek_token(BNODE));
2525 0         0 # return 1 if ($self->_peek_token(NIL));
2526 0 0       0 # return 0;
2527 0         0 # }
2528              
2529 0         0 my $self = shift;
2530             if ($self->_test_token(VAR)) {
2531             $self->_Var();
2532 0         0 } elsif ($self->_test_token(LTLT)) {
2533 0 0       0 $self->_QuotedTP();
2534 0         0 } else {
  0         0  
2535 0         0 $self->_GraphTerm;
2536             }
2537             }
2538              
2539             my $self = shift;
2540             if ($self->_test_token(VAR)) {
2541             $self->_Var;
2542             } else {
2543             $self->_GraphTerm;
2544 122     122   170 }
2545 122 100       231 }
2546 121 50       274  
2547 121         326 # [43] VarOrIRIref ::= Var | IRIref
2548             my $self = shift;
2549             return 1 if ($self->_IRIref_test);
2550             return 1 if ($self->_test_token(VAR));
2551 1     1   3 return 0;
2552 1 50       2 }
2553 1         6  
2554             my $self = shift;
2555 0         0 if ($self->_test_token(VAR)) {
2556             $self->_Var;
2557             } else {
2558             $self->_IRIref;
2559             }
2560             }
2561 0     0   0  
2562 0 0       0 # [44] Var ::= VAR1 | VAR2
2563 0         0 my $self = shift;
2564             if ($self->{__data_pattern}) {
2565 0         0 croak "Syntax error: Variable found where Term expected";
2566             }
2567 0         0  
2568 0         0 my $var = $self->_expected_token(VAR);
2569             $self->_add_stack( Attean::Variable->new( $var->value ) );
2570 0         0 }
  0         0  
2571 0         0  
2572 0         0 # [45] GraphTerm ::= IRIref | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL
  0         0  
2573 0         0 my $self = shift;
2574 0         0 if ($self->_test_token(BOOLEAN)) {
2575             my $b = $self->_BooleanLiteral;
2576             $self->_add_stack( $b );
2577             } elsif ($self->_test_token(NIL)) {
2578             my $n = $self->_NIL;
2579 1     1   2 $self->_add_stack( $n );
2580 1         4 } elsif ($self->_test_token(ANON) or $self->_test_token(BNODE)) {
2581 1         5 my $b = $self->_BlankNode;
2582 1         4 $self->_add_stack( $b );
2583 1         3 } elsif ($self->_test_token(INTEGER) or $self->_test_token(DECIMAL) or $self->_test_token(DOUBLE) or $self->_test_token(MINUS) or $self->_test_token(PLUS)) {
  1         3  
2584             my $l = $self->_NumericLiteral;
2585 1         3 $self->_add_stack( $l );
2586 1         4 } elsif ($self->_test_literal_token) {
2587 1         3 my $l = $self->_RDFLiteral;
  1         4  
2588             $self->_add_stack( $l );
2589             } else {
2590 1         4 $self->_IRIref;
2591             }
2592 1         13 }
2593 1         66  
2594 1         2 # [46] Expression ::= ConditionalOrExpression
2595             my $self = shift;
2596 1         19 $self->_ConditionalOrExpression;
2597 1         79 }
2598 1         71  
2599             # [47] ConditionalOrExpression ::= ConditionalAndExpression ( '||' ConditionalAndExpression )*
2600             my $self = shift;
2601 1         55 my @list;
2602 1         5
2603 2         8 $self->_ConditionalAndExpression;
2604 2         29 push(@list, splice(@{ $self->{_stack} }));
2605 2         99
2606 2         5 while ($self->_test_token(OROR)) {
2607 2         10 $self->_expected_token(OROR);
2608             $self->_ConditionalAndExpression;
2609 1         4 push(@list, splice(@{ $self->{_stack} }));
2610 1         4 }
2611 1         7
2612             if (scalar(@list) > 1) {
2613 1         4 my $algebra = Attean::BinaryExpression->new( operator => '||', children => [splice(@list, 0, 2)] );
2614             while (scalar(@list)) {
2615             $algebra = Attean::BinaryExpression->new( operator => '||', children => [$algebra, shift(@list)] );
2616             }
2617             $self->_add_stack($algebra);
2618 2     2   4 } else {
2619             $self->_add_stack( @list );
2620             }
2621             if (scalar(@{ $self->{_stack} }) == 0) {
2622 2 100       4 my $t = $self->_peek_token;
2623 1 50       5 $self->_token_error($t, "Missing conditional expression");
2624 1 50       5 }
2625 1 50       4 }
2626 1 50       7  
2627 1 50       3 # [48] ConditionalAndExpression ::= ValueLogical ( '&&' ValueLogical )*
2628 1 50       4 my $self = shift;
2629 1 50       8 $self->_ValueLogical;
2630 1         3 my @list = splice(@{ $self->{_stack} });
2631            
2632             while ($self->_test_token(ANDAND)) {
2633             $self->_expected_token(ANDAND);
2634 68     68   121 $self->_ValueLogical;
2635 68 50       178 push(@list, splice(@{ $self->{_stack} }));
2636 0         0 }
2637            
2638 68         167 if (scalar(@list) > 1) {
2639             my $algebra = Attean::BinaryExpression->new( operator => '&&', children => [splice(@list, 0, 2)] );
2640             while (scalar(@list)) {
2641             $algebra = Attean::BinaryExpression->new( operator => '&&', children => [$algebra, shift(@list)] );
2642             }
2643             $self->_add_stack($algebra);
2644             } else {
2645             $self->_add_stack( @list );
2646             }
2647             }
2648              
2649             # [49] ValueLogical ::= RelationalExpression
2650             my $self = shift;
2651             $self->_RelationalExpression;
2652             }
2653              
2654             # [50] RelationalExpression ::= NumericExpression ( '=' NumericExpression | '!=' NumericExpression | '<' NumericExpression | '>' NumericExpression | '<=' NumericExpression | '>=' NumericExpression )?
2655 121     121   176 my $self = shift;
2656 121 100       200 $self->_NumericExpression;
    100          
2657 81         239
2658             my $t = $self->_peek_token;
2659 3         13 my $type = $t->type;
2660             if ($type == EQUALS or $type == NOTEQUALS or $type == LE or $type == GE or $type == LT or $type == GT) {
2661 37         116 $self->_next_token;
2662             my @list = splice(@{ $self->{_stack} });
2663             my $op = $t->value;
2664             $self->_NumericExpression;
2665             push(@list, splice(@{ $self->{_stack} }));
2666 6     6   11 $self->_add_stack( $self->new_binary_expression( $op, @list ) );
2667 6 100       13 } elsif ($self->_test_token(KEYWORD, qr/^(NOT|IN)/)) {
2668 3         13 my @list = splice(@{ $self->{_stack} });
2669             my $not = $self->_optional_token(KEYWORD, 'NOT');
2670 3         11 $self->_expected_token(KEYWORD, 'IN');
2671             my $op = $not ? 'NOTIN' : 'IN';
2672             $self->_ExpressionList();
2673             push(@list, splice(@{ $self->{_stack} }));
2674             my $p = $self->new_function_expression( $op, @list );
2675             $self->_add_stack($p);
2676 1     1   4 }
2677 1 50       6 }
2678 1 50       4  
2679 1         8 my $self = shift;
2680             if ($self->_optional_token(NIL)) {
2681             return;
2682             } else {
2683 17     17   31 $self->_expected_token(LPAREN);
2684 17 100       40 my @args;
2685 5         21 unless ($self->_test_token(RPAREN)) {
2686             $self->_Expression;
2687 12         42 push( @args, splice(@{ $self->{_stack} }) );
2688             while ($self->_optional_token(COMMA)) {
2689             $self->_Expression;
2690             push( @args, splice(@{ $self->{_stack} }) );
2691             }
2692             }
2693 141     141   199 $self->_expected_token(RPAREN);
2694 141 50       320 $self->_add_stack( @args );
2695 0         0 }
2696             }
2697              
2698 141         316 # [51] NumericExpression ::= AdditiveExpression
2699 141         439 my $self = shift;
2700             $self->_AdditiveExpression;
2701             }
2702              
2703             # [52] AdditiveExpression ::= MultiplicativeExpression ( '+' MultiplicativeExpression | '-' MultiplicativeExpression | NumericLiteralPositive | NumericLiteralNegative )*
2704 46     46   78 my $self = shift;
2705 46 50 33     86 $self->_MultiplicativeExpression;
    100 66        
    50 66        
    100 33        
    100 33        
2706 0         0 my ($expr) = splice(@{ $self->{_stack} });
2707 0         0
2708             while ($self->_test_token(MINUS) or $self->_test_token(PLUS)) {
2709 1         7 my $t = $self->_next_token;
2710 1         70 my $op = ($t->type == MINUS) ? '-' : '+';
2711             $self->_MultiplicativeExpression;
2712 0         0 my ($rhs) = splice(@{ $self->{_stack} });
2713 0         0 $expr = $self->new_binary_expression( $op, $expr, $rhs );
2714             }
2715 11         27 $self->_add_stack( $expr );
2716 11         32 }
2717              
2718 9         40 # [53] MultiplicativeExpression ::= UnaryExpression ( '*' UnaryExpression | '/' UnaryExpression )*
2719 9         30 my $self = shift;
2720             $self->_UnaryExpression;
2721 25         79 my ($expr) = splice(@{ $self->{_stack} });
2722            
2723             while ($self->_test_token(STAR) or $self->_test_token(SLASH)) {
2724             my $t = $self->_next_token;
2725             my $op = ($t->type == STAR) ? '*' : '/';
2726             $self->_UnaryExpression;
2727 11     11   22 my ($rhs) = splice(@{ $self->{_stack} });
2728 11         31 $expr = $self->new_binary_expression( $op, $expr, $rhs );
2729             }
2730             $self->_add_stack( $expr );
2731             }
2732              
2733 11     11   16 # [54] UnaryExpression ::= '!' PrimaryExpression | '+' PrimaryExpression | '-' PrimaryExpression | PrimaryExpression
2734 11         16 my $self = shift;
2735             if ($self->_optional_token(BANG)) {
2736 11         31 $self->_PrimaryExpression;
2737 11         23 my ($expr) = splice(@{ $self->{_stack} });
  11         24  
2738             my $not = Attean::UnaryExpression->new( operator => '!', children => [$expr] );
2739 11         27 $self->_add_stack( $not );
2740 0         0 } elsif ($self->_optional_token(PLUS)) {
2741 0         0 $self->_PrimaryExpression;
2742 0         0 my ($expr) = splice(@{ $self->{_stack} });
  0         0  
2743            
2744             ### if it's just a literal, force the positive down into the literal
2745 11 50       39 if (blessed($expr) and $expr->isa('Attean::ValueExpression') and $expr->value->does('Attean::API::NumericLiteral')) {
2746 0         0 my $value = '+' . $expr->value->value;
2747 0         0 my $l = Attean::Literal->new( value => $value, datatype => $expr->value->datatype );
2748 0         0 my $lexpr = Attean::ValueExpression->new( value => $l );
2749             $self->_add_stack( $lexpr );
2750 0         0 } else {
2751             my $lexpr = Attean::ValueExpression->new( value => $expr );
2752 11         24 $self->_add_stack( $lexpr );
2753             }
2754 11 50       17 } elsif ($self->_optional_token(MINUS)) {
  11         54  
2755 0         0 $self->_PrimaryExpression;
2756 0         0 my ($expr) = splice(@{ $self->{_stack} });
2757            
2758             ### if it's just a literal, force the negative down into the literal instead of make an unnecessary multiplication.
2759             if (blessed($expr) and $expr->isa('Attean::ValueExpression') and $expr->value->does('Attean::API::NumericLiteral')) {
2760             my $value = -1 * $expr->value->value;
2761             my $l = Attean::Literal->new( value => $value, datatype => $expr->value->datatype );
2762 11     11   18 my $lexpr = Attean::ValueExpression->new( value => $l );
2763 11         30 $self->_add_stack( $lexpr );
2764 11         24 } else {
  11         29  
2765             my $int = 'http://www.w3.org/2001/XMLSchema#integer';
2766 11         30 my $l = Attean::Literal->new( value => '-1', datatype => $int );
2767 1         4 my $neg = $self->new_binary_expression( '*', Attean::ValueExpression->new( value => $l ), $expr );
2768 1         4 my $lexpr = Attean::ValueExpression->new( value => $neg );
2769 1         2 $self->_add_stack( $lexpr );
  1         5  
2770             }
2771             } else {
2772 11 100       32 $self->_PrimaryExpression;
2773 1         17 }
2774 1         23 }
2775 0         0  
2776             # [55] PrimaryExpression ::= BrackettedExpression | BuiltInCall | IRIrefOrFunction | RDFLiteral | NumericLiteral | BooleanLiteral | Var
2777 1         4 my $self = shift;
2778             my $t = $self->_peek_token;
2779 10         26 if ($self->_test_token(LPAREN)) {
2780             $self->_BrackettedExpression;
2781             } elsif ($self->_BuiltInCall_test) {
2782             $self->_BuiltInCall;
2783             } elsif ($self->_IRIref_test) {
2784             $self->_IRIrefOrFunction;
2785 12     12   17 my $v = pop(@{ $self->{_stack} });
2786 12         32 if ($v->does('Attean::API::IRI')) {
2787             $v = Attean::ValueExpression->new(value => $v);
2788             }
2789             $self->_add_stack($v);
2790             } elsif ($self->_test_token(VAR)) {
2791 12     12   17 $self->_Var;
2792 12         35 my $var = pop(@{ $self->{_stack} });
2793             my $expr = Attean::ValueExpression->new(value => $var);
2794 12         25 $self->_add_stack($expr);
2795 12         45 } elsif ($self->_test_token(BOOLEAN)) {
2796 12 100 33     156 my $b = $self->_BooleanLiteral;
    50 33        
      33        
      66        
      100        
2797 3         10 my $expr = Attean::ValueExpression->new(value => $b);
2798 3         9 $self->_add_stack($expr);
  3         14  
2799 3         9 } elsif ($self->_test_token(INTEGER) or $self->_test_token(DECIMAL) or $self->_test_token(DOUBLE) or $self->_test_token(PLUS) or $self->_test_token(MINUS)) {
2800 3         7 my $l = $self->_NumericLiteral;
2801 3         7 my $expr = Attean::ValueExpression->new(value => $l);
  3         8  
2802 3         10 $self->_add_stack($expr);
2803             } elsif ($self->_test_token(LTLT)) {
2804 0         0 $self->_ExprQuotedTP();
  0         0  
2805 0         0 my $tp = pop(@{ $self->{_stack} });
2806 0         0 my $expr = Attean::ValueExpression->new(value => $tp);
2807 0 0       0 $self->_add_stack($expr);
2808 0         0 } else {
2809 0         0 my $value = $self->_RDFLiteral;
  0         0  
2810 0         0 my $expr = Attean::ValueExpression->new(value => $value);
2811 0         0 $self->_add_stack($expr);
2812             }
2813             }
2814              
2815             my $self = shift;
2816 0     0   0 # '<<' ExprVarOrTerm Verb ExprVarOrTerm '>>'
2817 0 0       0 $self->_expected_token(LTLT);
2818 0         0 $self->_ExprVarOrTerm();
2819             $self->_Verb();
2820 0         0 $self->_ExprVarOrTerm();
2821 0         0 $self->_expected_token(GTGT);
2822 0 0       0
2823 0         0 my ($s, $p, $o) = splice(@{ $self->{_stack} }, -3);
2824 0         0
  0         0  
2825 0         0 $self->_add_stack( $self->__new_statement( $s, $p, $o ) );
2826 0         0 }
2827 0         0  
  0         0  
2828             my $self = shift;
2829             if ($self->_test_token(VAR)) {
2830 0         0 $self->_Var();
2831 0         0 } elsif ($self->_test_token(LTLT)) {
2832             $self->_ExprQuotedTP();
2833             } else {
2834             # TODO: this should prevent use of bnodes
2835             $self->_GraphTerm;
2836             my $term = ${ $self->{_stack} }[-1];
2837 15     15   21 if ($term->does('Attean::API::Blank')) {
2838 15         35 croak "Expecting (non-blank) RDF term but found blank";
2839             }
2840             }
2841             }
2842              
2843 15     15   23 # [56] BrackettedExpression ::= '(' Expression ')'
2844 15         41 # sub _BrackettedExpression_test {
2845 15         31 # my $self = shift;
  15         39  
2846             # return $self->_test_token(LPAREN);
2847 15   66     42 # }
2848 1         4  
2849 1 50       6 my $self = shift;
2850 1         4 $self->_expected_token(LPAREN);
2851 1         3 $self->_Expression;
  1         3  
2852 1         4 $self->_expected_token(RPAREN);
2853             }
2854 15         35  
2855             my $self = shift;
2856            
2857             my $op;
2858             my $custom_agg_iri;
2859 16     16   20 if (scalar(@_)) {
2860 16         52 $custom_agg_iri = shift->value;
2861 16         40 $op = 'CUSTOM';
  16         38  
2862             } else {
2863 16   33     40 my $t = $self->_expected_token(KEYWORD);
2864 0         0 $op = $t->value;
2865 0 0       0 }
2866 0         0 $self->_expected_token(LPAREN);
2867 0         0 my $distinct = 0;
  0         0  
2868 0         0 if ($self->_optional_token(KEYWORD, 'DISTINCT')) {
2869             $distinct = 1;
2870 16         55 }
2871            
2872             my $star = 0;
2873             my (@expr, %options);
2874             if ($self->_optional_token(STAR)) {
2875 16     16   24 $star = 1;
2876 16 100       32 } else {
    100          
    100          
2877 1         5 $self->_Expression;
2878 1         4 push(@expr, splice(@{ $self->{_stack} }));
  1         5  
2879 1         9 if ($op eq 'GROUP_CONCAT') {
2880 1         17 while ($self->_optional_token(COMMA)) {
2881             $self->_Expression;
2882 1         6 push(@expr, splice(@{ $self->{_stack} }));
2883 1         4 }
  1         3  
2884             if ($self->_optional_token(SEMICOLON)) {
2885             $self->_expected_token(KEYWORD, 'SEPARATOR');
2886 1 50 33     13 $self->_expected_token(EQUALS);
      33        
2887 1         19 my $sep = $self->_String;
2888 1         20 $options{ seperator } = $sep;
2889 1         106 }
2890 1         8 }
2891             }
2892 0         0 my $arg = join(',', map { blessed($_) ? $_->as_string : $_ } @expr);
2893 0         0 if ($distinct) {
2894             $arg = 'DISTINCT ' . $arg;
2895             }
2896 1         8 my $name = sprintf('%s(%s)', $op, $arg);
2897 1         3 $self->_expected_token(RPAREN);
  1         5  
2898            
2899             my $var = Attean::Variable->new( value => ".$name");
2900 1 50 33     18 my $agg = Attean::AggregateExpression->new(
      33        
2901 1         26 distinct => $distinct,
2902 1         24 operator => $op,
2903 1         108 children => [@expr],
2904 1         4 scalar_vars => \%options,
2905             variable => $var,
2906 0         0 custom_iri => $custom_agg_iri
2907 0         0 );
2908 0         0 $self->{build}{__aggregate}{ $name } = [ $var, $agg ];
2909 0         0 my $expr = Attean::ValueExpression->new(value => $var);
2910 0         0 $self->_add_stack($expr);
2911             }
2912              
2913 13         54 # [57] BuiltInCall ::= 'STR' '(' Expression ')' | 'LANG' '(' Expression ')' | 'LANGMATCHES' '(' Expression ',' Expression ')' | 'DATATYPE' '(' Expression ')' | 'BOUND' '(' Var ')' | 'sameTerm' '(' Expression ',' Expression ')' | 'isIRI' '(' Expression ')' | 'isURI' '(' Expression ')' | 'isBLANK' '(' Expression ')' | 'isLITERAL' '(' Expression ')' | RegexExpression
2914             my $self = shift;
2915             my $t = $self->_peek_token;
2916             return unless ($t);
2917             if ($self->{__aggregate_call_ok}) {
2918             return 1 if ($self->_test_token(KEYWORD, qr/^(MIN|MAX|COUNT|AVG|SUM|SAMPLE|GROUP_CONCAT)$/io));
2919 16     16   31 }
2920 16         32 return 1 if ($self->_test_token(KEYWORD, 'NOT'));
2921 16 50 100     38 return 1 if ($self->_test_token(KEYWORD, 'EXISTS'));
    100 100        
    100 66        
    100 66        
    50          
    100          
    50          
2922 0         0 return 1 if ($self->_test_token(KEYWORD, qr/^(ABS|CEIL|FLOOR|ROUND|CONCAT|SUBSTR|STRLEN|UCASE|LCASE|ENCODE_FOR_URI|CONTAINS|STRSTARTS|STRENDS|RAND|MD5|SHA1|SHA224|SHA256|SHA384|SHA512|HOURS|MINUTES|SECONDS|DAY|MONTH|YEAR|TIMEZONE|TZ|NOW)$/i));
2923             return 1 if ($self->_test_token(KEYWORD, qr/^(TRIPLE|ISTRIPLE|SUBJECT|PREDICATE|OBJECT)$/i));
2924 2         15 return ($self->_test_token(KEYWORD, qr/^(COALESCE|UUID|STRUUID|STR|STRDT|STRLANG|STRBEFORE|STRAFTER|REPLACE|BNODE|IRI|URI|LANG|LANGMATCHES|DATATYPE|BOUND|sameTerm|isIRI|isURI|isBLANK|isLITERAL|REGEX|IF|isNumeric)$/i));
2925             }
2926 1         5  
2927 1         3 my $self = shift;
  1         3  
2928 1 50       7 my $t = $self->_peek_token;
2929 0         0 if ($self->{__aggregate_call_ok} and $self->_test_token(KEYWORD, qr/^(MIN|MAX|COUNT|AVG|SUM|SAMPLE|GROUP_CONCAT)\b/io)) {
2930             $self->_Aggregate;
2931 1         23 } elsif ($self->_test_token(KEYWORD, qr/^(NOT|EXISTS)/)) {
2932             my $not = $self->_optional_token(KEYWORD, 'NOT');
2933 8         24 $self->_expected_token(KEYWORD, 'EXISTS');
2934 8         17 local($self->{filters}) = [];
  8         18  
2935 8         163 $self->_GroupGraphPattern;
2936 8         29 my $cont = $self->_remove_pattern;
2937             my $p = Attean::ExistsExpression->new( pattern => $cont );
2938 0         0 if ($not) {
2939 0         0 $p = Attean::UnaryExpression->new( operator => '!', children => [$p] );
2940 0         0 }
2941             $self->_add_stack($p);
2942 4         14 } elsif ($self->_test_token(KEYWORD, qr/^(COALESCE|BNODE|CONCAT|SUBSTR|RAND|NOW)/i)) {
2943 4         77 # n-arg functions that take expressions
2944 4         23 my $t = $self->_next_token;
2945             my $op = $t->value;
2946 1         4 my @args = $self->_ArgList;
2947 1         1 my $func = $self->new_function_expression( $op, @args );
  1         3  
2948 1         10 $self->_add_stack( $func );
2949 1         4 } elsif ($self->_test_token(KEYWORD, 'REGEX')) {
2950             $self->_RegexExpression;
2951 0         0 } else {
2952 0         0 my $t = $self->_next_token;
2953 0         0 my $op = $t->value;
2954             if ($op =~ /^(STR)?UUID$/i) {
2955             # no-arg functions
2956             $self->_expected_token(NIL);
2957             $self->_add_stack( $self->new_function_expression($op) );
2958 1     1   2 } elsif ($op =~ /^(STR|URI|IRI|LANG|DATATYPE|isIRI|isURI|isBLANK|isLITERAL|isNumeric|ABS|CEIL|FLOOR|ROUND|STRLEN|UCASE|LCASE|ENCODE_FOR_URI|MD5|SHA1|SHA224|SHA256|SHA384|SHA512|HOURS|MINUTES|SECONDS|DAY|MONTH|YEAR|TIMEZONE|TZ|ISTRIPLE|SUBJECT|PREDICATE|OBJECT)$/i) {
2959             ### one-arg functions that take an expression
2960 1         6 $self->_expected_token(LPAREN);
2961 1         3 $self->_Expression;
2962 1         5 my ($expr) = splice(@{ $self->{_stack} });
2963 1         6 $self->_add_stack( $self->new_function_expression($op, $expr) );
2964 1         30 $self->_expected_token(RPAREN);
2965             } elsif ($op =~ /^(STRDT|STRLANG|LANGMATCHES|sameTerm|CONTAINS|STRSTARTS|STRENDS|STRBEFORE|STRAFTER)$/i) {
2966 1         2 ### two-arg functions that take expressions
  1         4  
2967             $self->_expected_token(LPAREN);
2968 1         4 $self->_Expression;
2969             my ($arg1) = splice(@{ $self->{_stack} });
2970             $self->_expected_token(COMMA);
2971             $self->_Expression;
2972 2     2   3 my ($arg2) = splice(@{ $self->{_stack} });
2973 2 100       5 $self->_add_stack( $self->new_function_expression($op, $arg1, $arg2) );
    50          
2974 1         4 $self->_expected_token(RPAREN);
2975             } elsif ($op =~ /^(IF|REPLACE|TRIPLE)$/i) {
2976 0         0 ### three-arg functions that take expressions
2977             $self->_expected_token(LPAREN);
2978             $self->_Expression;
2979 1         4 my ($arg1) = splice(@{ $self->{_stack} });
2980 1         2 $self->_expected_token(COMMA);
  1         3  
2981 1 50       4 $self->_Expression;
2982 0         0 my ($arg2) = splice(@{ $self->{_stack} });
2983             $self->_expected_token(COMMA);
2984             $self->_Expression;
2985             my ($arg3) = splice(@{ $self->{_stack} });
2986             $self->_add_stack( $self->new_function_expression($op, $arg1, $arg2, $arg3) );
2987             $self->_expected_token(RPAREN);
2988             } else {
2989             ### BOUND(Var)
2990             $self->_expected_token(LPAREN);
2991             $self->_Var;
2992             my $var = pop(@{ $self->{_stack} });
2993             my $expr = Attean::ValueExpression->new(value => $var);
2994 5     5   10 $self->_add_stack( $self->new_function_expression($op, $expr) );
2995 5         13 $self->_expected_token(RPAREN);
2996 5         30 }
2997 5         14 }
2998             }
2999              
3000             # [58] RegexExpression ::= 'REGEX' '(' Expression ',' Expression ( ',' Expression )? ')'
3001 1     1   2 # sub _RegexExpression_test {
3002             # my $self = shift;
3003 1         2 # return $self->_test_token(KEYWORD, 'REGEX');
3004             # }
3005 1 50       15  
3006 0         0 my $self = shift;
3007 0         0 $self->_expected_token(KEYWORD, 'REGEX');
3008             $self->_expected_token(LPAREN);
3009 1         3 $self->_Expression;
3010 1         3 my $string = splice(@{ $self->{_stack} });
3011            
3012 1         6 $self->_expected_token(COMMA);
3013 1         1 $self->_Expression;
3014 1 50       3 my $pattern = splice(@{ $self->{_stack} });
3015 0         0
3016             my @args = ($string, $pattern);
3017             if ($self->_optional_token(COMMA)) {
3018 1         3 $self->_Expression;
3019 1         2 push(@args, splice(@{ $self->{_stack} }));
3020 1 50       3 }
3021 0         0
3022             $self->_expected_token(RPAREN);
3023 1         5 $self->_add_stack( $self->new_function_expression( 'REGEX', @args ) );
3024 1         3 }
  1         2  
3025 1 50       4  
3026 0         0 # [59] IRIrefOrFunction ::= IRIref ArgList?
3027 0         0 # sub _IRIrefOrFunction_test {
3028 0         0 # my $self = shift;
  0         0  
3029             # $self->_IRIref_test;
3030 0 0       0 # }
3031 0         0  
3032 0         0 my $self = shift;
3033 0         0 $self->_IRIref;
3034 0         0 if ($self->_ArgList_test) {
3035             my ($iri) = splice(@{ $self->{_stack} });
3036             if (my $func = Attean->get_global_aggregate($iri->value)) {
3037             # special-case: treat this as an aggregate invocation instead of a scalar function call, since there is a custom aggregate registered
3038 1 50       4 return $self->_Aggregate($iri);
  1         9  
3039 1 50       4 }
3040 0         0 my @args = $self->_ArgList;
3041             if ($iri->value =~ m<^http://www[.]w3[.]org/2001/XMLSchema#(?:integer|decimal|float|double|boolean|string|dateTime)$>) {
3042 1         5 my $expr = Attean::CastExpression->new( children => \@args, datatype => $iri );
3043 1         4 $self->_add_stack( $expr );
3044             } else {
3045 1         20 my $func = Attean::ValueExpression->new( value => $iri );
3046 1         52 my $expr = $self->new_function_expression( 'INVOKE', $func, @args );
3047             $self->_add_stack( $expr );
3048             }
3049             }
3050             }
3051              
3052             # [60] RDFLiteral ::= String ( LANGTAG | ( '^^' IRIref ) )?
3053             my $self = shift;
3054 1         20 my $value = $self->_String;
3055 1         23
3056 1         5 my $obj;
3057             if ($self->_test_token(LANG)) {
3058             my $t = $self->_expected_token(LANG);
3059             my $lang = $t->value;
3060             $obj = Attean::Literal->new( value => $value, language => $lang );
3061 23     23   61 } elsif ($self->_test_token(HATHAT)) {
3062 23         49 $self->_expected_token(HATHAT);
3063 23 100       74 $self->_IRIref;
3064 22 100       56 my ($iri) = splice(@{ $self->{_stack} });
3065 4 100       24 $obj = Attean::Literal->new( value => $value, datatype => $iri );
3066             } else {
3067 21 50       46 $obj = Attean::Literal->new( value => $value );
3068 21 50       40 }
3069 21 50       74
3070 21 50       77 return $obj;
3071 21         73 }
3072              
3073             # [61] NumericLiteral ::= NumericLiteralUnsigned | NumericLiteralPositive | NumericLiteralNegative
3074             # [62] NumericLiteralUnsigned ::= INTEGER | DECIMAL | DOUBLE
3075 2     2   4 # [63] NumericLiteralPositive ::= INTEGER_POSITIVE | DECIMAL_POSITIVE | DOUBLE_POSITIVE
3076 2         5 # [64] NumericLiteralNegative ::= INTEGER_NEGATIVE | DECIMAL_NEGATIVE | DOUBLE_NEGATIVE
3077 2 100 66     18 my $self = shift;
    50          
    50          
    50          
3078 1         4 my $sign = 0;
3079             if ($self->_optional_token(PLUS)) {
3080 0         0 $sign = '+';
3081 0         0 } elsif ($self->_optional_token(MINUS)) {
3082 0         0 $sign = '-';
3083 0         0 }
3084 0         0
3085 0         0 my $value;
3086 0 0       0 my $type;
3087 0         0 if (my $db = $self->_optional_token(DOUBLE)) {
3088             $value = $db->value;
3089 0         0 $type = Attean::IRI->new(value => 'http://www.w3.org/2001/XMLSchema#double', lazy => 1);
3090             } elsif (my $dc = $self->_optional_token(DECIMAL)) {
3091             $value = $dc->value;
3092 0         0 $type = Attean::IRI->new(value => 'http://www.w3.org/2001/XMLSchema#decimal', lazy => 1);
3093 0         0 } else {
3094 0         0 my $i = $self->_expected_token(INTEGER);
3095 0         0 $value = $i->value;
3096 0         0 $type = Attean::IRI->new(value => 'http://www.w3.org/2001/XMLSchema#integer', lazy => 1);
3097             }
3098 0         0
3099             if ($sign) {
3100 1         5 $value = $sign . $value;
3101 1         7 }
3102 1 50       15
    50          
    50          
    50          
3103             my $obj = Attean::Literal->new( value => $value, datatype => $type );
3104 0         0 # if ($self->{args}{canonicalize} and blessed($obj) and $obj->isa('RDF::Trine::Node::Literal')) {
3105 0         0 # $obj = $obj->canonicalize;
3106             # }
3107              
3108 0         0 return $obj;
3109 0         0 }
3110 0         0  
  0         0  
3111 0         0 # [65] BooleanLiteral ::= 'true' | 'false'
3112 0         0 my $self = shift;
3113             my $t = $self->_expected_token(BOOLEAN);
3114             my $bool = $t->value;
3115 0         0  
3116 0         0 my $obj = Attean::Literal->new( value => $bool, datatype => 'http://www.w3.org/2001/XMLSchema#boolean' );
3117 0         0 # if ($self->{args}{canonicalize} and blessed($obj) and $obj->isa('RDF::Trine::Node::Literal')) {
  0         0  
3118 0         0 # $obj = $obj->canonicalize;
3119 0         0 # }
3120 0         0 return $obj;
  0         0  
3121 0         0 }
3122 0         0  
3123             # [66] String ::= STRING_LITERAL1 | STRING_LITERAL2 | STRING_LITERAL_LONG1 | STRING_LITERAL_LONG2
3124             my $self = shift;
3125 0         0 my $value;
3126 0         0 my $string;
3127 0         0 my $t = $self->_peek_token;
  0         0  
3128 0         0 if ($string = $self->_optional_token(STRING1D)) {
3129 0         0 $value = $string->value;
3130 0         0 } elsif ($string = $self->_optional_token(STRING1S)) {
  0         0  
3131 0         0 $value = $string->value;
3132 0         0 } elsif ($string = $self->_optional_token(STRING3S)) {
3133 0         0 $value = $string->value;
  0         0  
3134 0         0 } elsif ($string = $self->_optional_token(STRING3D)) {
3135 0         0 $value = $string->value;
3136             } else {
3137             my $got = AtteanX::SPARQL::Constants::decrypt_constant($t->type);
3138 1         4 my $value = $t->value;
3139 1         4 croak "Expecting string literal but found $got '$value'";
3140 1         2 }
  1         3  
3141 1         17  
3142 1         18 $value =~ s/\\t/\t/g;
3143 1         4 $value =~ s/\\b/\n/g;
3144             $value =~ s/\\n/\n/g;
3145             $value =~ s/\\r/\x08/g;
3146             $value =~ s/\\"/"/g;
3147             $value =~ s/\\'/'/g;
3148             $value =~ s/\\\\/\\/g; # backslash must come last, so it doesn't accidentally create a new escape
3149             return $value;
3150             }
3151              
3152             # [67] IRIref ::= IRI_REF | PrefixedName
3153             my $self = shift;
3154             return 1 if ($self->_test_token(IRI));
3155 0     0   0 return 1 if ($self->_test_token(PREFIXNAME));
3156 0         0 return 0;
3157 0         0 }
3158 0         0  
3159 0         0  
  0         0  
3160             my $self = shift;
3161 0         0 if (my $t = $self->_optional_token(IRI)) {
3162 0         0 my $iri = $t->value;
3163 0         0 my $base = $self->__base;
  0         0  
3164             my $node = $self->new_iri( value => $iri, $base ? (base => $base) : () );
3165 0         0 $self->_add_stack( $node );
3166 0 0       0 } else {
3167 0         0 my $p = $self->_PrefixedName;
3168 0         0 $self->_add_stack( $p );
  0         0  
3169             }
3170             }
3171 0         0  
3172 0         0 # [68] PrefixedName ::= PNAME_LN | PNAME_NS
3173             my $self = shift;
3174             my $t = $self->_expected_token(PREFIXNAME);
3175             my ($ns, $local) = @{ $t->args };
3176             chop($ns);
3177             # $local =~ s{\\([-~.!&'()*+,;=:/?#@%_\$])}{$1}g;
3178            
3179             unless ($self->namespaces->namespace_uri($ns)) {
3180             croak "Syntax error: Use of undefined namespace '$ns'";
3181             }
3182 1     1   1
3183 1         4 my $iri = $self->namespaces->namespace_uri($ns)->iri($local);
3184 1 50       4 my $base = $self->__base;
3185 1         9 my $p = $self->new_iri( value => $iri->value, $base ? (base => $base) : () );
  1         5  
3186 1 50       10 return $p;
3187             }
3188 0         0  
3189             my $self = shift;
3190 1         4 # Var | BlankNode | iri | RDFLiteral | NumericLiteral | BooleanLiteral | QuotedTP
3191 1 50       7 if ($self->_test_token(LTLT)) {
3192 0         0 $self->_QuotedTP();
3193 0         0 } else {
3194             $self->_VarOrTerm;
3195 1         16 }
3196 1         12 }
3197 1         61  
3198             my $self = shift;
3199             #'<<' qtSubjectOrObject Verb qtSubjectOrObject '>>'
3200             $self->_expected_token(LTLT);
3201             $self->_qtSubjectOrObject();
3202             $self->_Verb();
3203             $self->_qtSubjectOrObject();
3204 9     9   19 $self->_expected_token(GTGT);
3205 9         41
3206             my ($s, $p, $o) = splice(@{ $self->{_stack} }, -3);
3207 9         21
3208 9 100       26 if ($self->{__data_pattern}) {
    50          
3209 2         18 foreach my $term ($s, $o) {
3210 2         13 if ($term->does('Attean::API::Blank')) {
3211 2         36 croak "Expecting (non-blank) RDF term in quoted triple, but found blank";
3212             }
3213 0         0 }
3214 0         0 }
3215 0         0  
  0         0  
3216 0         0 $self->_add_stack( $self->__new_statement( $s, $p, $o ) );
3217             }
3218 7         161  
3219             my $self = shift;
3220             #'<<' DataValueTerm Verb DataValueTerm '>>'
3221 9         24 local($self->{__data_pattern}) = 1;
3222             $self->_QuotedTP();
3223             }
3224              
3225             # [69] BlankNode ::= BLANK_NODE_LABEL | ANON
3226             my $self = shift;
3227             if (my $where = $self->{__no_bnodes}) {
3228             croak "Syntax error: Blank nodes not allowed in $where";
3229 15     15   29 }
3230 15         20 if (my $b = $self->_optional_token(BNODE)) {
3231 15 50       30 my $label = $b->value;
    50          
3232 0         0 return Attean::Blank->new($label);
3233             } else {
3234 0         0 $self->_expected_token(ANON);
3235             return Attean::Blank->new();
3236             }
3237 15         24 }
3238              
3239 15 100       29 my $self = shift;
    100          
3240 1         11 $self->_expected_token(NIL);
3241 1         18 return Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', lazy => 1);
3242             }
3243 1         4  
3244 1         20 my $self = shift;
3245             my $star = shift;
3246 13         31 my @exprs = @_;
3247 13         40
3248 13         203 if (my $computed_group_vars = delete( $self->{build}{__group_vars} )) {
3249             my $pattern = $self->{build}{triples}[0];
3250             foreach my $data (@$computed_group_vars) {
3251 15 50       1111 my ($var, $expr) = @$data;
3252 0         0 $pattern = Attean::Algebra::Extend->new( children => [$pattern], variable => $var, expression => $expr );
3253             }
3254             $self->{build}{triples}[0] = $pattern;
3255 15         290 }
3256            
3257             my $has_aggregation = 0;
3258             my $having_expr;
3259             my $aggdata = delete( $self->{build}{__aggregate} );
3260 15         3821 my $groupby = delete( $self->{build}{__group_by} ) || [];
3261             my @aggkeys = keys %{ $aggdata || {} };
3262             if (scalar(@aggkeys) or scalar(@$groupby)) {
3263             $has_aggregation++;
3264             my @aggs;
3265 0     0   0 foreach my $k (@aggkeys) {
3266 0         0 my ($var, $expr) = @{ $aggdata->{$k} };
3267 0         0 push(@aggs, $expr);
3268             }
3269 0         0
3270             my $pattern = $self->{build}{triples};
3271             my $ggp = shift(@$pattern);
3272             if (my $having = delete( $self->{build}{__having} )) {
3273 0         0 $having_expr = $having;
3274             }
3275             my $agg = Attean::Algebra::Group->new( children => [$ggp], groupby => $groupby, aggregates => \@aggs );
3276             push(@{ $self->{build}{triples} }, $agg);
3277             }
3278 9     9   16
3279 9         19 my %group_vars;
3280             my %agg_vars;
3281 9         24 if ($has_aggregation) {
3282 9 100       61 foreach my $agg_var (map { $_->[0] } values %$aggdata) {
    100          
    50          
    0          
3283 7         35 $agg_vars{ $agg_var->value }++;
3284             }
3285 1         11 foreach my $g (@$groupby) {
3286             if ($g->isa('Attean::ValueExpression') and $g->value->does('Attean::API::Variable')) {
3287 1         4 $group_vars{ $g->value->value }++;
3288             } else {
3289 0         0 $self->log->trace("Remaining GROUP BY clauses:\n" . Dumper($g));
3290             croak 'Unrecognized GROUP BY clauses, see trace log for details.';
3291 0         0 }
3292 0         0 }
3293 0         0 }
3294            
3295             my @project;
3296 9         35 my @vars;
3297 9         20 my @extend;
3298 9         19 if ($star) {
3299 9         21 my $pattern = ${ $self->{build}{triples} }[-1];
3300 9         543 push(@project, $pattern->in_scope_variables);
3301 9         20 if ($has_aggregation) {
3302 9         19 croak "Cannot SELECT * in an aggregate query";
3303 9         34 }
3304             } else {
3305             for (my $i = 0; $i < $#exprs; $i += 2) {
3306             my $k = $exprs[$i];
3307             my $v = $exprs[$i+1];
3308 204     204   797 if ($has_aggregation) {
3309 204 100       353 my @vars = $v->does('Attean::API::Variable') ? $v : $v->unaggregated_variables;
3310 155 100       356 foreach my $var (@vars) {
3311 116         360 my $name = $var->value;
3312             unless (exists $agg_vars{$name} or exists $group_vars{$name}) {
3313             croak "Cannot project variable ?$name that is not aggregated or used in grouping";
3314             }
3315             }
3316 90     90   144 }
3317 90 100       169
3318 62         196 push(@project, $k);
3319 62         170 if ($v->does('Attean::API::Variable')) {
3320 62 100       282 push(@vars, $v);
3321 62         10060 } else {
3322             push(@extend, $k, $v);
3323 28         80 }
3324 28         80 }
3325             }
3326              
3327             {
3328             my $pattern = pop(@{ $self->{build}{triples} });
3329             my %in_scope = map { $_ => 1 } $pattern->in_scope_variables;
3330 28     28   48 while (my($name, $expr) = splice(@extend, 0, 2)) {
3331 28         63 if (exists $in_scope{$name}) {
3332 28         55 croak "Syntax error: Already-bound variable ($name) used in project expression";
  28         94  
3333 28         57 }
3334             my $var = Attean::Variable->new( value => $name );
3335             $pattern = Attean::Algebra::Extend->new(children => [$pattern], variable => $var, expression => $expr);
3336 28 50       115 }
3337 0         0 push(@{ $self->{build}{triples} }, $pattern);
3338             }
3339            
3340 28         216 if ($having_expr) {
3341 28         8594 my $pattern = pop(@{ $self->{build}{triples} });
3342 28 50       145 my $filter = Attean::Algebra::Filter->new( children => [$pattern], expression => $having_expr );
3343 28         4938 push(@{ $self->{build}{triples} }, $filter);
3344             }
3345            
3346             if ($self->{build}{options}{orderby}) {
3347 6     6   8 my $order = delete $self->{build}{options}{orderby};
3348             my $pattern = pop(@{ $self->{build}{triples} });
3349 6 50       15 my @order = @$order;
3350 0         0 my @cmps;
3351             foreach my $o (@order) {
3352 6         21 my ($dir, $expr) = @$o;
3353             my $asc = ($dir eq 'ASC');
3354             push(@cmps, Attean::Algebra::Comparator->new(ascending => $asc, expression => $expr));
3355             }
3356             my $sort = Attean::Algebra::OrderBy->new( children => [$pattern], comparators => \@cmps );
3357 3     3   6 push(@{ $self->{build}{triples} }, $sort);
3358             }
3359 3         11  
3360 3         14 {
3361 3         16 my $pattern = pop(@{ $self->{build}{triples} });
3362 3         16 my $vars = [map { Attean::Variable->new(value => $_) } @project];
3363 3         12 if (scalar(@$vars)) {
3364             $pattern = Attean::Algebra::Project->new( children => [$pattern], variables => $vars);
3365 3         8 }
  3         13  
3366             push(@{ $self->{build}{triples} }, $pattern);
3367 3 50       11 }
3368 0         0
3369 0 0       0 if (my $level = $self->{build}{options}{distinct}) {
3370 0         0 delete $self->{build}{options}{distinct};
3371             my $pattern = pop(@{ $self->{build}{triples} });
3372             my $sort = ($level == 1)
3373             ? Attean::Algebra::Distinct->new( children => [$pattern] )
3374             : Attean::Algebra::Reduced->new( children => [$pattern] );
3375 3         15 push(@{ $self->{build}{triples} }, $sort);
3376             }
3377            
3378             if (exists $self->{build}{options}{offset} and exists $self->{build}{options}{limit}) {
3379 0     0   0 my $limit = delete $self->{build}{options}{limit};
3380             my $offset = delete $self->{build}{options}{offset};
3381 0         0 my $pattern = pop(@{ $self->{build}{triples} });
3382 0         0 my $sliced = Attean::Algebra::Slice->new( children => [$pattern], limit => $limit, offset => $offset );
3383             push(@{ $self->{build}{triples} }, $sliced);
3384             } elsif (exists $self->{build}{options}{offset}) {
3385             my $offset = delete $self->{build}{options}{offset};
3386             my $pattern = pop(@{ $self->{build}{triples} });
3387 0     0   0 my $sliced = Attean::Algebra::Slice->new( children => [$pattern], offset => $offset );
3388 0 0       0 push(@{ $self->{build}{triples} }, $sliced);
3389 0         0 } elsif (exists $self->{build}{options}{limit}) {
3390             my $limit = delete $self->{build}{options}{limit};
3391 0 0       0 my $pattern = pop(@{ $self->{build}{triples} });
3392 0         0 my $sliced = Attean::Algebra::Slice->new( children => [$pattern], limit => $limit );
3393 0         0 push(@{ $self->{build}{triples} }, $sliced);
3394             }
3395 0         0
3396 0         0 return @project;
3397             }
3398              
3399             ################################################################################
3400              
3401 1     1   2 =item C<< error >>
3402 1         3  
3403 1         17 Returns the error encountered during the last parse.
3404              
3405             =cut
3406              
3407 29     29   95 my $self = shift;
3408 29         55 my @triples = @_;
3409 29         72 my $container = $self->{ _pattern_container_stack }[0];
3410             push( @{ $container }, @triples );
3411 29 50       122 }
3412 0         0  
3413 0         0 my $self = shift;
3414 0         0 my $container = $self->{ _pattern_container_stack }[0];
3415 0         0 my $pattern = pop( @{ $container } );
3416             return $pattern;
3417 0         0 }
3418              
3419             my $self = shift;
3420 29         58 my $container = $self->{ _pattern_container_stack }[0];
3421 29         53 my $pattern = $container->[-1];
3422 29         62 return $pattern;
3423 29   100     140 }
3424 29 100       50  
  29         164  
3425 29 100 100     162 my $self = shift;
3426 2         4 my $hints = shift;
3427 2         4 push( @{ $self->{ _pattern_container_hints_stack }[0] }, $hints );
3428 2         6 }
3429 2         4  
  2         5  
3430 2         7 my $self = shift;
3431             my $cont = [];
3432             unshift( @{ $self->{ _pattern_container_stack } }, $cont );
3433 2         6 unshift( @{ $self->{ _pattern_container_hints_stack } }, [] );
3434 2         4 return $cont;
3435 2 100       8 }
3436 1         2  
3437             my $self = shift;
3438 2         21 my $hints = shift( @{ $self->{ _pattern_container_hints_stack } } );
3439 2         5 my $cont = shift( @{ $self->{ _pattern_container_stack } } );
  2         8  
3440             return ($cont, $hints);
3441             }
3442 29         71  
3443             my $self = shift;
3444 29 100       76 my @items = @_;
3445 2         7 push( @{ $self->{_stack} }, @items );
  2         7  
3446 2         10 }
3447              
3448 2         6 my $self = shift;
3449 2 50 33     16 my @filters = shift;
3450 2         43 push( @{ $self->{filters} }, @filters );
3451             }
3452 0         0  
3453 0         0 my $self = shift;
3454             my $build = $self->{build};
3455             if (blessed($build->{base})) {
3456             return $build->{base};
3457             } elsif (defined($build->{base})) {
3458 29         92 return $self->new_iri($build->{base});
3459             } else {
3460 29         0 return;
3461 29 100       92 }
3462 25         47 }
  25         70  
3463 25         133  
3464 25 50       1288 my $self = shift;
3465 0         0 my $s = shift;
3466             my $p = shift;
3467             my $o = shift;
3468 4         29 my $annot;
3469 5         16 if ($o->isa('AtteanX::Parser::SPARQL::ObjectWrapper')) {
3470 5         10 if (reftype($p) eq 'ARRAY' and $p->[0] eq 'PATH') {
3471 5 100       13 # this is actually a property path, for which annotations (stored in the ObjectWrapper) are forbidden
3472 3 100       23 croak "Syntax error: Cannot use SPARQL-star annotation syntax on a property path";
3473 3         31 }
3474 3         9 $annot = $o->annotations;
3475 3 100 100     14 $o = $o->value;
3476 1         25 }
3477             my $t = Attean::TriplePattern->new($s, $p, $o);
3478             my @st = ($t);
3479             if ($annot) {
3480             $s = $t;
3481 4         9 foreach my $pair (@$annot) {
3482 4 100       12 my ($p, $o) = @$pair;
3483 3         56 push(@st, $self->__new_statement($s, $p, $o));
3484             }
3485 1         19 }
3486             return @st;
3487             }
3488              
3489             my $self = shift;
3490             my $start = shift;
3491 28         88 my $pdata = shift;
  28         54  
  28         84  
3492 28         94 my $end = shift;
  51         1196  
3493 28         174 (undef, my $op, my @nodes) = @$pdata;
3494 1 50       11 my $path = $self->__new_path_pred($op, @nodes);
3495 0         0 return Attean::Algebra::Path->new( subject => $start, path => $path, object => $end );
3496             }
3497 1         26  
3498 1         54 my $self = shift;
3499             my $op = shift;
3500 28         90 my @nodes = @_;
  28         94  
3501              
3502             if ($op eq '!') {
3503 28 50       82 return Attean::Algebra::NegatedPropertySet->new( predicates => \@nodes );
3504 0         0 }
  0         0  
3505 0         0
3506 0         0 foreach my $i (0 .. $#nodes) {
  0         0  
3507             if (ref($nodes[$i]) eq 'ARRAY') {
3508             (undef, my @data) = @{ $nodes[$i] };
3509 28 100       114 $nodes[$i] = $self->__new_path_pred(@data);
3510 1         2 } elsif ($nodes[$i]->does('Attean::API::IRI')) {
3511 1         2 $nodes[$i] = Attean::Algebra::PredicatePath->new( predicate => $nodes[$i] );
  1         3  
3512 1         2 }
3513 1         2 }
3514 1         2
3515 1         2 if ($op eq '*') {
3516 1         2 return Attean::Algebra::ZeroOrMorePath->new( children => [@nodes] );
3517 1         9 } elsif ($op eq '+') {
3518             return Attean::Algebra::OneOrMorePath->new( children => [@nodes] );
3519 1         1803 } elsif ($op eq '?') {
3520 1         3 return Attean::Algebra::ZeroOrOnePath->new( children => [@nodes] );
  1         4  
3521             } elsif ($op eq '^') {
3522             return Attean::Algebra::InversePath->new( children => [@nodes] );
3523             } elsif ($op eq '/') {
3524 28         50 return Attean::Algebra::SequencePath->new( children => [@nodes] );
  28         41  
  28         81  
3525 28         64 } elsif ($op eq '|') {
  47         1823  
3526 28 100       1265 return Attean::Algebra::AlternativePath->new( children => [@nodes] );
3527 27         488 } else {
3528             $self->log->debug("Path $op:\n". Dumper(\@nodes));
3529 28         58 confess "Error in path $op. See debug log for details."
  28         97  
3530             }
3531             }
3532 28 100       115  
3533 1         2 # fix up BGPs that might actually have property paths in them. split those
3534 1         3 # out as their own path algebra objects, and join them with the bgp with a
  1         2  
3535 1 50       9 # ggp if necessary
3536             my $self = shift;
3537             my @patterns = @_;
3538 1         2 my @paths = grep { reftype($_->predicate) eq 'ARRAY' and $_->predicate->[0] eq 'PATH' } @patterns;
  1         4  
3539             my @triples = grep { blessed($_->predicate) } @patterns;
3540             if ($self->log->is_trace && (scalar(@patterns) > scalar(@paths) + scalar(@triples))) {
3541 28 100 100     210 $self->log->warn('More than just triples and paths passed to __new_bgp');
    100          
    50          
3542 1         3 $self->log->trace("Arguments to __new_bgp:\n" .Dumper(\@patterns));
3543 1         2 }
3544 1         2
  1         3  
3545 1         13 my $bgp = Attean::Algebra::BGP->new( triples => \@triples );
3546 1         3 if (@paths) {
  1         4  
3547             my @p;
3548 1         3 foreach my $p (@paths) {
3549 1         2 my $start = $p->subject;
  1         4  
3550 1         39 my $end = $p->object;
3551 1         2 my $pdata = $p->predicate;
  1         3  
3552             push(@p, $self->__new_path( $start, $pdata, $end ));
3553 0         0 }
3554 0         0 if (scalar(@triples)) {
  0         0  
3555 0         0 return $self->_new_join($bgp, @p);
3556 0         0 } else {
  0         0  
3557             return $self->_new_join(@p);
3558             }
3559 28         128 } else {
3560             return $bgp;
3561             }
3562             }
3563              
3564             =item C<new_binary_expression ( $operator, @operands )>
3565              
3566             Returns a new binary expression structure.
3567              
3568             =cut
3569              
3570             my $self = shift;
3571 183     183   306 my $op = shift;
3572 183         331 my @operands = @_[0,1];
3573 183         323 return Attean::BinaryExpression->new( operator => $op, children => \@operands );
3574 183         236 }
  183         490  
3575              
3576             =item C<new_function_expression ( $function, @operands )>
3577              
3578 17     17   39 Returns a new function expression structure.
3579 17         35  
3580 17         28 =cut
  17         35  
3581 17         42  
3582             my $self = shift;
3583             my $function = shift;
3584             my @operands = @_;
3585 38     38   62 my $base = $self->__base;
3586 38         77 return Attean::FunctionExpression->new( operator => $function, children => \@operands, $base ? (base => $base) : () );
3587 38         74 }
3588 38         76  
3589             my $self = shift;
3590             my @parts = @_;
3591             if (0 == scalar(@parts)) {
3592 1     1   2 return Attean::Algebra::BGP->new();
3593 1         3 } elsif (1 == scalar(@parts)) {
3594 1         2 return shift(@parts);
  1         4  
3595             } else {
3596             return Attean::Algebra::Join->new( children => \@parts );
3597             }
3598 168     168   228 }
3599 168         290  
3600 168         220 my $self = shift;
  168         376  
3601 168         230 my $l = $self->lexer;
  168         349  
3602 168         270 my $t = $l->peek;
3603             return unless ($t);
3604             while ($t == COMMENT) {
3605             $t = $l->peek;
3606 120     120   184 return unless ($t);
3607 120         149 }
  120         237  
3608 120         196 return $t;
  120         202  
3609 120         275 }
3610              
3611             my $self = shift;
3612             my $type = shift;
3613 487     487   7673 my $t = $self->_peek_token;
3614 487         858 return unless ($t);
3615 487         588 return if ($t->type != $type);
  487         1541  
3616             if (@_) {
3617             my $value = shift;
3618             if (ref($value) eq 'Regexp') {
3619 4     4   7 return unless ($t->value =~ $value);
3620 4         15 } else {
3621 4         7 return unless ($t->value eq $value);
  4         12  
3622             }
3623             }
3624             return 1;
3625 93     93   140 }
3626 93         143  
3627 93 100       448 my $self = shift;
    50          
3628 4         10 if ($self->_test_token(@_)) {
3629             return $self->_next_token;
3630 0         0 }
3631             return;
3632 89         160 }
3633              
3634             my $self = shift;
3635             my $l = $self->lexer;
3636             my $t = $l->next;
3637 73     73   122 while ($t->type == COMMENT) {
3638 73         110 $t = $l->peek;
3639 73         100 return unless ($t);
3640 73         91 }
3641 73         98 return $t;
3642 73 100       561 }
3643 2 50 33     11  
3644             my $self = shift;
3645 0         0 my $type = shift;
3646             if ($self->_test_token($type, @_)) {
3647 2         34 return $self->_next_token;
3648 2         15 } else {
3649             my $t = $self->_peek_token;
3650 73         1301 my $expecting = AtteanX::SPARQL::Constants::decrypt_constant($type);
3651 73         2627 my $got = blessed($t) ? AtteanX::SPARQL::Constants::decrypt_constant($t->type) : '(undef)';
3652 73 100       178 if (@_) {
3653 2         5 my $value = shift;
3654 2         5 if ($t) {
3655 3         6 my $value2 = $t->value;
3656 3         10 confess "Expecting $expecting '$value' but got $got '$value2' before " . $self->lexer->buffer;
3657             } else {
3658             confess "Expecting $expecting '$value' but found EOF";
3659 73         244 }
3660             } else {
3661             confess "Expecting $expecting but found $got before " . $self->lexer->buffer;
3662             }
3663 1     1   2 }
3664 1         2 }
3665 1         2  
3666 1         2 my $self = shift;
3667 1         4 my $t = shift;
3668 1         3 my $note = shift;
3669 1         116 my $got = blessed($t) ? AtteanX::SPARQL::Constants::decrypt_constant($t->type) : '(undef)';
3670             my $message = "$note but got $got";
3671             if ($t and $t->start_line > 0) {
3672             my $l = $t->start_line;
3673 2     2   3 my $c = $t->start_column;
3674 2         2 $message .= " at $l:$c";
3675 2         4 } else {
3676             my $n = $self->lexer->buffer;
3677 2 50       5 $n =~ s/\s+/ /g;
3678 0         0 $n =~ s/\s*$//;
3679             if ($n) {
3680             $message .= " near '$n'";
3681 2         6 }
3682 3 100       92 }
    50          
3683 1         2 croak $message;
  1         3  
3684 1         5 }
3685              
3686 2         67  
3687             use strict;
3688             use warnings;
3689             no warnings 'redefine';
3690 2 100       163 use Types::Standard qw(InstanceOf HashRef ArrayRef Bool Str Int);
    50          
    50          
    50          
    50          
    0          
3691 1         21  
3692             use Moo;
3693 0         0 has 'value' => (is => 'rw');
3694             has 'annotations' => (is => 'rw', isa => ArrayRef);
3695 0         0  
3696              
3697 0         0 1;
3698              
3699 1         21  
3700             =back
3701 0         0  
3702              
3703 0         0 =head1 BUGS
3704 0         0  
3705             Please report any bugs or feature requests to through the GitHub web interface
3706             at L<https://github.com/kasei/attean/issues>.
3707              
3708             =head1 SEE ALSO
3709              
3710              
3711              
3712 51     51   83 =head1 AUTHOR
3713 51         143  
3714 51 100       132 Gregory Todd Williams C<< <gwilliams@cpan.org> >>
  62         386  
3715 51         151  
  62         263  
3716 51 50 33     922 =head1 COPYRIGHT
3717 0         0  
3718 0         0 Copyright (c) 2014--2022 Gregory Todd Williams.
3719             This program is free software; you can redistribute it and/or modify it under
3720             the same terms as Perl itself.
3721 51         18399  
3722 51 100       158 =cut