Toby Inkster avatar Toby Inkster committed b17ba61

some advanced pattern-matching stuff.

this might be better moved into RDF::TriN3 somewhere.

Comments (0)

Files changed (7)

 use RDF::TrineShortcuts;
 use RDF::Closure;
 
-my $g = RDF::Closure::Model->new(undef, engine=>'notation3logic');
+%RDF::Closure::Engine::Notation3Logic::functions = %RDF::Query::functions;
+
+my $g = RDF::Closure::Model->new(RDF::Trine::Model->temporary_model, engine=>'notation3logic');
 
 rdf_parse(<<'NOTATION3', type=>'n3', model=>$g);
 
 @prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .
 @prefix ex:   <http://example.com/> .
 
+{ "Alice" <sparql:lcase> ?lower . } => { ?lower a ex:LowerCaseString . } .
+
 { ?p a ex:Person . } => { ?p a ex:Agent . } .
 
 ex:ALICE a ex:Person .
+use lib "lib";
+use lib "../RDF-Closure/lib";
+use Data::Dumper;
+use RDF::TrineShortcuts;
+use RDF::Trine qw[statement iri variable literal];
+use RDF::Closure::Engine::Notation3Logic::DummyQuery;
+use RDF::Closure::Engine::Notation3Logic::Model;
+use RDF::Closure::Engine::Notation3Logic::Pattern;
+use RDF::Trine::Namespace qw[rdf];
+my $ex = RDF::Trine::Namespace->new('http://example.com/');
+
+%RDF::Closure::Engine::Notation3Logic::functions = (
+	'http://example.com/lcase' => sub {
+		my ($dummy, $s, $o) = @_;
+		return undef unless $s->is_literal;
+		return undef unless !defined $o || $o->is_literal;
+		
+		if ($o)
+		{
+			return ((lc $s->literal_value) eq $o->literal_value);
+		}
+		
+		return RDF::Trine::Node::Literal->new(
+			lc $s->literal_value,
+			$s->literal_value_language,
+			$s->literal_datatype,
+			);
+	},
+	'http://example.com/ucase' => sub {
+		my ($dummy, $s, $o) = @_;
+		return undef unless $s->is_literal;
+		return undef unless !defined $o || $o->is_literal;
+		
+		if ($o)
+		{
+			return ((uc $s->literal_value) eq $o->literal_value);
+		}
+		
+		return RDF::Trine::Node::Literal->new(
+			uc $s->literal_value,
+			$s->literal_value_language,
+			$s->literal_datatype,
+			);
+	},
+);
+
+my $model = rdf_parse(<<'NOTATION3', type=>'n3', model=>RDF::Closure::Engine::Notation3Logic::Model->new);
+
+@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix owl:  <http://www.w3.org/2002/07/owl#> .
+@prefix xsd:  <http://www.w3.org/2001/XMLSchema#> .
+@prefix ex:   <http://example.com/> .
+
+ex:ALICE a ex:Person ;
+	ex:name "Alice" .
+
+ex:BOB a ex:Person ;
+	ex:name "Bob" .
+
+NOTATION3
+
+
+my $pattern = RDF::Closure::Engine::Notation3Logic::Pattern->new(
+	statement(variable('p'), $rdf->type, $ex->Person),
+	statement(variable('p'), $ex->name, variable('name')),
+	statement(variable('name'), $ex->lcase, variable('lowername')),
+	statement(literal('Alice'), $ex->lcase, variable('lowername')),
+	);
+
+my $i = $model->get_pattern($pattern);
+while (my $r = $i->next)
+{
+	print Dumper($r);
+}

lib/RDF/Closure/Engine/Notation3Logic.pm

 	;
 
 our $VERSION = '0.000_02';
+our %functions;
+
+sub new
+{
+	my ($class, $model, @rest) = @_;
+	$model = bless $model, 'RDF::Closure::Engine::Notation3Logic::Model';
+	$class->SUPER::new($model, @rest);
+}
 
 sub add_axioms
 {

lib/RDF/Closure/Engine/Notation3Logic/DummyQuery.pm

+package RDF::Closure::Engine::Notation3Logic::DummyQuery;
+
+our $AUTOLOAD;
+
+sub new
+{
+	my ($class, %args) = @_;
+	$args{useragent}  ||= LWP::UserAgent->new;
+	return bless \%args, $class;
+}
+
+sub AUTOLOAD
+{
+	my ($self) = @_;
+	if ($AUTOLOAD =~ /::([^:]+)$/)
+	{
+		return $self->{$1} if exists $self->{$1};
+	}
+	return;
+}
+
+1;

lib/RDF/Closure/Engine/Notation3Logic/Model.pm

+package RDF::Closure::Engine::Notation3Logic::Model;
+
+use common::sense;
+
+use base qw[RDF::Trine::Model];
+
+sub get_pattern
+{
+	my ($self, $pattern, @rest) = @_;
+
+	return $pattern->smart_match($self, @rest)
+		if $pattern->can('smart_match');
+	
+	return $self->SUPER::get_pattern($pattern, @rest);
+}
+
+1;
+

lib/RDF/Closure/Engine/Notation3Logic/Module/log.pm

 				
 				return if $t->forSome || $t->forAll || $p->forSome || $p->forAll; # not supported
 				
-				RDF::Closure::Rule::PatternMatcher
-					->new($p->pattern, $t->pattern)
-					->apply_to_closure($cl);
+				my $P = RDF::Closure::Engine::Notation3Logic::Pattern
+					->new($p->pattern->triples);
+				my $T = $t->pattern;
+				use Data::Dumper;
+				print "log.pm: ".Dumper($P);
+				
+				RDF::Closure::Rule::PatternMatcher->new($P, $T)->apply_to_closure($cl);
 				},
 			'log:implies',
 			),

lib/RDF/Closure/Engine/Notation3Logic/Pattern.pm

+package RDF::Closure::Engine::Notation3Logic::Pattern;
+
+use common::sense;
+
+use base qw[RDF::Trine::Pattern];
+
+use Scalar::Util qw[blessed];
+
+sub new
+{
+	my $class       = shift;
+	my @triples     = @_;
+	foreach my $t (@triples)
+	{
+		unless (blessed($t) and $t->isa('RDF::Trine::Statement'))
+		{
+			throw RDF::Trine::Error -text => "Patterns belonging to a BGP must be triples";
+		}
+	}
+	return bless([@triples], $class);
+}
+
+
+sub smart_match
+{
+	my ($self, $model, $context, %args) = @_;
+	
+	# Prepare set of functions...
+	my %functions = %RDF::Closure::Engine::Notation3Logic::functions;
+	if ($args{'functions'})
+	{
+		my %extras = %{ $args{'functions'} };
+		while (my ($uri, $code) = each %extras)
+		{
+			next unless ref($code) eq 'CODE';
+			$functions{$uri} = $code;
+		}
+	}
+	
+	my $dummy  = RDF::Closure::Engine::Notation3Logic::DummyQuery->new(model => $model);
+	my $smart  = [ $self->triples ];
+	my $basic  = [];
+	my $nonvar = [];
+	my $const  = [];
+	my $bound  = {};
+	my $conflict  = 0;
+	my $loopcount = 0;
+	my $grace = 0;
+	
+	use Data::Dumper;
+	my $process = sub
+	{
+		$loopcount++;
+		
+		my $done_something = 0;
+		
+		# Do what we can to move things from $smart to $const/$basic.
+		my @tmp = ();
+		ST: foreach my $st (@$smart)
+		{
+			$st = $st->bind_variables($bound);
+			my ($s, $p, $o) = $st->nodes;
+			
+			if ($p->is_resource and ref($functions{$p->uri}) eq 'CODE'
+			and !$st->referenced_variables)
+			{
+				my $f = $functions{$p->uri};
+				my $r = $f->($dummy, $s, $o);
+				if ($r)
+				{
+					push @$const, $st;
+				}
+				else
+				{
+					$conflict++;
+					last LOOP;
+				}				
+			}
+			elsif (!$st->referenced_variables)
+			{
+				if ($loopcount)
+				{
+					push @$const, $st;
+				}
+				else
+				{
+					push @$nonvar, $st;
+				}
+				$done_something++;
+			}
+			elsif ($p->is_resource and ref($functions{$p->uri}) eq 'CODE'
+			and !$s->is_variable and $o->is_variable)
+			{
+				my $f = $functions{$p->uri};
+				my $calc_o = $f->($dummy, $s);
+				if (defined $calc_o)
+				{
+					$bound->{ $o->name } = $calc_o;
+					push @tmp, RDF::Trine::Statement->new($s, $p, $calc_o);
+					$done_something++;
+				}
+			}
+			elsif ($p->is_resource and ref($functions{'inverse+'.$p->uri}) eq 'CODE'
+			and $s->is_variable and !$o->is_variable)
+			{
+				my $f = $functions{'inverse+'.$p->uri};
+				my $calc_s = $f->($dummy, $o);
+				if (defined $calc_s)
+				{
+					$bound->{ $s->name } = $calc_s;
+					push @tmp, RDF::Trine::Statement->new($calc_s, $p, $o);
+					$done_something++;
+				}
+			}
+			elsif ($p->is_resource and ref($functions{$p->uri}) ne 'CODE')
+			{
+				push @$basic, $st;
+				$done_something++;
+			}
+			else
+			{
+				push @tmp, $st;
+			}
+		}
+		# This is what's left of $smart.
+		$smart = [@tmp];
+		
+		# Do what we can to move things from $basic to $const.
+		@tmp = ();
+		ST: foreach my $st (@$basic)
+		{
+			$st = $st->bind_variables($bound);
+			my ($s, $p, $o) = $st->nodes;
+			
+			if ($p->is_resource and ref($functions{$p->uri}) eq 'CODE')
+			{
+				push @$smart, $st; # reluctantly push back to $smart
+				$done_something++;
+			}
+			elsif (!$st->referenced_variables)
+			{
+				push @$const, $st;
+				$done_something++;
+			}
+			else
+			{
+				push @tmp, $st;
+			}
+		}
+		# This is what's left of $basic.
+		$basic = [@tmp];
+		
+		return $done_something;
+	};
+	
+	# This is quite a crazy-looking loop structure. Just to explain what's
+	# going on here... we loop $process until we detect that no triples were
+	# moved between the different pots. After that, we allow it one more
+	# run of $process, and if that last "grace" run moves some triples, begin
+	# looping in earnest again.
+	my ($continue, $grace) = (1, 1);
+	while ($grace)
+	{
+		$continue = 1;
+		while ($continue)
+		{
+			$continue = $process->();
+		}
+		$grace = $process->();
+	}
+		
+	if (@$basic)
+	{
+		my $basic_pattern  = RDF::Trine::Pattern->new(@$basic, @$nonvar);
+		my $basic_iterator = $model->get_pattern($basic_pattern);
+		return RDF::Trine::Iterator::Bindings->new(sub {
+			my $this_iter    = shift;
+			my $basic_result = $basic_iterator->next;
+			return unless defined $basic_result;
+			
+			# TODO: check bindings in $basic_result against remaining functions in $smart.
+			# skip to next basic result (i.e. return $this_iter->next) otherwise.
+			
+			my %extended_result = %$basic_result;
+			while (my ($k, $v) = each %$bound)
+			{
+				$extended_result{$k} = $v;
+			}
+			return \%extended_result;
+		});
+	}
+}
+1;
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.