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   507959 use v5.14;
  12         43  
2 12     12   68 use warnings;
  12         23  
  12         658  
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.033.
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   67 use warnings;
  12         22  
  12         248  
76 12     12   49 no warnings 'redefine';
  12         28  
  12         325  
77 12     12   71 use Carp qw(cluck confess croak);
  12         19  
  12         542  
78 12     12   68  
  12         40  
  12         824  
79             use Attean;
80 12     12   71 use Data::Dumper;
  12         20  
  12         94  
81 12     12   96 use URI::NamespaceMap;
  12         27  
  12         596  
82 12     12   78 use List::MoreUtils qw(zip);
  12         22  
  12         497  
83 12     12   70 use AtteanX::Parser::SPARQLLex;
  12         29  
  12         197  
84 12     12   15932 use AtteanX::SPARQL::Constants;
  12         34  
  12         581  
85 12     12   85 use Types::Standard qw(InstanceOf HashRef ArrayRef Bool Str Int);
  12         26  
  12         2005  
86 12     12   86 use Scalar::Util qw(blessed looks_like_number reftype refaddr);
  12         22  
  12         80  
87 12     12   11338  
  12         36  
  12         592  
88             ######################################################################
89              
90             use Moo;
91 12     12   59  
  12         23  
  12         62  
92             has 'lexer' => (is => 'rw', isa => InstanceOf['AtteanX::Parser::SPARQLLex::Iterator']);
93             has 'args' => (is => 'ro', isa => HashRef);
94             has 'build' => (is => 'rw', isa => HashRef);
95             has 'update' => (is => 'rw', isa => Bool);
96             has 'baseURI' => (is => 'rw');
97             has '_stack' => (is => 'rw', isa => ArrayRef);
98             has 'filters' => (is => 'rw', isa => ArrayRef);
99             has 'counter' => (is => 'rw', isa => Int, default => 0);
100             has '_pattern_container_stack' => (is => 'rw', isa => ArrayRef);
101              
102              
103 8     8 1 36  
104             return [qw(application/sparql-query application/sparql-update)];
105 1     1 1 682 }
106              
107             state $ITEM_TYPE = Type::Tiny::Role->new(role => 'Attean::API::Algebra');
108 3     3 1 14 return $ITEM_TYPE;
109             }
110              
111             with 'Attean::API::AtOnceParser', 'Attean::API::Parser', 'Attean::API::AbbreviatingParser';
112 5     5 1 678 with 'MooX::Log::Any';
113 5         247  
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 57221 }
121 53         138 return \%a;
122 53   66     1082 }
123 53         54041  
124 53 100       189 ################################################################################
125 1         3  
126             my $self = shift;
127 53         788 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   103  
134 52         97 Parse the C<< $sparql >> query string and return the resulting
135 52     1   429 L<Attean::API::Algebra> object.
  1         3  
136 52         116  
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 27973 Parse the C<< $sparql >> update string and return the resulting
148 28 100       422 L<Attean::API::Algebra> object.
149 28         1147  
150 26         243 =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 975 =cut
162 14 50       321  
163 14         909 my $self = shift;
164 14         339 my $p = AtteanX::Parser::SPARQLLex->new();
165 13         146 my $l = $self->_configure_lexer( $p->parse_iter_from_io(@_) );
166             $self->lexer($l);
167             $self->baseURI($self->{args}{base});
168             my $q = $self->_parse();
169             return unless (ref($q));
170             my $a = $q->{triples}[0];
171             return unless (ref($a));
172             return $a;
173 2     2 1 4 }
174 2         34  
175 2         78 =item C<< parse_list_from_bytes( $bytes ) >>
176 2         40  
177 2         66 =cut
178 2         8  
179 2 50       8 my $self = shift;
180 2         4 my $p = AtteanX::Parser::SPARQLLex->new();
181 2 50       6 my $l = $self->_configure_lexer( $p->parse_iter_from_bytes(@_) );
182 2         21 $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 1121  
191 45         717 =item C<< parse_nodes ( $string ) >>
192 45         1554  
193 45         829 Returns a list of L<Attean::API::Term> or L<Attean::API::Variable> objects,
194 45         1472 parsed in SPARQL syntax from the supplied C<< $string >>. Parsing is ended
195 45         154 either upon seeing a DOT, or reaching the end of the string.
196 42 50       135  
197 42         79 =cut
198 42 50       109  
199 42         286 my $self = shift;
200             my $p = AtteanX::Parser::SPARQLLex->new();
201             my $bytes = shift;
202             my %args = @_;
203             my $commas = $args{'commas'} || 0;
204             my $l = $self->_configure_lexer( $p->parse_iter_from_bytes($bytes) );
205             $self->lexer($l);
206             $self->baseURI($self->{args}{base});
207             $self->build({base => $self->baseURI});
208            
209             my @nodes;
210             while ($self->_peek_token) {
211 5     5 1 79 if ($self->_Verb_test) {
212 5         82 $self->_Verb;
213 5         218 } else {
214 5         13 $self->_GraphNode;
215 5   50     26 }
216 5         26
217 5         98 if ($commas) {
218 5         164 $self->_optional_token(COMMA);
219 5         83 }
220            
221 5         109 push(@nodes, splice(@{ $self->{_stack} }));
222 5         20 if ($self->_test_token(DOT)) {
223 15 100       37 $self->log->notice('DOT seen in string, stopping here');
224 13         28 last;
225             }
226 2         8 }
227            
228             return @nodes;
229 15 50       641 }
230 0         0  
231             my $self = shift;
232            
233 15         22 unless ($self->update) {
  15         33  
234 15 50       43 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         54 $self->_stack([]);
241             $self->filters([]);
242             $self->_pattern_container_stack([]);
243             my $triples = $self->_push_pattern_container();
244 47     47   82 my $build = { sources => [], triples => $triples };
245             $self->build($build);
246 47 100       682 if ($self->baseURI) {
247 33         553 $build->{base} = $self->baseURI;
248 33 50       100 }
249 0         0  
250             $self->_RW_Query();
251             delete $build->{star};
252             my $data = $build;
253 47         862 return $data;
254 47         1668 }
255 47         1585  
256 47         1094 ################################################################################
257 47         146  
258 47         691  
259 47 100       1105 # [1] Query ::= Prologue ( SelectQuery | ConstructQuery | DescribeQuery | AskQuery | LoadUpdate )
260 1         4 my $self = shift;
261             $self->_Prologue;
262              
263 47         160 my $read_query = 0;
264 44         107 my $update = 0;
265 44         91 while (1) {
266 44         130 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   84 $read_query++;
275 47         149 } elsif ($self->_optional_token(KEYWORD, 'ASK')) {
276             $self->_AskQuery();
277 47         69 $read_query++;
278 47         71 } elsif ($self->_test_token(KEYWORD, 'CREATE')) {
279 47         68 unless ($self->update) {
280 49 100       108 croak "CREATE GRAPH update forbidden in read-only queries";
    100          
    100          
    100          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    100          
    50          
281 28         103 }
282 27         46 $update++;
283             $self->_CreateGraph();
284 3         12 } elsif ($self->_test_token(KEYWORD, 'DROP')) {
285 3         5 unless ($self->update) {
286             croak "DROP GRAPH update forbidden in read-only queries";
287 1         6 }
288 1         2 $update++;
289             $self->_DropGraph();
290 4         19 } elsif ($self->_test_token(KEYWORD, 'LOAD')) {
291 4         8 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       115 }
318 0         0 $self->_InsertDataUpdate();
319             } else {
320 9         53 $self->_InsertUpdate($graph);
321 9         15 }
322 9 50       18 } 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       20 $self->_DeleteDataUpdate();
    50          
328 3 100       8 } else {
329 1 50       13 $self->_DeleteUpdate($graph);
330 0         0 }
331             }
332 1         7 } elsif ($self->_test_token(KEYWORD, 'COPY')) {
333             $update++;
334 2         10 $self->_AddCopyMoveUpdate('COPY');
335             } elsif ($self->_test_token(KEYWORD, 'MOVE')) {
336             $update++;
337 6 100       14 $self->_AddCopyMoveUpdate('MOVE');
338 1 50       13 } elsif ($self->_test_token(KEYWORD, 'ADD')) {
339 0         0 $update++;
340             $self->_AddCopyMoveUpdate('ADD');
341 1         9 } elsif ($self->_test_token(SEMICOLON)) {
342             $self->_expected_token(SEMICOLON);
343 5         24 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         4  
354 3         7 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     14 last;
361 0         0 }
362             my $count = scalar(@{ $self->{build}{triples} });
363            
364 1         8 my $t = $self->_peek_token;
365 1         4 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       223 }
369 11 100       23  
370 2 50       8 if ($count == 0 or $count > 1) {
371 2         6 my @patterns = splice(@{ $self->{build}{triples} });
372             my %seen;
373             foreach my $p (@patterns) {
374 9         16 my @blanks = $p->blank_nodes;
375             foreach my $b (@blanks) {
376 44         61 if ($seen{$b->value}++) {
  44         123  
377             croak "Cannot re-use a blank node label in multiple update operations in a single request";
378 44         122 }
379 44 50       124 }
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     253 }
385 2         4
  2         8  
386 2         3 my %dataset;
387 2         5 foreach my $s (@{ $self->{build}{sources} }) {
388 4         52 my ($iri, $group) = @$s;
389 4         9 if ($group eq 'NAMED') {
390 0 0       0 push(@{ $dataset{named} }, $iri );
391 0         0 } else {
392             push(@{ $dataset{default} }, $iri );
393             }
394             }
395 2         16  
396 2         7 my $algebra = $self->{build}{triples}[0];
397 2         4
398             if ($update) {
399             $self->{build}{triples}[0] = Attean::Algebra::Update->new( children => [$algebra] );
400 44         69 } else {
401 44         65 $self->{build}{triples}[0] = Attean::Algebra::Query->new( children => [$algebra], dataset => \%dataset );
  44         139  
402 8         15 }
403 8 100       14 }
404 4         5  
  4         13  
405             my $self = shift;
406 4         5 return ($self->_test_token(KEYWORD, qr/^(SELECT|CONSTRUCT|DESCRIBE|ASK|LOAD|CLEAR|DROP|ADD|MOVE|COPY|CREATE|INSERT|DELETE|WITH)/i));
  4         8  
407             }
408              
409             # [2] Prologue ::= BaseDecl? PrefixDecl*
410 44         135 # [3] BaseDecl ::= 'BASE' IRI_REF
411             # [4] PrefixDecl ::= 'PREFIX' PNAME_NS IRI_REF
412 44 100       106 my $self = shift;
413 9         138  
414             my $base;
415 35         610 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   4 @base = $base;
421 2         10 $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   72 if (scalar(@args) > 1) {
429             croak "Syntax error: PREFIX namespace used a full PNAME_LN, not a PNAME_NS";
430 47         83 }
431             my $ns = substr($prefix->value, 0, length($prefix->value) - 1);
432 47 50       203 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         88 }
441 47         117  
442 15         77 $self->{build}{namespaces} = \%namespaces;
443 15         27 $self->{build}{base} = $base if (defined($base));
  15         63  
444 15 50       49  
445 0         0 # push(@data, (base => $base)) if (defined($base));
446             # return @data;
447 15         43 }
448 15         36  
449 15         47 my $self = shift;
450 15 50       45 $self->_expected_token(LBRACE);
451 0         0 local($self->{__data_pattern}) = 1;
452 0         0 my @triples = $self->_ModifyTemplate();
453             $self->_expected_token(RBRACE);
454 15         43  
455 15         97 my $insert = Attean::Algebra::Modify->new(insert => \@triples);
456             $self->_add_patterns( $insert );
457             $self->{build}{method} = 'UPDATE';
458 47         170 }
459 47 50       168  
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   2
467 1         3 my $delete = Attean::Algebra::Modify->new(delete => \@triples);
468 1         3 $self->_add_patterns( $delete );
469 1         3 $self->{build}{method} = 'UPDATE';
470 1         3 }
471              
472 1         12 my $self = shift;
473 1         4 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         3 @triples = map { $_->as_quad_pattern($graph) } @triples;
480 1         3 }
481 1         3  
482 1         3 my %dataset;
483 1         9 while ($self->_optional_token(KEYWORD, 'USING')) {
484             $self->{build}{custom_update_dataset} = 1;
485 1         17 my $named = 0;
486 1         4 if ($self->_optional_token(KEYWORD, 'NAMED')) {
487 1         3 $named = 1;
488             }
489             $self->_IRIref;
490             my ($iri) = splice( @{ $self->{_stack} } );
491 2     2   5 if ($named) {
492 2         3 $dataset{named}{$iri->value} = $iri;
493 2         7 } else {
494 2         8 push(@{ $dataset{default} }, $iri );
495 2         8 }
496             }
497 2 50       7  
498 0         0 $self->_expected_token(KEYWORD, 'WHERE');
  0         0  
499             if ($graph) {
500             $self->_GroupGraphPattern;
501 2         3 my $ggp = $self->_remove_pattern;
502 2         6 $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         6 my @triples = @_;
518 1 50       3
519 0         0 my %fresh_blank_map;
520 0         0 my @triples_with_fresh_bnodes;
521 0         0 foreach my $t (@triples) {
522 0         0 my @pos = ref($t)->variables;
523             if ($t->has_blanks) {
524 1         4 my @terms;
525             foreach my $term ($t->values) {
526             if ($term->does('Attean::API::Blank')) {
527 1         4 if (my $b = $fresh_blank_map{$term->value}) {
528             push(@terms, $b);
529 1         4 } else {
530 1         9 my $id = $self->counter;
531 1         4 $self->counter($id+1);
532 1         4 my $name = ".b-$id";
533             my $b = Attean::Blank->new($name);
534             push(@terms, $b);
535             $fresh_blank_map{$term->value} = $b;
536 1     1   2 }
537 1         2 } else {
538             push(@terms, $term);
539 1         2 }
540             }
541 1         3 push(@triples_with_fresh_bnodes, ref($t)->new(zip @pos, @terms));
542 1         4 } else {
543 1 50       4 push(@triples_with_fresh_bnodes, $t);
544 0         0 }
545 0         0 }
546 0 0       0 return @triples_with_fresh_bnodes;
547 0 0       0 }
548 0         0  
549             my $self = shift;
550 0         0 my $graph = shift;
551 0         0
552 0         0 my %dataset;
553 0         0 if ($self->_optional_token(KEYWORD, 'WHERE')) {
554 0         0 if ($graph) {
555 0         0 croak "Syntax error: WITH clause cannot be used with DELETE WHERE operations";
556             }
557             $self->_expected_token(LBRACE);
558 0         0 my @st = $self->_ModifyTemplate();
559             $self->_expected_token(RBRACE);
560             my @patterns;
561 0         0 my @triples;
562             my @quads;
563 1         3 my @blanks = grep { $_->does('Attean::API::Blank') } map { $_->values } @st;
564             if (scalar(@blanks) > 0) {
565             croak "Cannot use blank nodes in a DELETE pattern";
566 1         3 }
567             foreach my $s (@st) {
568             if ($s->does('Attean::API::QuadPattern')) {
569             push(@quads, $s);
570 5     5   9 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         8 } else {
574 5 50       19 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         11 while ($self->_optional_token(KEYWORD, 'USING')) {
606             $self->{build}{custom_update_dataset} = 1;
607 5         7 my $named = 0;
  5         15  
608 5         16 if ($self->_optional_token(KEYWORD, 'NAMED')) {
609 5         14 $named = 1;
610 5         11 }
611             $self->_IRIref;
612             my ($iri) = splice( @{ $self->{_stack} } );
613 5         9 if ($named) {
614 5 50       10 $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       12
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         11 $ggp = Attean::Algebra::Graph->new( children => [$ggp], graph => $graph );
627 9         29 $self->_add_patterns( $ggp );
628 9         12 } else {
629 9 100       15 $self->_GroupGraphPattern;
630 3         5 delete $self->{__no_bnodes};
631             }
632 9         34  
633 9         23 my $ggp = $self->_remove_pattern;
  9         19  
634 9 100       19  
635 3         12 my %args = (children => [$ggp], dataset => \%dataset);
636             if (scalar(@insert_triples)) {
637 6         9 $args{insert} = \@insert_triples;
  6         20  
638             }
639             if (scalar(@delete_triples)) {
640             $args{delete} = \@delete_triples;
641 5         13 my @blanks = grep { $_->does('Attean::API::Blank') } map { $_->values } @delete_triples;
642             if (scalar(@blanks) > 0) {
643 5 50       13 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         16 }
651 5         10  
652             my $self = shift;
653             return 1 if ($self->_TriplesBlock_test);
654 5         14 return 1 if ($self->_test_token(KEYWORD, 'GRAPH'));
655             return 0;
656 5         18 }
657 5 50       13  
658 0         0 my $self = shift;
659             my $graph = shift;
660 5 50       13
661 5         10 my @triples;
662 5         13 while ($self->_ModifyTemplate_test) {
  15         175  
  5         17  
663 5 50       69 push(@triples, $self->__ModifyTemplate( $graph ));
664 0         0 }
665            
666             return @triples;
667 5         93 }
668 5         18  
669 5         20 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   25 $self->_TriplesBlock;
675 18 100       43 (my $cont, undef) = $self->_pop_pattern_container; # ignore hints in a modify template
676 9 50       20 my ($bgp) = @{ $cont };
677 9         23 my @triples = @{ $bgp->triples };
678             if ($graph) {
679             @triples = map { $_->as_quad_pattern($graph) } @triples;
680             }
681 9     9   15
682 9         14 return @triples;
683             } else {
684 9         16 $self->_GraphGraphPattern;
685 9         39
686 9         24 {
687             my (@d) = splice(@{ $self->{_stack} });
688             $self->__handle_GraphPatternNotTriples( @d );
689 9         23 }
690            
691             my $data = $self->_remove_pattern;
692             my $graph = $data->graph;
693 9     9   15 my @bgps = $data->subpatterns_of_type('Attean::Algebra::BGP');
694 9         15 my @triples = map { $_->as_quad_pattern($graph) } map { @{ $_->triples } } @bgps;
695 9         27 return @triples;
696 9 50       21 }
697 9         28 }
698 9         29  
699 9         19 my $self = shift;
700 9         21 $self->_expected_token(KEYWORD, 'LOAD');
  9         17  
701 9         11 my $silent = $self->_optional_token(KEYWORD, 'SILENT') ? 1 : 0;
  9         34  
702 9 50       22 $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         36 $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   7 }
800 6 100       9 my $pattern = Attean::Algebra::Add->new( %args );
801 3         10 $self->_add_patterns( $pattern );
802             $self->{build}{method} = 'UPDATE';
803 3         5 }
804 3         9  
805 3         8 # [5] SelectQuery ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( Var+ | '*' ) DatasetClause* WhereClause SolutionModifier
  3         6  
806 3         9 my $self = shift;
807             if ($self->_optional_token(KEYWORD, qr/^(DISTINCT)/)) {
808             $self->{build}{options}{distinct} = 1;
809             } elsif ($self->_optional_token(KEYWORD, qr/^(REDUCED)/)) {
810             $self->{build}{options}{distinct} = 2;
811 3     3   6 }
812 3         5
813 3         5 my ($star, $exprs, $vars) = $self->__SelectVars;
814 3 50       4 my @exprs = @$exprs;
815            
816 3         7 $self->_DatasetClause();
817 3 50       11
    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       7 # $self->_Var;
824 3         6 # push( @vars, splice(@{ $self->{_stack} }));
825             my $parens = 0;
826 3         9 if ($self->_optional_token(NIL)) {
827 3 50       7 $parens = 1;
828 0         0 } else {
829             if ($self->_optional_token(LPAREN)) {
830 3         39 $parens = 1;
831 3         11 }
832 3         8 while ($self->_test_token(VAR)) {
833             $self->_Var;
834             push( @vars, splice(@{ $self->{_stack} }));
835             }
836             if ($parens) {
837 28     28   47 $self->_expected_token(RPAREN);
838 28 50       131 }
    100          
839 0         0 }
840            
841 1         4 my $count = scalar(@vars);
842             if (not($parens) and $count == 0) {
843             croak "Syntax error: Expected VAR in inline data declaration";
844 28         133 } elsif (not($parens) and $count > 1) {
845 28         67 croak "Syntax error: Inline data declaration can only have one variable when parens are omitted";
846             }
847 28         113
848             my $short = (not($parens) and $count == 1);
849 28         107 $self->_expected_token(LBRACE);
850 28         107 if ($self->_optional_token(NIL)) {
851            
852 28 100       83 } 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       3 }
858 0         0 } else {
859             while ($self->_BindingValue_test) {
860 1 50       3 $self->_BindingValue;
861 1         2 my ($term) = splice(@{ $self->{_stack} });
862             push( @{ $self->{build}{bindings}{terms} }, [$term] );
863 1         4 }
864 1         4 }
865 1         2 }
  1         4  
866            
867 1 50       3 $self->_expected_token(RBRACE);
868 1         3  
869             my $bindings = delete $self->{build}{bindings};
870             my @rows = @{ $bindings->{terms} || [] };
871             my @vbs;
872 1         2 foreach my $r (@rows) {
873 1 50 33     7 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     3 my $r = Attean::Result->new(bindings => \%d);
880 1         4 push(@vbs, $r);
881 1 50       2 }
882             my $table = Attean::Algebra::Table->new( variables => \@vars, rows => \@vbs );
883             my $pattern = pop(@{ $self->{build}{triples} });
884 1 50 0     4 push(@{ $self->{build}{triples} }, $self->_new_join($pattern, $table));
      33        
885 1         3 }
886 2         5
887 2         2 my %projected = map { $_ => 1 } $self->__solution_modifiers( $star, @exprs );
  2         7  
888             delete $self->{build}{options};
889             $self->{build}{method} = 'SELECT';
890 0         0 }
891 0         0  
892 0         0 my $self = shift;
  0         0  
893 0         0 my $star = 0;
  0         0  
894             my @vars;
895             my $count = 0;
896             my @exprs;
897             while ($self->_test_token(STAR) or $self->__SelectVar_test) {
898 1         4 if ($self->_test_token(STAR)) {
899             $self->{build}{star}++;
900 1         2 $self->_expected_token(STAR);
901 1 50       2 $star = 1;
  1         4  
902 1         4 $count++;
903 1         2 last;
904 2         4 } else {
905 2         2 my @s = $self->__SelectVar;
  2         5  
906 2 50       7 if (scalar(@s) > 1) {
907 2         8 my ($var, $expr) = @s;
908             push(@exprs, $var->value, $expr);
909             } else {
910 2         21 my $var = $s[0];
911 2         43 push(@exprs, $var->value, $var);
912             }
913 1         8 push(@vars, shift(@s));
914 1         2 $count++;
  1         4  
915 1         1 }
  1         4  
916             }
917            
918 28         124 my %seen;
  45         126  
919 27         70 foreach my $v (@vars) {
920 27         101 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   55 }
925 29         50 }
926 29         50 }
927 29         52
928 29         50 $self->{build}{variables} = \@vars;
929 29   100     67 if ($count == 0) {
930 32 100       70 croak "Syntax error: No select variable or expression specified";
931 25         76 }
932 25         80 return $star, \@exprs, \@vars;
933 25         41 }
934 25         37  
935 25         43 my $self = shift;
936             $self->_expected_token(LPAREN);
937 7         20 $self->_Expression;
938 7 100       18 my ($expr) = splice(@{ $self->{_stack} });
939 1         3 $self->_expected_token(KEYWORD, 'AS');
940 1         3 $self->_Var;
941             my ($var) = splice(@{ $self->{_stack} });
942 6         12 $self->_expected_token(RPAREN);
943 6         22
944             return ($var, $expr);
945 7         14 }
946 7         21  
947             my $self = shift;
948             local($self->{__aggregate_call_ok}) = 1;
949             # return 1 if $self->_BuiltInCall_test;
950 29         57 return 1 if $self->_test_token(LPAREN);
951 29         62 return $self->_test_token(VAR);
952 7 50       33 }
953 7         123  
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         81 $self->_Var;
961 29 50       95 my ($var) = splice(@{ $self->{_stack} });
962 0         0 return $var;
963             }
964 29         163 }
965              
966             # [6] ConstructQuery ::= 'CONSTRUCT' ConstructTemplate DatasetClause* WhereClause SolutionModifier
967             my $self = shift;
968 3     3   6 my $shortcut = 1;
969 3         9 if ($self->_test_token(LBRACE)) {
970 3         13 $shortcut = 0;
971 3         7 $self->_ConstructTemplate;
  3         10  
972 3         14 }
973 3         10 $self->_DatasetClause();
974 3         7 if ($shortcut) {
  3         8  
975 3         9 $self->_TriplesWhereClause;
976             } else {
977 3         10 $self->_WhereClause;
978             }
979            
980             $self->_SolutionModifier();
981 11     11   17
982 11         28 my $pattern = $self->{build}{triples}[0];
983             my $triples = delete $self->{build}{construct_triples};
984 11 100       24 if (blessed($triples) and $triples->isa('Attean::Algebra::BGP')) {
985 10         23 $triples = $triples->triples;
986             }
987             # my @triples;
988             # warn $triples;
989 7     7   11 # foreach my $t (@{ $triples // [] }) {
990 7         15 # if ($t->isa('Attean::Algebra::BGP')) {
991 7 100       12 # push(@triples, @{ $t->triples });
992 1         3 # } else {
993 1         3 # push(@triples, $t);
994             # }
995 6         19 # }
996 6         13 my $construct = Attean::Algebra::Construct->new( children => [$pattern], triples => $triples );
  6         14  
997 6         20 $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   6
1004 3         6 my $star = 0;
1005 3 50       8 if ($self->_optional_token(STAR)) {
1006 3         5 $star = 1;
1007 3         12 $self->{build}{variables} = ['*'];
1008             } else {
1009 3         13 $self->_VarOrIRIref;
1010 3 50       10 while ($self->_VarOrIRIref_test) {
1011 0         0 $self->_VarOrIRIref;
1012             }
1013 3         9 $self->{build}{variables} = [ splice(@{ $self->{_stack} }) ];
1014             }
1015            
1016 3         11 $self->_DatasetClause();
1017            
1018 3         7 if ($self->_WhereClause_test) {
1019 3         9 $self->_WhereClause;
1020 3 50 33     13 } 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         37  
1033 3         10 # [8] AskQuery ::= 'ASK' DatasetClause* WhereClause
1034 3         8 my $self = shift;
1035            
1036             $self->_DatasetClause();
1037            
1038             $self->_WhereClause;
1039 1     1   2
1040             $self->{build}{variables} = [];
1041 1         2 $self->{build}{method} = 'ASK';
1042 1 50       3
1043 0         0
1044 0         0 my $pattern = $self->{build}{triples}[0];
1045             $self->{build}{triples}[0] = Attean::Algebra::Ask->new( children => [$pattern] );
1046 1         5 }
1047 1         4  
1048 0         0 # sub _DatasetClause_test {
1049             # my $self = shift;
1050 1         3 # return $self->_test_token(KEYWORD, 'FROM');
  1         3  
1051             # }
1052              
1053 1         4 # [9] DatasetClause ::= 'FROM' ( DefaultGraphClause | NamedGraphClause )
1054             my $self = shift;
1055 1 50       4
1056 1         2 # 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         3 $self->_DefaultGraphClause;
1063 1         3 }
1064             }
1065 1         2 }
1066 1 50       3  
  0         0  
1067 1         19 # [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   6 }
1073              
1074 4         14 # [11] NamedGraphClause ::= 'NAMED' SourceSelector
1075             my $self = shift;
1076 4         19 $self->_expected_token(KEYWORD, 'NAMED');
1077             $self->_SourceSelector;
1078 4         13 my ($source) = splice(@{ $self->{_stack} });
1079 4         9 push( @{ $self->{build}{sources} }, [$source, 'NAMED'] );
1080             }
1081              
1082 4         8 # [12] SourceSelector ::= IRIref
1083 4         66 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   63 my $self = shift;
1094             $self->_optional_token(KEYWORD, 'WHERE');
1095             $self->_GroupGraphPattern;
1096 36         91
1097 36         94 my $ggp = $self->_peek_pattern;
1098 8 100       23 $self->_check_duplicate_blanks($ggp);
1099 4         12 }
1100              
1101 4         13 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   7 # foreach my $b (@blanks) {
1109 4         11 # my $id = $b->value;
1110 4         13 # if ($seen{ $id }++) {
  4         10  
1111 4         7 # warn $ggp->as_string;
  4         15  
1112             # croak "Same blank node identifier ($id) used in more than one BasicGraphPattern.";
1113             # }
1114             # }
1115             # }
1116 4     4   20 return 1;
1117 4         13 }
1118 4         14  
1119 4         14 my $self = shift;
  4         9  
1120 4         5 $self->_push_pattern_container;
  4         18  
1121            
1122             $self->_expected_token(KEYWORD, 'WHERE');
1123             $self->_expected_token(LBRACE);
1124             if ($self->_TriplesBlock_test) {
1125 8     8   12 $self->_TriplesBlock;
1126 8         20 }
1127             $self->_expected_token(RBRACE);
1128            
1129             my ($cont, $hints) = $self->_pop_pattern_container;
1130             $self->{build}{construct_triples} = $cont->[0];
1131 1     1   1
1132 1 50       3 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   77 # sub _Binding_test {
1138 37         102 # my $self = shift;
1139 37         144 # return $self->_test_token(LPAREN);
1140             # }
1141 37         132  
1142 37         111 my $self = shift;
1143             my $count = shift;
1144            
1145             $self->_expected_token(LPAREN);
1146 39     39   67
1147 39         69 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         70 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   2  
1190 2         3 my $self = shift;
1191             if ($self->_optional_token(LPAREN)) {
1192 2         4 $self->_Expression;
1193             my ($expr) = splice(@{ $self->{_stack} });
1194 2         3 if ($self->_optional_token(KEYWORD, 'AS')) {
1195 2         5 $self->_Var;
1196 2 50       5 my ($var) = splice(@{ $self->{_stack} });
1197 0         0 push(@{ $self->{build}{__group_vars} }, [$var, $expr]);
1198 0         0 my $vexpr = Attean::ValueExpression->new( value => $var );
1199             $self->_add_stack( $vexpr );
1200 2         6 } else {
1201 2         3 $self->_add_stack( $expr );
  2         7  
1202             }
1203 2         4 $self->_expected_token(RPAREN);
1204 2         4
1205             } elsif ($self->_IRIref_test) {
1206             $self->_FunctionCall;
1207             } elsif ($self->_BuiltInCall_test) {
1208 6     6   9 $self->_BuiltInCall;
1209 6 100       15 } else {
1210 4 50       10 $self->_Var;
1211 4 100       12 my $var = pop(@{ $self->{_stack} });
1212 1 50       4 my $expr = Attean::ValueExpression->new(value => $var);
1213 1 50       3 $self->_add_stack($expr);
1214 1 50       4 }
1215 1 50       2 }
1216 1         3  
1217             # [14] SolutionModifier ::= OrderClause? LimitOffsetClauses?
1218             my $self = shift;
1219             my $vars = shift // [];
1220 5     5   9
1221 5 50       11 if ($self->_test_token(KEYWORD, 'GROUP')) {
    50          
1222 0         0 $self->_GroupClause($vars);
  0         0  
1223             }
1224 0         0
1225             if ($self->_test_token(KEYWORD, 'RANK')) {
1226 5         16 $self->_RankClause;
1227             }
1228            
1229             if ($self->_test_token(KEYWORD, 'HAVING')) {
1230             $self->_HavingClause;
1231             }
1232 2     2   4
1233 2 50       4 if ($self->_OrderClause_test) {
1234 2 50       8 $self->_OrderClause;
1235 2 50       5 }
1236 2 50       5
1237 2         6 if ($self->_LimitOffsetClauses_test) {
1238             $self->_LimitOffsetClauses;
1239             }
1240             }
1241 2     2   3  
1242 2 50       6 my $self = shift;
    50          
    50          
1243 0         0 my $vars = shift;
1244 0         0 $self->_expected_token(KEYWORD, 'GROUP');
  0         0  
1245 0 0       0 $self->_expected_token(KEYWORD, 'BY');
1246 0         0
1247 0         0 if ($self->{build}{star}) {
  0         0  
1248 0         0 croak "Syntax error: SELECT * cannot be used with aggregate grouping";
  0         0  
1249 0         0 }
1250 0         0
1251             $self->{build}{__aggregate} ||= {};
1252 0         0 my @vars;
1253             $self->__GroupByVar;
1254 0         0 my ($v) = splice(@{ $self->{_stack} });
1255             push( @vars, $v );
1256             while ($self->__GroupByVar_test) {
1257 0         0 $self->__GroupByVar;
1258             my ($v) = splice(@{ $self->{_stack} });
1259 0         0 push( @vars, $v );
1260             }
1261 2         6  
1262 2         4 my %seen;
  2         5  
1263 2         37 foreach my $v (@vars) {
1264 2         6 my $var = $v->value;
1265             if ($var->does('Attean::API::Variable')) {
1266             my $name = $var->value;
1267             $seen{ $name }++;
1268             }
1269             }
1270 33     33   50
1271 33   100     105 # warn 'TODO: verify that projection only includes aggregates and grouping variables'; # XXXXX
1272             # foreach my $v (@$vars) {
1273 33 100       85 # if ($v->does('Attean::API::Variable')) {
1274 2         10 # 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       89 # # throw ::Error::ParseError -text => "Syntax error: Variable used in projection but not present in aggregate grouping ($name)";
1278 1         3 # }
1279             # }
1280             # }
1281 33 100       88
1282 1         3 $self->{build}{__group_by} = \@vars;
1283             }
1284              
1285 33 100       121 my $self = shift;
1286 1         3 $self->_expected_token(KEYWORD, 'RANK');
1287             $self->_expected_token(LPAREN);
1288             $self->_OrderCondition;
1289 33 100       108 my @order;
1290 2         5 push(@order, splice(@{ $self->{_stack} }));
1291             while ($self->_OrderCondition_test) {
1292             $self->_OrderCondition;
1293             push(@order, splice(@{ $self->{_stack} }));
1294             }
1295 2     2   3 $self->_expected_token(RPAREN);
1296 2         3 $self->_expected_token(KEYWORD, 'AS');
1297 2         7 $self->_Var;
1298 2         5 my ($var) = splice(@{ $self->{_stack} });
1299            
1300 2 50       8 my @exprs;
1301 0         0 my %ascending;
1302             foreach my $o (@order) {
1303             my ($dir, $expr) = @$o;
1304 2   100     9 push(@exprs, $expr);
1305 2         3 $ascending{ $expr->value->value } = ($dir eq 'ASC') ? 1 : 0; # TODO: support ranking by complex expressions, not just variables
1306 2         8 }
1307 2         4 my $r = Attean::AggregateExpression->new(
  2         6  
1308 2         4 distinct => 0,
1309 2         5 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         4
1317 2         8 $self->{build}{__aggregate}{ $var->value } = [ $var, $r ];
1318 2 50       6 }
1319 2         35  
1320 2         5 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         7  
1336             my $self = shift;
1337             if ($self->_LimitClause_test) {
1338             $self->_LimitClause;
1339 1     1   2 if ($self->_OffsetClause_test) {
1340 1         3 $self->_OffsetClause;
1341 1         3 }
1342 1         5 } else {
1343 1         2 $self->_OffsetClause;
1344 1         5 if ($self->_LimitClause_test) {
  1         2  
1345 1         3 $self->_LimitClause;
1346 0         0 }
1347 0         0 }
  0         0  
1348             }
1349 1         4  
1350 1         3 # [16] OrderClause ::= 'ORDER' 'BY' OrderCondition+
1351 1         2 my $self = shift;
1352 1         2 return 1 if ($self->_test_token(KEYWORD, 'ORDER'));
  1         2  
1353             return 0;
1354 1         2 }
1355              
1356 1         4 my $self = shift;
1357 1         2 $self->_expected_token(KEYWORD, 'ORDER');
1358 1         2 $self->_expected_token(KEYWORD, 'BY');
1359 1 50       6 my @order;
1360             $self->{build}{__aggregate} ||= {};
1361 1         12 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         18 # [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   2 return 1 if ($self->_test_token(VAR));
1376 1         3 return 1 if $self->_Constraint_test;
1377 1   50     3 return 0;
1378 1         3 }
1379 1         4  
1380 1         3 my $self = shift;
  1         2  
1381 1         4 my $dir = 'ASC';
1382             if (my $t = $self->_optional_token(KEYWORD, qr/^(ASC|DESC)/)) {
1383             $dir = $t->value;
1384             $self->_BrackettedExpression;
1385             } elsif ($self->_test_token(VAR)) {
1386 33     33   52 $self->_Var;
1387 33 100       79 my $var = pop(@{ $self->{_stack} });
1388 32 100       91 my $expr = Attean::ValueExpression->new(value => $var);
1389 31         87 $self->_add_stack($expr);
1390             } else {
1391             $self->_Constraint;
1392             }
1393 2     2   2 my ($expr) = splice(@{ $self->{_stack} });
1394 2 100       6 $self->_add_stack( [ $dir, $expr ] );
1395 1         4 }
1396 1 50       2  
1397 1         4 # [18] LimitClause ::= 'LIMIT' INTEGER
1398             my $self = shift;
1399             return ($self->_test_token(KEYWORD, 'LIMIT'));
1400 1         5 }
1401 1 50       203  
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   57 my $self = shift;
1410 33 100       97 return ($self->_test_token(KEYWORD, 'OFFSET'));
1411 32         90 }
1412              
1413             my $self = shift;
1414             $self->_expected_token(KEYWORD, 'OFFSET');
1415 1     1   2 my $t = $self->_expected_token(INTEGER);
1416 1         3 $self->{build}{options}{offset} = $t->value;
1417 1         2 }
1418 1         2  
1419 1   50     6 # [20] GroupGraphPattern ::= '{' TriplesBlock? ( ( GraphPatternNotTriples | Filter ) '.'? TriplesBlock? )* '}'
1420 1         2 my $self = shift;
1421 1         4
1422 1         1 $self->_expected_token(LBRACE);
  1         3  
1423 1         3
1424 0         0 if ($self->_SubSelect_test) {
1425 0         0 $self->_SubSelect;
  0         0  
1426             } else {
1427 1         4 $self->_GroupGraphPatternSub;
1428             }
1429              
1430             $self->_expected_token(RBRACE);
1431             }
1432 2     2   4  
1433 2 50       5 my $self = shift;
1434 2 50       5 $self->_push_pattern_container;
1435 2 50       7
1436 2 50       8 my $got_pattern = 0;
1437 2         5 my $need_dot = 0;
1438             if ($self->_TriplesBlock_test) {
1439             $need_dot = 1;
1440             $got_pattern++;
1441 2     2   4 $self->_TriplesBlock;
1442 2         5 }
1443 2 100       8
    50          
1444 1         2 while (not $self->_test_token(RBRACE)) {
1445 1         4 my $cur = $self->_peek_token;
1446             if ($self->_GraphPatternNotTriples_test) {
1447 1         3 $need_dot = 0;
1448 1         2 $got_pattern++;
  1         3  
1449 1         19 $self->_GraphPatternNotTriples;
1450 1         3 my (@data) = splice(@{ $self->{_stack} });
1451             $self->__handle_GraphPatternNotTriples( @data );
1452 0         0 } elsif ($self->_test_token(KEYWORD, 'FILTER')) {
1453             $got_pattern++;
1454 2         4 $need_dot = 0;
  2         5  
1455 2         7 $self->_Filter;
1456             }
1457            
1458             if ($need_dot or $self->_test_token(DOT)) {
1459             $self->_expected_token(DOT);
1460 3     3   18 if ($got_pattern) {
1461 3         7 $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         3 }
1467 1         3
1468 1         4 if ($self->_TriplesBlock_test) {
1469             my $peek = $self->_peek_pattern;
1470             if (blessed($peek) and $peek->isa('Attean::Algebra::BGP')) {
1471             $self->_TriplesBlock;
1472             my $rhs = $self->_remove_pattern;
1473 1     1   2 my $lhs = $self->_remove_pattern;
1474 1         3 if ($rhs->isa('Attean::Algebra::BGP')) {
1475             my $merged = $self->__new_bgp( map { @{ $_->triples } } ($lhs, $rhs) );
1476             $self->_add_patterns( $merged );
1477             } else {
1478 2     2   4 my $merged = $self->_new_join($lhs, $rhs);
1479 2         6 $self->_add_patterns( $merged );
1480 2         5 }
1481 2         6 } else {
1482             $self->_TriplesBlock;
1483             }
1484             }
1485              
1486 54     54   80 my $t = $self->_peek_token;
1487             last if (refaddr($t) == refaddr($cur));
1488 54         135 }
1489             my ($cont, $hints) = $self->_pop_pattern_container;
1490 54 100       198  
1491 1         2 my @filters = splice(@{ $self->{filters} });
1492             my @patterns;
1493 53         181 my $pattern = $self->_new_join(@$cont);
1494             $pattern->hints($hints);
1495             if (@filters) {
1496 54         140 while (my $f = shift @filters) {
1497             $pattern = Attean::Algebra::Filter->new( children => [$pattern], expression => $f );
1498             }
1499             }
1500 53     53   95 $self->_add_patterns( $pattern );
1501 53         161 }
1502              
1503 53         89 my $self = shift;
1504 53         75 my $data = shift;
1505 53 100       162 return unless ($data);
1506 41         76 my ($class, @args) = @$data;
1507 41         53 if ($class =~ /^Attean::Algebra::(LeftJoin|Minus)$/) {
1508 41         109 my ($cont, $hints) = $self->_pop_pattern_container;
1509             my $ggp = $self->_new_join(@$cont);
1510             $ggp->hints($hints);
1511 53         143 $self->_push_pattern_container;
1512 17         34 # my $ggp = $self->_remove_pattern();
1513 17 100       49 unless ($ggp) {
    50          
1514 13         23 $ggp = Attean::Algebra::BGP->new();
1515 13         22 }
1516 13         48
1517 13         23 my $opt = $class->new( children => [$ggp, @args] );
  13         34  
1518 13         43 $self->_add_patterns( $opt );
1519             } elsif ($class eq 'Attean::Algebra::Table') {
1520 4         7 my ($table) = @args;
1521 4         5 $self->_add_patterns( $table );
1522 4         9 } 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     81 $ggp->hints($hints);
1526 0         0 $self->_push_pattern_container;
1527 0 0       0 # my $ggp = $self->_remove_pattern();
1528 0         0 unless ($ggp) {
1529 0         0 $ggp = Attean::Algebra::BGP->new();
1530             }
1531 0         0 my ($var, $expr) = @args;
1532             my %in_scope = map { $_ => 1 } $ggp->in_scope_variables;
1533             if (exists $in_scope{ $var->value }) {
1534             croak "Syntax error: BIND used with variable already in scope";
1535 17 100       59 }
1536 1         4 my $bind = Attean::Algebra::Extend->new( children => [$ggp], variable => $var, expression => $expr );
1537 1 50 33     7 $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         4 } elsif ($class =~ /Attean::Algebra::(Union|Graph|Join)$/) {
1550             # no-op
1551             } else {
1552             croak 'Unrecognized GraphPattern: ' . $class;
1553 17         38 }
1554 17 50       127 }
1555              
1556 53         151 my $self = shift;
1557             return $self->_test_token(KEYWORD, 'SELECT');
1558 53         72 }
  53         127  
1559 53         82  
1560 53         127 my $self = shift;
1561 53         745 my $pattern;
1562 53 100       1375 {
1563 4         11 local($self->{namespaces}) = $self->{namespaces};
1564 4         60 local($self->{_stack}) = [];
1565             local($self->{filters}) = [];
1566             local($self->{_pattern_container_stack}) = [];
1567 53         137  
1568             my $triples = $self->_push_pattern_container();
1569             local($self->{build}) = { triples => $triples};
1570             if ($self->{baseURI}) {
1571 13     13   18 $self->{build}{base} = $self->{baseURI};
1572 13         21 }
1573 13 100       30
1574 12         29 $self->_expected_token(KEYWORD, 'SELECT');
1575 12 100       117 if (my $t = $self->_optional_token(KEYWORD, qr/^(DISTINCT|REDUCED)/)) {
    50          
    100          
    100          
    50          
1576 2         6 my $mod = $t->value;
1577 2         5 $self->{build}{options}{lc($mod)} = 1;
1578 2         29 }
1579 2         44
1580             my ($star, $exprs, $vars) = $self->__SelectVars;
1581 2 50       6 my @exprs = @$exprs;
1582 0         0
1583             $self->_WhereClause;
1584             $self->_SolutionModifier($vars);
1585 2         18
1586 2         9 if ($self->{build}{options}{orderby}) {
1587             my $order = delete $self->{build}{options}{orderby};
1588 0         0 my $pattern = pop(@{ $self->{build}{triples} });
1589 0         0
1590             my @order = @$order;
1591 2         7 my @cmps;
1592 2         8 foreach my $o (@order) {
1593 2         33 my ($dir, $expr) = @$o;
1594 2         60 my $asc = ($dir eq 'ASC');
1595             push(@cmps, Attean::Algebra::Comparator->new(ascending => $asc, expression => $expr));
1596 2 50       4 }
1597 0         0 my $sort = Attean::Algebra::OrderBy->new( children => [$pattern], comparators => \@cmps );
1598             push(@{ $self->{build}{triples} }, $sort);
1599 2         6 }
1600 2         10
  1         42  
1601 2 50       66 if ($self->_optional_token(KEYWORD, 'VALUES')) {
1602 0         0 my @vars;
1603             my $parens = 0;
1604 2         19 if ($self->_optional_token(LPAREN)) {
1605 2         9 $parens = 1;
1606             }
1607 2         5 while ($self->_test_token(VAR)) {
1608 2 50       6 $self->_Var;
1609             push( @vars, splice(@{ $self->{_stack} }));
1610 0         0 }
1611             if ($parens) {
1612             $self->_expected_token(RPAREN);
1613             }
1614 2         68 my $count = scalar(@vars);
1615 2         6 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   89 my $terms = $self->_Binding($count);
1626 54         130 push( @{ $self->{build}{bindings}{terms} }, $terms );
1627             }
1628             } else {
1629             while ($self->_BindingValue_test) {
1630 1     1   2 $self->_BindingValue;
1631 1         2 my ($term) = splice(@{ $self->{_stack} });
1632             push( @{ $self->{build}{bindings}{terms} }, [$term] );
1633 1         2 }
  1         2  
1634 1         2 }
1635 1         3
1636 1         2 $self->_expected_token(RBRACE);
1637             $self->{build}{bindings}{vars} = \@vars;
1638 1         2
1639 1         3 my $bindings = delete $self->{build}{bindings};
1640 1 50       3 my @rows = @{ $bindings->{terms} };
1641 0         0 my @vbs;
1642             foreach my $r (@rows) {
1643             my %d;
1644 1         2 foreach my $i (0 .. $#{ $r }) {
1645 1 50       3 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         4 }
1652             my $table = Attean::Algebra::Table->new( variables => \@vars, rows => \@vbs );
1653 1         12 my $pattern = pop(@{ $self->{build}{triples} });
1654 1         5 push(@{ $self->{build}{triples} }, $self->_new_join($pattern, $table));
1655             }
1656 1 50       3
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       5 # 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         4 # [22] GraphPatternNotTriples ::= OptionalGraphPattern | GroupOrUnionGraphPattern | GraphGraphPattern
1728             my $self = shift;
1729 1         2 return 1 if ($self->_test_token(LBRACE));
1730 1         3 my $t = $self->_peek_token;
1731 1         1 return unless ($t);
1732 1         8 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   143 $self->_ServiceGraphPattern;
1741             } elsif ($self->_test_token(KEYWORD, 'MINUS')) {
1742             $self->_MinusGraphPattern;
1743             } elsif ($self->_test_token(KEYWORD, 'BIND')) {
1744 109 100       207 $self->_Bind;
1745 59 50       119 } elsif ($self->_test_token(KEYWORD, 'HINT')) {
1746 59 50       126 $self->_Hint;
1747 59 50       135 } elsif ($self->_test_token(KEYWORD, 'OPTIONAL')) {
1748 59 100       127 $self->_OptionalGraphPattern;
1749 58 50       112 } elsif ($self->_test_token(LBRACE)) {
1750 58 100       104 $self->_GroupOrUnionGraphPattern;
1751 56 100       145 } else {
1752 46 50       121 $self->_GraphGraphPattern;
1753 46         110 }
1754             }
1755              
1756             my $self = shift;
1757 84     84   120 $self->_expected_token(KEYWORD, 'VALUES');
1758 84 100       152 my @vars;
1759 74 50       144
1760 74 100       155 my $parens = 0;
1761 73 100       160 if ($self->_optional_token(LPAREN)) {
1762 72 50       146 $parens = 1;
1763 72 50       135 }
1764 72 50       142 while ($self->_test_token(VAR)) {
1765 72 50       132 $self->_Var;
1766 72         186 push( @vars, splice(@{ $self->{_stack} }));
1767             }
1768             if ($parens) {
1769             $self->_expected_token(RPAREN);
1770             }
1771 51     51   82
1772 51         117 my $count = scalar(@vars);
1773 51         152 if (not($parens) and $count == 0) {
1774 51         156 croak "Syntax error: Expected VAR in inline data declaration";
1775 51         144 } elsif (not($parens) and $count > 1) {
1776 51         732 croak "Syntax error: Inline data declaration can only have one variable when parens are omitted";
1777 51         1518 }
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   73 while ($self->_test_token(LPAREN)) {
1785 51         75 my $terms = $self->_Binding($count);
1786 51         161 push( @rows, $terms );
1787             }
1788 51         135 } else {
1789 9 50       28 # { term term }
1790 0         0 while ($self->_BindingValue_test) {
1791             $self->_BindingValue;
1792 9         29 my ($term) = splice(@{ $self->{_stack} });
1793 9         13 push( @rows, [$term] );
1794 9 50       24 }
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   27 @d{ map { $_->value } @vars } = map { $_->does('Attean::API::TriplePattern') ? $_->as_triple : $_ } @$row;
1804 17 100       37 my $result = Attean::Result->new(bindings => \%d);
1805 14         32 push(@vbs, $result);
1806 14 50       38 }
1807 14 50       50 my $table = Attean::Algebra::Table->new( variables => \@vars, rows => \@vbs );
1808 14         47 $self->_add_stack( ['Attean::Algebra::Table', $table] );
1809            
1810             }
1811              
1812 13     13   18 my $self = shift;
1813 13 50       30 $self->_expected_token(KEYWORD, 'BIND');
    100          
    100          
    100          
    100          
    100          
    100          
1814 0         0 my ($var, $expr) = $self->_BrackettedAliasExpression;
1815             $self->_add_stack( ['Attean::Algebra::Extend', $var, $expr] );
1816 2         6 }
1817              
1818 1         4 my $self = shift;
1819             $self->_expected_token(KEYWORD, 'HINT');
1820 2         10 my $terms = $self->_HintTerms();
1821             $self->_add_hint($terms);
1822 1         6 }
1823              
1824 1         3 my $self = shift;
1825            
1826 3         8 $self->_expected_token(LPAREN);
1827            
1828 3         14 my @terms;
1829             while ($self->_BindingValue_test) {
1830             $self->_BindingValue;
1831             push(@terms, splice(@{ $self->{_stack} }));
1832             }
1833 0     0   0 $self->_expected_token(RPAREN);
1834 0         0 return \@terms;
1835 0         0 }
1836              
1837 0         0 my $self = shift;
1838 0 0       0 $self->_expected_token(KEYWORD, 'SERVICE');
1839 0         0 my $silent = $self->_optional_token(KEYWORD, 'SILENT') ? 1 : 0;
1840             $self->__close_bgp_with_filters;
1841 0         0 if ($self->_test_token(VAR)) {
1842 0         0 $self->_Var;
1843 0         0 } else {
  0         0  
1844             $self->_IRIref;
1845 0 0       0 }
1846 0         0 my ($endpoint) = splice( @{ $self->{_stack} } );
1847             $self->_GroupGraphPattern;
1848             my $ggp = $self->_remove_pattern;
1849 0         0
1850 0 0 0     0 my $opt = ['Attean::Algebra::Service', $endpoint, $ggp, ($silent ? 1 : 0)];
    0 0        
1851 0         0 $self->_add_stack( $opt );
1852             }
1853 0         0  
1854             # [23] OptionalGraphPattern ::= 'OPTIONAL' GroupGraphPattern
1855             # sub _OptionalGraphPattern_test {
1856 0   0     0 # my $self = shift;
1857 0         0 # return $self->_test_token(KEYWORD, 'OPTIONAL');
1858 0         0 # }
1859 0 0 0     0  
      0        
1860             my $self = shift;
1861 0         0 my @filters = splice(@{ $self->{filters} });
1862 0         0 if (@filters) {
1863 0         0 my ($cont, $hints) = $self->_pop_pattern_container;
1864             my $ggp = $self->_new_join(@$cont);
1865             $ggp->hints($hints);
1866             $self->_push_pattern_container;
1867 0         0 # my $ggp = $self->_remove_pattern();
1868 0         0 unless ($ggp) {
1869 0         0 $ggp = Attean::Algebra::BGP->new();
  0         0  
1870 0         0 }
1871             while (my $f = shift @filters) {
1872             $ggp = Attean::Algebra::Filter->new( children => [$ggp], expression => $f );
1873             }
1874 0         0 $self->_add_patterns($ggp);
1875             }
1876 0         0 }
1877 0         0  
1878 0         0 my $self = shift;
1879             $self->_expected_token(KEYWORD, 'OPTIONAL');
1880 0 0       0 $self->__close_bgp_with_filters;
  0         0  
  0         0  
1881 0         0
1882 0         0 $self->_GroupGraphPattern;
1883             my $ggp = $self->_remove_pattern;
1884 0         0 my $opt = ['Attean::Algebra::LeftJoin', $ggp];
1885 0         0 $self->_add_stack( $opt );
1886             }
1887              
1888             my $self = shift;
1889             $self->_expected_token(KEYWORD, 'MINUS');
1890 2     2   4 $self->__close_bgp_with_filters;
1891 2         7
1892 2         8 $self->_GroupGraphPattern;
1893 2         7 my $ggp = $self->_remove_pattern;
1894             my $opt = ['Attean::Algebra::Minus', $ggp];
1895             $self->_add_stack( $opt );
1896             }
1897 1     1   1  
1898 1         4 # [24] GraphGraphPattern ::= 'GRAPH' VarOrIRIref GroupGraphPattern
1899 1         4 my $self = shift;
1900 1         4 if ($self->{__data_pattern}) {
1901             if ($self->{__graph_nesting_level}++) {
1902             croak "Syntax error: Nested named GRAPH blocks not allowed in data template.";
1903             }
1904 1     1   2 }
1905            
1906 1         2 $self->_expected_token(KEYWORD, 'GRAPH');
1907             $self->_VarOrIRIref;
1908 1         2 my ($graph) = splice(@{ $self->{_stack} });
1909 1         3 if ($graph->does('Attean::API::IRI')) {
1910 3         11 $self->_GroupGraphPattern;
1911 3         6 } else {
  3         12  
1912             $self->_GroupGraphPattern;
1913 1         5 }
1914 1         3  
1915             if ($self->{__data_pattern}) {
1916             $self->{__graph_nesting_level}--;
1917             }
1918 2     2   4
1919 2         6 my $ggp = $self->_remove_pattern;
1920 2 50       6 my $pattern = Attean::Algebra::Graph->new( children => [$ggp], graph => $graph );
1921 2         7 $self->_add_patterns( $pattern );
1922 2 50       4 $self->_add_stack( [ 'Attean::Algebra::Graph' ] );
1923 0         0 }
1924              
1925 2         6 # [25] GroupOrUnionGraphPattern ::= GroupGraphPattern ( 'UNION' GroupGraphPattern )*
1926             # sub _GroupOrUnionGraphPattern_test {
1927 2         7 # my $self = shift;
  2         5  
1928 2         9 # return $self->_test_token(LBRACE);
1929 2         8 # }
1930              
1931 2 50       8 my $self = shift;
1932 2         5 $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   6 } else {
1943 4         5 $self->_add_patterns( $ggp );
  4         11  
1944 4 50       12 $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         9 }
1963 1         4  
1964             my $self = shift;
1965 1         5 if ($self->_test_token(LPAREN)) {
1966 1         4 $self->_BrackettedExpression();
1967 1         2 } elsif ($self->_BuiltInCall_test) {
1968 1         2 $self->_BuiltInCall();
1969             } else {
1970             $self->_FunctionCall();
1971             }
1972 1     1   2 }
1973 1         3  
1974 1         3 # [28] FunctionCall ::= IRIref ArgList
1975             # sub _FunctionCall_test {
1976 1         2 # my $self = shift;
1977 1         3 # return $self->_IRIref_test;
1978 1         3 # }
1979 1         3  
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       11 }
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         15 } else {
1993 3         10 my $func = Attean::ValueExpression->new( value => $iri );
  3         9  
1994 3 100       18 my $expr = $self->new_function_expression( 'INVOKE', $func, @args );
1995 2         54 $self->_add_stack( $expr );
1996             }
1997 1         45 }
1998              
1999             # [29] ArgList ::= ( NIL | '(' Expression ( ',' Expression )* ')' )
2000 3 50       14 my $self = shift;
2001 0         0 return 1 if $self->_test_token(NIL);
2002             return $self->_test_token(LPAREN);
2003             }
2004 3         11  
2005 3         57 my $self = shift;
2006 3         14 if ($self->_optional_token(NIL)) {
2007 3         10 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   5 }
2018 3         9 }
2019 3         12 $self->_expected_token(RPAREN);
2020 3 100       8 return @args;
2021 1         3 }
2022 1         5 }
2023 1         2  
2024 1         8 # [30] ConstructTemplate ::= '{' ConstructTriples? '}'
2025             my $self = shift;
2026 1         3 $self->_push_pattern_container;
2027 1         3 $self->_expected_token(LBRACE);
2028            
2029 2         7 if ($self->_ConstructTriples_test) {
2030 2         7 $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         9  
2038 4         13 # [31] ConstructTriples ::= TriplesSameSubject ( '.' ConstructTriples? )?
2039 4         10 my $self = shift;
  4         9  
2040 4         17 return $self->_TriplesBlock_test;
2041             }
2042              
2043             my $self = shift;
2044             $self->_TriplesSameSubject;
2045 2     2   3 while ($self->_optional_token(DOT)) {
2046 2 50       5 if ($self->_ConstructTriples_test) {
2047 2 50       7 $self->_TriplesSameSubject;
2048 2 50       6 }
2049 2         7 }
2050             }
2051              
2052             # [32] TriplesSameSubject ::= VarOrTerm PropertyListNotEmpty | TriplesNode PropertyList
2053 5     5   6 my $self = shift;
2054 5 100       10 my @triples;
    50          
2055 4         18 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         4 }
2072 1         2 }
  1         3  
2073 1 50       7
2074             $self->_add_patterns( @triples );
2075             # return @triples;
2076             }
2077 1         4  
2078             # TriplesSameSubjectPath ::= VarOrTerm PropertyListNotEmptyPath | TriplesNode PropertyListPath
2079 1 50       5 my $self = shift;
2080 0         0 my @triples;
2081 0         0 if ($self->_TriplesNode_test) {
2082             $self->_TriplesNode;
2083 1         14 my ($s) = splice(@{ $self->{_stack} });
2084 1         4 $self->_PropertyListPath;
2085 1         18 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   2 my ($s) = splice(@{ $self->{_stack} });
2092 1 50       3 $self->_PropertyListNotEmptyPath;
2093 1         3 my (@list) = splice(@{ $self->{_stack} });
2094             foreach my $data (@list) {
2095             push(@triples, $self->__new_statement( $s, @$data ));
2096             }
2097 2     2   3 }
2098 2 50       7 $self->_add_patterns( @triples );
2099 0         0 # return @triples;
2100             }
2101 2         7  
2102 2         10 # [33] PropertyListNotEmpty ::= Verb ObjectList ( ';' ( Verb ObjectList )? )*
2103 2 50       6 my $self = shift;
2104 2         9 $self->_Verb;
2105 2         3 my ($v) = splice(@{ $self->{_stack} });
  2         4  
2106 2         5 $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         6 $self->_Verb;
2112 2         4 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   6 $self->_add_stack( @props );
2119 3         9 }
2120 3         9  
2121             # [34] PropertyList ::= PropertyListNotEmpty?
2122 3 50       11 my $self = shift;
2123 3         10 if ($self->_Verb_test) {
2124             $self->_PropertyListNotEmpty;
2125             }
2126 3         10 }
2127 3         10  
2128 3         20 # [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   6 $self->_VerbSimple;
2134 3         9 }
2135             my ($v) = splice(@{ $self->{_stack} });
2136             $self->_ObjectList;
2137             my @l = splice(@{ $self->{_stack} });
2138 3     3   5 my @props = map { [$v, $_] } @l;
2139 3         10 while ($self->_optional_token(SEMICOLON)) {
2140 3         9 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   6 push(@props, map { [$v, $_] } @l);
2150 3         6 }
2151 3 100       8 }
2152 1         4 $self->_add_stack( @props );
2153 1         2 }
  1         3  
2154 1         3  
2155 1         2 # [34] PropertyListPath ::= PropertyListNotEmptyPath?
  1         2  
2156 1         3 my $self = shift;
2157 0         0 if ($self->_Verb_test) {
2158             $self->_PropertyListNotEmptyPath;
2159             }
2160 2         7 }
2161 2         4  
  2         6  
2162             # [35] ObjectList ::= Object ( ',' Object )*
2163 2         8 my $self = shift;
2164 2         3
  2         6  
2165 2         6 my @list;
2166 2         6 $self->_Object;
2167             push(@list, splice(@{ $self->{_stack} }));
2168            
2169             while ($self->_optional_token(COMMA)) {
2170 3         11 $self->_Object;
2171             push(@list, splice(@{ $self->{_stack} }));
2172             }
2173             $self->_add_stack( @list );
2174             }
2175              
2176 51     51   84 # [36] Object ::= GraphNode
2177 51         82 my $self = shift;
2178 51 50       150 $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         159 $self->_expected_token(RANNOT)
2188 51         110 }
  51         121  
2189 51         173 }
2190 51         81  
  51         99  
2191 51         101 # [37] Verb ::= VarOrIRIref | 'a'
2192 59         170 my $self = shift;
2193             return 1 if ($self->_test_token(A));
2194             return 1 if ($self->_test_token(VAR));
2195 51         162 return 1 if ($self->_IRIref_test);
2196             return 0;
2197             }
2198              
2199             my $self = shift;
2200             if ($self->_optional_token(A)) {
2201 2     2   3 my $type = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', lazy => 1);
2202 2         7 $self->_add_stack( $type );
2203 2         3 } else {
  2         6  
2204 2         8 $self->_VarOrIRIref;
2205 2         3 }
  2         6  
2206 2         5 }
  2         8  
2207 2         8  
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         5 }
2217              
2218             # VerbPath ::= Path
2219             my $self = shift;
2220             return 1 if ($self->_IRIref_test);
2221 1     1   2 return 1 if ($self->_test_token(HAT));
2222 1 50       3 return 1 if ($self->_test_token(OR));
2223 0         0 return 1 if ($self->_test_token(BANG));
2224             return 1 if ($self->_test_token(LPAREN));
2225             return 1 if ($self->_test_token(A));
2226             return 0;
2227             }
2228              
2229 53     53   92 my $self = shift;
2230 53 100       186 $self->_Path
2231 25         70 }
2232              
2233 28         78 # [74] Path ::= PathAlternative
2234             my $self = shift;
2235 53         110 $self->_PathAlternative;
  53         133  
2236 53         191 }
2237 53         84  
  53         119  
2238 53         115 ################################################################################
  53         173  
2239 53         129  
2240 9 50 33     38 # [75] PathAlternative ::= PathSequence ( '|' PathSequence )*
2241 9 50       21 my $self = shift;
2242 9         22 $self->_PathSequence;
2243             while ($self->_optional_token(OR)) {
2244 0         0 my ($lhs) = splice(@{ $self->{_stack} });
2245             # $self->_PathOneInPropertyClass;
2246 9         18 $self->_PathSequence;
  9         23  
2247 9         31 my ($rhs) = splice(@{ $self->{_stack} });
2248 9         15 $self->_add_stack( ['PATH', '|', $lhs, $rhs] );
  9         25  
2249 9         19 }
  9         35  
2250             }
2251              
2252 53         120 # [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   86 }
2266             my ($rhs) = splice(@{ $self->{_stack} });
2267 64         110 $self->_add_stack( ['PATH', $op, $lhs, $rhs] );
2268 64         184 }
2269 64         143 }
  64         169  
2270              
2271 64         149 # [77] PathElt ::= PathPrimary PathMod?
2272 0         0 my $self = shift;
2273 0         0 $self->_PathPrimary;
  0         0  
2274             # $self->__consume_ws_opt;
2275 64         144 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   86 $self->_add_stack( ['PATH', $mod, @path] );
2281 64         208 } else {
2282 64 100       202 # 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         6 # signed numeric object that follows the path
  2         9  
2285 2         13 $self->_add_stack( @path );
2286 2         7 }
  2         7  
2287 2         42 }
2288 2         1733 }
2289              
2290 2         8 # [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   25 } else {
2297 16 100       34 $self->_PathElt;
2298 14 100       26 }
2299 13 100       32 }
2300 3         7  
2301             # [79] PathMod ::= ( '*' | '?' | '+' | '{' ( Integer ( ',' ( '}' | Integer '}' ) | '}' ) ) )
2302             my $self = shift;
2303             return 1 if ($self->_test_token(STAR));
2304 19     19   31 return 1 if ($self->_test_token(QUESTION));
2305 19 100       48 return 1 if ($self->_test_token(PLUS));
2306 6         97 return 1 if ($self->_test_token(LBRACE));
2307 6         484 return 0;
2308             }
2309 13         34  
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   42 }
2321 28         57 $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   105 # my $value = 0;
2327 71 100       175 # if ($self->_test(qr/}/)) {
2328 35 50       89 # throw ::Error::ParseError -text => "Syntax error: Empty Path Modifier";
2329 35 50       81 # }
2330 35 50       73 # if ($self->_test($r_INTEGER)) {
2331 35 50       76 # $value = $self->_eat( $r_INTEGER );
2332 35 100       78 # $self->__consume_ws_opt;
2333 28         61 # }
2334             # if ($self->_test(qr/,/)) {
2335             # $self->_eat(qr/,/);
2336             # $self->__consume_ws_opt;
2337 34     34   51 # if ($self->_test(qr/}/)) {
2338 34         77 # $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   50 # $self->_eat(qr/}/);
2344 34         79 # $self->_add_stack( "$value-$end" );
2345             # }
2346             # } else {
2347             # $self->_eat(qr/}/);
2348             # $self->_add_stack( "$value" );
2349             # }
2350             }
2351 34     34   49 }
2352 34         85  
2353 34         72 # [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   46 } else {
2365 34         103 $self->_expected_token(LPAREN);
2366 34   66     78 $self->_Path;
2367 1         2 $self->_expected_token(RPAREN);
2368 1         1 }
  1         3  
2369 1 50       3 }
2370 1         2  
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         2  
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   53 }
2385 35         116 $self->_expected_token(RPAREN);
2386             $self->_add_stack( @nodes );
2387 35 100       118 } else {
2388 1         2 $self->_PathOneInPropertyClass;
  1         2  
2389 1         4 }
2390 1         2 }
  1         2  
2391 1 50       3  
2392 1         3 # [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   49 }
2405 35 50       77 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         90 $self->_add_stack( $type );
2411             }
2412             } else {
2413             $self->_IRIref;
2414             if ($rev) {
2415             my ($path) = splice(@{ $self->{_stack} });
2416 35     35   54 $self->_add_stack( [ 'PATH', '^', $path ] );
2417 35 100       86 }
2418 34 50       79 }
2419 34 50       67 }
2420 34 50       57  
2421 34         93 ################################################################################
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     2 return 1 if $self->_test_token(LBRACKET);
      33        
2427 1         2 return 0;
2428 1         2 }
2429 1 50       4  
    0          
2430 1         1 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   405 my $cur = $subj;
2471 35 100       80 my $last;
    50          
    0          
2472 29         68  
2473             my $first = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#first', lazy => 1);
2474 6         103 my $rest = Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#rest', lazy => 1);
2475 6         441 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   182 }
2545 122 100       205 }
2546 121 50       225  
2547 121         271 # [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         4  
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         3 my $b = $self->_BlankNode;
2582 1         3 $self->_add_stack( $b );
2583 1         1 } elsif ($self->_test_token(INTEGER) or $self->_test_token(DECIMAL) or $self->_test_token(DOUBLE) or $self->_test_token(MINUS) or $self->_test_token(PLUS)) {
  1         3  
2584             my $l = $self->_NumericLiteral;
2585 1         4 $self->_add_stack( $l );
2586 1         7 } elsif ($self->_test_literal_token) {
2587 1         2 my $l = $self->_RDFLiteral;
  1         16  
2588             $self->_add_stack( $l );
2589             } else {
2590 1         4 $self->_IRIref;
2591             }
2592 1         9 }
2593 1         51  
2594 1         1 # [46] Expression ::= ConditionalOrExpression
2595             my $self = shift;
2596 1         17 $self->_ConditionalOrExpression;
2597 1         69 }
2598 1         60  
2599             # [47] ConditionalOrExpression ::= ConditionalAndExpression ( '||' ConditionalAndExpression )*
2600             my $self = shift;
2601 1         46 my @list;
2602 1         8
2603 2         4 $self->_ConditionalAndExpression;
2604 2         30 push(@list, splice(@{ $self->{_stack} }));
2605 2         83
2606 2         3 while ($self->_test_token(OROR)) {
2607 2         4 $self->_expected_token(OROR);
2608             $self->_ConditionalAndExpression;
2609 1         2 push(@list, splice(@{ $self->{_stack} }));
2610 1         3 }
2611 1         4
2612             if (scalar(@list) > 1) {
2613 1         3 my $algebra = Attean::BinaryExpression->new( operator => '||', children => [splice(@list, 0, 2)] );
2614             while (scalar(@list)) {
2615             $algebra = Attean::BinaryExpression->new( operator => '||', children => [$algebra, shift(@list)] );
2616             }
2617             $self->_add_stack($algebra);
2618 2     2   4 } else {
2619             $self->_add_stack( @list );
2620             }
2621             if (scalar(@{ $self->{_stack} }) == 0) {
2622 2 100       3 my $t = $self->_peek_token;
2623 1 50       3 $self->_token_error($t, "Missing conditional expression");
2624 1 50       3 }
2625 1 50       3 }
2626 1 50       2  
2627 1 50       3 # [48] ConditionalAndExpression ::= ValueLogical ( '&&' ValueLogical )*
2628 1 50       3 my $self = shift;
2629 1 50       3 $self->_ValueLogical;
2630 1         3 my @list = splice(@{ $self->{_stack} });
2631            
2632             while ($self->_test_token(ANDAND)) {
2633             $self->_expected_token(ANDAND);
2634 68     68   98 $self->_ValueLogical;
2635 68 50       140 push(@list, splice(@{ $self->{_stack} }));
2636 0         0 }
2637            
2638 68         151 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   159 my $self = shift;
2656 121 100       214 $self->_NumericExpression;
    100          
2657 81         185
2658             my $t = $self->_peek_token;
2659 3         14 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         105 $self->_next_token;
2662             my @list = splice(@{ $self->{_stack} });
2663             my $op = $t->value;
2664             $self->_NumericExpression;
2665             push(@list, splice(@{ $self->{_stack} }));
2666 6     6   11 $self->_add_stack( $self->new_binary_expression( $op, @list ) );
2667 6 100       11 } elsif ($self->_test_token(KEYWORD, qr/^(NOT|IN)/)) {
2668 3         12 my @list = splice(@{ $self->{_stack} });
2669             my $not = $self->_optional_token(KEYWORD, 'NOT');
2670 3         14 $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   2 }
2677 1 50       4 }
2678 1 50       3  
2679 1         2 my $self = shift;
2680             if ($self->_optional_token(NIL)) {
2681             return;
2682             } else {
2683 17     17   27 $self->_expected_token(LPAREN);
2684 17 100       32 my @args;
2685 5         18 unless ($self->_test_token(RPAREN)) {
2686             $self->_Expression;
2687 12         44 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   184 $self->_expected_token(RPAREN);
2694 141 50       289 $self->_add_stack( @args );
2695 0         0 }
2696             }
2697              
2698 141         259 # [51] NumericExpression ::= AdditiveExpression
2699 141         360 my $self = shift;
2700             $self->_AdditiveExpression;
2701             }
2702              
2703             # [52] AdditiveExpression ::= MultiplicativeExpression ( '+' MultiplicativeExpression | '-' MultiplicativeExpression | NumericLiteralPositive | NumericLiteralNegative )*
2704 46     46   70 my $self = shift;
2705 46 50 33     94 $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         3 my $t = $self->_next_token;
2710 1         69 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         42 $self->_add_stack( $expr );
2716 11         28 }
2717              
2718 9         26 # [53] MultiplicativeExpression ::= UnaryExpression ( '*' UnaryExpression | '/' UnaryExpression )*
2719 9         28 my $self = shift;
2720             $self->_UnaryExpression;
2721 25         82 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   16 my ($rhs) = splice(@{ $self->{_stack} });
2728 11         29 $expr = $self->new_binary_expression( $op, $expr, $rhs );
2729             }
2730             $self->_add_stack( $expr );
2731             }
2732              
2733 11     11   15 # [54] UnaryExpression ::= '!' PrimaryExpression | '+' PrimaryExpression | '-' PrimaryExpression | PrimaryExpression
2734 11         17 my $self = shift;
2735             if ($self->_optional_token(BANG)) {
2736 11         31 $self->_PrimaryExpression;
2737 11         17 my ($expr) = splice(@{ $self->{_stack} });
  11         23  
2738             my $not = Attean::UnaryExpression->new( operator => '!', children => [$expr] );
2739 11         23 $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       36 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         20 $self->_add_stack( $lexpr );
2753             }
2754 11 50       17 } elsif ($self->_optional_token(MINUS)) {
  11         43  
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   14 my $lexpr = Attean::ValueExpression->new( value => $l );
2763 11         29 $self->_add_stack( $lexpr );
2764 11         22 } else {
  11         27  
2765             my $int = 'http://www.w3.org/2001/XMLSchema#integer';
2766 11         33 my $l = Attean::Literal->new( value => '-1', datatype => $int );
2767 1         3 my $neg = $self->new_binary_expression( '*', Attean::ValueExpression->new( value => $l ), $expr );
2768 1         4 my $lexpr = Attean::ValueExpression->new( value => $neg );
2769 1         2 $self->_add_stack( $lexpr );
  1         3  
2770             }
2771             } else {
2772 11 100       43 $self->_PrimaryExpression;
2773 1         15 }
2774 1         12 }
2775 0         0  
2776             # [55] PrimaryExpression ::= BrackettedExpression | BuiltInCall | IRIrefOrFunction | RDFLiteral | NumericLiteral | BooleanLiteral | Var
2777 1         3 my $self = shift;
2778             my $t = $self->_peek_token;
2779 10         60 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   12 my $v = pop(@{ $self->{_stack} });
2786 12         34 if ($v->does('Attean::API::IRI')) {
2787             $v = Attean::ValueExpression->new(value => $v);
2788             }
2789             $self->_add_stack($v);
2790             } elsif ($self->_test_token(VAR)) {
2791 12     12   17 $self->_Var;
2792 12         31 my $var = pop(@{ $self->{_stack} });
2793             my $expr = Attean::ValueExpression->new(value => $var);
2794 12         25 $self->_add_stack($expr);
2795 12         31 } elsif ($self->_test_token(BOOLEAN)) {
2796 12 100 33     132 my $b = $self->_BooleanLiteral;
    50 33        
      33        
      66        
      100        
2797 3         8 my $expr = Attean::ValueExpression->new(value => $b);
2798 3         4 $self->_add_stack($expr);
  3         7  
2799 3         9 } elsif ($self->_test_token(INTEGER) or $self->_test_token(DECIMAL) or $self->_test_token(DOUBLE) or $self->_test_token(PLUS) or $self->_test_token(MINUS)) {
2800 3         7 my $l = $self->_NumericLiteral;
2801 3         4 my $expr = Attean::ValueExpression->new(value => $l);
  3         7  
2802 3         9 $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   19 if ($term->does('Attean::API::Blank')) {
2838 15         32 croak "Expecting (non-blank) RDF term but found blank";
2839             }
2840             }
2841             }
2842              
2843 15     15   19 # [56] BrackettedExpression ::= '(' Expression ')'
2844 15         35 # sub _BrackettedExpression_test {
2845 15         21 # my $self = shift;
  15         29  
2846             # return $self->_test_token(LPAREN);
2847 15   66     30 # }
2848 1         2  
2849 1 50       4 my $self = shift;
2850 1         4 $self->_expected_token(LPAREN);
2851 1         2 $self->_Expression;
  1         2  
2852 1         3 $self->_expected_token(RPAREN);
2853             }
2854 15         29  
2855             my $self = shift;
2856            
2857             my $op;
2858             my $custom_agg_iri;
2859 16     16   17 if (scalar(@_)) {
2860 16         49 $custom_agg_iri = shift->value;
2861 16         49 $op = 'CUSTOM';
  16         31  
2862             } else {
2863 16   33     36 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         39 }
2871            
2872             my $star = 0;
2873             my (@expr, %options);
2874             if ($self->_optional_token(STAR)) {
2875 16     16   19 $star = 1;
2876 16 100       33 } else {
    100          
    100          
2877 1         4 $self->_Expression;
2878 1         3 push(@expr, splice(@{ $self->{_stack} }));
  1         3  
2879 1         8 if ($op eq 'GROUP_CONCAT') {
2880 1         13 while ($self->_optional_token(COMMA)) {
2881             $self->_Expression;
2882 1         3 push(@expr, splice(@{ $self->{_stack} }));
2883 1         3 }
  1         3  
2884             if ($self->_optional_token(SEMICOLON)) {
2885             $self->_expected_token(KEYWORD, 'SEPARATOR');
2886 1 50 33     13 $self->_expected_token(EQUALS);
      33        
2887 1         17 my $sep = $self->_String;
2888 1         18 $options{ seperator } = $sep;
2889 1         84 }
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         3 my $name = sprintf('%s(%s)', $op, $arg);
2897 1         2 $self->_expected_token(RPAREN);
  1         3  
2898            
2899             my $var = Attean::Variable->new( value => ".$name");
2900 1 50 33     16 my $agg = Attean::AggregateExpression->new(
      33        
2901 1         22 distinct => $distinct,
2902 1         21 operator => $op,
2903 1         86 children => [@expr],
2904 1         4 scalar_vars => \%options,
2905             variable => $var,
2906 0         0 custom_iri => $custom_agg_iri
2907 0         0 );
2908 0         0 $self->{build}{__aggregate}{ $name } = [ $var, $agg ];
2909 0         0 my $expr = Attean::ValueExpression->new(value => $var);
2910 0         0 $self->_add_stack($expr);
2911             }
2912              
2913 13         43 # [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   22 }
2920 16         26 return 1 if ($self->_test_token(KEYWORD, 'NOT'));
2921 16 50 100     33 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         7 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         4  
2927 1         1 my $self = shift;
  1         3  
2928 1 50       7 my $t = $self->_peek_token;
2929 0         0 if ($self->{__aggregate_call_ok} and $self->_test_token(KEYWORD, qr/^(MIN|MAX|COUNT|AVG|SUM|SAMPLE|GROUP_CONCAT)\b/io)) {
2930             $self->_Aggregate;
2931 1         21 } elsif ($self->_test_token(KEYWORD, qr/^(NOT|EXISTS)/)) {
2932             my $not = $self->_optional_token(KEYWORD, 'NOT');
2933 8         38 $self->_expected_token(KEYWORD, 'EXISTS');
2934 8         20 local($self->{filters}) = [];
  8         20  
2935 8         164 $self->_GroupGraphPattern;
2936 8         22 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         12 } elsif ($self->_test_token(KEYWORD, qr/^(COALESCE|BNODE|CONCAT|SUBSTR|RAND|NOW)/i)) {
2943 4         67 # n-arg functions that take expressions
2944 4         16 my $t = $self->_next_token;
2945             my $op = $t->value;
2946 1         6 my @args = $self->_ArgList;
2947 1         3 my $func = $self->new_function_expression( $op, @args );
  1         2  
2948 1         14 $self->_add_stack( $func );
2949 1         5 } elsif ($self->_test_token(KEYWORD, 'REGEX')) {
2950             $self->_RegexExpression;
2951 0         0 } else {
2952 0         0 my $t = $self->_next_token;
2953 0         0 my $op = $t->value;
2954             if ($op =~ /^(STR)?UUID$/i) {
2955             # no-arg functions
2956             $self->_expected_token(NIL);
2957             $self->_add_stack( $self->new_function_expression($op) );
2958 1     1   2 } elsif ($op =~ /^(STR|URI|IRI|LANG|DATATYPE|isIRI|isURI|isBLANK|isLITERAL|isNumeric|ABS|CEIL|FLOOR|ROUND|STRLEN|UCASE|LCASE|ENCODE_FOR_URI|MD5|SHA1|SHA224|SHA256|SHA384|SHA512|HOURS|MINUTES|SECONDS|DAY|MONTH|YEAR|TIMEZONE|TZ|ISTRIPLE|SUBJECT|PREDICATE|OBJECT)$/i) {
2959             ### one-arg functions that take an expression
2960 1         4 $self->_expected_token(LPAREN);
2961 1         5 $self->_Expression;
2962 1         6 my ($expr) = splice(@{ $self->{_stack} });
2963 1         5 $self->_add_stack( $self->new_function_expression($op, $expr) );
2964 1         44 $self->_expected_token(RPAREN);
2965             } elsif ($op =~ /^(STRDT|STRLANG|LANGMATCHES|sameTerm|CONTAINS|STRSTARTS|STRENDS|STRBEFORE|STRAFTER)$/i) {
2966 1         3 ### two-arg functions that take expressions
  1         4  
2967             $self->_expected_token(LPAREN);
2968 1         6 $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       6 $self->_add_stack( $self->new_function_expression($op, $arg1, $arg2) );
    50          
2974 1         5 $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         5 my ($arg1) = splice(@{ $self->{_stack} });
2980 1         2 $self->_expected_token(COMMA);
  1         4  
2981 1 50       17 $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   5 $self->_add_stack( $self->new_function_expression($op, $expr) );
2995 5         16 $self->_expected_token(RPAREN);
2996 5         17 }
2997 5         10 }
2998             }
2999              
3000             # [58] RegexExpression ::= 'REGEX' '(' Expression ',' Expression ( ',' Expression )? ')'
3001 1     1   1 # sub _RegexExpression_test {
3002             # my $self = shift;
3003 1         2 # return $self->_test_token(KEYWORD, 'REGEX');
3004             # }
3005 1 50       14  
3006 0         0 my $self = shift;
3007 0         0 $self->_expected_token(KEYWORD, 'REGEX');
3008             $self->_expected_token(LPAREN);
3009 1         2 $self->_Expression;
3010 1         2 my $string = splice(@{ $self->{_stack} });
3011            
3012 1         2 $self->_expected_token(COMMA);
3013 1         2 $self->_Expression;
3014 1 50       2 my $pattern = splice(@{ $self->{_stack} });
3015 0         0
3016             my @args = ($string, $pattern);
3017             if ($self->_optional_token(COMMA)) {
3018 1         2 $self->_Expression;
3019 1         2 push(@args, splice(@{ $self->{_stack} }));
3020 1 50       3 }
3021 0         0
3022             $self->_expected_token(RPAREN);
3023 1         5 $self->_add_stack( $self->new_function_expression( 'REGEX', @args ) );
3024 1         1 }
  1         2  
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       3 return $self->_Aggregate($iri);
  1         6  
3039 1 50       3 }
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         2 $self->_add_stack( $expr );
3044             } else {
3045 1         14 my $func = Attean::ValueExpression->new( value => $iri );
3046 1         43 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         15 my $value = $self->_String;
3055 1         19
3056 1         3 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   34 } elsif ($self->_test_token(HATHAT)) {
3062 23         44 $self->_expected_token(HATHAT);
3063 23 100       47 $self->_IRIref;
3064 22 100       51 my ($iri) = splice(@{ $self->{_stack} });
3065 4 100       15 $obj = Attean::Literal->new( value => $value, datatype => $iri );
3066             } else {
3067 21 50       53 $obj = Attean::Literal->new( value => $value );
3068 21 50       49 }
3069 21 50       77
3070 21 50       105 return $obj;
3071 21         64 }
3072              
3073             # [61] NumericLiteral ::= NumericLiteralUnsigned | NumericLiteralPositive | NumericLiteralNegative
3074             # [62] NumericLiteralUnsigned ::= INTEGER | DECIMAL | DOUBLE
3075 2     2   4 # [63] NumericLiteralPositive ::= INTEGER_POSITIVE | DECIMAL_POSITIVE | DOUBLE_POSITIVE
3076 2         3 # [64] NumericLiteralNegative ::= INTEGER_NEGATIVE | DECIMAL_NEGATIVE | DOUBLE_NEGATIVE
3077 2 100 66     12 my $self = shift;
    50          
    50          
    50          
3078 1         4 my $sign = 0;
3079             if ($self->_optional_token(PLUS)) {
3080 0         0 $sign = '+';
3081 0         0 } elsif ($self->_optional_token(MINUS)) {
3082 0         0 $sign = '-';
3083 0         0 }
3084 0         0
3085 0         0 my $value;
3086 0 0       0 my $type;
3087 0         0 if (my $db = $self->_optional_token(DOUBLE)) {
3088             $value = $db->value;
3089 0         0 $type = Attean::IRI->new(value => 'http://www.w3.org/2001/XMLSchema#double', lazy => 1);
3090             } elsif (my $dc = $self->_optional_token(DECIMAL)) {
3091             $value = $dc->value;
3092 0         0 $type = Attean::IRI->new(value => 'http://www.w3.org/2001/XMLSchema#decimal', lazy => 1);
3093 0         0 } else {
3094 0         0 my $i = $self->_expected_token(INTEGER);
3095 0         0 $value = $i->value;
3096 0         0 $type = Attean::IRI->new(value => 'http://www.w3.org/2001/XMLSchema#integer', lazy => 1);
3097             }
3098 0         0
3099             if ($sign) {
3100 1         3 $value = $sign . $value;
3101 1         3 }
3102 1 50       10
    50          
    50          
    50          
3103             my $obj = Attean::Literal->new( value => $value, datatype => $type );
3104 0         0 # if ($self->{args}{canonicalize} and blessed($obj) and $obj->isa('RDF::Trine::Node::Literal')) {
3105 0         0 # $obj = $obj->canonicalize;
3106             # }
3107              
3108 0         0 return $obj;
3109 0         0 }
3110 0         0  
  0         0  
3111 0         0 # [65] BooleanLiteral ::= 'true' | 'false'
3112 0         0 my $self = shift;
3113             my $t = $self->_expected_token(BOOLEAN);
3114             my $bool = $t->value;
3115 0         0  
3116 0         0 my $obj = Attean::Literal->new( value => $bool, datatype => 'http://www.w3.org/2001/XMLSchema#boolean' );
3117 0         0 # if ($self->{args}{canonicalize} and blessed($obj) and $obj->isa('RDF::Trine::Node::Literal')) {
  0         0  
3118 0         0 # $obj = $obj->canonicalize;
3119 0         0 # }
3120 0         0 return $obj;
  0         0  
3121 0         0 }
3122 0         0  
3123             # [66] String ::= STRING_LITERAL1 | STRING_LITERAL2 | STRING_LITERAL_LONG1 | STRING_LITERAL_LONG2
3124             my $self = shift;
3125 0         0 my $value;
3126 0         0 my $string;
3127 0         0 my $t = $self->_peek_token;
  0         0  
3128 0         0 if ($string = $self->_optional_token(STRING1D)) {
3129 0         0 $value = $string->value;
3130 0         0 } elsif ($string = $self->_optional_token(STRING1S)) {
  0         0  
3131 0         0 $value = $string->value;
3132 0         0 } elsif ($string = $self->_optional_token(STRING3S)) {
3133 0         0 $value = $string->value;
  0         0  
3134 0         0 } elsif ($string = $self->_optional_token(STRING3D)) {
3135 0         0 $value = $string->value;
3136             } else {
3137             my $got = AtteanX::SPARQL::Constants::decrypt_constant($t->type);
3138 1         4 my $value = $t->value;
3139 1         4 croak "Expecting string literal but found $got '$value'";
3140 1         2 }
  1         2  
3141 1         15  
3142 1         5 $value =~ s/\\t/\t/g;
3143 1         3 $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         4 my $iri = $self->namespaces->namespace_uri($ns)->iri($local);
3184 1 50       4 my $base = $self->__base;
3185 1         2 my $p = $self->new_iri( value => $iri->value, $base ? (base => $base) : () );
  1         3  
3186 1 50       8 return $p;
3187             }
3188 0         0  
3189             my $self = shift;
3190 1         4 # 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         15 }
3196 1         5 }
3197 1         59  
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   18 $self->_expected_token(GTGT);
3205 9         24
3206             my ($s, $p, $o) = splice(@{ $self->{_stack} }, -3);
3207 9         14
3208 9 100       21 if ($self->{__data_pattern}) {
    50          
3209 2         8 foreach my $term ($s, $o) {
3210 2         7 if ($term->does('Attean::API::Blank')) {
3211 2         33 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         106  
3219             my $self = shift;
3220             #'<<' DataValueTerm Verb DataValueTerm '>>'
3221 9         21 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   23 }
3230 15         22 if (my $b = $self->_optional_token(BNODE)) {
3231 15 50       24 my $label = $b->value;
    50          
3232 0         0 return Attean::Blank->new($label);
3233             } else {
3234 0         0 $self->_expected_token(ANON);
3235             return Attean::Blank->new();
3236             }
3237 15         24 }
3238              
3239 15 100       27 my $self = shift;
    100          
3240 1         6 $self->_expected_token(NIL);
3241 1         16 return Attean::IRI->new(value => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#nil', lazy => 1);
3242             }
3243 1         4  
3244 1         21 my $self = shift;
3245             my $star = shift;
3246 13         19 my @exprs = @_;
3247 13         29
3248 13         184 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       923 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         255 }
3256            
3257             my $has_aggregation = 0;
3258             my $having_expr;
3259             my $aggdata = delete( $self->{build}{__aggregate} );
3260 15         3261 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   18
3279 9         15 my %group_vars;
3280             my %agg_vars;
3281 9         23 if ($has_aggregation) {
3282 9 100       22 foreach my $agg_var (map { $_->[0] } values %$aggdata) {
    100          
    50          
    0          
3283 7         20 $agg_vars{ $agg_var->value }++;
3284             }
3285 1         3 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         22 my @vars;
3297 9         16 my @extend;
3298 9         18 if ($star) {
3299 9         15 my $pattern = ${ $self->{build}{triples} }[-1];
3300 9         508 push(@project, $pattern->in_scope_variables);
3301 9         15 if ($has_aggregation) {
3302 9         16 croak "Cannot SELECT * in an aggregate query";
3303 9         31 }
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   875 if ($has_aggregation) {
3309 204 100       334 my @vars = $v->does('Attean::API::Variable') ? $v : $v->unaggregated_variables;
3310 155 100       336 foreach my $var (@vars) {
3311 116         287 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   125 }
3317 90 100       185
3318 62         200 push(@project, $k);
3319 62         191 if ($v->does('Attean::API::Variable')) {
3320 62 100       273 push(@vars, $v);
3321 62         8868 } else {
3322             push(@extend, $k, $v);
3323 28         80 }
3324 28         87 }
3325             }
3326              
3327             {
3328             my $pattern = pop(@{ $self->{build}{triples} });
3329             my %in_scope = map { $_ => 1 } $pattern->in_scope_variables;
3330 28     28   40 while (my($name, $expr) = splice(@extend, 0, 2)) {
3331 28         86 if (exists $in_scope{$name}) {
3332 28         49 croak "Syntax error: Already-bound variable ($name) used in project expression";
  28         87  
3333 28         57 }
3334             my $var = Attean::Variable->new( value => $name );
3335             $pattern = Attean::Algebra::Extend->new(children => [$pattern], variable => $var, expression => $expr);
3336 28 50       173 }
3337 0         0 push(@{ $self->{build}{triples} }, $pattern);
3338             }
3339            
3340 28         202 if ($having_expr) {
3341 28         8557 my $pattern = pop(@{ $self->{build}{triples} });
3342 28 50       167 my $filter = Attean::Algebra::Filter->new( children => [$pattern], expression => $having_expr );
3343 28         4126 push(@{ $self->{build}{triples} }, $filter);
3344             }
3345            
3346             if ($self->{build}{options}{orderby}) {
3347 6     6   11 my $order = delete $self->{build}{options}{orderby};
3348             my $pattern = pop(@{ $self->{build}{triples} });
3349 6 50       16 my @order = @$order;
3350 0         0 my @cmps;
3351             foreach my $o (@order) {
3352 6         22 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   5 push(@{ $self->{build}{triples} }, $sort);
3358             }
3359 3         12  
3360 3         14 {
3361 3         14 my $pattern = pop(@{ $self->{build}{triples} });
3362 3         15 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         7 }
  3         11  
3366             push(@{ $self->{build}{triples} }, $pattern);
3367 3 50       13 }
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         14 push(@{ $self->{build}{triples} }, $sort);
3376             }
3377            
3378             if (exists $self->{build}{options}{offset} and exists $self->{build}{options}{limit}) {
3379 0     0   0 my $limit = delete $self->{build}{options}{limit};
3380             my $offset = delete $self->{build}{options}{offset};
3381 0         0 my $pattern = pop(@{ $self->{build}{triples} });
3382 0         0 my $sliced = Attean::Algebra::Slice->new( children => [$pattern], limit => $limit, offset => $offset );
3383             push(@{ $self->{build}{triples} }, $sliced);
3384             } elsif (exists $self->{build}{options}{offset}) {
3385             my $offset = delete $self->{build}{options}{offset};
3386             my $pattern = pop(@{ $self->{build}{triples} });
3387 0     0   0 my $sliced = Attean::Algebra::Slice->new( children => [$pattern], offset => $offset );
3388 0 0       0 push(@{ $self->{build}{triples} }, $sliced);
3389 0         0 } elsif (exists $self->{build}{options}{limit}) {
3390             my $limit = delete $self->{build}{options}{limit};
3391 0 0       0 my $pattern = pop(@{ $self->{build}{triples} });
3392 0         0 my $sliced = Attean::Algebra::Slice->new( children => [$pattern], limit => $limit );
3393 0         0 push(@{ $self->{build}{triples} }, $sliced);
3394             }
3395 0         0
3396 0         0 return @project;
3397             }
3398              
3399             ################################################################################
3400              
3401 1     1   2 =item C<< error >>
3402 1         3  
3403 1         17 Returns the error encountered during the last parse.
3404              
3405             =cut
3406              
3407 29     29   55 my $self = shift;
3408 29         46 my @triples = @_;
3409 29         71 my $container = $self->{ _pattern_container_stack }[0];
3410             push( @{ $container }, @triples );
3411 29 50       102 }
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         46 my $container = $self->{ _pattern_container_stack }[0];
3421 29         48 my $pattern = $container->[-1];
3422 29         54 return $pattern;
3423 29   100     140 }
3424 29 100       48  
  29         161  
3425 29 100 100     160 my $self = shift;
3426 2         4 my $hints = shift;
3427 2         4 push( @{ $self->{ _pattern_container_hints_stack }[0] }, $hints );
3428 2         5 }
3429 2         3  
  2         5  
3430 2         5 my $self = shift;
3431             my $cont = [];
3432             unshift( @{ $self->{ _pattern_container_stack } }, $cont );
3433 2         4 unshift( @{ $self->{ _pattern_container_hints_stack } }, [] );
3434 2         4 return $cont;
3435 2 100       5 }
3436 1         1  
3437             my $self = shift;
3438 2         19 my $hints = shift( @{ $self->{ _pattern_container_hints_stack } } );
3439 2         4 my $cont = shift( @{ $self->{ _pattern_container_stack } } );
  2         7  
3440             return ($cont, $hints);
3441             }
3442 29         62  
3443             my $self = shift;
3444 29 100       69 my @items = @_;
3445 2         5 push( @{ $self->{_stack} }, @items );
  2         5  
3446 2         8 }
3447              
3448 2         4 my $self = shift;
3449 2 50 33     15 my @filters = shift;
3450 2         32 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         72 return $self->new_iri($build->{base});
3459             } else {
3460 29         0 return;
3461 29 100       99 }
3462 25         38 }
  25         63  
3463 25         129  
3464 25 50       1157 my $self = shift;
3465 0         0 my $s = shift;
3466             my $p = shift;
3467             my $o = shift;
3468 4         32 my $annot;
3469 5         14 if ($o->isa('AtteanX::Parser::SPARQL::ObjectWrapper')) {
3470 5         10 if (reftype($p) eq 'ARRAY' and $p->[0] eq 'PATH') {
3471 5 100       11 # this is actually a property path, for which annotations (stored in the ObjectWrapper) are forbidden
3472 3 100       7 croak "Syntax error: Cannot use SPARQL-star annotation syntax on a property path";
3473 3         25 }
3474 3         8 $annot = $o->annotations;
3475 3 100 100     12 $o = $o->value;
3476 1         24 }
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       19 my ($p, $o) = @$pair;
3483 3         73 push(@st, $self->__new_statement($s, $p, $o));
3484             }
3485 1         15 }
3486             return @st;
3487             }
3488              
3489             my $self = shift;
3490             my $start = shift;
3491 28         88 my $pdata = shift;
  28         44  
  28         80  
3492 28         91 my $end = shift;
  51         1194  
3493 28         152 (undef, my $op, my @nodes) = @$pdata;
3494 1 50       2 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         19  
3498 1         62 my $self = shift;
3499             my $op = shift;
3500 28         76 my @nodes = @_;
  28         83  
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       106 $nodes[$i] = $self->__new_path_pred(@data);
3510 1         2 } elsif ($nodes[$i]->does('Attean::API::IRI')) {
3511 1         2 $nodes[$i] = Attean::Algebra::PredicatePath->new( predicate => $nodes[$i] );
  1         2  
3512 1         3 }
3513 1         1 }
3514 1         2
3515 1         2 if ($op eq '*') {
3516 1         2 return Attean::Algebra::ZeroOrMorePath->new( children => [@nodes] );
3517 1         7 } elsif ($op eq '+') {
3518             return Attean::Algebra::OneOrMorePath->new( children => [@nodes] );
3519 1         1597 } elsif ($op eq '?') {
3520 1         2 return Attean::Algebra::ZeroOrOnePath->new( children => [@nodes] );
  1         4  
3521             } elsif ($op eq '^') {
3522             return Attean::Algebra::InversePath->new( children => [@nodes] );
3523             } elsif ($op eq '/') {
3524 28         47 return Attean::Algebra::SequencePath->new( children => [@nodes] );
  28         42  
  28         69  
3525 28         56 } elsif ($op eq '|') {
  47         1574  
3526 28 100       1317 return Attean::Algebra::AlternativePath->new( children => [@nodes] );
3527 27         478 } else {
3528             $self->log->debug("Path $op:\n". Dumper(\@nodes));
3529 28         54 confess "Error in path $op. See debug log for details."
  28         89  
3530             }
3531             }
3532 28 100       106  
3533 1         2 # 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         2  
3535 1 50       8 # ggp if necessary
3536             my $self = shift;
3537             my @patterns = @_;
3538 1         2 my @paths = grep { reftype($_->predicate) eq 'ARRAY' and $_->predicate->[0] eq 'PATH' } @patterns;
  1         3  
3539             my @triples = grep { blessed($_->predicate) } @patterns;
3540             if ($self->log->is_trace && (scalar(@patterns) > scalar(@paths) + scalar(@triples))) {
3541 28 100 100     193 $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         1
  1         2  
3545 1         9 my $bgp = Attean::Algebra::BGP->new( triples => \@triples );
3546 1         3 if (@paths) {
  1         4  
3547             my @p;
3548 1         3 foreach my $p (@paths) {
3549 1         2 my $start = $p->subject;
  1         2  
3550 1         18 my $end = $p->object;
3551 1         2 my $pdata = $p->predicate;
  1         3  
3552             push(@p, $self->__new_path( $start, $pdata, $end ));
3553 0         0 }
3554 0         0 if (scalar(@triples)) {
  0         0  
3555 0         0 return $self->_new_join($bgp, @p);
3556 0         0 } else {
  0         0  
3557             return $self->_new_join(@p);
3558             }
3559 28         118 } 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   257 my $op = shift;
3572 183         277 my @operands = @_[0,1];
3573 183         301 return Attean::BinaryExpression->new( operator => $op, children => \@operands );
3574 183         214 }
  183         443  
3575              
3576             =item C<new_function_expression ( $function, @operands )>
3577              
3578 17     17   27 Returns a new function expression structure.
3579 17         30  
3580 17         26 =cut
  17         29  
3581 17         27  
3582             my $self = shift;
3583             my $function = shift;
3584             my @operands = @_;
3585 38     38   73 my $base = $self->__base;
3586 38         69 return Attean::FunctionExpression->new( operator => $function, children => \@operands, $base ? (base => $base) : () );
3587 38         62 }
3588 38         68  
3589             my $self = shift;
3590             my @parts = @_;
3591             if (0 == scalar(@parts)) {
3592 1     1   1 return Attean::Algebra::BGP->new();
3593 1         3 } elsif (1 == scalar(@parts)) {
3594 1         2 return shift(@parts);
  1         5  
3595             } else {
3596             return Attean::Algebra::Join->new( children => \@parts );
3597             }
3598 168     168   218 }
3599 168         241  
3600 168         218 my $self = shift;
  168         396  
3601 168         199 my $l = $self->lexer;
  168         294  
3602 168         221 my $t = $l->peek;
3603             return unless ($t);
3604             while ($t == COMMENT) {
3605             $t = $l->peek;
3606 120     120   154 return unless ($t);
3607 120         144 }
  120         226  
3608 120         147 return $t;
  120         185  
3609 120         260 }
3610              
3611             my $self = shift;
3612             my $type = shift;
3613 487     487   6794 my $t = $self->_peek_token;
3614 487         764 return unless ($t);
3615 487         561 return if ($t->type != $type);
  487         1378  
3616             if (@_) {
3617             my $value = shift;
3618             if (ref($value) eq 'Regexp') {
3619 4     4   6 return unless ($t->value =~ $value);
3620 4         9 } else {
3621 4         4 return unless ($t->value eq $value);
  4         10  
3622             }
3623             }
3624             return 1;
3625 93     93   145 }
3626 93         170  
3627 93 100       446 my $self = shift;
    50          
3628 4         10 if ($self->_test_token(@_)) {
3629             return $self->_next_token;
3630 0         0 }
3631             return;
3632 89         153 }
3633              
3634             my $self = shift;
3635             my $l = $self->lexer;
3636             my $t = $l->next;
3637 73     73   125 while ($t->type == COMMENT) {
3638 73         97 $t = $l->peek;
3639 73         102 return unless ($t);
3640 73         91 }
3641 73         98 return $t;
3642 73 100       545 }
3643 2 50 33     15  
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         19 } else {
3649             my $t = $self->_peek_token;
3650 73         1135 my $expecting = AtteanX::SPARQL::Constants::decrypt_constant($type);
3651 73         2353 my $got = blessed($t) ? AtteanX::SPARQL::Constants::decrypt_constant($t->type) : '(undef)';
3652 73 100       155 if (@_) {
3653 2         7 my $value = shift;
3654 2         5 if ($t) {
3655 3         8 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         210 }
3660             } else {
3661             confess "Expecting $expecting but found $got before " . $self->lexer->buffer;
3662             }
3663 1     1   3 }
3664 1         1 }
3665 1         1  
3666 1         2 my $self = shift;
3667 1         2 my $t = shift;
3668 1         4 my $note = shift;
3669 1         120 my $got = blessed($t) ? AtteanX::SPARQL::Constants::decrypt_constant($t->type) : '(undef)';
3670             my $message = "$note but got $got";
3671             if ($t and $t->start_line > 0) {
3672             my $l = $t->start_line;
3673 2     2   3 my $c = $t->start_column;
3674 2         3 $message .= " at $l:$c";
3675 2         3 } else {
3676             my $n = $self->lexer->buffer;
3677 2 50       5 $n =~ s/\s+/ /g;
3678 0         0 $n =~ s/\s*$//;
3679             if ($n) {
3680             $message .= " near '$n'";
3681 2         4 }
3682 3 100       78 }
    50          
3683 1         2 croak $message;
  1         2  
3684 1         5 }
3685              
3686 2         57  
3687             use strict;
3688             use warnings;
3689             no warnings 'redefine';
3690 2 100       151 use Types::Standard qw(InstanceOf HashRef ArrayRef Bool Str Int);
    50          
    50          
    50          
    50          
    0          
3691 1         17  
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         18  
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   79 =head1 AUTHOR
3713 51         89  
3714 51 100       88 Gregory Todd Williams C<< <gwilliams@cpan.org> >>
  62         344  
3715 51         99  
  62         232  
3716 51 50 33     817 =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         17138  
3722 51 100       146 =cut