shell bypass 403
# RDF::Query::Parser::SPARQL11
# -----------------------------------------------------------------------------
=head1 NAME
RDF::Query::Parser::SPARQL11 - SPARQL 1.1 Parser.
=head1 VERSION
This document describes RDF::Query::Parser::SPARQL11 version 2.918.
=head1 SYNOPSIS
use RDF::Query::Parser::SPARQL11;
my $parser = RDF::Query::Parser::SPARQL11->new();
my $iterator = $parser->parse( $query, $base_uri );
=head1 DESCRIPTION
...
=head1 METHODS
Beyond the methods documented below, this class inherits methods from the
L<RDF::Query::Parser> class.
=over 4
=cut
package RDF::Query::Parser::SPARQL11;
use strict;
use warnings;
no warnings 'redefine';
use base qw(RDF::Query::Parser);
use URI;
use Data::Dumper;
use RDF::Query::Error qw(:try);
use RDF::Query::Parser;
use RDF::Query::Algebra;
use RDF::Trine::Namespace qw(rdf);
use Scalar::Util qw(blessed looks_like_number reftype);
######################################################################
our ($VERSION);
BEGIN {
$VERSION = '2.918';
}
######################################################################
my $rdf = RDF::Trine::Namespace->new('http://www.w3.org/1999/02/22-rdf-syntax-ns#');
my $xsd = RDF::Trine::Namespace->new('http://www.w3.org/2001/XMLSchema#');
our $r_ECHAR = qr/\\([tbnrf\\"'])/o;
our $r_STRING_LITERAL1 = qr/'(([^\x{27}\x{5C}\x{0A}\x{0D}])|${r_ECHAR})*'/o;
our $r_STRING_LITERAL2 = qr/"(([^\x{22}\x{5C}\x{0A}\x{0D}])|${r_ECHAR})*"/o;
our $r_STRING_LITERAL_LONG1 = qr/'''(('|'')?([^'\\]|${r_ECHAR}))*'''/o;
our $r_STRING_LITERAL_LONG2 = qr/"""(("|"")?([^"\\]|${r_ECHAR}))*"""/o;
our $r_LANGTAG = qr/@[a-zA-Z]+(-[a-zA-Z0-9]+)*/o;
our $r_IRI_REF = qr/<([^<>"{}|^`\\\x{00}-\x{20}])*>/o;
our $r_PN_CHARS_BASE = qr/([A-Z]|[a-z]|[\x{00C0}-\x{00D6}]|[\x{00D8}-\x{00F6}]|[\x{00F8}-\x{02FF}]|[\x{0370}-\x{037D}]|[\x{037F}-\x{1FFF}]|[\x{200C}-\x{200D}]|[\x{2070}-\x{218F}]|[\x{2C00}-\x{2FEF}]|[\x{3001}-\x{D7FF}]|[\x{F900}-\x{FDCF}]|[\x{FDF0}-\x{FFFD}]|[\x{10000}-\x{EFFFF}])/o;
our $r_PN_CHARS_U = qr/([_]|${r_PN_CHARS_BASE})/o;
our $r_VARNAME = qr/((${r_PN_CHARS_U}|[0-9])(${r_PN_CHARS_U}|[0-9]|\x{00B7}|[\x{0300}-\x{036F}]|[\x{203F}-\x{2040}])*)/o;
our $r_VAR1 = qr/[?]${r_VARNAME}/o;
our $r_VAR2 = qr/[\$]${r_VARNAME}/o;
our $r_PN_CHARS = qr/${r_PN_CHARS_U}|-|[0-9]|\x{00B7}|[\x{0300}-\x{036F}]|[\x{203F}-\x{2040}]/o;
our $r_PN_PREFIX = qr/(${r_PN_CHARS_BASE}((${r_PN_CHARS}|[.])*${r_PN_CHARS})?)/o;
our $r_PN_LOCAL_ESCAPED = qr{(\\([-~.!&'()*+,;=/?#@%_\$]))|%[0-9A-Fa-f]{2}}o;
our $r_PN_LOCAL = qr/((${r_PN_CHARS_U}|[:0-9]|${r_PN_LOCAL_ESCAPED})((${r_PN_CHARS}|${r_PN_LOCAL_ESCAPED}|[:.])*(${r_PN_CHARS}|[:]|${r_PN_LOCAL_ESCAPED}))?)/o;
our $r_PN_LOCAL_BNODE = qr/((${r_PN_CHARS_U}|[0-9])((${r_PN_CHARS}|[.])*${r_PN_CHARS})?)/o;
our $r_PNAME_NS = qr/((${r_PN_PREFIX})?:)/o;
our $r_PNAME_LN = qr/(${r_PNAME_NS}${r_PN_LOCAL})/o;
our $r_EXPONENT = qr/[eE][-+]?\d+/o;
our $r_DOUBLE = qr/\d+[.]\d*${r_EXPONENT}|[.]\d+${r_EXPONENT}|\d+${r_EXPONENT}/o;
our $r_DECIMAL = qr/(\d+[.]\d*)|([.]\d+)/o;
our $r_INTEGER = qr/\d+/o;
our $r_BLANK_NODE_LABEL = qr/_:${r_PN_LOCAL_BNODE}/o;
our $r_ANON = qr/\[[\t\r\n ]*\]/o;
our $r_NIL = qr/\([\n\r\t ]*\)/o;
our $r_AGGREGATE_CALL = qr/(MIN|MAX|COUNT|AVG|SUM|SAMPLE|GROUP_CONCAT)\b/io;
=item C<< new >>
Returns a new SPARQL 1.1 parser object.
=cut
sub new {
my $class = shift;
my %args = @_;
my $self = bless({
args => \%args,
bindings => {},
bnode_id => 0,
}, $class);
return $self;
}
################################################################################
=item C<< parse ( $query, $base_uri, $update_flag ) >>
Parses the C<< $query >>, using the given C<< $base_uri >>.
If C<< $update_flag >> is true, the query will be parsed allowing
SPARQL 1.1 Update statements.
=cut
sub parse {
my $self = shift;
my $input = shift;
unless (defined($input)) {
$self->{build} = undef;
$self->{error} = "No query string found to parse";
return;
}
my $baseuri = shift;
my $update = shift || 0;
$input =~ s/\\u([0-9A-Fa-f]{4})/chr(hex($1))/ge;
$input =~ s/\\U([0-9A-Fa-f]{8})/chr(hex($1))/ge;
delete $self->{error};
local($self->{namespaces}) = {};
local($self->{blank_ids}) = 1;
local($self->{baseURI}) = $baseuri;
local($self->{tokens}) = $input;
local($self->{stack}) = [];
local($self->{filters}) = [];
local($self->{pattern_container_stack}) = [];
local($self->{update}) = $update;
my $triples = $self->_push_pattern_container();
local($self->{build});
my $build = { sources => [], triples => $triples };
$self->{build} = $build;
if ($baseuri) {
$self->{build}{base} = $baseuri;
}
try {
$self->_RW_Query();
} catch RDF::Query::Error with {
my $e = shift;
$self->{build} = undef;
$build = undef;
$self->{error} = $e->stacktrace
} otherwise {
my $e = shift;
$self->{build} = undef;
$build = undef;
$self->{error} = $e->stacktrace
};
delete $self->{build}{star};
my $data = $build;
# $data->{triples} = $self->_pop_pattern_container();
return $data;
}
=item C<< parse_pattern ( $pattern, $base_uri, \%namespaces ) >>
Parses the C<< $pattern >>, using the given C<< $base_uri >> and returns a
RDF::Query::Algebra pattern.
=cut
sub parse_pattern {
my $self = shift;
my $input = shift;
my $baseuri = shift;
my $ns = shift;
$input =~ s/\\u([0-9A-Fa-f]{4})/chr(hex($1))/ge;
$input =~ s/\\U([0-9A-Fa-f]{8})/chr(hex($1))/ge;
delete $self->{error};
local($self->{namespaces}) = $ns;
local($self->{blank_ids}) = 1;
local($self->{baseURI}) = $baseuri;
local($self->{tokens}) = $input;
local($self->{stack}) = [];
local($self->{filters}) = [];
local($self->{pattern_container_stack}) = [];
my $triples = $self->_push_pattern_container();
$self->{build} = { sources => [], triples => $triples };
if ($baseuri) {
$self->{build}{base} = $baseuri;
}
try {
$self->_GroupGraphPattern();
} catch RDF::Query::Error with {
my $e = shift;
$self->{build} = undef;
$self->{error} = $e->text;
};
my $data = delete $self->{build};
return $data->{triples}[0];
}
=item C<< parse_expr ( $pattern, $base_uri, \%namespaces ) >>
Parses the C<< $pattern >>, using the given C<< $base_uri >> and returns a
RDF::Query::Expression pattern.
=cut
sub parse_expr {
my $self = shift;
my $input = shift;
my $baseuri = shift;
my $ns = shift;
$input =~ s/\\u([0-9A-Fa-f]{4})/chr(hex($1))/ge;
$input =~ s/\\U([0-9A-Fa-f]{8})/chr(hex($1))/ge;
delete $self->{error};
local($self->{namespaces}) = $ns;
local($self->{blank_ids}) = 1;
local($self->{baseURI}) = $baseuri;
local($self->{tokens}) = $input;
local($self->{stack}) = [];
local($self->{filters}) = [];
local($self->{pattern_container_stack}) = [];
my $triples = $self->_push_pattern_container();
$self->{build} = { sources => [], triples => $triples };
if ($baseuri) {
$self->{build}{base} = $baseuri;
}
try {
$self->_Expression();
} catch RDF::Query::Error with {
my $e = shift;
$self->{build} = undef;
$self->{error} = $e->text;
};
my $data = splice(@{ $self->{stack} });
return $data;
}
################################################################################
# [1] Query ::= Prologue ( SelectQuery | ConstructQuery | DescribeQuery | AskQuery | LoadUpdate )
sub _RW_Query {
my $self = shift;
$self->__consume_ws_opt;
$self->_Prologue;
$self->__consume_ws_opt;
my $read_query = 0;
while (1) {
if ($self->_test(qr/SELECT/i)) {
$self->_SelectQuery();
$read_query++;
} elsif ($self->_test(qr/CONSTRUCT/i)) {
$self->_ConstructQuery();
$read_query++;
} elsif ($self->_test(qr/DESCRIBE/i)) {
$self->_DescribeQuery();
$read_query++;
} elsif ($self->_test(qr/ASK/i)) {
$self->_AskQuery();
$read_query++;
} elsif ($self->_test(qr/CREATE\s+(SILENT\s+)?GRAPH/i)) {
throw RDF::Query::Error::PermissionError -text => "CREATE GRAPH update forbidden in read-only queries"
unless ($self->{update});
$self->_CreateGraph();
} elsif ($self->_test(qr/DROP\s+(SILENT\s+)?/i)) {
throw RDF::Query::Error::PermissionError -text => "DROP GRAPH update forbidden in read-only queries"
unless ($self->{update});
$self->_DropGraph();
} elsif ($self->_test(qr/LOAD\s+(SILENT\s+)?/i)) {
throw RDF::Query::Error::PermissionError -text => "LOAD update forbidden in read-only queries"
unless ($self->{update});
$self->_LoadUpdate();
} elsif ($self->_test(qr/CLEAR\s+(SILENT\s+)?/i)) {
throw RDF::Query::Error::PermissionError -text => "CLEAR GRAPH update forbidden in read-only queries"
unless ($self->{update});
$self->_ClearGraphUpdate();
} elsif ($self->_test(qr/(WITH|INSERT|DELETE)/i)) {
throw RDF::Query::Error::PermissionError -text => "INSERT/DELETE update forbidden in read-only queries"
unless ($self->{update});
my ($graph);
if ($self->_test(qr/WITH/)) {
$self->{build}{custom_update_dataset} = 1;
$self->_eat(qr/WITH/i);
$self->__consume_ws_opt;
$self->_IRIref;
($graph) = splice( @{ $self->{stack} } );
$self->__consume_ws_opt;
}
if ($self->_test(qr/INSERT/ims)) {
$self->_eat(qr/INSERT/i);
$self->__consume_ws_opt;
if ($self->_test(qr/DATA/i)) {
throw RDF::Query::Error::PermissionError -text => "INSERT DATA update forbidden in read-only queries"
unless ($self->{update});
$self->_eat(qr/DATA/i);
$self->__consume_ws_opt;
$self->_InsertDataUpdate();
} else {
$self->_InsertUpdate($graph);
}
} elsif ($self->_test(qr/DELETE/ims)) {
$self->_eat(qr/DELETE/i);
$self->__consume_ws_opt;
if ($self->_test(qr/DATA/i)) {
throw RDF::Query::Error::PermissionError -text => "DELETE DATA update forbidden in read-only queries"
unless ($self->{update});
$self->_eat(qr/DATA/i);
$self->__consume_ws_opt;
$self->_DeleteDataUpdate();
} else {
$self->_DeleteUpdate($graph);
}
}
} elsif ($self->_test(qr/COPY/i)) {
$self->_CopyUpdate();
} elsif ($self->_test(qr/MOVE/i)) {
$self->_MoveUpdate();
} elsif ($self->_test(qr/ADD/i)) {
$self->_AddUpdate();
} elsif ($self->_test(qr/;/)) {
$self->_eat(qr/;/) ;
$self->__consume_ws_opt;
next if ($self->_Query_test);
last;
} elsif ($self->{tokens} eq '') {
last;
} else {
my $l = Log::Log4perl->get_logger("rdf.query");
if ($l->is_debug) {
$l->logcluck("Syntax error: Expected query type with input <<$self->{tokens}>>");
}
throw RDF::Query::Error::ParseError -text => 'Syntax error: Expected query type';
}
last if ($read_query);
$self->__consume_ws_opt;
if ($self->_test(qr/;/)) {
$self->_eat(qr/;/) ;
$self->__consume_ws_opt;
if ($self->_Query_test) {
next;
}
}
last;
}
# $self->_eat(qr/;/) if ($self->_test(qr/;/));
$self->__consume_ws_opt;
my $count = scalar(@{ $self->{build}{triples} });
my $remaining = $self->{tokens};
if ($remaining =~ m/\S/) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Remaining input after query: $remaining";
}
if ($count == 0 or $count > 1) {
my @patterns = splice(@{ $self->{build}{triples} });
my $pattern = RDF::Query::Algebra::Sequence->new( @patterns );
$pattern->check_duplicate_blanks;
$self->{build}{triples} = [ $pattern ];
}
# my %query = (%p, %body);
# return \%query;
}
sub _Query_test {
my $self = shift;
return 1 if ($self->_test(qr/SELECT|CONSTRUCT|DESCRIBE|ASK|LOAD|CLEAR|DROP|ADD|MOVE|COPY|CREATE|INSERT|DELETE|WITH/i));
return 0;
}
# [2] Prologue ::= BaseDecl? PrefixDecl*
# [3] BaseDecl ::= 'BASE' IRI_REF
# [4] PrefixDecl ::= 'PREFIX' PNAME_NS IRI_REF
sub _Prologue {
my $self = shift;
my $base;
my @base;
if ($self->_test( qr/BASE/i )) {
$self->_eat( qr/BASE/i );
$self->__consume_ws_opt;
my $iriref = $self->_eat( $r_IRI_REF );
my $iri = substr($iriref,1,length($iriref)-2);
$base = RDF::Query::Node::Resource->new( $iri );
@base = $base;
$self->__consume_ws_opt;
$self->{base} = $base;
}
my %namespaces;
while ($self->_test( qr/PREFIX/i )) {
$self->_eat( qr/PREFIX/i );
$self->__consume_ws_opt;
my $prefix = $self->_eat( $r_PNAME_NS );
my $ns = substr($prefix, 0, length($prefix) - 1);
if ($ns eq '') {
$ns = '__DEFAULT__';
}
$self->__consume_ws_opt;
my $iriref = $self->_eat( $r_IRI_REF );
my $iri = substr($iriref,1,length($iriref)-2);
if (@base) {
my $r = RDF::Query::Node::Resource->new( $iri, @base );
$iri = $r->uri_value;
}
$self->__consume_ws_opt;
$namespaces{ $ns } = $iri;
$self->{namespaces}{$ns} = $iri;
}
$self->{build}{namespaces} = \%namespaces;
$self->{build}{base} = $base if (defined($base));
# push(@data, (base => $base)) if (defined($base));
# return @data;
}
sub _InsertDataUpdate {
my $self = shift;
$self->_eat('{');
$self->__consume_ws_opt;
local($self->{__data_pattern}) = 1;
$self->_ModifyTemplate();
$self->__consume_ws_opt;
my $data = $self->_remove_pattern;
$self->_eat('}');
$self->__consume_ws_opt;
my $empty = RDF::Query::Algebra::GroupGraphPattern->new();
my $insert = RDF::Query::Algebra::Update->new(undef, $data, $empty, undef, 1);
$self->_add_patterns( $insert );
$self->{build}{method} = 'UPDATE';
}
sub _DeleteDataUpdate {
my $self = shift;
$self->_eat('{');
$self->__consume_ws_opt;
local($self->{__data_pattern}) = 1;
local($self->{__no_bnodes}) = "DELETE DATA block";
$self->_ModifyTemplate();
$self->__consume_ws_opt;
my $data = $self->_remove_pattern;
$self->_eat('}');
$self->__consume_ws_opt;
my $empty = RDF::Query::Algebra::GroupGraphPattern->new();
my $delete = RDF::Query::Algebra::Update->new($data, undef, $empty, undef, 1);
$self->_add_patterns( $delete );
$self->{build}{method} = 'UPDATE';
}
sub _InsertUpdate {
my $self = shift;
my $graph = shift;
$self->_eat('{');
$self->__consume_ws_opt;
$self->_ModifyTemplate();
$self->__consume_ws_opt;
my $data = $self->_remove_pattern;
$self->_eat('}');
$self->__consume_ws_opt;
if ($graph) {
$data = RDF::Query::Algebra::NamedGraph->new( $graph, $data );
}
my %dataset;
while ($self->_test(qr/USING/i)) {
$self->{build}{custom_update_dataset} = 1;
$self->_eat(qr/USING/i);
$self->__consume_ws_opt;
my $named = 0;
if ($self->_test(qr/NAMED/i)) {
$self->_eat(qr/NAMED/i);
$self->__consume_ws_opt;
$named = 1;
}
$self->_IRIref;
my ($iri) = splice( @{ $self->{stack} } );
if ($named) {
$dataset{named}{$iri->uri_value} = $iri;
} else {
push(@{ $dataset{default} }, $iri );
}
$self->__consume_ws_opt;
}
$self->_eat(qr/WHERE/i);
$self->__consume_ws_opt;
if ($graph) {
# local($self->{named_graph}) = $graph;
$self->_GroupGraphPattern;
my $ggp = $self->_remove_pattern;
$ggp = RDF::Query::Algebra::NamedGraph->new( $graph, $ggp );
$self->_add_patterns( $ggp );
} else {
$self->_GroupGraphPattern;
}
my $ggp = $self->_remove_pattern;
my @ds_keys = keys %dataset;
unless (@ds_keys) {
$dataset{ default } = [$graph || ()];
}
my $insert = RDF::Query::Algebra::Update->new(undef, $data, $ggp, \%dataset, 0);
$self->_add_patterns( $insert );
$self->{build}{method} = 'UPDATE';
}
sub _DeleteUpdate {
my $self = shift;
my $graph = shift;
my ($delete_data, $insert_data);
my %dataset;
my $delete_where = 0;
if ($self->_test(qr/WHERE/i)) {
if ($graph) {
throw RDF::Query::Error::ParseError -text => "Syntax error: WITH clause cannot be used with DELETE WHERE operations";
}
$delete_where = 1;
} else {
{
local($self->{__no_bnodes}) = "DELETE block";
$self->_eat('{');
$self->__consume_ws_opt;
$self->_ModifyTemplate( $graph );
$self->__consume_ws_opt;
$self->_eat('}');
}
$delete_data = $self->_remove_pattern;
$self->__consume_ws_opt;
if ($self->_test(qr/INSERT/i)) {
$self->_eat(qr/INSERT/i);
$self->__consume_ws_opt;
$self->_eat('{');
$self->__consume_ws_opt;
$self->_ModifyTemplate( $graph );
$self->__consume_ws_opt;
$self->_eat('}');
$self->__consume_ws_opt;
$insert_data = $self->_remove_pattern;
}
while ($self->_test(qr/USING/i)) {
$self->{build}{custom_update_dataset} = 1;
$self->_eat(qr/USING/i);
$self->__consume_ws_opt;
my $named = 0;
if ($self->_test(qr/NAMED/i)) {
$self->_eat(qr/NAMED/i);
$self->__consume_ws_opt;
$named = 1;
}
$self->_IRIref;
my ($iri) = splice( @{ $self->{stack} } );
if ($named) {
$dataset{named}{$iri->uri_value} = $iri;
} else {
push(@{ $dataset{default} }, $iri );
}
$self->__consume_ws_opt;
}
}
$self->_eat(qr/WHERE/i);
$self->__consume_ws_opt;
if ($graph) {
# local($self->{named_graph}) = $graph;
$self->{__no_bnodes} = "DELETE WHERE block" if ($delete_where);
$self->_GroupGraphPattern;
delete $self->{__no_bnodes};
my $ggp = $self->_remove_pattern;
$ggp = RDF::Query::Algebra::NamedGraph->new( $graph, $ggp );
$self->_add_patterns( $ggp );
} else {
$self->{__no_bnodes} = "DELETE WHERE block" if ($delete_where);
$self->_GroupGraphPattern;
delete $self->{__no_bnodes};
}
my $ggp = $self->_remove_pattern;
if ($delete_where) {
$delete_data = $ggp;
}
my @ds_keys = keys %dataset;
if ($graph and not(scalar(@ds_keys))) {
$dataset{ default } = [$graph || ()];
}
my $insert = RDF::Query::Algebra::Update->new($delete_data, $insert_data, $ggp, \%dataset, 0);
$self->_add_patterns( $insert );
$self->{build}{method} = 'UPDATE';
}
sub _ModifyTemplate_test {
my $self = shift;
return 1 if ($self->_TriplesBlock_test);
return 1 if ($self->_test(qr/GRAPH/i));
return 0;
}
sub _ModifyTemplate {
my $self = shift;
my $graph = shift;
local($self->{named_graph});
if ($graph) {
$self->{named_graph} = $graph;
}
# $self->__ModifyTemplate;
# $self->__consume_ws_opt;
# my $data = $self->_remove_pattern;
# $data = RDF::Query::Algebra::GroupGraphPattern->new( $data ) unless ($data->isa('RDF::Query::Algebra::GroupGraphPattern'));
my $data;
while ($self->_ModifyTemplate_test) {
$self->__ModifyTemplate( $graph );
$self->__consume_ws_opt;
my $d = $self->_remove_pattern;
my @patterns = blessed($data) ? $data->patterns : ();
$data = RDF::Query::Algebra::GroupGraphPattern->new( @patterns, $d );
}
$data = RDF::Query::Algebra::GroupGraphPattern->new() unless (blessed($data));
$data = RDF::Query::Algebra::GroupGraphPattern->new( $data ) unless ($data->isa('RDF::Query::Algebra::GroupGraphPattern'));
$self->_add_patterns( $data );
}
sub __ModifyTemplate {
my $self = shift;
my $graph = shift;
local($self->{_modify_template}) = 1;
if ($self->_TriplesBlock_test) {
my $data;
$self->_push_pattern_container;
$self->_TriplesBlock;
($data) = @{ $self->_pop_pattern_container };
if ($graph) {
my $ggp = RDF::Query::Algebra::GroupGraphPattern->new( $data );
my $data = RDF::Query::Algebra::NamedGraph->new( $graph, $ggp );
}
$self->_add_patterns( $data );
} else {
$self->_GraphGraphPattern;
{
my (@d) = splice(@{ $self->{stack} });
$self->__handle_GraphPatternNotTriples( @d );
}
}
}
sub _LoadUpdate {
my $self = shift;
my $op = $self->_eat(qr/LOAD\s+(SILENT\s+)?/i);
my $silent = ($op =~ /SILENT/);
$self->__consume_ws_opt;
$self->_IRIref;
my ($iri) = splice( @{ $self->{stack} } );
$self->__consume_ws_opt;
if ($self->_test(qr/INTO GRAPH/i)) {
$self->_eat(qr/INTO GRAPH/i);
$self->_ws;
$self->_IRIref;
my ($graph) = splice( @{ $self->{stack} } );
my $pat = RDF::Query::Algebra::Load->new( $iri, $graph, $silent );
$self->_add_patterns( $pat );
} else {
my $pat = RDF::Query::Algebra::Load->new( $iri, undef, $silent );
$self->_add_patterns( $pat );
}
$self->{build}{method} = 'LOAD';
}
sub _CreateGraph {
my $self = shift;
my $op = $self->_eat(qr/CREATE\s+(SILENT\s+)?GRAPH/i);
my $silent = ($op =~ /SILENT/i);
$self->_ws;
$self->_IRIref;
my ($graph) = splice( @{ $self->{stack} } );
my $pat = RDF::Query::Algebra::Create->new( $graph );
$self->_add_patterns( $pat );
$self->{build}{method} = 'CREATE';
}
sub _ClearGraphUpdate {
my $self = shift;
my $op = $self->_eat(qr/CLEAR(\s+SILENT)?/i);
my $silent = ($op =~ /SILENT/i);
$self->_ws;
if ($self->_test(qr/GRAPH/i)) {
$self->_eat(qr/GRAPH/i);
$self->_ws;
$self->_IRIref;
my ($graph) = splice( @{ $self->{stack} } );
my $pat = RDF::Query::Algebra::Clear->new( $graph );
$self->_add_patterns( $pat );
} elsif ($self->_test(qr/DEFAULT/i)) {
$self->_eat(qr/DEFAULT/i);
my $pat = RDF::Query::Algebra::Clear->new( RDF::Trine::Node::Nil->new );
$self->_add_patterns( $pat );
} elsif ($self->_test(qr/NAMED/i)) {
$self->_eat(qr/NAMED/i);
my $pat = RDF::Query::Algebra::Clear->new( RDF::Query::Node::Resource->new('tag:gwilliams@cpan.org,2010-01-01:RT:NAMED') );
$self->_add_patterns( $pat );
} elsif ($self->_test(qr/ALL/i)) {
$self->_eat(qr/ALL/i);
my $pat = RDF::Query::Algebra::Clear->new( RDF::Query::Node::Resource->new('tag:gwilliams@cpan.org,2010-01-01:RT:ALL') );
$self->_add_patterns( $pat );
}
$self->{build}{method} = 'CLEAR';
}
sub _DropGraph {
my $self = shift;
my $op = $self->_eat(qr/DROP(\s+SILENT)?/i);
my $silent = ($op =~ /SILENT/i);
$self->_ws;
if ($self->_test(qr/GRAPH/i)) {
$self->_eat(qr/GRAPH/i);
$self->_ws;
$self->_IRIref;
my ($graph) = splice( @{ $self->{stack} } );
my $pat = RDF::Query::Algebra::Clear->new( $graph );
$self->_add_patterns( $pat );
} elsif ($self->_test(qr/DEFAULT/i)) {
$self->_eat(qr/DEFAULT/i);
my $pat = RDF::Query::Algebra::Clear->new( RDF::Trine::Node::Nil->new );
$self->_add_patterns( $pat );
} elsif ($self->_test(qr/NAMED/i)) {
$self->_eat(qr/NAMED/i);
my $pat = RDF::Query::Algebra::Clear->new( RDF::Query::Node::Resource->new('tag:gwilliams@cpan.org,2010-01-01:RT:NAMED') );
$self->_add_patterns( $pat );
} elsif ($self->_test(qr/ALL/i)) {
$self->_eat(qr/ALL/i);
my $pat = RDF::Query::Algebra::Clear->new( RDF::Query::Node::Resource->new('tag:gwilliams@cpan.org,2010-01-01:RT:ALL') );
$self->_add_patterns( $pat );
}
$self->{build}{method} = 'CLEAR';
}
sub __graph {
my $self = shift;
if ($self->_test(qr/DEFAULT/i)) {
$self->_eat(qr/DEFAULT/i);
return RDF::Trine::Node::Nil->new();
} else {
if ($self->_test(qr/GRAPH/)) {
$self->_eat(qr/GRAPH/i);
$self->__consume_ws_opt;
}
$self->_IRIref;
my ($g) = splice( @{ $self->{stack} } );
return $g;
}
}
sub _CopyUpdate {
my $self = shift;
my $op = $self->_eat(qr/COPY(\s+SILENT)?/i);
my $silent = ($op =~ /SILENT/i);
$self->_ws;
my $from = $self->__graph();
$self->_ws;
$self->_eat(qr/TO/i);
$self->_ws;
my $to = $self->__graph();
my $pattern = RDF::Query::Algebra::Copy->new( $from, $to, $silent );
$self->_add_patterns( $pattern );
$self->{build}{method} = 'UPDATE';
}
sub _MoveUpdate {
my $self = shift;
my $op = $self->_eat(qr/MOVE(\s+SILENT)?/i);
my $silent = ($op =~ /SILENT/i);
$self->_ws;
my $from = $self->__graph();
$self->_ws;
$self->_eat(qr/TO/i);
$self->_ws;
my $to = $self->__graph();
my $pattern = RDF::Query::Algebra::Move->new( $from, $to, $silent );
$self->_add_patterns( $pattern );
$self->{build}{method} = 'UPDATE';
}
sub _AddUpdate {
my $self = shift;
my $op = $self->_eat(qr/ADD(\s+SILENT)?/i);
my $silent = ($op =~ /SILENT/i);
$self->_ws;
return $self->__UpdateShortcuts( 'ADD', $silent );
}
sub __UpdateShortcuts {
my $self = shift;
my $op = shift;
my $silent = shift;
my ($from, $to);
if ($self->_test(qr/DEFAULT/i)) {
$self->_eat(qr/DEFAULT/i);
} else {
if ($self->_test(qr/GRAPH/)) {
$self->_eat(qr/GRAPH/i);
$self->__consume_ws_opt;
}
$self->_IRIref;
($from) = splice( @{ $self->{stack} } );
}
$self->_ws;
$self->_eat(qr/TO/i);
$self->_ws;
if ($self->_test(qr/DEFAULT/i)) {
$self->_eat(qr/DEFAULT/i);
} else {
if ($self->_test(qr/GRAPH/)) {
$self->_eat(qr/GRAPH/i);
$self->__consume_ws_opt;
}
$self->_IRIref;
($to) = splice( @{ $self->{stack} } );
}
my $from_pattern = RDF::Query::Algebra::GroupGraphPattern->new(
RDF::Query::Algebra::BasicGraphPattern->new(
RDF::Query::Algebra::Triple->new(
map { RDF::Query::Node::Variable->new( $_ ) } qw(s p o)
)
)
);
if (defined($from)) {
$from_pattern = RDF::Query::Algebra::NamedGraph->new( $from, $from_pattern );
}
my $to_pattern = RDF::Query::Algebra::GroupGraphPattern->new(
RDF::Query::Algebra::BasicGraphPattern->new(
RDF::Query::Algebra::Triple->new(
map { RDF::Query::Node::Variable->new( $_ ) } qw(s p o)
)
)
);
if (defined($to)) {
$to_pattern = RDF::Query::Algebra::NamedGraph->new( $to, $to_pattern );
}
my $to_graph = $to || RDF::Trine::Node::Nil->new;
my $from_graph = $from || RDF::Trine::Node::Nil->new;
my $drop_to = RDF::Query::Algebra::Clear->new( $to_graph, $silent );
my $update = RDF::Query::Algebra::Update->new( undef, $to_pattern, $from_pattern, undef, 0 );
my $drop_from = RDF::Query::Algebra::Clear->new( $from_graph );
my $pattern;
if ($op eq 'MOVE') {
$pattern = RDF::Query::Algebra::Sequence->new( $drop_to, $update, $drop_from );
} elsif ($op eq 'COPY') {
$pattern = RDF::Query::Algebra::Sequence->new( $drop_to, $update );
} else {
$pattern = $update;
}
$self->_add_patterns( $pattern );
$self->{build}{method} = 'UPDATE';
}
# [5] SelectQuery ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( Var+ | '*' ) DatasetClause* WhereClause SolutionModifier
sub _SelectQuery {
my $self = shift;
$self->_eat(qr/SELECT/i);
$self->__consume_ws;
if ($self->{tokens} =~ m/^(DISTINCT|REDUCED)/i) {
my $mod = $self->_eat( qr/DISTINCT|REDUCED/i );
$self->__consume_ws;
$self->{build}{options}{lc($mod)} = 1;
}
my $star = $self->__SelectVars;
$self->_DatasetClause();
$self->__consume_ws_opt;
$self->_WhereClause;
if ($star) {
my $triples = $self->{build}{triples} || [];
my @vars;
foreach my $t (@$triples) {
my @v = $t->potentially_bound;
push(@vars, @v);
}
@vars = RDF::Query::_uniq( @vars );
push( @{ $self->{build}{variables} }, map { $self->new_variable($_) } @vars );
}
$self->__consume_ws_opt;
$self->_SolutionModifier();
$self->__consume_ws_opt;
if ($self->_test( qr/VALUES/i )) {
$self->_eat( qr/VALUES/i );
$self->__consume_ws_opt;
my @vars;
# $self->_Var;
# push( @vars, splice(@{ $self->{stack} }));
# $self->__consume_ws_opt;
my $parens = 0;
if ($self->_test(qr/[(]/)) {
$self->_eat( qr/[(]/ );
$parens = 1;
}
while ($self->_test(qr/[\$?]/)) {
$self->_Var;
push( @vars, splice(@{ $self->{stack} }));
$self->__consume_ws_opt;
}
if ($parens) {
$self->_eat( qr/[)]/ );
}
$self->__consume_ws_opt;
my $count = scalar(@vars);
if (not($parens) and $count == 0) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Expected VAR in inline data declaration";
} elsif (not($parens) and $count > 1) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Inline data declaration can only have one variable when parens are omitted";
}
my $short = (not($parens) and $count == 1);
$self->_eat('{');
$self->__consume_ws_opt;
if (not($short) or ($short and $self->_Binding_test)) {
while ($self->_Binding_test) {
my $terms = $self->_Binding($count);
push( @{ $self->{build}{bindings}{terms} }, $terms );
$self->__consume_ws_opt;
}
} else {
while ($self->_BindingValue_test) {
$self->_BindingValue;
$self->__consume_ws_opt;
my ($term) = splice(@{ $self->{stack} });
push( @{ $self->{build}{bindings}{terms} }, [$term] );
$self->__consume_ws_opt;
}
}
$self->_eat('}');
$self->__consume_ws_opt;
$self->{build}{bindings}{vars} = \@vars;
}
$self->__solution_modifiers( $star );
my $pattern = $self->{build}{triples}[0];
my @agg = $pattern->subpatterns_of_type( 'RDF::Query::Algebra::Aggregate', 'RDF::Query::Algebra::SubSelect' );
if (@agg) {
my ($agg) = @agg;
my @gvars = $agg->groupby;
if (scalar(@gvars) == 0) {
# aggregate query with no explicit group keys
foreach my $v (@{ $self->{build}{variables} }) {
if ($v->isa('RDF::Query::Node::Variable')) {
my $name = $v->name;
throw RDF::Query::Error::ParseError -text => "Syntax error: Variable used in projection but not present in aggregate grouping ($name)";
}
}
}
}
delete $self->{build}{options};
$self->{build}{method} = 'SELECT';
}
sub __SelectVars {
my $self = shift;
my $star = 0;
my @vars;
my $count = 0;
while ($self->_test('*') or $self->__SelectVar_test) {
if ($self->_test('*')) {
$self->{build}{star}++;
$self->_eat('*');
$star = 1;
$self->__consume_ws_opt;
$count++;
} else {
$self->__SelectVar;
push( @vars, splice(@{ $self->{stack} }));
$self->__consume_ws_opt;
$count++;
}
}
my %seen;
foreach my $v (@vars) {
if ($v->isa('RDF::Query::Node::Variable') or $v->isa('RDF::Query::Expression::Alias')) {
my $name = $v->name;
if ($v->isa('RDF::Query::Expression::Alias')) {
if ($seen{ $name }) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Repeated variable ($name) used in projection list";
}
}
$seen{ $name }++;
}
}
$self->{build}{variables} = \@vars;
if ($count == 0) {
throw RDF::Query::Error::ParseError -text => "Syntax error: No select variable or expression specified";
}
return $star;
}
sub _BrackettedAliasExpression {
my $self = shift;
$self->_eat('(');
$self->__consume_ws_opt;
$self->_Expression;
my ($expr) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_eat(qr/AS/i);
$self->__consume_ws_opt;
$self->_Var;
my ($var) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_eat(')');
my $alias = $self->new_alias_expression( $var, $expr );
$self->_add_stack( $alias );
}
sub __SelectVar_test {
my $self = shift;
local($self->{__aggregate_call_ok}) = 1;
# return 1 if $self->_BuiltInCall_test;
return 1 if $self->_test( qr/[(]/i);
return $self->{tokens} =~ m'^[?$]';
}
sub __SelectVar {
my $self = shift;
local($self->{__aggregate_call_ok}) = 1;
if ($self->_test('(')) {
$self->_BrackettedAliasExpression;
# } elsif ($self->_BuiltInCall_test) {
# $self->_BuiltInCall;
} else {
$self->_Var;
}
}
# [6] ConstructQuery ::= 'CONSTRUCT' ConstructTemplate DatasetClause* WhereClause SolutionModifier
sub _ConstructQuery {
my $self = shift;
$self->_eat(qr/CONSTRUCT/i);
$self->__consume_ws_opt;
my $shortcut = 1;
if ($self->_test( qr/[{]/ )) {
$shortcut = 0;
$self->_ConstructTemplate;
$self->__consume_ws_opt;
}
$self->_DatasetClause();
$self->__consume_ws_opt;
if ($shortcut) {
$self->_TriplesWhereClause;
} else {
$self->_WhereClause;
}
$self->__consume_ws_opt;
$self->_SolutionModifier();
my $pattern = $self->{build}{triples}[0];
my $triples = delete $self->{build}{construct_triples};
my $construct = RDF::Query::Algebra::Construct->new( $pattern, $triples );
$self->{build}{triples}[0] = $construct;
$self->{build}{method} = 'CONSTRUCT';
}
# [7] DescribeQuery ::= 'DESCRIBE' ( VarOrIRIref+ | '*' ) DatasetClause* WhereClause? SolutionModifier
sub _DescribeQuery {
my $self = shift;
$self->_eat(qr/DESCRIBE/i);
$self->_ws;
if ($self->_test('*')) {
$self->_eat('*');
$self->{build}{variables} = ['*'];
$self->__consume_ws_opt;
} else {
$self->_VarOrIRIref;
$self->__consume_ws_opt;
while ($self->_VarOrIRIref_test) {
$self->_VarOrIRIref;
$self->__consume_ws_opt;
}
$self->{build}{variables} = [ splice(@{ $self->{stack} }) ];
}
$self->_DatasetClause();
$self->__consume_ws_opt;
if ($self->_WhereClause_test) {
$self->_WhereClause;
} else {
my $pattern = RDF::Query::Algebra::GroupGraphPattern->new();
$self->_add_patterns( $pattern );
}
$self->__consume_ws_opt;
$self->_SolutionModifier();
$self->{build}{method} = 'DESCRIBE';
}
# [8] AskQuery ::= 'ASK' DatasetClause* WhereClause
sub _AskQuery {
my $self = shift;
$self->_eat(qr/ASK/i);
$self->__consume_ws_opt;
$self->_DatasetClause();
$self->__consume_ws_opt;
$self->_WhereClause;
$self->{build}{variables} = [];
$self->{build}{method} = 'ASK';
}
sub _DatasetClause_test {
my $self = shift;
return $self->_test( qr/FROM/i );
}
# [9] DatasetClause ::= 'FROM' ( DefaultGraphClause | NamedGraphClause )
sub _DatasetClause {
my $self = shift;
# my @dataset;
$self->{build}{sources} = [];
while ($self->_test( qr/FROM/i )) {
$self->_eat( qr/FROM/i );
$self->__consume_ws;
if ($self->_test( qr/NAMED/i )) {
$self->_NamedGraphClause;
} else {
$self->_DefaultGraphClause;
}
$self->__consume_ws_opt;
}
}
# [10] DefaultGraphClause ::= SourceSelector
sub _DefaultGraphClause {
my $self = shift;
$self->_SourceSelector;
my ($source) = splice(@{ $self->{stack} });
push( @{ $self->{build}{sources} }, [$source] );
}
# [11] NamedGraphClause ::= 'NAMED' SourceSelector
sub _NamedGraphClause {
my $self = shift;
$self->_eat( qr/NAMED/i );
$self->__consume_ws_opt;
$self->_SourceSelector;
my ($source) = splice(@{ $self->{stack} });
push( @{ $self->{build}{sources} }, [$source, 'NAMED'] );
}
# [12] SourceSelector ::= IRIref
sub _SourceSelector {
my $self = shift;
$self->_IRIref;
}
# [13] WhereClause ::= 'WHERE'? GroupGraphPattern
sub _WhereClause_test {
my $self = shift;
return $self->_test( qr/WHERE|{/i );
}
sub _WhereClause {
my $self = shift;
if ($self->_test( qr/WHERE/i )) {
$self->_eat( qr/WHERE/i );
}
$self->__consume_ws_opt;
$self->_GroupGraphPattern;
my $ggp = $self->_peek_pattern;
$ggp->check_duplicate_blanks;
}
sub _TriplesWhereClause {
my $self = shift;
$self->_push_pattern_container;
$self->_eat( qr/WHERE/i );
$self->__consume_ws_opt;
$self->_eat(qr/{/);
$self->__consume_ws_opt;
if ($self->_TriplesBlock_test) {
$self->_TriplesBlock;
}
$self->_eat(qr/}/);
my $cont = $self->_pop_pattern_container;
$self->{build}{construct_triples} = $cont->[0];
my $pattern = RDF::Query::Algebra::GroupGraphPattern->new( @$cont );
$self->_add_patterns( $pattern );
}
sub _Binding_test {
my $self = shift;
return $self->_test( '(' );
}
sub _Binding {
my $self = shift;
my $count = shift;
$self->_eat( '(' );
$self->__consume_ws_opt;
my @terms;
foreach my $i (1..$count) {
unless ($self->_BindingValue_test) {
my $found = $i-1;
throw RDF::Query::Error::ParseError -text => "Syntax error: Expected $count BindingValues but only found $found";
}
$self->_BindingValue;
push( @terms, splice(@{ $self->{stack} }));
$self->__consume_ws_opt;
}
$self->__consume_ws_opt;
$self->_eat( ')' );
return \@terms;
}
sub _BindingValue_test {
my $self = shift;
return 1 if ($self->_IRIref_test);
return 1 if ($self->_test(qr/UNDEF|[<'".0-9]|(true|false)\b|_:|\([\n\r\t ]*\)/));
return 0;
}
sub _BindingValue {
my $self = shift;
if ($self->_test(qr/UNDEF/i)) {
$self->_eat(qr/UNDEF/i);
push(@{ $self->{stack} }, undef);
} else {
$self->_GraphTerm;
}
}
# [20] GroupCondition ::= ( BuiltInCall | FunctionCall | '(' Expression ( 'AS' Var )? ')' | Var )
sub __GroupByVar_test {
my $self = shift;
return 1 if ($self->_BuiltInCall_test);
return 1 if ($self->_IRIref_test);
return 1 if ($self->_test( qr/[(]/i ));
return 1 if ($self->_test(qr/[\$?]/));
}
sub __GroupByVar {
my $self = shift;
if ($self->_test('(')) {
$self->_eat('(');
$self->__consume_ws_opt;
$self->_Expression;
my ($expr) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
if ($self->_test(qr/AS/i)) {
$self->_eat('AS');
$self->__consume_ws_opt;
$self->_Var;
my ($var) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
my $alias = $self->new_alias_expression( $var, $expr );
$self->_add_stack( $alias );
} else {
$self->_add_stack( $expr );
}
$self->_eat(')');
} elsif ($self->_IRIref_test) {
$$self->_FunctionCall;
} elsif ($self->_BuiltInCall_test) {
$self->_BuiltInCall;
} else {
$self->_Var;
}
}
# [14] SolutionModifier ::= OrderClause? LimitOffsetClauses?
sub _SolutionModifier {
my $self = shift;
if ($self->_test( qr/GROUP\s+BY/i )) {
$self->_GroupClause;
$self->__consume_ws_opt;
}
if ($self->_test( qr/HAVING/i )) {
$self->_HavingClause;
$self->__consume_ws_opt;
}
if ($self->_OrderClause_test) {
$self->_OrderClause;
$self->__consume_ws_opt;
}
if ($self->_LimitOffsetClauses_test) {
$self->_LimitOffsetClauses;
}
}
sub _GroupClause {
my $self = shift;
$self->_eat( qr/GROUP\s+BY/i );
if ($self->{build}{star}) {
throw RDF::Query::Error::ParseError -text => "Syntax error: SELECT * cannot be used with aggregate grouping";
}
$self->{build}{__aggregate} ||= {};
my @vars;
$self->__consume_ws_opt;
$self->__GroupByVar;
my ($v) = splice(@{ $self->{stack} });
push( @vars, $v );
$self->__consume_ws_opt;
while ($self->__GroupByVar_test) {
$self->__GroupByVar;
my ($v) = splice(@{ $self->{stack} });
push( @vars, $v );
$self->__consume_ws_opt;
}
my %seen;
foreach my $v (@vars) {
if ($v->isa('RDF::Query::Node::Variable') or $v->isa('RDF::Query::Expression::Alias')) {
my $name = $v->name;
$seen{ $name }++;
}
}
foreach my $v (@{ $self->{build}{variables} }) {
if ($v->isa('RDF::Query::Node::Variable')) {
my $name = $v->name;
unless ($seen{ $name }) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Variable used in projection but not present in aggregate grouping ($name)";
}
} elsif ($v->isa('RDF::Query::Expression::Alias')) {
my $expr = $v->expression;
# warn 'expression: ' . Dumper($expr);
if ($expr->isa('RDF::Query::Node::Variable::ExpressionProxy')) {
# RDF::Query::Node::Variable::ExpressionProxy is used for aggregate operations.
# we can ignore these because any variable used in an aggreate is valid, even if it's not mentioned in the grouping keys
} elsif ($expr->isa('RDF::Query::Expression')) {
my @vars = $expr->nonaggregated_referenced_variables;
foreach my $name (@vars) {
unless ($seen{ $name }) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Variable used in projection but not present in aggregate grouping ($name)";
}
}
}
}
}
$self->{build}{__group_by} = \@vars;
$self->__consume_ws_opt;
}
sub _HavingClause {
my $self = shift;
$self->_eat(qr/HAVING/i);
$self->__consume_ws_opt;
$self->{build}{__aggregate} ||= {};
local($self->{__aggregate_call_ok}) = 1;
$self->_Constraint;
my ($expr) = splice(@{ $self->{stack} });
$self->{build}{__having} = $expr;
}
# [15] LimitOffsetClauses ::= ( LimitClause OffsetClause? | OffsetClause LimitClause? )
sub _LimitOffsetClauses_test {
my $self = shift;
return $self->_test( qr/LIMIT|OFFSET/i );
}
sub _LimitOffsetClauses {
my $self = shift;
if ($self->_LimitClause_test) {
$self->_LimitClause;
$self->__consume_ws_opt;
if ($self->_OffsetClause_test) {
$self->_OffsetClause;
}
} else {
$self->_OffsetClause;
$self->__consume_ws_opt;
if ($self->_LimitClause_test) {
$self->_LimitClause;
}
}
}
# [16] OrderClause ::= 'ORDER' 'BY' OrderCondition+
sub _OrderClause_test {
my $self = shift;
return $self->_test( qr/ORDER[\n\r\t ]+BY/i );
}
sub _OrderClause {
my $self = shift;
$self->_eat( qr/ORDER/i );
$self->__consume_ws;
$self->_eat( qr/BY/i );
$self->__consume_ws_opt;
my @order;
$self->{build}{__aggregate} ||= {};
local($self->{__aggregate_call_ok}) = 1;
$self->_OrderCondition;
$self->__consume_ws_opt;
push(@order, splice(@{ $self->{stack} }));
while ($self->_OrderCondition_test) {
$self->_OrderCondition;
$self->__consume_ws_opt;
push(@order, splice(@{ $self->{stack} }));
}
$self->{build}{options}{orderby} = \@order;
}
# [17] OrderCondition ::= ( ( 'ASC' | 'DESC' ) BrackettedExpression ) | ( Constraint | Var )
sub _OrderCondition_test {
my $self = shift;
return 1 if $self->_test( qr/ASC|DESC|[?\$]/i );
return 1 if $self->_Constraint_test;
return 0;
}
sub _OrderCondition {
my $self = shift;
my $dir = 'ASC';
if ($self->_test( qr/ASC|DESC/i )) {
$dir = uc( $self->_eat( qr/ASC|DESC/i ) );
$self->__consume_ws_opt;
$self->_BrackettedExpression;
} elsif ($self->_test( qr/[?\$]/ )) {
$self->_Var;
} else {
$self->_Constraint;
}
my ($expr) = splice(@{ $self->{stack} });
$self->_add_stack( [ $dir, $expr ] );
}
# [18] LimitClause ::= 'LIMIT' INTEGER
sub _LimitClause_test {
my $self = shift;
return $self->_test( qr/LIMIT/i );
}
sub _LimitClause {
my $self = shift;
$self->_eat( qr/LIMIT/i );
$self->__consume_ws;
my $limit = $self->_eat( $r_INTEGER );
$self->{build}{options}{limit} = $limit;
}
# [19] OffsetClause ::= 'OFFSET' INTEGER
sub _OffsetClause_test {
my $self = shift;
return $self->_test( qr/OFFSET/i );
}
sub _OffsetClause {
my $self = shift;
$self->_eat( qr/OFFSET/i );
$self->__consume_ws;
my $off = $self->_eat( $r_INTEGER );
$self->{build}{options}{offset} = $off;
}
# [20] GroupGraphPattern ::= '{' TriplesBlock? ( ( GraphPatternNotTriples | Filter ) '.'? TriplesBlock? )* '}'
sub _GroupGraphPattern {
my $self = shift;
$self->_eat('{');
$self->__consume_ws_opt;
if ($self->_SubSelect_test) {
$self->_SubSelect;
} else {
$self->_GroupGraphPatternSub;
}
$self->__consume_ws_opt;
$self->_eat('}');
}
sub _GroupGraphPatternSub {
my $self = shift;
$self->_push_pattern_container;
my $got_pattern = 0;
my $need_dot = 0;
if ($self->_TriplesBlock_test) {
$need_dot = 1;
$got_pattern++;
$self->_TriplesBlock;
$self->__consume_ws_opt;
}
my $pos = length($self->{tokens});
while (not $self->_test('}')) {
if ($self->_GraphPatternNotTriples_test) {
$need_dot = 0;
$got_pattern++;
$self->_GraphPatternNotTriples;
$self->__consume_ws_opt;
my (@data) = splice(@{ $self->{stack} });
$self->__handle_GraphPatternNotTriples( @data );
$self->__consume_ws_opt;
} elsif ($self->_test( qr/FILTER/i )) {
$got_pattern++;
$need_dot = 0;
$self->_Filter;
$self->__consume_ws_opt;
}
if ($need_dot or $self->_test('.')) {
$self->_eat('.');
if ($got_pattern) {
$need_dot = 0;
$got_pattern = 0;
} else {
throw RDF::Query::Error::ParseError -text => "Syntax error: Extra dot found without preceding pattern";
}
$self->__consume_ws_opt;
}
if ($self->_TriplesBlock_test) {
my $peek = $self->_peek_pattern;
if (blessed($peek) and $peek->isa('RDF::Query::Algebra::BasicGraphPattern')) {
$self->_TriplesBlock;
my $rhs = $self->_remove_pattern;
my $lhs = $self->_remove_pattern;
if ($rhs->isa('RDF::Query::Algebra::BasicGraphPattern')) {
my $merged = $self->__new_bgp( map { $_->triples } ($lhs, $rhs) );
$self->_add_patterns( $merged );
} else {
my $merged = RDF::Query::Algebra::GroupGraphPattern->new($lhs, $rhs);
$self->_add_patterns( $merged );
}
} else {
$self->_TriplesBlock;
}
$self->__consume_ws_opt;
}
$self->__consume_ws_opt;
last unless ($self->_test( qr/\S/ ));
my $new = length($self->{tokens});
if ($pos == $new) {
# we haven't progressed, and so would infinite loop if we don't break out and throw an error.
$self->_syntax_error('');
} else {
$pos = $new;
}
}
my $cont = $self->_pop_pattern_container;
my @filters = splice(@{ $self->{filters} });
my @patterns;
my $pattern = RDF::Query::Algebra::GroupGraphPattern->new( @$cont );
if (@filters) {
while (my $f = shift @filters) {
$pattern = RDF::Query::Algebra::Filter->new( $f, $pattern );
}
}
$self->_add_patterns( $pattern );
}
sub __handle_GraphPatternNotTriples {
my $self = shift;
my $data = shift;
my ($class, @args) = @$data;
if ($class =~ /^RDF::Query::Algebra::(Optional|Minus)$/) {
my $cont = $self->_pop_pattern_container;
my $ggp = RDF::Query::Algebra::GroupGraphPattern->new( @$cont );
$self->_push_pattern_container;
# my $ggp = $self->_remove_pattern();
unless ($ggp) {
$ggp = RDF::Query::Algebra::GroupGraphPattern->new();
}
my $opt = $class->new( $ggp, @args );
$self->_add_patterns( $opt );
} elsif ($class eq 'RDF::Query::Algebra::Table') {
my ($table) = @args;
$self->_add_patterns( $table );
} elsif ($class eq 'RDF::Query::Algebra::Extend') {
my $cont = $self->_pop_pattern_container;
my $ggp = RDF::Query::Algebra::GroupGraphPattern->new( @$cont );
$self->_push_pattern_container;
# my $ggp = $self->_remove_pattern();
unless ($ggp) {
$ggp = RDF::Query::Algebra::GroupGraphPattern->new();
}
my $alias = $args[0];
my %in_scope = map { $_ => 1 } $ggp->potentially_bound();
my $var = $alias->name;
if (exists $in_scope{ $var }) {
throw RDF::Query::Error::QueryPatternError -text => "Syntax error: BIND used with variable already in scope";
}
my $bind = $class->new( $ggp, [$alias] );
$self->_add_patterns( $bind );
} elsif ($class eq 'RDF::Query::Algebra::Service') {
my ($endpoint, $pattern, $silent) = @args;
if ($endpoint->isa('RDF::Query::Node::Variable')) {
# SERVICE ?var
my $cont = $self->_pop_pattern_container;
my $ggp = RDF::Query::Algebra::GroupGraphPattern->new( @$cont );
$self->_push_pattern_container;
# my $ggp = $self->_remove_pattern();
unless ($ggp) {
$ggp = RDF::Query::Algebra::GroupGraphPattern->new();
}
my $service = $class->new( $endpoint, $pattern, $silent, $ggp );
$self->_add_patterns( $service );
} else {
# SERVICE <endpoint>
# no-op
my $service = $class->new( $endpoint, $pattern, $silent );
$self->_add_patterns( $service );
}
} elsif ($class =~ /RDF::Query::Algebra::(Union|NamedGraph|GroupGraphPattern)$/) {
# no-op
} else {
throw RDF::Query::Error::ParseError 'Unrecognized GraphPattern: ' . $class;
}
}
sub _SubSelect_test {
my $self = shift;
return $self->_test(qr/SELECT/i);
}
sub _SubSelect {
my $self = shift;
my $pattern;
{
local($self->{error});
local($self->{namespaces}) = $self->{namespaces};
local($self->{blank_ids}) = $self->{blank_ids};
local($self->{stack}) = [];
local($self->{filters}) = [];
local($self->{pattern_container_stack}) = [];
my $triples = $self->_push_pattern_container();
local($self->{build}) = { triples => $triples};
if ($self->{baseURI}) {
$self->{build}{base} = $self->{baseURI};
}
$self->_eat(qr/SELECT/i);
$self->__consume_ws;
if ($self->{tokens} =~ m/^(DISTINCT|REDUCED)/i) {
my $mod = $self->_eat( qr/DISTINCT|REDUCED/i );
$self->__consume_ws;
$self->{build}{options}{lc($mod)} = 1;
}
my $star = $self->__SelectVars;
$self->__consume_ws_opt;
$self->_WhereClause;
if ($star) {
my $triples = $self->{build}{triples} || [];
my @vars;
foreach my $t (@$triples) {
my @v = $t->potentially_bound;
push(@vars, @v);
}
@vars = RDF::Query::_uniq( @vars );
push( @{ $self->{build}{variables} }, map { $self->new_variable($_) } @vars );
}
$self->__consume_ws_opt;
$self->_SolutionModifier();
if ($self->{build}{options}{orderby}) {
my $order = delete $self->{build}{options}{orderby};
my $pattern = pop(@{ $self->{build}{triples} });
my $sort = RDF::Query::Algebra::Sort->new( $pattern, @$order );
push(@{ $self->{build}{triples} }, $sort);
}
$self->__consume_ws_opt;
if ($self->_test( qr/VALUES/i )) {
$self->_eat( qr/VALUES/i );
$self->__consume_ws_opt;
my @vars;
my $parens = 0;
if ($self->_test(qr/[(]/)) {
$self->_eat( qr/[(]/ );
$parens = 1;
}
while ($self->_test(qr/[\$?]/)) {
$self->_Var;
push( @vars, splice(@{ $self->{stack} }));
$self->__consume_ws_opt;
}
if ($parens) {
$self->_eat( qr/[)]/ );
}
$self->__consume_ws_opt;
my $count = scalar(@vars);
if (not($parens) and $count == 0) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Expected VAR in inline data declaration";
} elsif (not($parens) and $count > 1) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Inline data declaration can only have one variable when parens are omitted";
}
my $short = (not($parens) and $count == 1);
$self->_eat('{');
$self->__consume_ws_opt;
if (not($short) or ($short and $self->_Binding_test)) {
while ($self->_Binding_test) {
my $terms = $self->_Binding($count);
push( @{ $self->{build}{bindings}{terms} }, $terms );
$self->__consume_ws_opt;
}
} else {
while ($self->_BindingValue_test) {
$self->_BindingValue;
$self->__consume_ws_opt;
my ($term) = splice(@{ $self->{stack} });
push( @{ $self->{build}{bindings}{terms} }, [$term] );
$self->__consume_ws_opt;
}
}
$self->_eat('}');
$self->__consume_ws_opt;
$self->{build}{bindings}{vars} = \@vars;
}
$self->__solution_modifiers( $star );
delete $self->{build}{options};
my $data = delete $self->{build};
$data->{method} = 'SELECT';
my $query = RDF::Query->_new(
base => $self->{baseURI},
# parser => $self,
parsed => { %$data },
);
$pattern = RDF::Query::Algebra::SubSelect->new( $query );
}
$self->_add_patterns( $pattern );
}
# [21] TriplesBlock ::= TriplesSameSubject ( '.' TriplesBlock? )?
sub _TriplesBlock_test {
my $self = shift;
# VarOrTerm | TriplesNode -> (Var | GraphTerm) | (Collection | BlankNodePropertyList) -> Var | IRIref | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL | Collection | BlankNodePropertyList
# but since a triple can't start with a literal, this is reduced to:
# Var | IRIref | BlankNode | NIL
return $self->_test(qr/[\$?]|<|_:|\[[\n\r\t ]*\]|\([\n\r\t ]*\)|\[|[[(]|${r_PNAME_NS}/);
}
sub _TriplesBlock {
my $self = shift;
$self->_push_pattern_container;
$self->__TriplesBlock;
my $triples = $self->_pop_pattern_container;
my $bgp = $self->__new_bgp( @$triples );
$self->_add_patterns( $bgp );
}
## this one (with two underscores) doesn't pop patterns off the stack and make a BGP.
## instead, things are left on the stack so we can recurse without doing the wrong thing.
## the one with one underscore (_TriplesBlock) will pop everything off and make the BGP.
sub __TriplesBlock {
my $self = shift;
my $got_dot = 0;
TRIPLESBLOCKLOOP:
$self->_TriplesSameSubjectPath;
$self->__consume_ws_opt;
while ($self->_test('.')) {
if ($got_dot) {
throw RDF::Query::Error::ParseError -text => "Syntax error: found extra DOT after TriplesBlock";
}
$self->_eat('.');
$got_dot++;
$self->__consume_ws_opt;
if ($self->_TriplesBlock_test) {
$got_dot = 0;
goto TRIPLESBLOCKLOOP;
}
$self->__consume_ws_opt;
}
$self->__consume_ws_opt;
}
# [22] GraphPatternNotTriples ::= OptionalGraphPattern | GroupOrUnionGraphPattern | GraphGraphPattern
sub _GraphPatternNotTriples_test {
my $self = shift;
return 1 if $self->_test(qr/VALUES/i); # InlineDataClause
return $self->_test(qr/BIND|SERVICE|MINUS|OPTIONAL|{|GRAPH/i);
}
sub _GraphPatternNotTriples {
my $self = shift;
if ($self->_test(qr/VALUES/i)) {
$self->_InlineDataClause;
} elsif ($self->_test(qr/SERVICE/i)) {
$self->_ServiceGraphPattern;
} elsif ($self->_test(qr/MINUS/i)) {
$self->_MinusGraphPattern;
} elsif ($self->_test(qr/BIND/i)) {
$self->_Bind;
} elsif ($self->_OptionalGraphPattern_test) {
$self->_OptionalGraphPattern;
} elsif ($self->_GroupOrUnionGraphPattern_test) {
$self->_GroupOrUnionGraphPattern;
} else {
$self->_GraphGraphPattern;
}
}
sub _InlineDataClause {
my $self = shift;
$self->_eat( qr/VALUES/i );
$self->__consume_ws_opt;
my @vars;
my $parens = 0;
if ($self->_test(qr/[(]/)) {
$self->_eat( qr/[(]/ );
$self->__consume_ws_opt;
$parens = 1;
}
while ($self->_test(qr/[\$?]/)) {
$self->_Var;
push( @vars, splice(@{ $self->{stack} }));
$self->__consume_ws_opt;
}
if ($parens) {
$self->_eat( qr/[)]/ );
$self->__consume_ws_opt;
}
my $count = scalar(@vars);
if (not($parens) and $count == 0) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Expected VAR in inline data declaration";
} elsif (not($parens) and $count > 1) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Inline data declaration can only have one variable when parens are omitted";
}
my $short = (not($parens) and $count == 1);
$self->_eat('{');
$self->__consume_ws_opt;
my @rows;
if (not($short) or ($short and $self->_Binding_test)) {
# { (term) (term) }
while ($self->_Binding_test) {
my $terms = $self->_Binding($count);
push( @rows, $terms );
$self->__consume_ws_opt;
}
} else {
# { term term }
while ($self->_BindingValue_test) {
$self->_BindingValue;
$self->__consume_ws_opt;
my ($term) = splice(@{ $self->{stack} });
push( @rows, [$term] );
}
}
$self->_eat('}');
$self->__consume_ws_opt;
my @vbs = map { my %d; @d{ map { $_->name } @vars } = @$_; RDF::Query::VariableBindings->new(\%d) } @rows;
my $table = RDF::Query::Algebra::Table->new( [ map { $_->name } @vars ], @vbs );
$self->_add_stack( ['RDF::Query::Algebra::Table', $table] );
}
sub _Bind {
my $self = shift;
$self->_eat(qr/BIND/i);
$self->__consume_ws_opt;
$self->_BrackettedAliasExpression;
my ($alias) = splice(@{ $self->{stack} });
$self->_add_stack( ['RDF::Query::Algebra::Extend', $alias] );
}
sub _ServiceGraphPattern {
my $self = shift;
my $op = $self->_eat( qr/SERVICE(\s+SILENT)?/i );
my $silent = ($op =~ /SILENT/i);
$self->__consume_ws_opt;
$self->__close_bgp_with_filters;
if ($self->_test(qr/[\$?]/)) {
$self->_Var;
} else {
$self->_IRIref;
}
my ($endpoint) = splice( @{ $self->{stack} } );
$self->__consume_ws_opt;
$self->_GroupGraphPattern;
my $ggp = $self->_remove_pattern;
# my $pattern = RDF::Query::Algebra::Service->new( $endpoint, $ggp, $silent );
# $self->_add_patterns( $pattern );
my $opt = ['RDF::Query::Algebra::Service', $endpoint, $ggp, ($silent ? 1 : 0)];
$self->_add_stack( $opt );
}
# [23] OptionalGraphPattern ::= 'OPTIONAL' GroupGraphPattern
sub _OptionalGraphPattern_test {
my $self = shift;
return $self->_test( qr/OPTIONAL/i );
}
sub __close_bgp_with_filters {
my $self = shift;
my @filters = splice(@{ $self->{filters} });
if (@filters) {
my $cont = $self->_pop_pattern_container;
my $ggp = RDF::Query::Algebra::GroupGraphPattern->new( @$cont );
$self->_push_pattern_container;
# my $ggp = $self->_remove_pattern();
unless ($ggp) {
$ggp = RDF::Query::Algebra::GroupGraphPattern->new();
}
while (my $f = shift @filters) {
$ggp = RDF::Query::Algebra::Filter->new( $f, $ggp );
}
$self->_add_patterns($ggp);
}
}
sub _OptionalGraphPattern {
my $self = shift;
$self->_eat( qr/OPTIONAL/i );
$self->__close_bgp_with_filters;
$self->__consume_ws_opt;
$self->_GroupGraphPattern;
my $ggp = $self->_remove_pattern;
my $opt = ['RDF::Query::Algebra::Optional', $ggp];
$self->_add_stack( $opt );
}
sub _MinusGraphPattern {
my $self = shift;
$self->_eat( qr/MINUS/i );
$self->__close_bgp_with_filters;
$self->__consume_ws_opt;
$self->_GroupGraphPattern;
my $ggp = $self->_remove_pattern;
my $opt = ['RDF::Query::Algebra::Minus', $ggp];
$self->_add_stack( $opt );
}
# [24] GraphGraphPattern ::= 'GRAPH' VarOrIRIref GroupGraphPattern
sub _GraphGraphPattern {
my $self = shift;
if ($self->{__data_pattern}) {
if ($self->{__graph_nesting_level}++) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Nested named GRAPH blocks not allowed in data template.";
}
}
$self->_eat( qr/GRAPH\b/i );
$self->__consume_ws_opt;
$self->_VarOrIRIref;
my ($graph) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
if ($graph->isa('RDF::Trine::Node::Resource')) {
local($self->{named_graph}) = $graph;
$self->_GroupGraphPattern;
} else {
$self->_GroupGraphPattern;
}
if ($self->{__data_pattern}) {
$self->{__graph_nesting_level}--;
}
my $ggp = $self->_remove_pattern;
my $pattern = RDF::Query::Algebra::NamedGraph->new( $graph, $ggp );
$self->_add_patterns( $pattern );
$self->_add_stack( [ 'RDF::Query::Algebra::NamedGraph' ] );
}
# [25] GroupOrUnionGraphPattern ::= GroupGraphPattern ( 'UNION' GroupGraphPattern )*
sub _GroupOrUnionGraphPattern_test {
my $self = shift;
return $self->_test('{');
}
sub _GroupOrUnionGraphPattern {
my $self = shift;
$self->_GroupGraphPattern;
my $ggp = $self->_remove_pattern;
$self->__consume_ws_opt;
if ($self->_test( qr/UNION/i )) {
while ($self->_test( qr/UNION/i )) {
$self->_eat( qr/UNION/i );
$self->__consume_ws_opt;
$self->_GroupGraphPattern;
$self->__consume_ws_opt;
my $rhs = $self->_remove_pattern;
$ggp = RDF::Query::Algebra::Union->new( $ggp, $rhs );
}
$self->_add_patterns( $ggp );
$self->_add_stack( [ 'RDF::Query::Algebra::Union' ] );
} else {
$self->_add_patterns( $ggp );
$self->_add_stack( [ 'RDF::Query::Algebra::GroupGraphPattern' ] );
}
}
# [26] Filter ::= 'FILTER' Constraint
sub _Filter {
my $self = shift;
$self->_eat( qr/FILTER/i );
$self->__consume_ws_opt;
$self->_Constraint;
my ($expr) = splice(@{ $self->{stack} });
$self->_add_filter( $expr );
}
# [27] Constraint ::= BrackettedExpression | BuiltInCall | FunctionCall
sub _Constraint_test {
my $self = shift;
return 1 if $self->_test( qr/[(]/ );
return 1 if $self->_BuiltInCall_test;
return 1 if $self->_FunctionCall_test;
return 0;
}
sub _Constraint {
my $self = shift;
if ($self->_BrackettedExpression_test) {
$self->_BrackettedExpression();
} elsif ($self->_BuiltInCall_test) {
$self->_BuiltInCall();
} else {
$self->_FunctionCall();
}
}
# [28] FunctionCall ::= IRIref ArgList
sub _FunctionCall_test {
my $self = shift;
return $self->_IRIref_test;
}
sub _FunctionCall {
my $self = shift;
$self->_IRIref;
my ($iri) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_ArgList;
my @args = splice(@{ $self->{stack} });
my $func = $self->new_function_expression( $iri, @args );
$self->_add_stack( $func );
}
# [29] ArgList ::= ( NIL | '(' Expression ( ',' Expression )* ')' )
sub _ArgList_test {
my $self = shift;
return $self->_test('(');
}
sub _ArgList {
my $self = shift;
$self->_eat('(');
$self->__consume_ws_opt;
my @args;
unless ($self->_test(')')) {
$self->_Expression;
push( @args, splice(@{ $self->{stack} }) );
while ($self->_test(',')) {
$self->_eat(',');
$self->__consume_ws_opt;
$self->_Expression;
push( @args, splice(@{ $self->{stack} }) );
}
}
$self->_eat(')');
$self->_add_stack( @args );
}
# [30] ConstructTemplate ::= '{' ConstructTriples? '}'
sub _ConstructTemplate {
my $self = shift;
$self->_push_pattern_container;
$self->_eat( '{' );
$self->__consume_ws_opt;
if ($self->_ConstructTriples_test) {
$self->_ConstructTriples;
}
$self->__consume_ws_opt;
$self->_eat( '}' );
my $cont = $self->_pop_pattern_container;
$self->{build}{construct_triples} = $cont;
}
# [31] ConstructTriples ::= TriplesSameSubject ( '.' ConstructTriples? )?
sub _ConstructTriples_test {
my $self = shift;
return $self->_TriplesBlock_test;
}
sub _ConstructTriples {
my $self = shift;
$self->_TriplesSameSubject;
$self->__consume_ws_opt;
while ($self->_test(qr/[.]/)) {
$self->_eat( qr/[.]/ );
$self->__consume_ws_opt;
if ($self->_ConstructTriples_test) {
$self->_TriplesSameSubject;
}
}
}
# [32] TriplesSameSubject ::= VarOrTerm PropertyListNotEmpty | TriplesNode PropertyList
sub _TriplesSameSubject {
my $self = shift;
my @triples;
if ($self->_TriplesNode_test) {
$self->_TriplesNode;
my ($s) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_PropertyList;
$self->__consume_ws_opt;
my @list = splice(@{ $self->{stack} });
foreach my $data (@list) {
push(@triples, $self->__new_statement( $s, @$data ));
}
} else {
$self->_VarOrTerm;
my ($s) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_PropertyListNotEmpty;
$self->__consume_ws_opt;
my (@list) = splice(@{ $self->{stack} });
foreach my $data (@list) {
push(@triples, $self->__new_statement( $s, @$data ));
}
}
$self->_add_patterns( @triples );
# return @triples;
}
# TriplesSameSubjectPath ::= VarOrTerm PropertyListNotEmptyPath | TriplesNode PropertyListPath
sub _TriplesSameSubjectPath {
my $self = shift;
my @triples;
if ($self->_TriplesNode_test) {
$self->_TriplesNode;
my ($s) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_PropertyListPath;
$self->__consume_ws_opt;
my @list = splice(@{ $self->{stack} });
foreach my $data (@list) {
push(@triples, $self->__new_statement( $s, @$data ));
}
} else {
$self->_VarOrTerm;
my ($s) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_PropertyListNotEmptyPath;
$self->__consume_ws_opt;
my (@list) = splice(@{ $self->{stack} });
foreach my $data (@list) {
push(@triples, $self->__new_statement( $s, @$data ));
}
}
$self->_add_patterns( @triples );
# return @triples;
}
# [33] PropertyListNotEmpty ::= Verb ObjectList ( ';' ( Verb ObjectList )? )*
sub _PropertyListNotEmpty {
my $self = shift;
$self->_Verb;
my ($v) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_ObjectList;
my @l = splice(@{ $self->{stack} });
my @props = map { [$v, $_] } @l;
while ($self->_test(qr'\s*;')) {
$self->_eat(';');
$self->__consume_ws_opt;
if ($self->_Verb_test) {
$self->_Verb;
my ($v) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_ObjectList;
my @l = splice(@{ $self->{stack} });
push(@props, map { [$v, $_] } @l);
}
}
$self->_add_stack( @props );
}
# [34] PropertyList ::= PropertyListNotEmpty?
sub _PropertyList {
my $self = shift;
if ($self->_Verb_test) {
$self->_PropertyListNotEmpty;
}
}
# [33] PropertyListNotEmptyPath ::= (VerbPath | VerbSimple) ObjectList ( ';' ( (VerbPath | VerbSimple) ObjectList )? )*
sub _PropertyListNotEmptyPath {
my $self = shift;
if ($self->_VerbPath_test) {
$self->_VerbPath;
} else {
$self->_VerbSimple;
}
my ($v) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_ObjectList;
my @l = splice(@{ $self->{stack} });
my @props = map { [$v, $_] } @l;
while ($self->_test(qr'\s*;')) {
$self->_eat(';');
$self->__consume_ws_opt;
if ($self->_VerbPath_test or $self->_VerbSimple_test) {
if ($self->_VerbPath_test) {
$self->_VerbPath;
} else {
$self->_VerbSimple;
}
my ($v) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_ObjectList;
my @l = splice(@{ $self->{stack} });
push(@props, map { [$v, $_] } @l);
}
}
$self->_add_stack( @props );
}
# [34] PropertyListPath ::= PropertyListNotEmptyPath?
sub _PropertyListPath {
my $self = shift;
if ($self->_Verb_test) {
$self->_PropertyListNotEmptyPath;
}
}
# [35] ObjectList ::= Object ( ',' Object )*
sub _ObjectList {
my $self = shift;
my @list;
$self->_Object;
push(@list, splice(@{ $self->{stack} }));
$self->__consume_ws_opt;
while ($self->_test(',')) {
$self->_eat(',');
$self->__consume_ws_opt;
$self->_Object;
push(@list, splice(@{ $self->{stack} }));
$self->__consume_ws_opt;
}
$self->_add_stack( @list );
}
# [36] Object ::= GraphNode
sub _Object {
my $self = shift;
$self->_GraphNode;
}
# [37] Verb ::= VarOrIRIref | 'a'
sub _Verb_test {
my $self = shift;
return $self->_test( qr/a[\n\t\r <]|[?\$]|<|${r_PNAME_LN}|${r_PNAME_NS}/ );
}
sub _Verb {
my $self = shift;
if ($self->_test(qr/a[\n\t\r <]/)) {
$self->_eat('a');
$self->__consume_ws;
my $type = RDF::Query::Node::Resource->new( $rdf->type->uri_value );
$self->_add_stack( $type );
} else {
$self->_VarOrIRIref;
}
}
# VerbSimple ::= Var
sub _VerbSimple_test {
my $self = shift;
return 1 if ($self->_test(qr/[\$?]/));
}
sub _VerbSimple {
my $self = shift;
$self->_Var;
}
# VerbPath ::= Path
sub _VerbPath_test {
my $self = shift;
return 1 if ($self->_IRIref_test);
return 1 if ($self->_test(qr/\^|[|(a!]/));
return 0;
}
sub _VerbPath {
my $self = shift;
$self->_Path
}
# [74] Path ::= PathAlternative
sub _Path {
my $self = shift;
# my $distinct = 1;
# if ($self->_test(qr/DISTINCT[(]/i)) {
# $self->_eat(qr/DISTINCT[(]/i);
# $self->__consume_ws_opt;
# $distinct = 1;
# }
$self->_PathAlternative;
# if ($distinct) {
# $self->__consume_ws_opt;
# $self->_eat(qr/[)]/);
# $self->__consume_ws_opt;
# my ($path) = splice(@{ $self->{stack} });
# $self->_add_stack( ['PATH', 'DISTINCT', $path] );
# }
}
################################################################################
# [75] PathAlternative ::= PathSequence ( '|' PathSequence )*
sub _PathAlternative {
my $self = shift;
$self->_PathSequence;
$self->__consume_ws_opt;
while ($self->_test(qr/[|]/)) {
my ($lhs) = splice(@{ $self->{stack} });
$self->_eat(qr/[|]/);
$self->__consume_ws_opt;
# $self->_PathOneInPropertyClass;
$self->_PathSequence;
$self->__consume_ws_opt;
my ($rhs) = splice(@{ $self->{stack} });
$self->_add_stack( ['PATH', '|', $lhs, $rhs] );
}
}
# [76] PathSequence ::= PathEltOrInverse ( '/' PathEltOrInverse | '^' PathElt )*
sub _PathSequence {
my $self = shift;
$self->_PathEltOrInverse;
$self->__consume_ws_opt;
while ($self->_test(qr<[/^]>)) {
my $op;
my ($lhs) = splice(@{ $self->{stack} });
if ($self->_test(qr</>)) {
$op = $self->_eat(qr</>);
$self->__consume_ws_opt;
$self->_PathEltOrInverse;
} else {
$op = $self->_eat(qr<\^>);
$self->__consume_ws_opt;
$self->_PathElt;
}
my ($rhs) = splice(@{ $self->{stack} });
$self->_add_stack( ['PATH', $op, $lhs, $rhs] );
}
}
# [77] PathElt ::= PathPrimary PathMod?
sub _PathElt {
my $self = shift;
$self->_PathPrimary;
# $self->__consume_ws_opt;
if ($self->_PathMod_test) {
my @path = splice(@{ $self->{stack} });
$self->_PathMod;
my ($mod) = splice(@{ $self->{stack} });
if (defined($mod)) {
$self->_add_stack( ['PATH', $mod, @path] );
} else {
# this might happen if we descend into _PathMod by mistaking a + as
# a path modifier, but _PathMod figures out it's actually part of a
# signed numeric object that follows the path
$self->_add_stack( @path );
}
}
}
# [78] PathEltOrInverse ::= PathElt | '^' PathElt
sub _PathEltOrInverse {
my $self = shift;
if ($self->_test(qr/\^/)) {
$self->_eat(qr<\^>);
$self->__consume_ws_opt;
$self->_PathElt;
my @props = splice(@{ $self->{stack} });
$self->_add_stack( [ 'PATH', '^', @props ] );
} else {
$self->_PathElt;
}
}
# [79] PathMod ::= ( '*' | '?' | '+' | '{' ( Integer ( ',' ( '}' | Integer '}' ) | '}' ) ) )
sub _PathMod_test {
my $self = shift;
return 1 if ($self->_test(qr/[*?+{]/));
}
sub _PathMod {
my $self = shift;
if ($self->_test(qr/[*?+]/)) {
if ($self->_test(qr/[+][.0-9]/)) {
return;
} else {
$self->_add_stack( $self->_eat(qr/[*?+]/) );
$self->__consume_ws_opt;
}
### path repetition range syntax :path{n,m}; removed from 1.1 Query 2LC
# } else {
# $self->_eat(qr/{/);
# $self->__consume_ws_opt;
# my $value = 0;
# if ($self->_test(qr/}/)) {
# throw RDF::Query::Error::ParseError -text => "Syntax error: Empty Path Modifier";
# }
# if ($self->_test($r_INTEGER)) {
# $value = $self->_eat( $r_INTEGER );
# $self->__consume_ws_opt;
# }
# if ($self->_test(qr/,/)) {
# $self->_eat(qr/,/);
# $self->__consume_ws_opt;
# if ($self->_test(qr/}/)) {
# $self->_eat(qr/}/);
# $self->_add_stack( "$value-" );
# } else {
# my $end = $self->_eat( $r_INTEGER );
# $self->__consume_ws_opt;
# $self->_eat(qr/}/);
# $self->_add_stack( "$value-$end" );
# }
# } else {
# $self->_eat(qr/}/);
# $self->_add_stack( "$value" );
# }
}
}
# [80] PathPrimary ::= ( IRIref | 'a' | '!' PathNegatedPropertyClass | '(' Path ')' )
sub _PathPrimary {
my $self = shift;
if ($self->_IRIref_test) {
$self->_IRIref;
} elsif ($self->_test(qr/a[\n\t\r <]/)) {
$self->_eat(qr/a/);
my $type = RDF::Query::Node::Resource->new( $rdf->type->uri_value );
$self->_add_stack( $type );
} elsif ($self->_test(qr/[!]/)) {
$self->_eat(qr/[!]/);
$self->__consume_ws_opt;
$self->_PathNegatedPropertyClass;
my (@path) = splice(@{ $self->{stack} });
$self->_add_stack( ['PATH', '!', @path] );
} else {
$self->_eat(qr/[(]/);
$self->__consume_ws_opt;
$self->_Path;
$self->__consume_ws_opt;
$self->_eat(qr/[)]/);
}
}
# [81] PathNegatedPropertyClass ::= ( PathOneInPropertyClass | '(' ( PathOneInPropertyClass ( '|' PathOneInPropertyClass )* )? ')' )
sub _PathNegatedPropertyClass {
my $self = shift;
if ($self->_test(qr/[(]/)) {
$self->_eat(qr/[(]/);
$self->__consume_ws_opt;
my @nodes;
if ($self->_PathOneInPropertyClass_test) {
$self->_PathOneInPropertyClass;
push(@nodes, splice(@{ $self->{stack} }));
$self->__consume_ws_opt;
while ($self->_test(qr/[|]/)) {
$self->_eat(qr/[|]/);
$self->__consume_ws_opt;
$self->_PathOneInPropertyClass;
$self->__consume_ws_opt;
push(@nodes, splice(@{ $self->{stack} }));
# $self->_add_stack( ['PATH', '|', $lhs, $rhs] );
}
}
$self->_eat(qr/[)]/);
$self->_add_stack( @nodes );
} else {
$self->_PathOneInPropertyClass;
}
}
# [82] PathOneInPropertyClass ::= IRIref | 'a'
sub _PathOneInPropertyClass_test {
my $self = shift;
return 1 if $self->_IRIref_test;
return 1 if ($self->_test(qr/a[|)\n\t\r <]/));
return 1 if ($self->_test(qr/\^/));
return 0;
}
sub _PathOneInPropertyClass {
my $self = shift;
my $rev = 0;
if ($self->_test(qr/\^/)) {
$self->_eat(qr/\^/);
$rev = 1;
}
if ($self->_test(qr/a[|)\n\t\r <]/)) {
$self->_eat(qr/a/);
my $type = RDF::Query::Node::Resource->new( $rdf->type->uri_value );
if ($rev) {
$self->_add_stack( [ 'PATH', '^', $type ] );
} else {
$self->_add_stack( $type );
}
} else {
$self->_IRIref;
if ($rev) {
my ($path) = splice(@{ $self->{stack} });
$self->_add_stack( [ 'PATH', '^', $path ] );
}
}
}
################################################################################
# [38] TriplesNode ::= Collection | BlankNodePropertyList
sub _TriplesNode_test {
my $self = shift;
return $self->_test(qr/[[(](?![\n\r\t ]*\])(?![\n\r\t ]*\))/);
}
sub _TriplesNode {
my $self = shift;
if ($self->_test(qr/\(/)) {
$self->_Collection;
} else {
$self->_BlankNodePropertyList;
}
}
# [39] BlankNodePropertyList ::= '[' PropertyListNotEmpty ']'
sub _BlankNodePropertyList {
my $self = shift;
if (my $where = $self->{__no_bnodes}) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Blank nodes not allowed in $where";
}
$self->_eat('[');
$self->__consume_ws_opt;
# $self->_PropertyListNotEmpty;
$self->_PropertyListNotEmptyPath;
$self->__consume_ws_opt;
$self->_eat(']');
my @props = splice(@{ $self->{stack} });
my $subj = $self->new_blank;
my @triples = map { $self->__new_statement( $subj, @$_ ) } @props;
$self->_add_patterns( @triples );
$self->_add_stack( $subj );
}
# [40] Collection ::= '(' GraphNode+ ')'
sub _Collection {
my $self = shift;
$self->_eat('(');
$self->__consume_ws_opt;
$self->_GraphNode;
$self->__consume_ws_opt;
my @nodes;
push(@nodes, splice(@{ $self->{stack} }));
while ($self->_GraphNode_test) {
$self->_GraphNode;
$self->__consume_ws_opt;
push(@nodes, splice(@{ $self->{stack} }));
}
$self->_eat(')');
my $subj = $self->new_blank;
my $cur = $subj;
my $last;
my $first = RDF::Query::Node::Resource->new( $rdf->first->uri_value );
my $rest = RDF::Query::Node::Resource->new( $rdf->rest->uri_value );
my $nil = RDF::Query::Node::Resource->new( $rdf->nil->uri_value );
my @triples;
foreach my $node (@nodes) {
push(@triples, $self->__new_statement( $cur, $first, $node ) );
my $new = $self->new_blank;
push(@triples, $self->__new_statement( $cur, $rest, $new ) );
$last = $cur;
$cur = $new;
}
pop(@triples);
push(@triples, $self->__new_statement( $last, $rest, $nil ));
$self->_add_patterns( @triples );
$self->_add_stack( $subj );
}
# [41] GraphNode ::= VarOrTerm | TriplesNode
sub _GraphNode_test {
my $self = shift;
# VarOrTerm | TriplesNode -> (Var | GraphTerm) | (Collection | BlankNodePropertyList) -> Var | IRIref | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL | Collection | BlankNodePropertyList
# but since a triple can't start with a literal, this is reduced to:
# Var | IRIref | BlankNode | NIL
return $self->_test(qr/[\$?]|<|['"]|(true\b|false\b)|([+-]?\d)|_:|${r_ANON}|${r_NIL}|\[|[[(]/);
}
sub _GraphNode {
my $self = shift;
if ($self->_TriplesNode_test) {
$self->_TriplesNode;
} else {
$self->_VarOrTerm;
}
}
# [42] VarOrTerm ::= Var | GraphTerm
sub _VarOrTerm_test {
my $self = shift;
return 1 if ($self->_test(qr/[\$?]/));
return 1 if ($self->_IRIref_test);
return 1 if ($self->_test(qr/[<'".0-9]|(true|false)\b|_:|\([\n\r\t ]*\)/));
return 0;
}
sub _VarOrTerm {
my $self = shift;
if ($self->{tokens} =~ m'^[?$]') {
$self->_Var;
} else {
$self->_GraphTerm;
}
}
# [43] VarOrIRIref ::= Var | IRIref
sub _VarOrIRIref_test {
my $self = shift;
return $self->_test(qr/[\$?]|<|${r_PNAME_LN}|${r_PNAME_NS}/);
}
sub _VarOrIRIref {
my $self = shift;
if ($self->{tokens} =~ m'^[?$]') {
$self->_Var;
} else {
$self->_IRIref;
}
}
# [44] Var ::= VAR1 | VAR2
sub _Var {
my $self = shift;
if ($self->{__data_pattern}) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Variable found where Term expected";
}
my $var = ($self->_test( $r_VAR1 )) ? $self->_eat( $r_VAR1 ) : $self->_eat( $r_VAR2 );
$self->_add_stack( RDF::Query::Node::Variable->new( substr($var,1) ) );
}
# [45] GraphTerm ::= IRIref | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL
sub _GraphTerm {
my $self = shift;
if ($self->_test(qr/(true|false)\b/)) {
$self->_BooleanLiteral;
} elsif ($self->_test('(')) {
$self->_NIL;
} elsif ($self->_test( $r_ANON ) or $self->_test('_:')) {
$self->_BlankNode;
} elsif ($self->_test(qr/[-+]?\d/)) {
$self->_NumericLiteral;
} elsif ($self->_test(qr/['"]/)) {
$self->_RDFLiteral;
} else {
$self->_IRIref;
}
}
# [46] Expression ::= ConditionalOrExpression
sub _Expression {
my $self = shift;
$self->_ConditionalOrExpression;
}
# [47] ConditionalOrExpression ::= ConditionalAndExpression ( '||' ConditionalAndExpression )*
sub _ConditionalOrExpression {
my $self = shift;
my @list;
$self->_ConditionalAndExpression;
push(@list, splice(@{ $self->{stack} }));
$self->__consume_ws_opt;
while ($self->_test('||')) {
$self->_eat('||');
$self->__consume_ws_opt;
$self->_ConditionalAndExpression;
push(@list, splice(@{ $self->{stack} }));
}
if (scalar(@list) > 1) {
$self->_add_stack( $self->new_function_expression( 'sparql:logical-or', @list ) );
} else {
$self->_add_stack( @list );
}
Carp::confess $self->{tokens} if (scalar(@{ $self->{stack} }) == 0);
}
# [48] ConditionalAndExpression ::= ValueLogical ( '&&' ValueLogical )*
sub _ConditionalAndExpression {
my $self = shift;
$self->_ValueLogical;
my @list = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
while ($self->_test('&&')) {
$self->_eat('&&');
$self->__consume_ws_opt;
$self->_ValueLogical;
push(@list, splice(@{ $self->{stack} }));
}
if (scalar(@list) > 1) {
$self->_add_stack( $self->new_function_expression( 'sparql:logical-and', @list ) );
} else {
$self->_add_stack( @list );
}
}
# [49] ValueLogical ::= RelationalExpression
sub _ValueLogical {
my $self = shift;
$self->_RelationalExpression;
}
# [50] RelationalExpression ::= NumericExpression ( '=' NumericExpression | '!=' NumericExpression | '<' NumericExpression | '>' NumericExpression | '<=' NumericExpression | '>=' NumericExpression )?
sub _RelationalExpression {
my $self = shift;
$self->_NumericExpression;
$self->__consume_ws_opt;
if ($self->_test(qr/[!<>]?=|[<>]/)) {
if ($self->_test( $r_IRI_REF )) {
throw RDF::Query::Error::ParseError -text => "Syntax error: IRI found where expression expected";
}
my @list = splice(@{ $self->{stack} });
my $op = $self->_eat(qr/[!<>]?=|[<>]/);
$op = '==' if ($op eq '=');
$self->__consume_ws_opt;
$self->_NumericExpression;
push(@list, splice(@{ $self->{stack} }));
$self->_add_stack( $self->new_binary_expression( $op, @list ) );
} elsif ($self->_test(qr/(NOT )?IN/)) {
my @list = splice(@{ $self->{stack} });
my $op = lc($self->_eat(qr/(NOT )?IN/));
$op =~ s/\s+//g;
$self->__consume_ws_opt;
$self->_ExpressionList();
push(@list, splice(@{ $self->{stack} }));
$self->_add_stack( $self->new_function_expression( "sparql:$op", @list ) );
}
}
sub _ExpressionList {
my $self = shift;
$self->_eat('(');
$self->__consume_ws_opt;
my @args;
unless ($self->_test(')')) {
$self->_Expression;
push( @args, splice(@{ $self->{stack} }) );
while ($self->_test(',')) {
$self->_eat(',');
$self->__consume_ws_opt;
$self->_Expression;
push( @args, splice(@{ $self->{stack} }) );
}
}
$self->_eat(')');
$self->_add_stack( @args );
}
# [51] NumericExpression ::= AdditiveExpression
sub _NumericExpression {
my $self = shift;
$self->_AdditiveExpression;
}
# [52] AdditiveExpression ::= MultiplicativeExpression ( '+' MultiplicativeExpression | '-' MultiplicativeExpression | NumericLiteralPositive | NumericLiteralNegative )*
sub _AdditiveExpression {
my $self = shift;
$self->_MultiplicativeExpression;
my ($expr) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
while ($self->_test(qr/[-+]/)) {
my $op = $self->_eat(qr/[-+]/);
$self->__consume_ws_opt;
$self->_MultiplicativeExpression;
my ($rhs) = splice(@{ $self->{stack} });
$expr = $self->new_binary_expression( $op, $expr, $rhs );
}
$self->_add_stack( $expr );
}
# [53] MultiplicativeExpression ::= UnaryExpression ( '*' UnaryExpression | '/' UnaryExpression )*
sub _MultiplicativeExpression {
my $self = shift;
$self->_UnaryExpression;
my ($expr) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
while ($self->_test(qr#[*/]#)) {
my $op = $self->_eat(qr#[*/]#);
$self->__consume_ws_opt;
$self->_UnaryExpression;
my ($rhs) = splice(@{ $self->{stack} });
$expr = $self->new_binary_expression( $op, $expr, $rhs );
}
$self->_add_stack( $expr );
}
# [54] UnaryExpression ::= '!' PrimaryExpression | '+' PrimaryExpression | '-' PrimaryExpression | PrimaryExpression
sub _UnaryExpression {
my $self = shift;
if ($self->_test('!')) {
$self->_eat('!');
$self->__consume_ws_opt;
$self->_PrimaryExpression;
my ($expr) = splice(@{ $self->{stack} });
my $not = $self->new_unary_expression( '!', $expr );
$self->_add_stack( $not );
} elsif ($self->_test('+')) {
$self->_eat('+');
$self->__consume_ws_opt;
$self->_PrimaryExpression;
my ($expr) = splice(@{ $self->{stack} });
### if it's just a literal, force the positive down into the literal
if (blessed($expr) and $expr->isa('RDF::Trine::Node::Literal') and $expr->is_numeric_type) {
my $value = '+' . $expr->literal_value;
$expr->literal_value( $value );
$self->_add_stack( $expr );
} else {
$self->_add_stack( $expr );
}
} elsif ($self->_test('-')) {
$self->_eat('-');
$self->__consume_ws_opt;
$self->_PrimaryExpression;
my ($expr) = splice(@{ $self->{stack} });
### if it's just a literal, force the negative down into the literal instead of make an unnecessary multiplication.
if (blessed($expr) and $expr->isa('RDF::Trine::Node::Literal') and $expr->is_numeric_type) {
my $value = -1 * $expr->literal_value;
$expr->literal_value( $value );
$self->_add_stack( $expr );
} else {
my $int = $xsd->integer->uri_value;
my $neg = $self->new_binary_expression( '*', $self->new_literal('-1', undef, $int), $expr );
$self->_add_stack( $neg );
}
} else {
$self->_PrimaryExpression;
}
}
# [55] PrimaryExpression ::= BrackettedExpression | BuiltInCall | IRIrefOrFunction | RDFLiteral | NumericLiteral | BooleanLiteral | Var
sub _PrimaryExpression {
my $self = shift;
if ($self->_BrackettedExpression_test) {
$self->_BrackettedExpression;
} elsif ($self->_BuiltInCall_test) {
$self->_BuiltInCall;
} elsif ($self->_IRIref_test) {
$self->_IRIrefOrFunction;
} elsif ($self->_test(qr/[\$?]/)) {
$self->_Var;
} elsif ($self->_test(qr/(true|false)\b/)) {
$self->_BooleanLiteral;
} elsif ($self->_test(qr/[-+]?\d/)) {
$self->_NumericLiteral;
} else { # if ($self->_test(qr/['"]/)) {
$self->_RDFLiteral;
}
}
# [56] BrackettedExpression ::= '(' Expression ')'
sub _BrackettedExpression_test {
my $self = shift;
return $self->_test('(');
}
sub _BrackettedExpression {
my $self = shift;
$self->_eat('(');
$self->__consume_ws_opt;
$self->_Expression;
$self->__consume_ws_opt;
$self->_eat(')');
}
sub _Aggregate {
my $self = shift;
my $op = uc( $self->_eat( $r_AGGREGATE_CALL ) );
$self->__consume_ws_opt;
$self->_eat('(');
$self->__consume_ws_opt;
my $distinct = 0;
if ($self->_test( qr/DISTINCT/i )) {
$self->_eat( qr/DISTINCT\s*/i );
$self->__consume_ws_opt;
$distinct = 1;
}
my (@expr, %options);
if ($self->_test('*')) {
@expr = $self->_eat('*');
} else {
$self->_Expression;
push(@expr, splice(@{ $self->{stack} }));
if ($op eq 'GROUP_CONCAT') {
$self->__consume_ws_opt;
while ($self->_test(qr/,/)) {
$self->_eat(qr/,/);
$self->__consume_ws_opt;
$self->_Expression;
push(@expr, splice(@{ $self->{stack} }));
}
$self->__consume_ws_opt;
if ($self->_test(qr/;/)) {
$self->_eat(qr/;/);
$self->__consume_ws_opt;
if ($self->{args}{allow_typos}) {
$self->_eat(qr/SEP[AE]RATOR/i); # accept common typo
} else {
$self->_eat(qr/SEPARATOR/i);
}
$self->__consume_ws_opt;
$self->_eat(qr/=/);
$self->__consume_ws_opt;
$self->_String;
my ($sep) = splice(@{ $self->{stack} });
$options{ seperator } = $sep;
}
}
}
$self->__consume_ws_opt;
my $arg = join(',', map { blessed($_) ? $_->as_sparql : $_ } @expr);
if ($distinct) {
$arg = 'DISTINCT ' . $arg;
}
my $name = sprintf('%s(%s)', $op, $arg);
$self->_eat(')');
$self->{build}{__aggregate}{ $name } = [ (($distinct) ? "${op}-DISTINCT" : $op), \%options, @expr ];
my @vars = grep { blessed($_) and $_->isa('RDF::Query::Node::Variable') } @expr;
$self->_add_stack( RDF::Query::Node::Variable::ExpressionProxy->new($name, @vars) );
}
# [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
sub _BuiltInCall_test {
my $self = shift;
if ($self->{__aggregate_call_ok}) {
return 1 if ($self->_test( $r_AGGREGATE_CALL ));
}
return 1 if $self->_test(qr/((NOT\s+)?EXISTS)|COALESCE/i);
return 1 if $self->_test(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);
return $self->_test(qr/UUID|STRUUID|STR|STRDT|STRLANG|STRBEFORE|STRAFTER|REPLACE|BNODE|IRI|URI|LANG|LANGMATCHES|DATATYPE|BOUND|sameTerm|isIRI|isURI|isBLANK|isLITERAL|REGEX|IF|isNumeric/i);
}
sub _BuiltInCall {
my $self = shift;
if ($self->{__aggregate_call_ok} and $self->_test( $r_AGGREGATE_CALL )) {
$self->_Aggregate;
} elsif ($self->_test(qr/(NOT\s+)?EXISTS/i)) {
my $op = $self->_eat(qr/(NOT\s+)?EXISTS/i);
$self->__consume_ws_opt;
local($self->{filters}) = [];
$self->_GroupGraphPattern;
my $cont = $self->_remove_pattern;
my $iri = RDF::Query::Node::Resource->new( 'sparql:exists' );
my $func = $self->new_function_expression($iri, $cont);
if ($op =~ /^NOT/i) {
$self->_add_stack( $self->new_unary_expression( '!', $func ) );
} else {
$self->_add_stack( $func );
}
} elsif ($self->_test(qr/COALESCE|BNODE|CONCAT|SUBSTR|RAND|NOW/i)) {
# n-arg functions that take expressions
my $op = $self->_eat(qr/COALESCE|BNODE|CONCAT|SUBSTR|RAND|NOW/i);
my $iri = RDF::Query::Node::Resource->new( 'sparql:' . lc($op) );
$self->_ArgList;
my @args = splice(@{ $self->{stack} });
my $func = $self->new_function_expression( $iri, @args );
$self->_add_stack( $func );
} elsif ($self->_RegexExpression_test) {
$self->_RegexExpression;
} else {
my $op = $self->_eat( qr/\w+/ );
my $iri = RDF::Query::Node::Resource->new( 'sparql:' . lc($op) );
$self->__consume_ws_opt;
$self->_eat('(');
$self->__consume_ws_opt;
if ($op =~ /^(STR)?UUID$/i) {
# no-arg functions
$self->_add_stack( $self->new_function_expression($iri) );
} 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)$/i) {
### one-arg functions that take an expression
$self->_Expression;
my ($expr) = splice(@{ $self->{stack} });
$self->_add_stack( $self->new_function_expression($iri, $expr) );
} elsif ($op =~ /^(STRDT|STRLANG|LANGMATCHES|sameTerm|CONTAINS|STRSTARTS|STRENDS|STRBEFORE|STRAFTER)$/i) {
### two-arg functions that take expressions
$self->_Expression;
my ($arg1) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_eat(',');
$self->__consume_ws_opt;
$self->_Expression;
my ($arg2) = splice(@{ $self->{stack} });
$self->_add_stack( $self->new_function_expression($iri, $arg1, $arg2) );
} elsif ($op =~ /^(IF|REPLACE)$/i) {
### three-arg functions that take expressions
$self->_Expression;
my ($arg1) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_eat(',');
$self->__consume_ws_opt;
$self->_Expression;
my ($arg2) = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_eat(',');
$self->__consume_ws_opt;
$self->_Expression;
my ($arg3) = splice(@{ $self->{stack} });
$self->_add_stack( $self->new_function_expression($iri, $arg1, $arg2, $arg3) );
} else {
### BOUND(Var)
$self->_Var;
my ($expr) = splice(@{ $self->{stack} });
$self->_add_stack( $self->new_function_expression($iri, $expr) );
}
$self->__consume_ws_opt;
$self->_eat(')');
}
}
# [58] RegexExpression ::= 'REGEX' '(' Expression ',' Expression ( ',' Expression )? ')'
sub _RegexExpression_test {
my $self = shift;
return $self->_test( qr/REGEX/i );
}
sub _RegexExpression {
my $self = shift;
$self->_eat( qr/REGEX/i );
$self->__consume_ws_opt;
$self->_eat('(');
$self->__consume_ws_opt;
$self->_Expression;
my $string = splice(@{ $self->{stack} });
$self->__consume_ws_opt;
$self->_eat(',');
$self->__consume_ws_opt;
$self->_Expression;
my $pattern = splice(@{ $self->{stack} });
my @args = ($string, $pattern);
if ($self->_test(',')) {
$self->_eat(',');
$self->__consume_ws_opt;
$self->_Expression;
push(@args, splice(@{ $self->{stack} }));
}
$self->__consume_ws_opt;
$self->_eat(')');
my $iri = RDF::Query::Node::Resource->new( 'sparql:regex' );
$self->_add_stack( $self->new_function_expression( $iri, @args ) );
}
# [59] IRIrefOrFunction ::= IRIref ArgList?
sub _IRIrefOrFunction_test {
my $self = shift;
$self->_IRIref_test;
}
sub _IRIrefOrFunction {
my $self = shift;
$self->_IRIref;
if ($self->_ArgList_test) {
my ($iri) = splice(@{ $self->{stack} });
$self->_ArgList;
my @args = splice(@{ $self->{stack} });
my $func = $self->new_function_expression( $iri, @args );
$self->_add_stack( $func );
}
}
# [60] RDFLiteral ::= String ( LANGTAG | ( '^^' IRIref ) )?
sub _RDFLiteral {
my $self = shift;
$self->_String;
my @args = splice(@{ $self->{stack} });
if ($self->_test('@')) {
my $lang = $self->_eat( $r_LANGTAG );
substr($lang,0,1) = ''; # remove '@'
push(@args, lc($lang));
} elsif ($self->_test('^^')) {
$self->_eat('^^');
push(@args, undef);
$self->_IRIref;
my ($iri) = splice(@{ $self->{stack} });
push(@args, $iri->uri_value);
}
my $obj = RDF::Query::Node::Literal->new( @args );
if ($self->{args}{canonicalize} and blessed($obj) and $obj->isa('RDF::Trine::Node::Literal')) {
$obj = $obj->canonicalize;
}
$self->_add_stack( $obj );
}
# [61] NumericLiteral ::= NumericLiteralUnsigned | NumericLiteralPositive | NumericLiteralNegative
# [62] NumericLiteralUnsigned ::= INTEGER | DECIMAL | DOUBLE
# [63] NumericLiteralPositive ::= INTEGER_POSITIVE | DECIMAL_POSITIVE | DOUBLE_POSITIVE
# [64] NumericLiteralNegative ::= INTEGER_NEGATIVE | DECIMAL_NEGATIVE | DOUBLE_NEGATIVE
sub _NumericLiteral {
my $self = shift;
my $sign = 0;
if ($self->_test('+')) {
$self->_eat('+');
$sign = '+';
} elsif ($self->_test('-')) {
$self->_eat('-');
$sign = '-';
}
my $value;
my $type;
if ($self->_test( $r_DOUBLE )) {
$value = $self->_eat( $r_DOUBLE );
my $double = RDF::Query::Node::Resource->new( $xsd->double->uri_value );
$type = $double
} elsif ($self->_test( $r_DECIMAL )) {
$value = $self->_eat( $r_DECIMAL );
my $decimal = RDF::Query::Node::Resource->new( $xsd->decimal->uri_value );
$type = $decimal;
} else {
$value = $self->_eat( $r_INTEGER );
my $integer = RDF::Query::Node::Resource->new( $xsd->integer->uri_value );
$type = $integer;
}
if ($sign) {
$value = $sign . $value;
}
my $obj = RDF::Query::Node::Literal->new( $value, undef, $type->uri_value );
if ($self->{args}{canonicalize} and blessed($obj) and $obj->isa('RDF::Trine::Node::Literal')) {
$obj = $obj->canonicalize;
}
$self->_add_stack( $obj );
}
# [65] BooleanLiteral ::= 'true' | 'false'
sub _BooleanLiteral {
my $self = shift;
my $bool = $self->_eat(qr/(true|false)\b/);
my $obj = RDF::Query::Node::Literal->new( $bool, undef, $xsd->boolean->uri_value );
if ($self->{args}{canonicalize} and blessed($obj) and $obj->isa('RDF::Trine::Node::Literal')) {
$obj = $obj->canonicalize;
}
$self->_add_stack( $obj );
}
# [66] String ::= STRING_LITERAL1 | STRING_LITERAL2 | STRING_LITERAL_LONG1 | STRING_LITERAL_LONG2
sub _String {
my $self = shift;
my $value;
if ($self->_test( $r_STRING_LITERAL_LONG1 )) {
my $string = $self->_eat( $r_STRING_LITERAL_LONG1 );
$value = substr($string, 3, length($string) - 6);
} elsif ($self->_test( $r_STRING_LITERAL_LONG2 )) {
my $string = $self->_eat( $r_STRING_LITERAL_LONG2 );
$value = substr($string, 3, length($string) - 6);
} elsif ($self->_test( $r_STRING_LITERAL1 )) {
my $string = $self->_eat( $r_STRING_LITERAL1 );
$value = substr($string, 1, length($string) - 2);
} else { # ($self->_test( $r_STRING_LITERAL2 )) {
my $string = $self->_eat( $r_STRING_LITERAL2 );
$value = substr($string, 1, length($string) - 2);
}
# $value =~ s/(${r_ECHAR})/"$1"/ge;
$value =~ s/\\t/\t/g;
$value =~ s/\\b/\n/g;
$value =~ s/\\n/\n/g;
$value =~ s/\\r/\x08/g;
$value =~ s/\\"/"/g;
$value =~ s/\\'/'/g;
$value =~ s/\\\\/\\/g; # backslash must come last, so it doesn't accidentally create a new escape
$self->_add_stack( $value );
}
# [67] IRIref ::= IRI_REF | PrefixedName
sub _IRIref_test {
my $self = shift;
return $self->_test(qr/<|${r_PNAME_LN}|${r_PNAME_NS}/);
}
sub _IRIref {
my $self = shift;
if ($self->_test( $r_IRI_REF )) {
my $iri = $self->_eat( $r_IRI_REF );
my $node = RDF::Query::Node::Resource->new( substr($iri,1,length($iri)-2), $self->__base );
$self->_add_stack( $node );
} else {
$self->_PrefixedName;
}
}
# [68] PrefixedName ::= PNAME_LN | PNAME_NS
sub _PrefixedName {
my $self = shift;
if ($self->_test( $r_PNAME_LN )) {
my $ln = $self->_eat( $r_PNAME_LN );
my ($ns,$local) = split(/:/, $ln, 2);
if ($ns eq '') {
$ns = '__DEFAULT__';
}
$local =~ s{\\([-~.!&'()*+,;=:/?#@%_\$])}{$1}g;
unless (exists $self->{namespaces}{$ns}) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Use of undefined namespace '$ns'";
}
my $iri = $self->{namespaces}{$ns} . $local;
$self->_add_stack( RDF::Query::Node::Resource->new( $iri, $self->__base ) );
} else {
my $ns = $self->_eat( $r_PNAME_NS );
if ($ns eq ':') {
$ns = '__DEFAULT__';
} else {
chop($ns);
}
unless (exists $self->{namespaces}{$ns}) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Use of undefined namespace '$ns'";
}
my $iri = $self->{namespaces}{$ns};
$self->_add_stack( RDF::Query::Node::Resource->new( $iri, $self->__base ) );
}
}
# [69] BlankNode ::= BLANK_NODE_LABEL | ANON
sub _BlankNode {
my $self = shift;
if (my $where = $self->{__no_bnodes}) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Blank nodes not allowed in $where";
}
if ($self->_test( $r_BLANK_NODE_LABEL )) {
my $label = $self->_eat( $r_BLANK_NODE_LABEL );
my $id = substr($label,2);
$self->_add_stack( $self->new_blank($id) );
} else {
$self->_eat( $r_ANON );
$self->_add_stack( $self->new_blank );
}
}
sub _NIL {
my $self = shift;
$self->_eat( $r_NIL );
my $nil = RDF::Query::Node::Resource->new( $rdf->nil->uri_value );
$self->_add_stack( $nil );
}
sub __solution_modifiers {
my $self = shift;
my $star = shift;
my $having_expr;
my $aggdata = delete( $self->{build}{__aggregate} );
my @aggkeys = keys %{ $aggdata || {} };
if (scalar(@aggkeys)) {
my $groupby = delete( $self->{build}{__group_by} ) || [];
my $pattern = $self->{build}{triples};
my $ggp = shift(@$pattern);
if (my $having = delete( $self->{build}{__having} )) {
$having_expr = $having;
}
my $agg = RDF::Query::Algebra::Aggregate->new( $ggp, $groupby, { expressions => [%$aggdata] } );
push(@{ $self->{build}{triples} }, $agg);
}
my $vars = [ @{ $self->{build}{variables} } ];
{
my @vars = grep { $_->isa('RDF::Query::Expression::Alias') } @$vars;
if (scalar(@vars)) {
my $pattern = pop(@{ $self->{build}{triples} });
my @bound = $pattern->potentially_bound;
my %bound = map { $_ => 1 } @bound;
foreach my $v (@vars) {
my $name = $v->name;
if ($bound{ $name }) {
throw RDF::Query::Error::ParseError -text => "Syntax error: Already-bound variable ($name) used in project expression";
}
}
my $proj = RDF::Query::Algebra::Extend->new( $pattern, $vars );
push(@{ $self->{build}{triples} }, $proj);
}
}
if ($having_expr) {
my $pattern = pop(@{ $self->{build}{triples} });
my $filter = RDF::Query::Algebra::Filter->new( $having_expr, $pattern );
push(@{ $self->{build}{triples} }, $filter);
}
if ($self->{build}{options}{orderby}) {
my $order = delete $self->{build}{options}{orderby};
my $pattern = pop(@{ $self->{build}{triples} });
my $sort = RDF::Query::Algebra::Sort->new( $pattern, @$order );
push(@{ $self->{build}{triples} }, $sort);
}
{
my $pattern = pop(@{ $self->{build}{triples} });
my $proj = RDF::Query::Algebra::Project->new( $pattern, $vars );
push(@{ $self->{build}{triples} }, $proj);
}
if ($self->{build}{options}{distinct}) {
delete $self->{build}{options}{distinct};
my $pattern = pop(@{ $self->{build}{triples} });
my $sort = RDF::Query::Algebra::Distinct->new( $pattern );
push(@{ $self->{build}{triples} }, $sort);
}
if (exists $self->{build}{options}{offset}) {
my $offset = delete $self->{build}{options}{offset};
my $pattern = pop(@{ $self->{build}{triples} });
my $offseted = RDF::Query::Algebra::Offset->new( $pattern, $offset );
push(@{ $self->{build}{triples} }, $offseted);
}
if (exists $self->{build}{options}{limit}) {
my $limit = delete $self->{build}{options}{limit};
my $pattern = pop(@{ $self->{build}{triples} });
my $limited = RDF::Query::Algebra::Limit->new( $pattern, $limit );
push(@{ $self->{build}{triples} }, $limited);
}
}
################################################################################
=item C<< error >>
Returns the error encountered during the last parse.
=cut
sub error {
my $self = shift;
return $self->{error};
}
sub _add_patterns {
my $self = shift;
my @triples = @_;
my $container = $self->{ pattern_container_stack }[0];
push( @{ $container }, @triples );
}
sub _remove_pattern {
my $self = shift;
my $container = $self->{ pattern_container_stack }[0];
my $pattern = pop( @{ $container } );
return $pattern;
}
sub _peek_pattern {
my $self = shift;
my $container = $self->{ pattern_container_stack }[0];
my $pattern = $container->[-1];
return $pattern;
}
sub _push_pattern_container {
my $self = shift;
my $cont = [];
unshift( @{ $self->{ pattern_container_stack } }, $cont );
return $cont;
}
sub _pop_pattern_container {
my $self = shift;
my $cont = shift( @{ $self->{ pattern_container_stack } } );
return $cont;
}
sub _add_stack {
my $self = shift;
my @items = @_;
push( @{ $self->{stack} }, @items );
}
sub _add_filter {
my $self = shift;
my @filters = shift;
push( @{ $self->{filters} }, @filters );
}
sub _eat {
my $self = shift;
my $thing = shift;
if (not(length($self->{tokens}))) {
$self->_syntax_error("No tokens left");
}
# if (substr($self->{tokens}, 0, 1) eq '^') {
# Carp::cluck( "eating $thing with input $self->{tokens}" );
# }
if (ref($thing) and $thing->isa('Regexp')) {
if ($self->{tokens} =~ /^($thing)/) {
my $match = $1;
substr($self->{tokens}, 0, length($match)) = '';
return $match;
}
$self->_syntax_error( "Expected $thing" );
} elsif (looks_like_number( $thing )) {
my ($token) = substr( $self->{tokens}, 0, $thing, '' );
return $token
} else {
### thing is a string
if (substr($self->{tokens}, 0, length($thing)) eq $thing) {
substr($self->{tokens}, 0, length($thing)) = '';
return $thing;
} else {
$self->_syntax_error( "Expected $thing" );
}
}
print $thing;
throw RDF::Query::Error;
}
sub _syntax_error {
my $self = shift;
my $thing = shift;
my $expect = $thing;
my $level = 2;
while (my $sub = (caller($level++))[3]) {
if ($sub =~ m/::_([A-Z]\w*)$/) {
$expect = $1;
last;
}
}
my $l = Log::Log4perl->get_logger("rdf.query.parser.sparql");
if ($l->is_debug) {
$l->logcluck("Syntax error eating $thing with input <<$self->{tokens}>>");
}
my $near = "'" . substr($self->{tokens}, 0, 20) . "...'";
$near =~ s/[\r\n ]+/ /g;
if ($thing) {
# Carp::cluck Dumper($self->{tokens}); # XXX
throw RDF::Query::Error::ParseError -text => "Syntax error: $thing in $expect near $near";
} else {
throw RDF::Query::Error::ParseError -text => "Syntax error: Expected $expect near $near";
}
}
sub _test {
my $self = shift;
my $thing = shift;
if (blessed($thing) and $thing->isa('Regexp')) {
if ($self->{tokens} =~ m/^$thing/) {
return 1;
} else {
return 0;
}
} else {
if (substr($self->{tokens}, 0, length($thing)) eq $thing) {
return 1;
} else {
return 0;
}
}
}
sub _ws_test {
my $self = shift;
unless (length($self->{tokens})) {
return 0;
}
if ($self->{tokens} =~ m/^[\t\r\n #]/) {
return 1;
} else {
return 0;
}
}
sub _ws {
my $self = shift;
### #x9 | #xA | #xD | #x20 | comment
if ($self->_test('#')) {
$self->_eat(qr/#[^\x0d\x0a]*.?/);
} else {
$self->_eat(qr/[\n\r\t ]/);
}
}
sub __consume_ws_opt {
my $self = shift;
if ($self->_ws_test) {
$self->__consume_ws;
}
}
sub __consume_ws {
my $self = shift;
$self->_ws;
while ($self->_ws_test()) {
$self->_ws()
}
}
sub __base {
my $self = shift;
my $build = $self->{build};
if (defined($build->{base})) {
return $build->{base};
} else {
return;
}
}
sub __new_statement {
my $self = shift;
my @nodes = @_;
if ($self->{_modify_template} and my $graph = $self->{named_graph} and $self->{named_graph}->isa('RDF::Trine::Node::Resource')) {
return RDF::Query::Algebra::Quad->new( @nodes, $graph );
} else {
return RDF::Query::Algebra::Triple->_new( @nodes );
}
}
sub __new_path {
my $self = shift;
my $start = shift;
my $pdata = shift;
my $end = shift;
(undef, my $op, my @nodes) = @$pdata;
@nodes = map { $self->__strip_path( $_ ) } @nodes;
# if (my $graph = $self->{named_graph} and $self->{named_graph}->isa('RDF::Trine::Node::Resource')) {
# return RDF::Query::Algebra::Path->new( $start, [$op, @nodes], $end, $graph );
# } else {
return RDF::Query::Algebra::Path->new( $start, [$op, @nodes], $end );
# }
}
sub __strip_path {
my $self = shift;
my $path = shift;
if (blessed($path)) {
return $path;
} elsif (reftype($path) eq 'ARRAY' and $path->[0] eq 'PATH') {
(undef, my $op, my @nodes) = @$path;
return [$op, map { $self->__strip_path($_) } @nodes];
} else {
return $path;
}
}
sub __new_bgp {
# fix up BGPs that might actually have property paths in them. split those
# out as their own path algebra objects, and join them with the bgp with a
# ggp if necessary
my $self = shift;
my @patterns = @_;
my @paths = grep { reftype($_->predicate) eq 'ARRAY' and $_->predicate->[0] eq 'PATH' } @patterns;
my @triples = grep { blessed($_->predicate) } @patterns;
if (scalar(@patterns) > scalar(@paths) + scalar(@triples)) {
Carp::cluck "more than just triples and paths passed to __new_bgp: " . Dumper(\@patterns);
}
my $bgp = RDF::Query::Algebra::BasicGraphPattern->new( @triples );
if (@paths) {
my @p;
foreach my $p (@paths) {
my $start = $p->subject;
my $end = $p->object;
my $pdata = $p->predicate;
push(@p, $self->__new_path( $start, $pdata, $end ));
}
my $pgroup = (scalar(@p) == 1)
? $p[0]
: RDF::Query::Algebra::GroupGraphPattern->new( @p );
if (scalar(@triples)) {
return RDF::Query::Algebra::GroupGraphPattern->new( $bgp, $pgroup );
} else {
return $pgroup;
}
} else {
return $bgp;
}
}
1;
__END__
=back
=cut