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   553301 use v5.14;
  12         51  
2 12     12   67 use warnings;
  12         27  
  12         691  
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   82 use warnings;
  12         27  
  12         293  
76 12     12   507 no warnings 'redefine';
  12         28  
  12         363  
77 12     12   81 use Carp qw(cluck confess croak);
  12         27  
  12         510  
78 12     12   68  
  12         24  
  12         921  
79             use Attean;
80 12     12   71 use Data::Dumper;
  12         27  
  12         97  
81 12     12   67 use URI::NamespaceMap;
  12         39  
  12         576  
82 12     12   116 use List::MoreUtils qw(zip);
  12         34  
  12         531  
83 12     12   76 use AtteanX::Parser::SPARQLLex;
  12         27  
  12         189  
84 12     12   17674 use AtteanX::SPARQL::Constants;
  12         42  
  12         659  
85 12     12   104 use Types::Standard qw(InstanceOf HashRef ArrayRef Bool Str Int);
  12         26  
  12         2281  
86 12     12   72 use Scalar::Util qw(blessed looks_like_number reftype refaddr);
  12         29  
  12         97  
87 12     12   12513  
  12         22  
  12         680  
88             ######################################################################
89              
90             use Moo;
91 12     12   72  
  12         29  
  12         71  
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 37  
104             return [qw(application/sparql-query application/sparql-update)];
105 1     1 1 966 }
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 885 with 'MooX::Log::Any';
113 5         288  
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 64260 }
121 53         180 return \%a;
122 53   66     1345 }
123 53         67176  
124 53 100       324 ################################################################################
125 1         3  
126             my $self = shift;
127 53         914 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   136  
134 52         114 Parse the C<< $sparql >> query string and return the resulting
135 52     1   565 L<Attean::API::Algebra> object.
  1         8  
136 52         166  
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 33111 Parse the C<< $sparql >> update string and return the resulting
148 28 100       612 L<Attean::API::Algebra> object.
149 28         1521  
150 26         321 =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 1092 =cut
162 14 50       687  
163 14         1408 my $self = shift;
164 14         535 my $p = AtteanX::Parser::SPARQLLex->new();
165 13         416 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 5 }
174 2         40  
175 2         94 =item C<< parse_list_from_bytes( $bytes ) >>
176 2         48  
177 2         77 =cut
178 2         9  
179 2 50       9 my $self = shift;
180 2         7 my $p = AtteanX::Parser::SPARQLLex->new();
181 2 50       8 my $l = $self->_configure_lexer( $p->parse_iter_from_bytes(@_) );
182 2         17 $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 1669  
191 45         904 =item C<< parse_nodes ( $string ) >>
192 45         2166  
193 45         1059 Returns a list of L<Attean::API::Term> or L<Attean::API::Variable> objects,
194 45         1944 parsed in SPARQL syntax from the supplied C<< $string >>. Parsing is ended
195 45         230 either upon seeing a DOT, or reaching the end of the string.
196 42 50       190  
197 42         122 =cut
198 42 50       160  
199 42         423 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 84 if ($self->_Verb_test) {
212 5         94 $self->_Verb;
213 5         198 } else {
214 5         15 $self->_GraphNode;
215 5   50     28 }
216 5         24
217 5         103 if ($commas) {
218 5         216 $self->_optional_token(COMMA);
219 5         101 }
220            
221 5         123 push(@nodes, splice(@{ $self->{_stack} }));
222 5         21 if ($self->_test_token(DOT)) {
223 15 100       42 $self->log->notice('DOT seen in string, stopping here');
224 13         33 last;
225             }
226 2         8 }
227            
228             return @nodes;
229 15 50       60 }
230 0         0  
231             my $self = shift;
232            
233 15         21 unless ($self->update) {
  15         36  
234 15 50       36 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         46 $self->_stack([]);
241             $self->filters([]);
242             $self->_pattern_container_stack([]);
243             my $triples = $self->_push_pattern_container();
244 47     47   112 my $build = { sources => [], triples => $triples };
245             $self->build($build);
246 47 100       849 if ($self->baseURI) {
247 33         687 $build->{base} = $self->baseURI;
248 33 50       126 }
249 0         0  
250             $self->_RW_Query();
251             delete $build->{star};
252             my $data = $build;
253 47         1023 return $data;
254 47         2152 }
255 47         1966  
256 47         1429 ################################################################################
257 47         226  
258 47         813  
259 47 100       1367 # [1] Query ::= Prologue ( SelectQuery | ConstructQuery | DescribeQuery | AskQuery | LoadUpdate )
260 1         5 my $self = shift;
261             $self->_Prologue;
262              
263 47         213 my $read_query = 0;
264 44         142 my $update = 0;
265 44         121 while (1) {
266 44         133 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   95 $read_query++;
275 47         196 } elsif ($self->_optional_token(KEYWORD, 'ASK')) {
276             $self->_AskQuery();
277 47         102 $read_query++;
278 47         133 } elsif ($self->_test_token(KEYWORD, 'CREATE')) {
279 47         108 unless ($self->update) {
280 49 100       155 croak "CREATE GRAPH update forbidden in read-only queries";
    100          
    100          
    100          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    100          
    50          
281 28         155 }
282 27         62 $update++;
283             $self->_CreateGraph();
284 3         20 } elsif ($self->_test_token(KEYWORD, 'DROP')) {
285 3         7 unless ($self->update) {
286             croak "DROP GRAPH update forbidden in read-only queries";
287 1         8 }
288 1         3 $update++;
289             $self->_DropGraph();
290 4         27 } elsif ($self->_test_token(KEYWORD, 'LOAD')) {
291 4         10 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       247 }
318 0         0 $self->_InsertDataUpdate();
319             } else {
320 9         88 $self->_InsertUpdate($graph);
321 9         21 }
322 9 50       32 } 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       35 $self->_DeleteDataUpdate();
    50          
328 3 100       14 } else {
329 1 50       20 $self->_DeleteUpdate($graph);
330 0         0 }
331             }
332 1         11 } elsif ($self->_test_token(KEYWORD, 'COPY')) {
333             $update++;
334 2         12 $self->_AddCopyMoveUpdate('COPY');
335             } elsif ($self->_test_token(KEYWORD, 'MOVE')) {
336             $update++;
337 6 100       36 $self->_AddCopyMoveUpdate('MOVE');
338 1 50       17 } elsif ($self->_test_token(KEYWORD, 'ADD')) {
339 0         0 $update++;
340             $self->_AddCopyMoveUpdate('ADD');
341 1         10 } elsif ($self->_test_token(SEMICOLON)) {
342             $self->_expected_token(SEMICOLON);
343 5         33 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         10  
354 3         23 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         10 my $t = $self->_peek_token;
365 1         5 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       353 }
369 11 100       56  
370 2 50       27 if ($count == 0 or $count > 1) {
371 2         11 my @patterns = splice(@{ $self->{build}{triples} });
372             my %seen;
373             foreach my $p (@patterns) {
374 9         34 my @blanks = $p->blank_nodes;
375             foreach my $b (@blanks) {
376 44         95 if ($seen{$b->value}++) {
  44         152  
377             croak "Cannot re-use a blank node label in multiple update operations in a single request";
378 44         165 }
379 44 50       206 }
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     325 }
385 2         6
  2         12  
386 2         5 my %dataset;
387 2         8 foreach my $s (@{ $self->{build}{sources} }) {
388 4         48 my ($iri, $group) = @$s;
389 4         12 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         37  
396 2         19 my $algebra = $self->{build}{triples}[0];
397 2         10
398             if ($update) {
399             $self->{build}{triples}[0] = Attean::Algebra::Update->new( children => [$algebra] );
400 44         101 } else {
401 44         96 $self->{build}{triples}[0] = Attean::Algebra::Query->new( children => [$algebra], dataset => \%dataset );
  44         174  
402 8         22 }
403 8 100       26 }
404 4         9  
  4         18  
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         16  
407             }
408              
409             # [2] Prologue ::= BaseDecl? PrefixDecl*
410 44         130 # [3] BaseDecl ::= 'BASE' IRI_REF
411             # [4] PrefixDecl ::= 'PREFIX' PNAME_NS IRI_REF
412 44 100       161 my $self = shift;
413 9         207  
414             my $base;
415 35         720 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   5 @base = $base;
421 2         13 $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   97 if (scalar(@args) > 1) {
429             croak "Syntax error: PREFIX namespace used a full PNAME_LN, not a PNAME_NS";
430 47         181 }
431             my $ns = substr($prefix->value, 0, length($prefix->value) - 1);
432 47 50       207 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         111 }
441 47         421  
442 15         84 $self->{build}{namespaces} = \%namespaces;
443 15         36 $self->{build}{base} = $base if (defined($base));
  15         59  
444 15 50       62  
445 0         0 # push(@data, (base => $base)) if (defined($base));
446             # return @data;
447 15         49 }
448 15         43  
449 15         71 my $self = shift;
450 15 50       50 $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         49  
455 15         110 my $insert = Attean::Algebra::Modify->new(insert => \@triples);
456             $self->_add_patterns( $insert );
457             $self->{build}{method} = 'UPDATE';
458 47         216 }
459 47 50       234  
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   5
467 1         4 my $delete = Attean::Algebra::Modify->new(delete => \@triples);
468 1         4 $self->_add_patterns( $delete );
469 1         5 $self->{build}{method} = 'UPDATE';
470 1         5 }
471              
472 1         16 my $self = shift;
473 1         16 my $graph = shift;
474 1         4 $self->_expected_token(LBRACE);
475             my @triples = $self->_ModifyTemplate();
476             $self->_expected_token(RBRACE);
477            
478 1     1   2 if ($graph) {
479 1         6 @triples = map { $_->as_quad_pattern($graph) } @triples;
480 1         10 }
481 1         5  
482 1         5 my %dataset;
483 1         5 while ($self->_optional_token(KEYWORD, 'USING')) {
484             $self->{build}{custom_update_dataset} = 1;
485 1         27 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   6 if ($named) {
492 2         6 $dataset{named}{$iri->value} = $iri;
493 2         11 } else {
494 2         13 push(@{ $dataset{default} }, $iri );
495 2         10 }
496             }
497 2 50       7  
498 0         0 $self->_expected_token(KEYWORD, 'WHERE');
  0         0  
499             if ($graph) {
500             $self->_GroupGraphPattern;
501 2         9 my $ggp = $self->_remove_pattern;
502 2         13 $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         11 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         9 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         7 } else {
530 1         20 my $id = $self->counter;
531 1         11 $self->counter($id+1);
532 1         9 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   3 }
537 1         3 } else {
538             push(@terms, $term);
539 1         3 }
540             }
541 1         4 push(@triples_with_fresh_bnodes, ref($t)->new(zip @pos, @terms));
542 1         7 } else {
543 1 50       8 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         5 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         6 }
567             foreach my $s (@st) {
568             if ($s->does('Attean::API::QuadPattern')) {
569             push(@quads, $s);
570 5     5   21 my $tp = $s->as_triple_pattern;
571 5         10 my $bgp = Attean::Algebra::BGP->new( triples => [$tp] );
572             push(@patterns, Attean::Algebra::Graph->new( graph => $s->graph, children => [$bgp] ));
573 5         14 } else {
574 5 50       22 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         13 while ($self->_optional_token(KEYWORD, 'USING')) {
606             $self->{build}{custom_update_dataset} = 1;
607 5         15 my $named = 0;
  5         23  
608 5         28 if ($self->_optional_token(KEYWORD, 'NAMED')) {
609 5         29 $named = 1;
610 5         25 }
611             $self->_IRIref;
612             my ($iri) = splice( @{ $self->{_stack} } );
613 5         13 if ($named) {
614 5 50       24 $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       22
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         22 $ggp = Attean::Algebra::Graph->new( children => [$ggp], graph => $graph );
627 9         48 $self->_add_patterns( $ggp );
628 9         20 } else {
629 9 100       25 $self->_GroupGraphPattern;
630 3         7 delete $self->{__no_bnodes};
631             }
632 9         65  
633 9         39 my $ggp = $self->_remove_pattern;
  9         38  
634 9 100       31  
635 3         42 my %args = (children => [$ggp], dataset => \%dataset);
636             if (scalar(@insert_triples)) {
637 6         14 $args{insert} = \@insert_triples;
  6         31  
638             }
639             if (scalar(@delete_triples)) {
640             $args{delete} = \@delete_triples;
641 5         30 my @blanks = grep { $_->does('Attean::API::Blank') } map { $_->values } @delete_triples;
642             if (scalar(@blanks) > 0) {
643 5 50       24 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         37 }
651 5         21  
652             my $self = shift;
653             return 1 if ($self->_TriplesBlock_test);
654 5         28 return 1 if ($self->_test_token(KEYWORD, 'GRAPH'));
655             return 0;
656 5         33 }
657 5 50       20  
658 0         0 my $self = shift;
659             my $graph = shift;
660 5 50       22
661 5         21 my @triples;
662 5         20 while ($self->_ModifyTemplate_test) {
  15         239  
  5         31  
663 5 50       88 push(@triples, $self->__ModifyTemplate( $graph ));
664 0         0 }
665            
666             return @triples;
667 5         147 }
668 5         31  
669 5         44 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   48 $self->_TriplesBlock;
675 18 100       73 (my $cont, undef) = $self->_pop_pattern_container; # ignore hints in a modify template
676 9 50       34 my ($bgp) = @{ $cont };
677 9         37 my @triples = @{ $bgp->triples };
678             if ($graph) {
679             @triples = map { $_->as_quad_pattern($graph) } @triples;
680             }
681 9     9   21
682 9         20 return @triples;
683             } else {
684 9         22 $self->_GraphGraphPattern;
685 9         39
686 9         56 {
687             my (@d) = splice(@{ $self->{_stack} });
688             $self->__handle_GraphPatternNotTriples( @d );
689 9         42 }
690            
691             my $data = $self->_remove_pattern;
692             my $graph = $data->graph;
693 9     9   21 my @bgps = $data->subpatterns_of_type('Attean::Algebra::BGP');
694 9         20 my @triples = map { $_->as_quad_pattern($graph) } map { @{ $_->triples } } @bgps;
695 9         66 return @triples;
696 9 50       36 }
697 9         55 }
698 9         38  
699 9         37 my $self = shift;
700 9         21 $self->_expected_token(KEYWORD, 'LOAD');
  9         22  
701 9         23 my $silent = $self->_optional_token(KEYWORD, 'SILENT') ? 1 : 0;
  9         56  
702 9 50       37 $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         77 $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   17 }
800 6 100       22 my $pattern = Attean::Algebra::Add->new( %args );
801 3         20 $self->_add_patterns( $pattern );
802             $self->{build}{method} = 'UPDATE';
803 3         12 }
804 3         20  
805 3         13 # [5] SelectQuery ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( Var+ | '*' ) DatasetClause* WhereClause SolutionModifier
  3         10  
806 3         16 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   7 }
812 3         6
813 3         16 my ($star, $exprs, $vars) = $self->__SelectVars;
814 3 50       16 my @exprs = @$exprs;
815            
816 3         15 $self->_DatasetClause();
817 3 50       19
    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       18 # $self->_Var;
824 3         10 # push( @vars, splice(@{ $self->{_stack} }));
825             my $parens = 0;
826 3         14 if ($self->_optional_token(NIL)) {
827 3 50       14 $parens = 1;
828 0         0 } else {
829             if ($self->_optional_token(LPAREN)) {
830 3         77 $parens = 1;
831 3         24 }
832 3         23 while ($self->_test_token(VAR)) {
833             $self->_Var;
834             push( @vars, splice(@{ $self->{_stack} }));
835             }
836             if ($parens) {
837 28     28   75 $self->_expected_token(RPAREN);
838 28 50       152 }
    100          
839 0         0 }
840            
841 1         6 my $count = scalar(@vars);
842             if (not($parens) and $count == 0) {
843             croak "Syntax error: Expected VAR in inline data declaration";
844 28         159 } elsif (not($parens) and $count > 1) {
845 28         79 croak "Syntax error: Inline data declaration can only have one variable when parens are omitted";
846             }
847 28         121
848             my $short = (not($parens) and $count == 1);
849 28         132 $self->_expected_token(LBRACE);
850 28         132 if ($self->_optional_token(NIL)) {
851            
852 28 100       110 } else {
853 1         2 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       4 }
858 0         0 } else {
859             while ($self->_BindingValue_test) {
860 1 50       6 $self->_BindingValue;
861 1         3 my ($term) = splice(@{ $self->{_stack} });
862             push( @{ $self->{build}{bindings}{terms} }, [$term] );
863 1         6 }
864 1         8 }
865 1         2 }
  1         6  
866            
867 1 50       6 $self->_expected_token(RBRACE);
868 1         5  
869             my $bindings = delete $self->{build}{bindings};
870             my @rows = @{ $bindings->{terms} || [] };
871             my @vbs;
872 1         4 foreach my $r (@rows) {
873 1 50 33     13 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         6 push(@vbs, $r);
881 1 50       5 }
882             my $table = Attean::Algebra::Table->new( variables => \@vars, rows => \@vbs );
883             my $pattern = pop(@{ $self->{build}{triples} });
884 1 50 0     8 push(@{ $self->{build}{triples} }, $self->_new_join($pattern, $table));
      33        
885 1         5 }
886 2         17
887 2         5 my %projected = map { $_ => 1 } $self->__solution_modifiers( $star, @exprs );
  2         16  
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         6 if ($self->_test_token(STAR)) {
899             $self->{build}{star}++;
900 1         4 $self->_expected_token(STAR);
901 1 50       3 $star = 1;
  1         6  
902 1         2 $count++;
903 1         3 last;
904 2         7 } else {
905 2         4 my @s = $self->__SelectVar;
  2         11  
906 2 50       15 if (scalar(@s) > 1) {
907 2         22 my ($var, $expr) = @s;
908             push(@exprs, $var->value, $expr);
909             } else {
910 2         34 my $var = $s[0];
911 2         64 push(@exprs, $var->value, $var);
912             }
913 1         14 push(@vars, shift(@s));
914 1         3 $count++;
  1         7  
915 1         3 }
  1         9  
916             }
917            
918 28         190 my %seen;
  45         163  
919 27         90 foreach my $v (@vars) {
920 27         123 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   70 }
925 29         56 }
926 29         62 }
927 29         56
928 29         56 $self->{build}{variables} = \@vars;
929 29   100     98 if ($count == 0) {
930 32 100       110 croak "Syntax error: No select variable or expression specified";
931 25         123 }
932 25         106 return $star, \@exprs, \@vars;
933 25         82 }
934 25         61  
935 25         50 my $self = shift;
936             $self->_expected_token(LPAREN);
937 7         43 $self->_Expression;
938 7 100       21 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         11 $self->_expected_token(RPAREN);
943 6         24
944             return ($var, $expr);
945 7         16 }
946 7         17  
947             my $self = shift;
948             local($self->{__aggregate_call_ok}) = 1;
949             # return 1 if $self->_BuiltInCall_test;
950 29         68 return 1 if $self->_test_token(LPAREN);
951 29         80 return $self->_test_token(VAR);
952 7 50       39 }
953 7         137  
954 7 50       27 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         113 $self->_Var;
961 29 50       104 my ($var) = splice(@{ $self->{_stack} });
962 0         0 return $var;
963             }
964 29         131 }
965              
966             # [6] ConstructQuery ::= 'CONSTRUCT' ConstructTemplate DatasetClause* WhereClause SolutionModifier
967             my $self = shift;
968 3     3   9 my $shortcut = 1;
969 3         13 if ($self->_test_token(LBRACE)) {
970 3         22 $shortcut = 0;
971 3         23 $self->_ConstructTemplate;
  3         17  
972 3         14 }
973 3         12 $self->_DatasetClause();
974 3         10 if ($shortcut) {
  3         15  
975 3         14 $self->_TriplesWhereClause;
976             } else {
977 3         11 $self->_WhereClause;
978             }
979            
980             $self->_SolutionModifier();
981 11     11   20
982 11         32 my $pattern = $self->{build}{triples}[0];
983             my $triples = delete $self->{build}{construct_triples};
984 11 100       25 if (blessed($triples) and $triples->isa('Attean::Algebra::BGP')) {
985 10         28 $triples = $triples->triples;
986             }
987             # my @triples;
988             # warn $triples;
989 7     7   13 # foreach my $t (@{ $triples // [] }) {
990 7         21 # if ($t->isa('Attean::Algebra::BGP')) {
991 7 100       25 # push(@triples, @{ $t->triples });
992 1         5 # } else {
993 1         4 # push(@triples, $t);
994             # }
995 6         26 # }
996 6         15 my $construct = Attean::Algebra::Construct->new( children => [$pattern], triples => $triples );
  6         14  
997 6         23 $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         9 my $star = 0;
1005 3 50       14 if ($self->_optional_token(STAR)) {
1006 3         11 $star = 1;
1007 3         22 $self->{build}{variables} = ['*'];
1008             } else {
1009 3         19 $self->_VarOrIRIref;
1010 3 50       13 while ($self->_VarOrIRIref_test) {
1011 0         0 $self->_VarOrIRIref;
1012             }
1013 3         16 $self->{build}{variables} = [ splice(@{ $self->{_stack} }) ];
1014             }
1015            
1016 3         20 $self->_DatasetClause();
1017            
1018 3         13 if ($self->_WhereClause_test) {
1019 3         11 $self->_WhereClause;
1020 3 50 33     20 } 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         64  
1033 3         14 # [8] AskQuery ::= 'ASK' DatasetClause* WhereClause
1034 3         14 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       5
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         8 }
1047 1         6  
1048 0         0 # sub _DatasetClause_test {
1049             # my $self = shift;
1050 1         4 # return $self->_test_token(KEYWORD, 'FROM');
  1         9  
1051             # }
1052              
1053 1         7 # [9] DatasetClause ::= 'FROM' ( DefaultGraphClause | NamedGraphClause )
1054             my $self = shift;
1055 1 50       6
1056 1         7 # 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         7 $self->_DefaultGraphClause;
1063 1         6 }
1064             }
1065 1         5 }
1066 1 50       8  
  0         0  
1067 1         41 # [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   8 }
1073              
1074 4         20 # [11] NamedGraphClause ::= 'NAMED' SourceSelector
1075             my $self = shift;
1076 4         21 $self->_expected_token(KEYWORD, 'NAMED');
1077             $self->_SourceSelector;
1078 4         17 my ($source) = splice(@{ $self->{_stack} });
1079 4         43 push( @{ $self->{build}{sources} }, [$source, 'NAMED'] );
1080             }
1081              
1082 4         13 # [12] SourceSelector ::= IRIref
1083 4         91 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   79 my $self = shift;
1094             $self->_optional_token(KEYWORD, 'WHERE');
1095             $self->_GroupGraphPattern;
1096 36         111
1097 36         127 my $ggp = $self->_peek_pattern;
1098 8 100       38 $self->_check_duplicate_blanks($ggp);
1099 4         22 }
1100              
1101 4         27 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   14 # foreach my $b (@blanks) {
1109 4         22 # my $id = $b->value;
1110 4         18 # if ($seen{ $id }++) {
  4         15  
1111 4         13 # warn $ggp->as_string;
  4         28  
1112             # croak "Same blank node identifier ($id) used in more than one BasicGraphPattern.";
1113             # }
1114             # }
1115             # }
1116 4     4   11 return 1;
1117 4         21 }
1118 4         21  
1119 4         21 my $self = shift;
  4         18  
1120 4         8 $self->_push_pattern_container;
  4         90  
1121            
1122             $self->_expected_token(KEYWORD, 'WHERE');
1123             $self->_expected_token(LBRACE);
1124             if ($self->_TriplesBlock_test) {
1125 8     8   20 $self->_TriplesBlock;
1126 8         43 }
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   84 # sub _Binding_test {
1138 37         133 # my $self = shift;
1139 37         211 # return $self->_test_token(LPAREN);
1140             # }
1141 37         204  
1142 37         158 my $self = shift;
1143             my $count = shift;
1144            
1145             $self->_expected_token(LPAREN);
1146 39     39   74
1147 39         81 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         79 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   6  
1190 2         6 my $self = shift;
1191             if ($self->_optional_token(LPAREN)) {
1192 2         8 $self->_Expression;
1193             my ($expr) = splice(@{ $self->{_stack} });
1194 2         5 if ($self->_optional_token(KEYWORD, 'AS')) {
1195 2         14 $self->_Var;
1196 2 50       10 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         10 } else {
1201 2         6 $self->_add_stack( $expr );
  2         13  
1202             }
1203 2         11 $self->_expected_token(RPAREN);
1204 2         8
1205             } elsif ($self->_IRIref_test) {
1206             $self->_FunctionCall;
1207             } elsif ($self->_BuiltInCall_test) {
1208 6     6   10 $self->_BuiltInCall;
1209 6 100       22 } else {
1210 4 50       21 $self->_Var;
1211 4 100       24 my $var = pop(@{ $self->{_stack} });
1212 1 50       3 my $expr = Attean::ValueExpression->new(value => $var);
1213 1 50       6 $self->_add_stack($expr);
1214 1 50       4 }
1215 1 50       4 }
1216 1         3  
1217             # [14] SolutionModifier ::= OrderClause? LimitOffsetClauses?
1218             my $self = shift;
1219             my $vars = shift // [];
1220 5     5   8
1221 5 50       18 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         23 $self->_RankClause;
1227             }
1228            
1229             if ($self->_test_token(KEYWORD, 'HAVING')) {
1230             $self->_HavingClause;
1231             }
1232 2     2   7
1233 2 50       8 if ($self->_OrderClause_test) {
1234 2 50       12 $self->_OrderClause;
1235 2 50       10 }
1236 2 50       12
1237 2         16 if ($self->_LimitOffsetClauses_test) {
1238             $self->_LimitOffsetClauses;
1239             }
1240             }
1241 2     2   6  
1242 2 50       7 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         14  
1262 2         7 my %seen;
  2         10  
1263 2         39 foreach my $v (@vars) {
1264 2         8 my $var = $v->value;
1265             if ($var->does('Attean::API::Variable')) {
1266             my $name = $var->value;
1267             $seen{ $name }++;
1268             }
1269             }
1270 33     33   65
1271 33   100     107 # warn 'TODO: verify that projection only includes aggregates and grouping variables'; # XXXXX
1272             # foreach my $v (@$vars) {
1273 33 100       104 # 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       146 # # throw ::Error::ParseError -text => "Syntax error: Variable used in projection but not present in aggregate grouping ($name)";
1278 1         4 # }
1279             # }
1280             # }
1281 33 100       130
1282 1         5 $self->{build}{__group_by} = \@vars;
1283             }
1284              
1285 33 100       187 my $self = shift;
1286 1         4 $self->_expected_token(KEYWORD, 'RANK');
1287             $self->_expected_token(LPAREN);
1288             $self->_OrderCondition;
1289 33 100       165 my @order;
1290 2         9 push(@order, splice(@{ $self->{_stack} }));
1291             while ($self->_OrderCondition_test) {
1292             $self->_OrderCondition;
1293             push(@order, splice(@{ $self->{_stack} }));
1294             }
1295 2     2   5 $self->_expected_token(RPAREN);
1296 2         5 $self->_expected_token(KEYWORD, 'AS');
1297 2         8 $self->_Var;
1298 2         7 my ($var) = splice(@{ $self->{_stack} });
1299            
1300 2 50       22 my @exprs;
1301 0         0 my %ascending;
1302             foreach my $o (@order) {
1303             my ($dir, $expr) = @$o;
1304 2   100     17 push(@exprs, $expr);
1305 2         4 $ascending{ $expr->value->value } = ($dir eq 'ASC') ? 1 : 0; # TODO: support ranking by complex expressions, not just variables
1306 2         11 }
1307 2         8 my $r = Attean::AggregateExpression->new(
  2         6  
1308 2         5 distinct => 0,
1309 2         9 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         6
1317 2         7 $self->{build}{__aggregate}{ $var->value } = [ $var, $r ];
1318 2 50       10 }
1319 2         40  
1320 2         6 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         9  
1336             my $self = shift;
1337             if ($self->_LimitClause_test) {
1338             $self->_LimitClause;
1339 1     1   3 if ($self->_OffsetClause_test) {
1340 1         6 $self->_OffsetClause;
1341 1         5 }
1342 1         10 } else {
1343 1         2 $self->_OffsetClause;
1344 1         3 if ($self->_LimitClause_test) {
  1         4  
1345 1         4 $self->_LimitClause;
1346 0         0 }
1347 0         0 }
  0         0  
1348             }
1349 1         33  
1350 1         4 # [16] OrderClause ::= 'ORDER' 'BY' OrderCondition+
1351 1         3 my $self = shift;
1352 1         2 return 1 if ($self->_test_token(KEYWORD, 'ORDER'));
  1         4  
1353             return 0;
1354 1         4 }
1355              
1356 1         4 my $self = shift;
1357 1         4 $self->_expected_token(KEYWORD, 'ORDER');
1358 1         2 $self->_expected_token(KEYWORD, 'BY');
1359 1 50       7 my @order;
1360             $self->{build}{__aggregate} ||= {};
1361 1         13 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         22 # [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   4 return 1 if ($self->_test_token(VAR));
1376 1         5 return 1 if $self->_Constraint_test;
1377 1   50     5 return 0;
1378 1         4 }
1379 1         4  
1380 1         2 my $self = shift;
  1         3  
1381 1         5 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   66 $self->_Var;
1387 33 100       95 my $var = pop(@{ $self->{_stack} });
1388 32 100       134 my $expr = Attean::ValueExpression->new(value => $var);
1389 31         142 $self->_add_stack($expr);
1390             } else {
1391             $self->_Constraint;
1392             }
1393 2     2   3 my ($expr) = splice(@{ $self->{_stack} });
1394 2 100       9 $self->_add_stack( [ $dir, $expr ] );
1395 1         7 }
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       3  
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   81 my $self = shift;
1410 33 100       108 return ($self->_test_token(KEYWORD, 'OFFSET'));
1411 32         117 }
1412              
1413             my $self = shift;
1414             $self->_expected_token(KEYWORD, 'OFFSET');
1415 1     1   2 my $t = $self->_expected_token(INTEGER);
1416 1         6 $self->{build}{options}{offset} = $t->value;
1417 1         10 }
1418 1         2  
1419 1   50     10 # [20] GroupGraphPattern ::= '{' TriplesBlock? ( ( GraphPatternNotTriples | Filter ) '.'? TriplesBlock? )* '}'
1420 1         3 my $self = shift;
1421 1         6
1422 1         3 $self->_expected_token(LBRACE);
  1         3  
1423 1         7
1424 0         0 if ($self->_SubSelect_test) {
1425 0         0 $self->_SubSelect;
  0         0  
1426             } else {
1427 1         5 $self->_GroupGraphPatternSub;
1428             }
1429              
1430             $self->_expected_token(RBRACE);
1431             }
1432 2     2   6  
1433 2 50       8 my $self = shift;
1434 2 50       10 $self->_push_pattern_container;
1435 2 50       9
1436 2 50       11 my $got_pattern = 0;
1437 2         8 my $need_dot = 0;
1438             if ($self->_TriplesBlock_test) {
1439             $need_dot = 1;
1440             $got_pattern++;
1441 2     2   5 $self->_TriplesBlock;
1442 2         7 }
1443 2 100       11
    50          
1444 1         4 while (not $self->_test_token(RBRACE)) {
1445 1         6 my $cur = $self->_peek_token;
1446             if ($self->_GraphPatternNotTriples_test) {
1447 1         4 $need_dot = 0;
1448 1         5 $got_pattern++;
  1         4  
1449 1         24 $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         8 $need_dot = 0;
  2         7  
1455 2         10 $self->_Filter;
1456             }
1457            
1458             if ($need_dot or $self->_test_token(DOT)) {
1459             $self->_expected_token(DOT);
1460 3     3   5 if ($got_pattern) {
1461 3         8 $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         2 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   5 my $merged = $self->_new_join($lhs, $rhs);
1479 2         8 $self->_add_patterns( $merged );
1480 2         6 }
1481 2         9 } else {
1482             $self->_TriplesBlock;
1483             }
1484             }
1485              
1486 54     54   114 my $t = $self->_peek_token;
1487             last if (refaddr($t) == refaddr($cur));
1488 54         193 }
1489             my ($cont, $hints) = $self->_pop_pattern_container;
1490 54 100       261  
1491 1         11 my @filters = splice(@{ $self->{filters} });
1492             my @patterns;
1493 53         269 my $pattern = $self->_new_join(@$cont);
1494             $pattern->hints($hints);
1495             if (@filters) {
1496 54         203 while (my $f = shift @filters) {
1497             $pattern = Attean::Algebra::Filter->new( children => [$pattern], expression => $f );
1498             }
1499             }
1500 53     53   123 $self->_add_patterns( $pattern );
1501 53         235 }
1502              
1503 53         107 my $self = shift;
1504 53         98 my $data = shift;
1505 53 100       236 return unless ($data);
1506 41         86 my ($class, @args) = @$data;
1507 41         88 if ($class =~ /^Attean::Algebra::(LeftJoin|Minus)$/) {
1508 41         172 my ($cont, $hints) = $self->_pop_pattern_container;
1509             my $ggp = $self->_new_join(@$cont);
1510             $ggp->hints($hints);
1511 53         198 $self->_push_pattern_container;
1512 17         54 # my $ggp = $self->_remove_pattern();
1513 17 100       79 unless ($ggp) {
    50          
1514 13         38 $ggp = Attean::Algebra::BGP->new();
1515 13         26 }
1516 13         56
1517 13         36 my $opt = $class->new( children => [$ggp, @args] );
  13         48  
1518 13         72 $self->_add_patterns( $opt );
1519             } elsif ($class eq 'Attean::Algebra::Table') {
1520 4         7 my ($table) = @args;
1521 4         11 $self->_add_patterns( $table );
1522 4         17 } 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     111 $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       81 }
1536 1         5 my $bind = Attean::Algebra::Extend->new( children => [$ggp], variable => $var, expression => $expr );
1537 1 50 33     8 $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         6 } elsif ($class =~ /Attean::Algebra::(Union|Graph|Join)$/) {
1550             # no-op
1551             } else {
1552             croak 'Unrecognized GraphPattern: ' . $class;
1553 17         70 }
1554 17 50       204 }
1555              
1556 53         203 my $self = shift;
1557             return $self->_test_token(KEYWORD, 'SELECT');
1558 53         115 }
  53         195  
1559 53         111  
1560 53         185 my $self = shift;
1561 53         975 my $pattern;
1562 53 100       1661 {
1563 4         14 local($self->{namespaces}) = $self->{namespaces};
1564 4         81 local($self->{_stack}) = [];
1565             local($self->{filters}) = [];
1566             local($self->{_pattern_container_stack}) = [];
1567 53         198  
1568             my $triples = $self->_push_pattern_container();
1569             local($self->{build}) = { triples => $triples};
1570             if ($self->{baseURI}) {
1571 13     13   29 $self->{build}{base} = $self->{baseURI};
1572 13         25 }
1573 13 100       57
1574 12         42 $self->_expected_token(KEYWORD, 'SELECT');
1575 12 100       157 if (my $t = $self->_optional_token(KEYWORD, qr/^(DISTINCT|REDUCED)/)) {
    50          
    100          
    100          
    50          
1576 2         9 my $mod = $t->value;
1577 2         12 $self->{build}{options}{lc($mod)} = 1;
1578 2         38 }
1579 2         56
1580             my ($star, $exprs, $vars) = $self->__SelectVars;
1581 2 50       8 my @exprs = @$exprs;
1582 0         0
1583             $self->_WhereClause;
1584             $self->_SolutionModifier($vars);
1585 2         30
1586 2         16 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         11 my @cmps;
1592 2         10 foreach my $o (@order) {
1593 2         42 my ($dir, $expr) = @$o;
1594 2         80 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         6 }
1600 2         14
  1         65  
1601 2 50       56 if ($self->_optional_token(KEYWORD, 'VALUES')) {
1602 0         0 my @vars;
1603             my $parens = 0;
1604 2         30 if ($self->_optional_token(LPAREN)) {
1605 2         19 $parens = 1;
1606             }
1607 2         6 while ($self->_test_token(VAR)) {
1608 2 50       17 $self->_Var;
1609             push( @vars, splice(@{ $self->{_stack} }));
1610 0         0 }
1611             if ($parens) {
1612             $self->_expected_token(RPAREN);
1613             }
1614 2         135 my $count = scalar(@vars);
1615 2         12 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   118 my $terms = $self->_Binding($count);
1626 54         178 push( @{ $self->{build}{bindings}{terms} }, $terms );
1627             }
1628             } else {
1629             while ($self->_BindingValue_test) {
1630 1     1   6 $self->_BindingValue;
1631 1         1 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         5 my $bindings = delete $self->{build}{bindings};
1640 1 50       6 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       14 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         6 }
1652             my $table = Attean::Algebra::Table->new( variables => \@vars, rows => \@vbs );
1653 1         15 my $pattern = pop(@{ $self->{build}{triples} });
1654 1         4 push(@{ $self->{build}{triples} }, $self->_new_join($pattern, $table));
1655             }
1656 1 50       7
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         9 # [22] GraphPatternNotTriples ::= OptionalGraphPattern | GroupOrUnionGraphPattern | GraphGraphPattern
1728             my $self = shift;
1729 1         3 return 1 if ($self->_test_token(LBRACE));
1730 1         3 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         4  
1736             my $self = shift;
1737             if ($self->_test_token(KEYWORD, 'VALUES')) {
1738             $self->_InlineDataClause;
1739             } elsif ($self->_test_token(KEYWORD, 'SERVICE')) {
1740 109     109   221 $self->_ServiceGraphPattern;
1741             } elsif ($self->_test_token(KEYWORD, 'MINUS')) {
1742             $self->_MinusGraphPattern;
1743             } elsif ($self->_test_token(KEYWORD, 'BIND')) {
1744 109 100       317 $self->_Bind;
1745 59 50       206 } elsif ($self->_test_token(KEYWORD, 'HINT')) {
1746 59 50       185 $self->_Hint;
1747 59 50       161 } elsif ($self->_test_token(KEYWORD, 'OPTIONAL')) {
1748 59 100       184 $self->_OptionalGraphPattern;
1749 58 50       198 } elsif ($self->_test_token(LBRACE)) {
1750 58 100       177 $self->_GroupOrUnionGraphPattern;
1751 56 100       213 } else {
1752 46 50       168 $self->_GraphGraphPattern;
1753 46         190 }
1754             }
1755              
1756             my $self = shift;
1757 84     84   148 $self->_expected_token(KEYWORD, 'VALUES');
1758 84 100       243 my @vars;
1759 74 50       203
1760 74 100       209 my $parens = 0;
1761 73 100       205 if ($self->_optional_token(LPAREN)) {
1762 72 50       218 $parens = 1;
1763 72 50       212 }
1764 72 50       193 while ($self->_test_token(VAR)) {
1765 72 50       214 $self->_Var;
1766 72         255 push( @vars, splice(@{ $self->{_stack} }));
1767             }
1768             if ($parens) {
1769             $self->_expected_token(RPAREN);
1770             }
1771 51     51   125
1772 51         161 my $count = scalar(@vars);
1773 51         214 if (not($parens) and $count == 0) {
1774 51         239 croak "Syntax error: Expected VAR in inline data declaration";
1775 51         255 } elsif (not($parens) and $count > 1) {
1776 51         927 croak "Syntax error: Inline data declaration can only have one variable when parens are omitted";
1777 51         2143 }
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   112 while ($self->_test_token(LPAREN)) {
1785 51         96 my $terms = $self->_Binding($count);
1786 51         261 push( @rows, $terms );
1787             }
1788 51         185 } else {
1789 9 50       39 # { term term }
1790 0         0 while ($self->_BindingValue_test) {
1791             $self->_BindingValue;
1792 9         40 my ($term) = splice(@{ $self->{_stack} });
1793 9         26 push( @rows, [$term] );
1794 9 50       52 }
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   37 @d{ map { $_->value } @vars } = map { $_->does('Attean::API::TriplePattern') ? $_->as_triple : $_ } @$row;
1804 17 100       45 my $result = Attean::Result->new(bindings => \%d);
1805 14         41 push(@vbs, $result);
1806 14 50       60 }
1807 14 50       63 my $table = Attean::Algebra::Table->new( variables => \@vars, rows => \@vbs );
1808 14         50 $self->_add_stack( ['Attean::Algebra::Table', $table] );
1809            
1810             }
1811              
1812 13     13   31 my $self = shift;
1813 13 50       42 $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         17 }
1817              
1818 1         6 my $self = shift;
1819             $self->_expected_token(KEYWORD, 'HINT');
1820 2         10 my $terms = $self->_HintTerms();
1821             $self->_add_hint($terms);
1822 1         5 }
1823              
1824 1         8 my $self = shift;
1825            
1826 3         18 $self->_expected_token(LPAREN);
1827            
1828 3         13 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   5 $self->__close_bgp_with_filters;
1891 2         7
1892 2         10 $self->_GroupGraphPattern;
1893 2         10 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         13 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         5 $self->_expected_token(KEYWORD, 'GRAPH');
1907             $self->_VarOrIRIref;
1908 1         2 my ($graph) = splice(@{ $self->{_stack} });
1909 1         7 if ($graph->does('Attean::API::IRI')) {
1910 3         14 $self->_GroupGraphPattern;
1911 3         5 } else {
  3         10  
1912             $self->_GroupGraphPattern;
1913 1         5 }
1914 1         4  
1915             if ($self->{__data_pattern}) {
1916             $self->{__graph_nesting_level}--;
1917             }
1918 2     2   18
1919 2         16 my $ggp = $self->_remove_pattern;
1920 2 50       20 my $pattern = Attean::Algebra::Graph->new( children => [$ggp], graph => $graph );
1921 2         13 $self->_add_patterns( $pattern );
1922 2 50       8 $self->_add_stack( [ 'Attean::Algebra::Graph' ] );
1923 0         0 }
1924              
1925 2         12 # [25] GroupOrUnionGraphPattern ::= GroupGraphPattern ( 'UNION' GroupGraphPattern )*
1926             # sub _GroupOrUnionGraphPattern_test {
1927 2         9 # my $self = shift;
  2         10  
1928 2         12 # return $self->_test_token(LBRACE);
1929 2         18 # }
1930              
1931 2 50       11 my $self = shift;
1932 2         10 $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   10 } else {
1943 4         8 $self->_add_patterns( $ggp );
  4         18  
1944 4 50       18 $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         4 }
1963 1         10  
1964             my $self = shift;
1965 1         5 if ($self->_test_token(LPAREN)) {
1966 1         5 $self->_BrackettedExpression();
1967 1         3 } elsif ($self->_BuiltInCall_test) {
1968 1         3 $self->_BuiltInCall();
1969             } else {
1970             $self->_FunctionCall();
1971             }
1972 1     1   4 }
1973 1         6  
1974 1         6 # [28] FunctionCall ::= IRIref ArgList
1975             # sub _FunctionCall_test {
1976 1         8 # my $self = shift;
1977 1         5 # return $self->_IRIref_test;
1978 1         6 # }
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   7
1985 3 50       10 }
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         13 $self->_add_stack( $expr );
1992 3         14 } else {
1993 3         11 my $func = Attean::ValueExpression->new( value => $iri );
  3         10  
1994 3 100       17 my $expr = $self->new_function_expression( 'INVOKE', $func, @args );
1995 2         68 $self->_add_stack( $expr );
1996             }
1997 1         27 }
1998              
1999             # [29] ArgList ::= ( NIL | '(' Expression ( ',' Expression )* ')' )
2000 3 50       16 my $self = shift;
2001 0         0 return 1 if $self->_test_token(NIL);
2002             return $self->_test_token(LPAREN);
2003             }
2004 3         14  
2005 3         60 my $self = shift;
2006 3         19 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   13 }
2018 3         17 }
2019 3         15 $self->_expected_token(RPAREN);
2020 3 100       10 return @args;
2021 1         8 }
2022 1         9 }
2023 1         8  
2024 1         21 # [30] ConstructTemplate ::= '{' ConstructTriples? '}'
2025             my $self = shift;
2026 1         6 $self->_push_pattern_container;
2027 1         6 $self->_expected_token(LBRACE);
2028            
2029 2         12 if ($self->_ConstructTriples_test) {
2030 2         11 $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   8 }
2037 4         15  
2038 4         23 # [31] ConstructTriples ::= TriplesSameSubject ( '.' ConstructTriples? )?
2039 4         12 my $self = shift;
  4         13  
2040 4         19 return $self->_TriplesBlock_test;
2041             }
2042              
2043             my $self = shift;
2044             $self->_TriplesSameSubject;
2045 2     2   5 while ($self->_optional_token(DOT)) {
2046 2 50       10 if ($self->_ConstructTriples_test) {
2047 2 50       9 $self->_TriplesSameSubject;
2048 2 50       8 }
2049 2         6 }
2050             }
2051              
2052             # [32] TriplesSameSubject ::= VarOrTerm PropertyListNotEmpty | TriplesNode PropertyList
2053 5     5   15 my $self = shift;
2054 5 100       14 my @triples;
    50          
2055 4         20 if ($self->_TriplesNode_test) {
2056             $self->_TriplesNode;
2057 0         0 my ($s) = splice(@{ $self->{_stack} });
2058             $self->_PropertyList;
2059 1         4 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         5 }
2072 1         5 }
  1         3  
2073 1 50       7
2074             $self->_add_patterns( @triples );
2075             # return @triples;
2076             }
2077 1         5  
2078             # TriplesSameSubjectPath ::= VarOrTerm PropertyListNotEmptyPath | TriplesNode PropertyListPath
2079 1 50       7 my $self = shift;
2080 0         0 my @triples;
2081 0         0 if ($self->_TriplesNode_test) {
2082             $self->_TriplesNode;
2083 1         15 my ($s) = splice(@{ $self->{_stack} });
2084 1         6 $self->_PropertyListPath;
2085 1         20 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   5 my ($s) = splice(@{ $self->{_stack} });
2092 1 50       4 $self->_PropertyListNotEmptyPath;
2093 1         4 my (@list) = splice(@{ $self->{_stack} });
2094             foreach my $data (@list) {
2095             push(@triples, $self->__new_statement( $s, @$data ));
2096             }
2097 2     2   5 }
2098 2 50       6 $self->_add_patterns( @triples );
2099 0         0 # return @triples;
2100             }
2101 2         8  
2102 2         3 # [33] PropertyListNotEmpty ::= Verb ObjectList ( ';' ( Verb ObjectList )? )*
2103 2 50       8 my $self = shift;
2104 2         9 $self->_Verb;
2105 2         4 my ($v) = splice(@{ $self->{_stack} });
  2         7  
2106 2         9 $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         8 $self->_Verb;
2112 2         10 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   10 $self->_add_stack( @props );
2119 3         17 }
2120 3         18  
2121             # [34] PropertyList ::= PropertyListNotEmpty?
2122 3 50       25 my $self = shift;
2123 3         15 if ($self->_Verb_test) {
2124             $self->_PropertyListNotEmpty;
2125             }
2126 3         17 }
2127 3         20  
2128 3         15 # [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         15 }
2135             my ($v) = splice(@{ $self->{_stack} });
2136             $self->_ObjectList;
2137             my @l = splice(@{ $self->{_stack} });
2138 3     3   9 my @props = map { [$v, $_] } @l;
2139 3         17 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   10 push(@props, map { [$v, $_] } @l);
2150 3         6 }
2151 3 100       15 }
2152 1         7 $self->_add_stack( @props );
2153 1         3 }
  1         4  
2154 1         5  
2155 1         3 # [34] PropertyListPath ::= PropertyListNotEmptyPath?
  1         5  
2156 1         3 my $self = shift;
2157 0         0 if ($self->_Verb_test) {
2158             $self->_PropertyListNotEmptyPath;
2159             }
2160 2         12 }
2161 2         7  
  2         9  
2162             # [35] ObjectList ::= Object ( ',' Object )*
2163 2         12 my $self = shift;
2164 2         5
  2         8  
2165 2         9 my @list;
2166 2         14 $self->_Object;
2167             push(@list, splice(@{ $self->{_stack} }));
2168            
2169             while ($self->_optional_token(COMMA)) {
2170 3         22 $self->_Object;
2171             push(@list, splice(@{ $self->{_stack} }));
2172             }
2173             $self->_add_stack( @list );
2174             }
2175              
2176 51     51   96 # [36] Object ::= GraphNode
2177 51         126 my $self = shift;
2178 51 50       262 $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         282 $self->_expected_token(RANNOT)
2188 51         159 }
  51         170  
2189 51         273 }
2190 51         131  
  51         150  
2191 51         221 # [37] Verb ::= VarOrIRIref | 'a'
2192 59         254 my $self = shift;
2193             return 1 if ($self->_test_token(A));
2194             return 1 if ($self->_test_token(VAR));
2195 51         600 return 1 if ($self->_IRIref_test);
2196             return 0;
2197             }
2198              
2199             my $self = shift;
2200             if ($self->_optional_token(A)) {
2201 2     2   7 my $type = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', lazy => 1);
2202 2         12 $self->_add_stack( $type );
2203 2         6 } else {
  2         10  
2204 2         12 $self->_VarOrIRIref;
2205 2         4 }
  2         10  
2206 2         8 }
  2         9  
2207 2         10  
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         16 }
2217              
2218             # VerbPath ::= Path
2219             my $self = shift;
2220             return 1 if ($self->_IRIref_test);
2221 1     1   1 return 1 if ($self->_test_token(HAT));
2222 1 50       6 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   110 my $self = shift;
2230 53 100       228 $self->_Path
2231 25         112 }
2232              
2233 28         116 # [74] Path ::= PathAlternative
2234             my $self = shift;
2235 53         139 $self->_PathAlternative;
  53         184  
2236 53         277 }
2237 53         137  
  53         171  
2238 53         159 ################################################################################
  53         248  
2239 53         180  
2240 9 50 33     50 # [75] PathAlternative ::= PathSequence ( '|' PathSequence )*
2241 9 50       28 my $self = shift;
2242 9         38 $self->_PathSequence;
2243             while ($self->_optional_token(OR)) {
2244 0         0 my ($lhs) = splice(@{ $self->{_stack} });
2245             # $self->_PathOneInPropertyClass;
2246 9         26 $self->_PathSequence;
  9         31  
2247 9         33 my ($rhs) = splice(@{ $self->{_stack} });
2248 9         33 $self->_add_stack( ['PATH', '|', $lhs, $rhs] );
  9         26  
2249 9         24 }
  9         43  
2250             }
2251              
2252 53         842 # [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   130 }
2266             my ($rhs) = splice(@{ $self->{_stack} });
2267 64         115 $self->_add_stack( ['PATH', $op, $lhs, $rhs] );
2268 64         230 }
2269 64         160 }
  64         215  
2270              
2271 64         232 # [77] PathElt ::= PathPrimary PathMod?
2272 0         0 my $self = shift;
2273 0         0 $self->_PathPrimary;
  0         0  
2274             # $self->__consume_ws_opt;
2275 64         219 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   113 $self->_add_stack( ['PATH', $mod, @path] );
2281 64         278 } else {
2282 64 100       760 # 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         3 # signed numeric object that follows the path
  2         6  
2285 2         10 $self->_add_stack( @path );
2286 2         7 }
  2         5  
2287 2         30 }
2288 2         1676 }
2289              
2290 2         6 # [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       33 $self->_PathElt;
2298 14 100       27 }
2299 13 100       32 }
2300 3         10  
2301             # [79] PathMod ::= ( '*' | '?' | '+' | '{' ( Integer ( ',' ( '}' | Integer '}' ) | '}' ) ) )
2302             my $self = shift;
2303             return 1 if ($self->_test_token(STAR));
2304 19     19   29 return 1 if ($self->_test_token(QUESTION));
2305 19 100       55 return 1 if ($self->_test_token(PLUS));
2306 6         100 return 1 if ($self->_test_token(LBRACE));
2307 6         490 return 0;
2308             }
2309 13         38  
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   81 }
2321 28         96 $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   140 # my $value = 0;
2327 71 100       245 # if ($self->_test(qr/}/)) {
2328 35 50       133 # throw ::Error::ParseError -text => "Syntax error: Empty Path Modifier";
2329 35 50       127 # }
2330 35 50       118 # if ($self->_test($r_INTEGER)) {
2331 35 50       119 # $value = $self->_eat( $r_INTEGER );
2332 35 100       111 # $self->__consume_ws_opt;
2333 28         112 # }
2334             # if ($self->_test(qr/,/)) {
2335             # $self->_eat(qr/,/);
2336             # $self->__consume_ws_opt;
2337 34     34   85 # if ($self->_test(qr/}/)) {
2338 34         98 # $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   55 # $self->_eat(qr/}/);
2344 34         153 # $self->_add_stack( "$value-$end" );
2345             # }
2346             # } else {
2347             # $self->_eat(qr/}/);
2348             # $self->_add_stack( "$value" );
2349             # }
2350             }
2351 34     34   62 }
2352 34         117  
2353 34         99 # [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         121 $self->_expected_token(LPAREN);
2366 34   66     89 $self->_Path;
2367 1         2 $self->_expected_token(RPAREN);
2368 1         2 }
  1         4  
2369 1 50       3 }
2370 1         3  
2371 1         2 # [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         4 $self->_PathOneInPropertyClass;
  1         3  
2378 1         4 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   65 }
2385 35         146 $self->_expected_token(RPAREN);
2386             $self->_add_stack( @nodes );
2387 35 100       159 } else {
2388 1         3 $self->_PathOneInPropertyClass;
  1         5  
2389 1         4 }
2390 1         3 }
  1         6  
2391 1 50       3  
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   55 }
2405 35 50       110 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         126 $self->_add_stack( $type );
2411             }
2412             } else {
2413             $self->_IRIref;
2414             if ($rev) {
2415             my ($path) = splice(@{ $self->{_stack} });
2416 35     35   82 $self->_add_stack( [ 'PATH', '^', $path ] );
2417 35 100       98 }
2418 34 50       116 }
2419 34 50       99 }
2420 34 50       91  
2421 34         116 ################################################################################
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     4 return 1 if $self->_test_token(LBRACKET);
      33        
2427 1         3 return 0;
2428 1         11 }
2429 1 50       5  
    0          
2430 1         3 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   60 my $cur = $subj;
2471 35 100       88 my $last;
    50          
    0          
2472 29         119  
2473             my $first = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', lazy => 1);
2474 6         122 my $rest = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', lazy => 1);
2475 6         494 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   225 }
2545 122 100       292 }
2546 121 50       339  
2547 121         372 # [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       3 }
2553 1         8  
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         4 my $b = $self->_BlankNode;
2582 1         4 $self->_add_stack( $b );
2583 1         2 } 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         4  
2584             my $l = $self->_NumericLiteral;
2585 1         4 $self->_add_stack( $l );
2586 1         4 } elsif ($self->_test_literal_token) {
2587 1         4 my $l = $self->_RDFLiteral;
  1         5  
2588             $self->_add_stack( $l );
2589             } else {
2590 1         4 $self->_IRIref;
2591             }
2592 1         14 }
2593 1         63  
2594 1         2 # [46] Expression ::= ConditionalOrExpression
2595             my $self = shift;
2596 1         19 $self->_ConditionalOrExpression;
2597 1         83 }
2598 1         76  
2599             # [47] ConditionalOrExpression ::= ConditionalAndExpression ( '||' ConditionalAndExpression )*
2600             my $self = shift;
2601 1         55 my @list;
2602 1         3
2603 2         17 $self->_ConditionalAndExpression;
2604 2         30 push(@list, splice(@{ $self->{_stack} }));
2605 2         100
2606 2         5 while ($self->_test_token(OROR)) {
2607 2         3 $self->_expected_token(OROR);
2608             $self->_ConditionalAndExpression;
2609 1         4 push(@list, splice(@{ $self->{_stack} }));
2610 1         4 }
2611 1         4
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   5 } else {
2619             $self->_add_stack( @list );
2620             }
2621             if (scalar(@{ $self->{_stack} }) == 0) {
2622 2 100       6 my $t = $self->_peek_token;
2623 1 50       4 $self->_token_error($t, "Missing conditional expression");
2624 1 50       4 }
2625 1 50       5 }
2626 1 50       4  
2627 1 50       5 # [48] ConditionalAndExpression ::= ValueLogical ( '&&' ValueLogical )*
2628 1 50       5 my $self = shift;
2629 1 50       4 $self->_ValueLogical;
2630 1         4 my @list = splice(@{ $self->{_stack} });
2631            
2632             while ($self->_test_token(ANDAND)) {
2633             $self->_expected_token(ANDAND);
2634 68     68   146 $self->_ValueLogical;
2635 68 50       238 push(@list, splice(@{ $self->{_stack} }));
2636 0         0 }
2637            
2638 68         211 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   241 my $self = shift;
2656 121 100       282 $self->_NumericExpression;
    100          
2657 81         323
2658             my $t = $self->_peek_token;
2659 3         15 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         133 $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   10 $self->_add_stack( $self->new_binary_expression( $op, @list ) );
2667 6 100       22 } 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         31 $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       7 }
2678 1 50       4  
2679 1         6 my $self = shift;
2680             if ($self->_optional_token(NIL)) {
2681             return;
2682             } else {
2683 17     17   33 $self->_expected_token(LPAREN);
2684 17 100       41 my @args;
2685 5         23 unless ($self->_test_token(RPAREN)) {
2686             $self->_Expression;
2687 12         43 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   243 $self->_expected_token(RPAREN);
2694 141 50       390 $self->_add_stack( @args );
2695 0         0 }
2696             }
2697              
2698 141         393 # [51] NumericExpression ::= AdditiveExpression
2699 141         583 my $self = shift;
2700             $self->_AdditiveExpression;
2701             }
2702              
2703             # [52] AdditiveExpression ::= MultiplicativeExpression ( '+' MultiplicativeExpression | '-' MultiplicativeExpression | NumericLiteralPositive | NumericLiteralNegative )*
2704 46     46   93 my $self = shift;
2705 46 50 33     137 $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         6 my $t = $self->_next_token;
2710 1         98 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         62 $self->_add_stack( $expr );
2716 11         64 }
2717              
2718 9         60 # [53] MultiplicativeExpression ::= UnaryExpression ( '*' UnaryExpression | '/' UnaryExpression )*
2719 9         40 my $self = shift;
2720             $self->_UnaryExpression;
2721 25         87 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   20 my ($rhs) = splice(@{ $self->{_stack} });
2728 11         38 $expr = $self->new_binary_expression( $op, $expr, $rhs );
2729             }
2730             $self->_add_stack( $expr );
2731             }
2732              
2733 11     11   23 # [54] UnaryExpression ::= '!' PrimaryExpression | '+' PrimaryExpression | '-' PrimaryExpression | PrimaryExpression
2734 11         18 my $self = shift;
2735             if ($self->_optional_token(BANG)) {
2736 11         41 $self->_PrimaryExpression;
2737 11         28 my ($expr) = splice(@{ $self->{_stack} });
  11         28  
2738             my $not = Attean::UnaryExpression->new( operator => '!', children => [$expr] );
2739 11         38 $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       60 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         28 $self->_add_stack( $lexpr );
2753             }
2754 11 50       22 } elsif ($self->_optional_token(MINUS)) {
  11         40  
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   20 my $lexpr = Attean::ValueExpression->new( value => $l );
2763 11         39 $self->_add_stack( $lexpr );
2764 11         31 } else {
  11         34  
2765             my $int = 'http://www.w3.org/2001/XMLSchema#integer';
2766 11         35 my $l = Attean::Literal->new( value => '-1', datatype => $int );
2767 1         51 my $neg = $self->new_binary_expression( '*', Attean::ValueExpression->new( value => $l ), $expr );
2768 1         4 my $lexpr = Attean::ValueExpression->new( value => $neg );
2769 1         3 $self->_add_stack( $lexpr );
  1         4  
2770             }
2771             } else {
2772 11 100       49 $self->_PrimaryExpression;
2773 1         18 }
2774 1         15 }
2775 0         0  
2776             # [55] PrimaryExpression ::= BrackettedExpression | BuiltInCall | IRIrefOrFunction | RDFLiteral | NumericLiteral | BooleanLiteral | Var
2777 1         5 my $self = shift;
2778             my $t = $self->_peek_token;
2779 10         28 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   25 my $v = pop(@{ $self->{_stack} });
2786 12         48 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   19 $self->_Var;
2792 12         36 my $var = pop(@{ $self->{_stack} });
2793             my $expr = Attean::ValueExpression->new(value => $var);
2794 12         42 $self->_add_stack($expr);
2795 12         40 } elsif ($self->_test_token(BOOLEAN)) {
2796 12 100 33     180 my $b = $self->_BooleanLiteral;
    50 33        
      33        
      66        
      100        
2797 3         10 my $expr = Attean::ValueExpression->new(value => $b);
2798 3         5 $self->_add_stack($expr);
  3         9  
2799 3         8 } 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         11 my $l = $self->_NumericLiteral;
2801 3         6 my $expr = Attean::ValueExpression->new(value => $l);
  3         7  
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   26 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   22 # [56] BrackettedExpression ::= '(' Expression ')'
2844 15         47 # sub _BrackettedExpression_test {
2845 15         38 # my $self = shift;
  15         39  
2846             # return $self->_test_token(LPAREN);
2847 15   66     43 # }
2848 1         6  
2849 1 50       7 my $self = shift;
2850 1         7 $self->_expected_token(LPAREN);
2851 1         3 $self->_Expression;
  1         4  
2852 1         7 $self->_expected_token(RPAREN);
2853             }
2854 15         35  
2855             my $self = shift;
2856            
2857             my $op;
2858             my $custom_agg_iri;
2859 16     16   27 if (scalar(@_)) {
2860 16         61 $custom_agg_iri = shift->value;
2861 16         56 $op = 'CUSTOM';
  16         50  
2862             } else {
2863 16   33     52 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         59 }
2871            
2872             my $star = 0;
2873             my (@expr, %options);
2874             if ($self->_optional_token(STAR)) {
2875 16     16   27 $star = 1;
2876 16 100       43 } else {
    100          
    100          
2877 1         6 $self->_Expression;
2878 1         4 push(@expr, splice(@{ $self->{_stack} }));
  1         3  
2879 1         10 if ($op eq 'GROUP_CONCAT') {
2880 1         16 while ($self->_optional_token(COMMA)) {
2881             $self->_Expression;
2882 1         4 push(@expr, splice(@{ $self->{_stack} }));
2883 1         5 }
  1         5  
2884             if ($self->_optional_token(SEMICOLON)) {
2885             $self->_expected_token(KEYWORD, 'SEPARATOR');
2886 1 50 33     14 $self->_expected_token(EQUALS);
      33        
2887 1         19 my $sep = $self->_String;
2888 1         20 $options{ seperator } = $sep;
2889 1         99 }
2890 1         4 }
2891             }
2892 0         0 my $arg = join(',', map { blessed($_) ? $_->as_string : $_ } @expr);
2893 0         0 if ($distinct) {
2894             $arg = 'DISTINCT ' . $arg;
2895             }
2896 1         5 my $name = sprintf('%s(%s)', $op, $arg);
2897 1         3 $self->_expected_token(RPAREN);
  1         4  
2898            
2899             my $var = Attean::Variable->new( value => ".$name");
2900 1 50 33     20 my $agg = Attean::AggregateExpression->new(
      33        
2901 1         33 distinct => $distinct,
2902 1         33 operator => $op,
2903 1         110 children => [@expr],
2904 1         9 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         59 # [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   29 }
2920 16         34 return 1 if ($self->_test_token(KEYWORD, 'NOT'));
2921 16 50 100     47 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         10 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       11 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         25 } elsif ($self->_test_token(KEYWORD, qr/^(NOT|EXISTS)/)) {
2932             my $not = $self->_optional_token(KEYWORD, 'NOT');
2933 8         53 $self->_expected_token(KEYWORD, 'EXISTS');
2934 8         16 local($self->{filters}) = [];
  8         26  
2935 8         169 $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         18 } elsif ($self->_test_token(KEYWORD, qr/^(COALESCE|BNODE|CONCAT|SUBSTR|RAND|NOW)/i)) {
2943 4         88 # n-arg functions that take expressions
2944 4         19 my $t = $self->_next_token;
2945             my $op = $t->value;
2946 1         6 my @args = $self->_ArgList;
2947 1         2 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   1 } 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         4 $self->_expected_token(LPAREN);
2961 1         5 $self->_Expression;
2962 1         5 my ($expr) = splice(@{ $self->{_stack} });
2963 1         5 $self->_add_stack( $self->new_function_expression($op, $expr) );
2964 1         32 $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         5  
2967             $self->_expected_token(LPAREN);
2968 1         5 $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         14 $self->_expected_token(RPAREN);
2996 5         24 }
2997 5         13 }
2998             }
2999              
3000             # [58] RegexExpression ::= 'REGEX' '(' Expression ',' Expression ( ',' Expression )? ')'
3001 1     1   1 # sub _RegexExpression_test {
3002             # my $self = shift;
3003 1         3 # return $self->_test_token(KEYWORD, 'REGEX');
3004             # }
3005 1 50       17  
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         5 my $string = splice(@{ $self->{_stack} });
3011            
3012 1         3 $self->_expected_token(COMMA);
3013 1         4 $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         3 push(@args, splice(@{ $self->{_stack} }));
3020 1 50       3 }
3021 0         0
3022             $self->_expected_token(RPAREN);
3023 1         8 $self->_add_stack( $self->new_function_expression( 'REGEX', @args ) );
3024 1         2 }
  1         3  
3025 1 50       3  
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       6 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         4 my $expr = Attean::CastExpression->new( children => \@args, datatype => $iri );
3043 1         3 $self->_add_stack( $expr );
3044             } else {
3045 1         21 my $func = Attean::ValueExpression->new( value => $iri );
3046 1         54 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         29 my $value = $self->_String;
3055 1         22
3056 1         4 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   43 } elsif ($self->_test_token(HATHAT)) {
3062 23         48 $self->_expected_token(HATHAT);
3063 23 100       64 $self->_IRIref;
3064 22 100       65 my ($iri) = splice(@{ $self->{_stack} });
3065 4 100       16 $obj = Attean::Literal->new( value => $value, datatype => $iri );
3066             } else {
3067 21 50       59 $obj = Attean::Literal->new( value => $value );
3068 21 50       51 }
3069 21 50       84
3070 21 50       111 return $obj;
3071 21         78 }
3072              
3073             # [61] NumericLiteral ::= NumericLiteralUnsigned | NumericLiteralPositive | NumericLiteralNegative
3074             # [62] NumericLiteralUnsigned ::= INTEGER | DECIMAL | DOUBLE
3075 2     2   6 # [63] NumericLiteralPositive ::= INTEGER_POSITIVE | DECIMAL_POSITIVE | DOUBLE_POSITIVE
3076 2         7 # [64] NumericLiteralNegative ::= INTEGER_NEGATIVE | DECIMAL_NEGATIVE | DOUBLE_NEGATIVE
3077 2 100 66     16 my $self = shift;
    50          
    50          
    50          
3078 1         5 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         4 $value = $sign . $value;
3101 1         3 }
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         3 croak "Expecting string literal but found $got '$value'";
3140 1         3 }
  1         11  
3141 1         17  
3142 1         6 $value =~ s/\\t/\t/g;
3143 1         7 $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   2
3183 1         6 my $iri = $self->namespaces->namespace_uri($ns)->iri($local);
3184 1 50       216 my $base = $self->__base;
3185 1         3 my $p = $self->new_iri( value => $iri->value, $base ? (base => $base) : () );
  1         4  
3186 1 50       11 return $p;
3187             }
3188 0         0  
3189             my $self = shift;
3190 1         5 # Var | BlankNode | iri | RDFLiteral | NumericLiteral | BooleanLiteral | QuotedTP
3191 1 50       5 if ($self->_test_token(LTLT)) {
3192 0         0 $self->_QuotedTP();
3193 0         0 } else {
3194             $self->_VarOrTerm;
3195 1         16 }
3196 1         7 }
3197 1         72  
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   20 $self->_expected_token(GTGT);
3205 9         37
3206             my ($s, $p, $o) = splice(@{ $self->{_stack} }, -3);
3207 9         17
3208 9 100       32 if ($self->{__data_pattern}) {
    50          
3209 2         7 foreach my $term ($s, $o) {
3210 2         7 if ($term->does('Attean::API::Blank')) {
3211 2         30 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         162  
3219             my $self = shift;
3220             #'<<' DataValueTerm Verb DataValueTerm '>>'
3221 9         31 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   35 }
3230 15         31 if (my $b = $self->_optional_token(BNODE)) {
3231 15 50       45 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         37 }
3238              
3239 15 100       67 my $self = shift;
    100          
3240 1         5 $self->_expected_token(NIL);
3241 1         19 return Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', lazy => 1);
3242             }
3243 1         3  
3244 1         30 my $self = shift;
3245             my $star = shift;
3246 13         56 my @exprs = @_;
3247 13         77
3248 13         321 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       1743 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         372 }
3256            
3257             my $has_aggregation = 0;
3258             my $having_expr;
3259             my $aggdata = delete( $self->{build}{__aggregate} );
3260 15         4852 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   19
3279 9         21 my %group_vars;
3280             my %agg_vars;
3281 9         25 if ($has_aggregation) {
3282 9 100       35 foreach my $agg_var (map { $_->[0] } values %$aggdata) {
    100          
    50          
    0          
3283 7         31 $agg_vars{ $agg_var->value }++;
3284             }
3285 1         6 foreach my $g (@$groupby) {
3286             if ($g->isa('Attean::ValueExpression') and $g->value->does('Attean::API::Variable')) {
3287 1         3 $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         27 my @vars;
3297 9         27 my @extend;
3298 9         22 if ($star) {
3299 9         21 my $pattern = ${ $self->{build}{triples} }[-1];
3300 9         19 push(@project, $pattern->in_scope_variables);
3301 9         20 if ($has_aggregation) {
3302 9         22 croak "Cannot SELECT * in an aggregate query";
3303 9         38 }
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   340 if ($has_aggregation) {
3309 204 100       403 my @vars = $v->does('Attean::API::Variable') ? $v : $v->unaggregated_variables;
3310 155 100       439 foreach my $var (@vars) {
3311 116         444 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   151 }
3317 90 100       210
3318 62         227 push(@project, $k);
3319 62         270 if ($v->does('Attean::API::Variable')) {
3320 62 100       460 push(@vars, $v);
3321 62         12138 } else {
3322             push(@extend, $k, $v);
3323 28         98 }
3324 28         97 }
3325             }
3326              
3327             {
3328             my $pattern = pop(@{ $self->{build}{triples} });
3329             my %in_scope = map { $_ => 1 } $pattern->in_scope_variables;
3330 28     28   50 while (my($name, $expr) = splice(@extend, 0, 2)) {
3331 28         541 if (exists $in_scope{$name}) {
3332 28         58 croak "Syntax error: Already-bound variable ($name) used in project expression";
  28         94  
3333 28         66 }
3334             my $var = Attean::Variable->new( value => $name );
3335             $pattern = Attean::Algebra::Extend->new(children => [$pattern], variable => $var, expression => $expr);
3336 28 50       140 }
3337 0         0 push(@{ $self->{build}{triples} }, $pattern);
3338             }
3339            
3340 28         190 if ($having_expr) {
3341 28         8798 my $pattern = pop(@{ $self->{build}{triples} });
3342 28 50       169 my $filter = Attean::Algebra::Filter->new( children => [$pattern], expression => $having_expr );
3343 28         4585 push(@{ $self->{build}{triples} }, $filter);
3344             }
3345            
3346             if ($self->{build}{options}{orderby}) {
3347 6     6   10 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         24 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   8 push(@{ $self->{build}{triples} }, $sort);
3358             }
3359 3         11  
3360 3         13 {
3361 3         18 my $pattern = pop(@{ $self->{build}{triples} });
3362 3         17 my $vars = [map { Attean::Variable->new(value => $_) } @project];
3363 3         10 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         4  
3403 1         21 Returns the error encountered during the last parse.
3404              
3405             =cut
3406              
3407 29     29   66 my $self = shift;
3408 29         63 my @triples = @_;
3409 29         79 my $container = $self->{ _pattern_container_stack }[0];
3410             push( @{ $container }, @triples );
3411 29 50       136 }
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         61 my $container = $self->{ _pattern_container_stack }[0];
3421 29         55 my $pattern = $container->[-1];
3422 29         67 return $pattern;
3423 29   100     176 }
3424 29 100       68  
  29         171  
3425 29 100 100     201 my $self = shift;
3426 2         6 my $hints = shift;
3427 2         5 push( @{ $self->{ _pattern_container_hints_stack }[0] }, $hints );
3428 2         6 }
3429 2         2  
  2         8  
3430 2         6 my $self = shift;
3431             my $cont = [];
3432             unshift( @{ $self->{ _pattern_container_stack } }, $cont );
3433 2         6 unshift( @{ $self->{ _pattern_container_hints_stack } }, [] );
3434 2         5 return $cont;
3435 2 100       8 }
3436 1         3  
3437             my $self = shift;
3438 2         29 my $hints = shift( @{ $self->{ _pattern_container_hints_stack } } );
3439 2         5 my $cont = shift( @{ $self->{ _pattern_container_stack } } );
  2         10  
3440             return ($cont, $hints);
3441             }
3442 29         96  
3443             my $self = shift;
3444 29 100       96 my @items = @_;
3445 2         8 push( @{ $self->{_stack} }, @items );
  2         8  
3446 2         9 }
3447              
3448 2         6 my $self = shift;
3449 2 50 33     24 my @filters = shift;
3450 2         42 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         168 return $self->new_iri($build->{base});
3459             } else {
3460 29         0 return;
3461 29 100       80 }
3462 25         46 }
  25         109  
3463 25         174  
3464 25 50       1365 my $self = shift;
3465 0         0 my $s = shift;
3466             my $p = shift;
3467             my $o = shift;
3468 4         17 my $annot;
3469 5         11 if ($o->isa('AtteanX::Parser::SPARQL::ObjectWrapper')) {
3470 5         13 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       9 croak "Syntax error: Cannot use SPARQL-star annotation syntax on a property path";
3473 3         30 }
3474 3         8 $annot = $o->annotations;
3475 3 100 100     14 $o = $o->value;
3476 1         31 }
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       16 my ($p, $o) = @$pair;
3483 3         64 push(@st, $self->__new_statement($s, $p, $o));
3484             }
3485 1         18 }
3486             return @st;
3487             }
3488              
3489             my $self = shift;
3490             my $start = shift;
3491 28         102 my $pdata = shift;
  28         59  
  28         103  
3492 28         109 my $end = shift;
  51         1218  
3493 28         213 (undef, my $op, my @nodes) = @$pdata;
3494 1 50       4 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         25  
3498 1         63 my $self = shift;
3499             my $op = shift;
3500 28         93 my @nodes = @_;
  28         107  
3501              
3502             if ($op eq '!') {
3503 28 50       84 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       131 $nodes[$i] = $self->__new_path_pred(@data);
3510 1         4 } elsif ($nodes[$i]->does('Attean::API::IRI')) {
3511 1         2 $nodes[$i] = Attean::Algebra::PredicatePath->new( predicate => $nodes[$i] );
  1         3  
3512 1         3 }
3513 1         2 }
3514 1         4
3515 1         2 if ($op eq '*') {
3516 1         3 return Attean::Algebra::ZeroOrMorePath->new( children => [@nodes] );
3517 1         9 } elsif ($op eq '+') {
3518             return Attean::Algebra::OneOrMorePath->new( children => [@nodes] );
3519 1         1940 } elsif ($op eq '?') {
3520 1         7 return Attean::Algebra::ZeroOrOnePath->new( children => [@nodes] );
  1         5  
3521             } elsif ($op eq '^') {
3522             return Attean::Algebra::InversePath->new( children => [@nodes] );
3523             } elsif ($op eq '/') {
3524 28         59 return Attean::Algebra::SequencePath->new( children => [@nodes] );
  28         41  
  28         79  
3525 28         75 } elsif ($op eq '|') {
  47         1880  
3526 28 100       1324 return Attean::Algebra::AlternativePath->new( children => [@nodes] );
3527 27         548 } else {
3528             $self->log->debug("Path $op:\n". Dumper(\@nodes));
3529 28         72 confess "Error in path $op. See debug log for details."
  28         109  
3530             }
3531             }
3532 28 100       126  
3533 1         4 # fix up BGPs that might actually have property paths in them. split those
3534 1         2 # out as their own path algebra objects, and join them with the bgp with a
  1         3  
3535 1 50       12 # ggp if necessary
3536             my $self = shift;
3537             my @patterns = @_;
3538 1         13 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     246 $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         4  
3545 1         15 my $bgp = Attean::Algebra::BGP->new( triples => \@triples );
3546 1         5 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         36 my $end = $p->object;
3551 1         3 my $pdata = $p->predicate;
  1         6  
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         132 } 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   356 my $op = shift;
3572 183         380 my @operands = @_[0,1];
3573 183         419 return Attean::BinaryExpression->new( operator => $op, children => \@operands );
3574 183         270 }
  183         653  
3575              
3576             =item C<new_function_expression ( $function, @operands )>
3577              
3578 17     17   51 Returns a new function expression structure.
3579 17         53  
3580 17         29 =cut
  17         52  
3581 17         51  
3582             my $self = shift;
3583             my $function = shift;
3584             my @operands = @_;
3585 38     38   89 my $base = $self->__base;
3586 38         85 return Attean::FunctionExpression->new( operator => $function, children => \@operands, $base ? (base => $base) : () );
3587 38         84 }
3588 38         74  
3589             my $self = shift;
3590             my @parts = @_;
3591             if (0 == scalar(@parts)) {
3592 1     1   2 return Attean::Algebra::BGP->new();
3593 1         2 } elsif (1 == scalar(@parts)) {
3594 1         3 return shift(@parts);
  1         5  
3595             } else {
3596             return Attean::Algebra::Join->new( children => \@parts );
3597             }
3598 168     168   332 }
3599 168         353  
3600 168         275 my $self = shift;
  168         546  
3601 168         255 my $l = $self->lexer;
  168         407  
3602 168         309 my $t = $l->peek;
3603             return unless ($t);
3604             while ($t == COMMENT) {
3605             $t = $l->peek;
3606 120     120   203 return unless ($t);
3607 120         195 }
  120         303  
3608 120         179 return $t;
  120         247  
3609 120         308 }
3610              
3611             my $self = shift;
3612             my $type = shift;
3613 487     487   8930 my $t = $self->_peek_token;
3614 487         963 return unless ($t);
3615 487         687 return if ($t->type != $type);
  487         1770  
3616             if (@_) {
3617             my $value = shift;
3618             if (ref($value) eq 'Regexp') {
3619 4     4   7 return unless ($t->value =~ $value);
3620 4         9 } else {
3621 4         7 return unless ($t->value eq $value);
  4         14  
3622             }
3623             }
3624             return 1;
3625 93     93   193 }
3626 93         185  
3627 93 100       624 my $self = shift;
    50          
3628 4         9 if ($self->_test_token(@_)) {
3629             return $self->_next_token;
3630 0         0 }
3631             return;
3632 89         204 }
3633              
3634             my $self = shift;
3635             my $l = $self->lexer;
3636             my $t = $l->next;
3637 73     73   145 while ($t->type == COMMENT) {
3638 73         137 $t = $l->peek;
3639 73         206 return unless ($t);
3640 73         115 }
3641 73         124 return $t;
3642 73 100       919 }
3643 2 50 33     12  
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         27 } else {
3649             my $t = $self->_peek_token;
3650 73         1552 my $expecting = AtteanX::SPARQL::Constants::decrypt_constant($type);
3651 73         3013 my $got = blessed($t) ? AtteanX::SPARQL::Constants::decrypt_constant($t->type) : '(undef)';
3652 73 100       226 if (@_) {
3653 2         5 my $value = shift;
3654 2         5 if ($t) {
3655 3         6 my $value2 = $t->value;
3656 3         11 confess "Expecting $expecting '$value' but got $got '$value2' before " . $self->lexer->buffer;
3657             } else {
3658             confess "Expecting $expecting '$value' but found EOF";
3659 73         274 }
3660             } else {
3661             confess "Expecting $expecting but found $got before " . $self->lexer->buffer;
3662             }
3663 1     1   2 }
3664 1         1 }
3665 1         1  
3666 1         2 my $self = shift;
3667 1         3 my $t = shift;
3668 1         5 my $note = shift;
3669 1         147 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   4 my $c = $t->start_column;
3674 2         4 $message .= " at $l:$c";
3675 2         3 } else {
3676             my $n = $self->lexer->buffer;
3677 2 50       9 $n =~ s/\s+/ /g;
3678 0         0 $n =~ s/\s*$//;
3679             if ($n) {
3680             $message .= " near '$n'";
3681 2         4 }
3682 3 100       107 }
    50          
3683 1         2 croak $message;
  1         4  
3684 1         5 }
3685              
3686 2         70  
3687             use strict;
3688             use warnings;
3689             no warnings 'redefine';
3690 2 100       202 use Types::Standard qw(InstanceOf HashRef ArrayRef Bool Str Int);
    50          
    50          
    50          
    50          
    0          
3691 1         22  
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         23  
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   108 =head1 AUTHOR
3713 51         151  
3714 51 100       137 Gregory Todd Williams C<< <gwilliams@cpan.org> >>
  62         516  
3715 51         131  
  62         329  
3716 51 50 33     1041 =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         20704  
3722 51 100       210 =cut