From eeb7e2ca9463ea67889362aa6573aae4ddf9c272 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 14 Oct 2018 15:38:06 -0700 Subject: [PATCH 01/40] Run 2.2.2 on Travis. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 80f381d..731ec8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ before_install: "gem update --system" env: - CI=true rvm: - - 2.2 + - 2.2.2 - 2.3 - 2.4 - 2.5 From 8fbbffab2bf21ce24121397a3c059f2db49c4dc2 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 20 Dec 2018 16:49:30 -0800 Subject: [PATCH 02/40] Update travis config to deal with rubygems not supporting ruby < 2.3 any longer. See https://github.com/rubygems/rubygems/issues/2534. --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 731ec8f..657afad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ language: ruby bundler_args: --without debug script: "bundle exec rspec spec" -before_install: "gem update --system" - +before_install: + - 'gem update --system --conservative || (gem i "rubygems-update:~>2.7" --no-document && update_rubygems)' + - 'gem update bundler --conservative' env: - CI=true rvm: From 9f4ff9b245da2bb43e0edcfab499084f6e1a9200 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 24 Dec 2018 12:05:42 -0800 Subject: [PATCH 03/40] Add 2.6 to Travis RVM matrix. Use WebMock from GitHub until 2.6 support released. --- .travis.yml | 1 + Gemfile | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 657afad..3a1eb50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ rvm: - 2.3 - 2.4 - 2.5 + - 2.6 - jruby-9 - rbx-3 cache: bundler diff --git a/Gemfile b/Gemfile index 6e29b8e..3de047a 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,9 @@ group :development do gem "rdf-isomorphic", github: "ruby-rdf/rdf-isomorphic", branch: "develop" gem "rdf-xsd", github: "ruby-rdf/rdf-xsd", branch: "develop" gem "json-ld", github: "ruby-rdf/json-ld", branch: "develop" + + # Until version >= 3.4.2 with support for Ruby 2.6 + gem "webmock", git: "git@github.com:bblimke/webmock.git" end group :debug do From 7c73dc2fe9507591da48d31c97fbef39a6974b1b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 24 Dec 2018 12:11:56 -0800 Subject: [PATCH 04/40] Change Github pull of webmock --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 3de047a..f843a62 100644 --- a/Gemfile +++ b/Gemfile @@ -11,7 +11,7 @@ group :development do gem "json-ld", github: "ruby-rdf/json-ld", branch: "develop" # Until version >= 3.4.2 with support for Ruby 2.6 - gem "webmock", git: "git@github.com:bblimke/webmock.git" + gem "webmock", github: "bblimke/webmock" end group :debug do From 210f164d941f09bbcffac90d492cb9a562a7ba25 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 28 Dec 2018 17:05:20 -0800 Subject: [PATCH 05/40] Update swap_spec and suite_helper. Test more ParserTest manifests. --- Gemfile | 1 + rdf-n3.gemspec | 9 +++-- spec/format_spec.rb | 3 +- spec/reader_spec.rb | 3 +- spec/spec_helper.rb | 6 ---- spec/suite_helper.rb | 81 ++++++++++++++++++++++++++++++++++++++++---- spec/swap | 1 + spec/swap_spec.rb | 63 ++++++++++++++++++---------------- spec/writer_spec.rb | 3 +- 9 files changed, 117 insertions(+), 53 deletions(-) create mode 120000 spec/swap diff --git a/Gemfile b/Gemfile index f843a62..85bdc43 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gem "rdf", github: "ruby-rdf/rdf", branch: "develop" group :development do gem "rdf-spec", github: "ruby-rdf/rdf-spec", branch: "develop" gem "rdf-isomorphic", github: "ruby-rdf/rdf-isomorphic", branch: "develop" + gem "rdf-trig", github: "ruby-rdf/rdf-trig", branch: "develop" gem "rdf-xsd", github: "ruby-rdf/rdf-xsd", branch: "develop" gem "json-ld", github: "ruby-rdf/json-ld", branch: "develop" diff --git a/rdf-n3.gemspec b/rdf-n3.gemspec index 3fb657e..f8fe8ed 100755 --- a/rdf-n3.gemspec +++ b/rdf-n3.gemspec @@ -22,14 +22,13 @@ Gem::Specification.new do |gem| gem.requirements = [] gem.add_dependency 'rdf', '~> 3.0' - gem.add_development_dependency 'open-uri-cached', '~> 0.0', '>= 0.0.5' - #gem.add_development_dependency 'json-ld', '~> 3.0' - gem.add_development_dependency 'json-ld', '>= 2.2', '< 4.0' - gem.add_development_dependency 'rspec', '~> 3.7' + gem.add_development_dependency 'json-ld', '~> 3.0' + gem.add_development_dependency 'rspec', '~> 3.8' gem.add_development_dependency 'rspec-its', '~> 1.2' gem.add_development_dependency 'rdf-spec', '~> 3.0' gem.add_development_dependency 'rdf-isomorphic', '~> 3.0' - gem.add_development_dependency 'yard' , '~> 0.9.12' + gem.add_development_dependency 'rdf-trig', '~> 3.0' + gem.add_development_dependency 'yard' , '~> 0.9.16' gem.post_install_message = nil end diff --git a/spec/format_spec.rb b/spec/format_spec.rb index a142891..e008b9d 100644 --- a/spec/format_spec.rb +++ b/spec/format_spec.rb @@ -1,5 +1,4 @@ -$:.unshift "." -require File.join(File.dirname(__FILE__), 'spec_helper') +require_relative 'spec_helper' require 'rdf/spec/format' describe RDF::N3::Format do diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 2c3d3dc..ff1269b 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -1,6 +1,5 @@ # coding: utf-8 -$:.unshift "." -require File.join(File.dirname(__FILE__), 'spec_helper') +require_relative 'spec_helper' require 'rdf/spec/reader' describe "RDF::N3::Reader" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fb11ee6..dc0974e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,12 +11,6 @@ require 'matchers' require 'rdf/isomorphic' require 'yaml' # XXX should be in open-uri/cached -require 'open-uri/cached' - -# Create and maintain a cache of downloaded URIs -URI_CACHE = File.expand_path(File.join(File.dirname(__FILE__), "uri-cache")) -Dir.mkdir(URI_CACHE) unless File.directory?(URI_CACHE) -OpenURI::Cache.class_eval { @cache_path = URI_CACHE } ::RSpec.configure do |c| c.filter_run focus: true diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 9a8d799..5ea7418 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -1,24 +1,91 @@ require 'rdf/n3' require 'json/ld' +# For now, override RDF::Utils::File.open_file to look for the file locally before attempting to retrieve it +module RDF::Util + module File + REMOTE_PATH = "http://www.w3.org/2000/10/swap/" + LOCAL_PATH = ::File.expand_path("../swap", __FILE__) + '/' + + class << self + alias_method :original_open_file, :open_file + end + + ## + # Override to use Patron for http and https, Kernel.open otherwise. + # + # @param [String] filename_or_url to open + # @param [Hash{Symbol => Object}] options + # @option options [Array, String] :headers + # HTTP Request headers. + # @return [IO] File stream + # @yield [IO] File stream + def self.open_file(filename_or_url, options = {}, &block) + case + when filename_or_url.to_s =~ /^file:/ + path = filename_or_url[5..-1] + Kernel.open(path.to_s, options, &block) + when (filename_or_url.to_s =~ %r{^#{REMOTE_PATH}} && Dir.exist?(LOCAL_PATH)) + #puts "attempt to open #{filename_or_url} locally" + localpath = filename_or_url.to_s.sub(REMOTE_PATH, LOCAL_PATH) + response = begin + ::File.open(localpath) + rescue Errno::ENOENT => e + raise IOError, e.message + end + document_options = { + base_uri: RDF::URI(filename_or_url), + charset: Encoding::UTF_8, + code: 200, + headers: {} + } + #puts "use #{filename_or_url} locally" + document_options[:headers][:content_type] = case filename_or_url.to_s + when /\.ttl$/ then 'text/turtle' + when /\.n3$/ then 'text/n3' + when /\.nt$/ then 'application/n-triples' + when /\.jsonld$/ then 'application/ld+json' + else 'unknown' + end + + document_options[:headers][:content_type] = response.content_type if response.respond_to?(:content_type) + # For overriding content type from test data + document_options[:headers][:content_type] = options[:contentType] if options[:contentType] + + remote_document = RDF::Util::File::RemoteDocument.new(response.read, document_options) + if block_given? + yield remote_document + else + remote_document + end + else + original_open_file(filename_or_url, options, &block) + end + end + end +end + module Fixtures module SuiteTest BASE = "http://www.w3.org/2000/10/swap/test/" CONTEXT = JSON.parse(%q({ - "@vocab": "http://www.w3.org/2004/11/n3test#", + "n3test": "http://www.w3.org/2004/11/n3test#", - "inputDocument": {"@type": "@id"}, - "outputDocument": {"@type": "@id"} + "inputDocument": {"@id": "n3test:inputDocument", "@type": "@id"}, + "outputDocument": {"@id": "n3test:outputDocument", "@type": "@id"}, + "description": "n3test:description" })) class Entry < JSON::LD::Resource def self.open(file) #puts "open: #{file}" - prefixes = {} g = RDF::Repository.load(file, format: :n3) JSON::LD::API.fromRDF(g) do |expanded| JSON::LD::API.compact(expanded, CONTEXT) do |doc| - doc['@graph'].each {|r| yield Entry.new(r) if r['@type']} + doc['@graph'].map {|r| Entry.new(r)}. + reject {|r| Array(r.attributes['@type']).empty?}. + sort_by(&:name). + each {|t| yield(t)} end end end @@ -29,7 +96,7 @@ def base end def name - base.to_s.split('/').last.sub('.n3', '') + id.to_s.split('#').last end # Alias data and query @@ -42,7 +109,7 @@ def expected end def positive_test? - !attributes['@type'].match(/Negative/) + !attributes['@type'].to_s.match(/Negative/) end def negative_test? diff --git a/spec/swap b/spec/swap new file mode 120000 index 0000000..3978bda --- /dev/null +++ b/spec/swap @@ -0,0 +1 @@ +../../swap/ \ No newline at end of file diff --git a/spec/swap_spec.rb b/spec/swap_spec.rb index 079b9af..0b92891 100644 --- a/spec/swap_spec.rb +++ b/spec/swap_spec.rb @@ -1,29 +1,31 @@ -$:.unshift "." -require File.join(File.dirname(__FILE__), 'spec_helper') +require_relative 'spec_helper' +require 'rdf/trig' # For formatting error descriptions describe RDF::N3::Reader do # W3C N3 Test suite from http://www.w3.org/2000/10/swap/test/n3parser.tests describe "w3c swap tests" do - require 'suite_helper' + require_relative 'suite_helper' + + %w(n3parser.tests n3/n3-full.tests n3/n3-rdf.tests).each do |man| + describe man do + Fixtures::SuiteTest::Entry.open(Fixtures::SuiteTest::BASE + man) do |t| + #next unless t.subject.to_s =~ /rdfms-rdf-names-use/ + #next unless t.name =~ /11/ + #puts t.inspect + next if %w(keywords1 keywords2 n3parser.tests strquot + numbers qvars1 qvars2 lists too-nested equals1).include?(t.name) + specify "#{t.name}: #{t.description}" do + case t.name + when *%w(n3_10006 n3_100009) + pending("needs checking") + when *%w(n3_10004 n3_10007 n3_10014) + pending("Formulae inferrence not supported") + when *%w(n3_10009 n3_10015 n3_10017) + pending("Verified test results are incorrect") + when *%w(n3_10013) + pending("Isomorphic compare issue") + end - %w(n3parser.tests).each do |man| - Fixtures::SuiteTest::Entry.open(Fixtures::SuiteTest::BASE + man) do |t| - #next unless t.subject.to_s =~ /rdfms-rdf-names-use/ - #next unless t.name =~ /11/ - #puts t.inspect - next if %w(keywords1 keywords2 n3parser.tests contexts strquot - numbers qvars1 qvars2 lists too-nested equals1).include?(t.name) - specify "#{t.name}: #{t.description}" do - case t.name - when 'n3_10012' - pending("Skip long input file") - when *%w(n3_10004 n3_10007 n3_10014 n3_10015 n3_10017) - pending("Formulae inferrence not supported") - when *%w(n3_10003 n3_10006 n3_10009) - pending("Verified test results are incorrect") - when *%w(n3_10008 n3_10013) - pending("Isomorphic compare issue") - else t.logger = RDF::Spec.logger t.logger.info t.inspect t.logger.info "source:\n#{t.input}" @@ -35,6 +37,14 @@ logger: t.logger) graph = RDF::Repository.new + output_graph = if t.evaluate? + begin + format = detect_format(t.outputDocument) + RDF::Repository.load(t.outputDocument, format: format, base_uri: t.inputDocument) + rescue Exception => e + expect(e.message).to produce("Exception loading output #{e.inspect}", t.logger) + end + end if t.positive_test? begin @@ -44,22 +54,17 @@ end if t.evaluate? - output_graph = begin - format = detect_format(t.outputDocument) - RDF::Repository.load(t.outputDocument, format: format, base_uri: t.inputDocument) - rescue Exception => e - expect(e.message).to produce("Not exception #{e.inspect}", t.logger) - end - expect(graph).to be_equivalent_graph(output_graph, t) else expect(graph).to be_enumerable end - else + elsif t.syntax? expect { graph << reader graph.dump(:ntriples).should produce("not this", t.logger) }.to raise_error(RDF::ReaderError) + else + expect(graph).not_to be_equivalent_graph(output_graph, t) end end end diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 302297a..c3d46b1 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -1,6 +1,5 @@ # coding: utf-8 -$:.unshift "." -require File.join(File.dirname(__FILE__), 'spec_helper') +require_relative 'spec_helper' require 'rdf/spec/writer' describe RDF::N3::Writer do From aedfbb9cdfc9a638c7fca78746e9df49f2c86ac2 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 1 Jan 2019 12:29:07 -0800 Subject: [PATCH 06/40] Update Gemfile. --- Gemfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/Gemfile b/Gemfile index 85bdc43..2b8358d 100644 --- a/Gemfile +++ b/Gemfile @@ -10,9 +10,6 @@ group :development do gem "rdf-trig", github: "ruby-rdf/rdf-trig", branch: "develop" gem "rdf-xsd", github: "ruby-rdf/rdf-xsd", branch: "develop" gem "json-ld", github: "ruby-rdf/json-ld", branch: "develop" - - # Until version >= 3.4.2 with support for Ruby 2.6 - gem "webmock", github: "bblimke/webmock" end group :debug do From 281f70789a75a890396c5c0631498fae90c71cf3 Mon Sep 17 00:00:00 2001 From: Arthur Dingemans Date: Fri, 19 Apr 2019 09:37:47 +0200 Subject: [PATCH 07/40] Correctly mark list values as seen --- lib/rdf/n3/writer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index b90c694..a0eedf2 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -330,7 +330,7 @@ def order_subjects # Mark as seen lists that are part of another list @lists.values.map(&:statements). flatten.each do |st| - seen[st.object] if @lists.has_key?(st.object) + seen[st.object] = true if @lists.has_key?(st.object) end # Sort subjects by resources over bnodes, ref_counts and the subject URI itself From b04a183450b18b0c9ce8557d3938528006dcebc7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 31 Dec 2018 13:38:51 -0800 Subject: [PATCH 08/40] Add :existentials and :universals to RDF::Enumerable. --- lib/rdf/n3.rb | 1 + lib/rdf/n3/extensions.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 lib/rdf/n3/extensions.rb diff --git a/lib/rdf/n3.rb b/lib/rdf/n3.rb index 29d2f77..c60c454 100644 --- a/lib/rdf/n3.rb +++ b/lib/rdf/n3.rb @@ -1,5 +1,6 @@ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..'))) require 'rdf' +require 'rdf/n3/extensions' module RDF ## diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb new file mode 100644 index 0000000..e349fcd --- /dev/null +++ b/lib/rdf/n3/extensions.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +require 'rdf' + +# Monkey-patch RDF::Enumerable to add `:existentials` and `:univerals` accessors +module RDF + module Enumerable + # Existential quantifiers defined on this enumerable + # @return [Array] + attr_accessor :existentials + + # Universal quantifiers defined on this enumerable + # @return [Array] + attr_accessor :universals + end +end From 40f64062dc4fe76a95392efb3e314bb940a5c1fe Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 31 Dec 2018 13:41:20 -0800 Subject: [PATCH 09/40] Improve debug output using depth. --- lib/rdf/n3/reader.rb | 33 ++++++++++--------- lib/rdf/n3/reader/parser.rb | 66 +++++++++++++++++++------------------ 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 1a3c2fd..1850868 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -46,6 +46,7 @@ class Reader < RDF::Reader # @raise [Error]:: Raises RDF::ReaderError if validating and an error is found def initialize(input = $stdin, options = {}, &block) super do + @options = {log_depth: 0}.merge(@options) input.rewind if input.respond_to?(:rewind) @input = input.respond_to?(:read) ? input : StringIO.new(input.to_s) @lineno = 0 @@ -125,7 +126,7 @@ def each_triple(&block) # Start of production def onStart(prod) handler = "#{prod}Start".to_sym - log_debug("#{handler}(#{respond_to?(handler, true)})", prod) + log_debug("#{handler}(#{respond_to?(handler, true)})", prod, depth: depth) @productions << prod send(handler, prod) if respond_to?(handler, true) end @@ -134,7 +135,7 @@ def onStart(prod) def onFinish prod = @productions.pop() handler = "#{prod}Finish".to_sym - log_debug("#{handler}(#{respond_to?(handler, true)})") {"#{prod}: #{@prod_data.last.inspect}"} + log_debug("#{handler}(#{respond_to?(handler, true)})", depth: depth) {"#{prod}: #{@prod_data.last.inspect}"} send(handler) if respond_to?(handler, true) end @@ -143,7 +144,7 @@ def onToken(prod, tok) unless @productions.empty? parentProd = @productions.last handler = "#{parentProd}Token".to_sym - log_debug("#{handler}(#{respond_to?(handler, true)})") {"#{prod}, #{tok}: #{@prod_data.last.inspect}"} + log_debug("#{handler}(#{respond_to?(handler, true)})", depth: depth) {"#{prod}, #{tok}: #{@prod_data.last.inspect}"} send(handler, prod, tok) if respond_to?(handler, true) else error("Token has no parent production") @@ -190,9 +191,9 @@ def declarationFinish # This means that <#foo> can be written :foo and using @keywords one can reduce that to foo. namespace(nil, uri.match(/[\/\#]$/) ? base_uri : process_uri("#{uri}#")) - log_debug("declarationFinish[@base]") {"@base=#{base_uri}"} + log_debug("declarationFinish[@base]", depth: depth) {"@base=#{base_uri}"} when "@keywords" - log_debug("declarationFinish[@keywords]") {@keywords.inspect} + log_debug("declarationFinish[@keywords]", depth: depth) {@keywords.inspect} # Keywords are handled in tokenizer and maintained in @keywords array if (@keywords & N3_KEYWORDS) != @keywords error("Undefined keywords used: #{(@keywords - N3_KEYWORDS).to_sentence}") if validate? @@ -242,7 +243,7 @@ def expressionFinish # If we're in teh middle of a pathtail, append if @prod_data.last[:pathtail] && expression[:pathitem] && expression[:pathtail] path_list = [expression[:pathitem]] + expression[:pathtail] - log_debug("expressionFinish(pathtail)") {"set pathtail to #{path_list.inspect}"} + log_debug("expressionFinish(pathtail)", depth: depth) {"set pathtail to #{path_list.inspect}"} @prod_data.last[:pathtail] = path_list dir_list = [expression[:direction]] if expression[:direction] @@ -406,7 +407,7 @@ def simpleStatementFinish properties.each do |p| predicate = p[:verb] next unless predicate - log_debug("simpleStatementFinish(pred)") {predicate.to_s} + log_debug("simpleStatementFinish(pred)", depth: depth) {predicate.to_s} error(%(Illegal statment: "#{predicate}" missing object)) unless p.has_key?(:object) objects = Array(p[:object]) objects.each do |object| @@ -509,14 +510,14 @@ def verbFinish ################### def process_anonnode(anonnode) - log_debug("process_anonnode") {anonnode.inspect} + log_debug("process_anonnode", depth: depth) {anonnode.inspect} if anonnode[:propertylist] properties = anonnode[:propertylist] bnode = RDF::Node.new properties.each do |p| predicate = p[:verb] - log_debug("process_anonnode(verb)") {predicate.inspect} + log_debug("process_anonnode(verb)", depth: depth) {predicate.inspect} objects = Array(p[:object]) objects.each { |object| add_triple("anonnode", bnode, predicate, object) } end @@ -539,7 +540,7 @@ def process_anonnode(anonnode) # # Create triple and return property used for next iteration def process_path(expression) - log_debug("process_path") {expression.inspect} + log_debug("process_path", depth: depth) {expression.inspect} pathitem = expression[:pathitem] pathtail = expression[:pathtail] @@ -583,17 +584,17 @@ def process_qname(tok) end uri = if prefix(prefix) - log_debug('process_qname(ns)') {"#{prefix(prefix)}, #{name}"} + log_debug('process_qname(ns)', depth: depth) {"#{prefix(prefix)}, #{name}"} ns(prefix, name) elsif prefix == '_' log_debug('process_qname(bnode)', name) bnode(name) else - log_debug('process_qname(default_ns)', name) + log_debug('process_qname(default_ns)', name, depth: depth) namespace(nil, uri("#{base_uri}#")) unless prefix(nil) ns(nil, name) end - log_debug('process_qname') {uri.inspect} + log_debug('process_qname', depth: depth) {uri.inspect} uri end @@ -635,7 +636,7 @@ def add_triple(node, subject, predicate, object) graph_name_opts = @formulae.last ? {graph_name: @formulae.last} : {} statement = RDF::Statement(subject, predicate, object, graph_name_opts) - log_debug(node) {statement.to_s} + log_debug(node, depth: depth) {statement.to_s} @callback.call(statement) end @@ -644,7 +645,7 @@ def namespace(prefix, uri) if uri == '#' uri = prefix(nil).to_s + '#' end - log_debug("namespace") {"'#{prefix}' <#{uri}>"} + log_debug("namespace", depth: depth) {"'#{prefix}' <#{uri}>"} prefix(prefix, uri(uri)) end @@ -674,7 +675,7 @@ def uri(value, append = nil) def ns(prefix, suffix) base = prefix(prefix).to_s suffix = suffix.to_s.sub(/^\#/, "") if base.index("#") - log_debug("ns") {"base: '#{base}', suffix: '#{suffix}'"} + log_debug("ns", depth: depth) {"base: '#{base}', suffix: '#{suffix}'"} uri(base + suffix.to_s) end end diff --git a/lib/rdf/n3/reader/parser.rb b/lib/rdf/n3/reader/parser.rb index 14fc805..a502622 100644 --- a/lib/rdf/n3/reader/parser.rb +++ b/lib/rdf/n3/reader/parser.rb @@ -8,11 +8,11 @@ module Parser SINGLE_CHARACTER_SELECTORS = %{\t\r\n !\"#$\%&'()*.,+/;<=>?[\\]^`{|}~} NOT_QNAME_CHARS = SINGLE_CHARACTER_SELECTORS + "@" NOT_NAME_CHARS = NOT_QNAME_CHARS + ":" - + def error(str) log_error(str, lineno: @lineno, exception: RDF::ReaderError) end - + def parse(prod) todo_stack = [{prod: prod, terms: nil}] while !todo_stack.empty? @@ -20,12 +20,12 @@ def parse(prod) if todo_stack.last[:terms].nil? todo_stack.last[:terms] = [] tok = self.token - #log_debug("parse tok: '#{tok}'") {"prod #{todo_stack.last[:prod]}"} - + #log_debug("parse tok: '#{tok}'", depth: depth) {"prod #{todo_stack.last[:prod]}"} + # Got an opened production onStart(abbr(todo_stack.last[:prod])) break if tok.nil? - + cur_prod = todo_stack.last[:prod] prod_branch = @branches[cur_prod] error("No branches found for '#{abbr(cur_prod)}'") if prod_branch.nil? @@ -35,15 +35,15 @@ def parse(prod) expected = prod_branch.values.uniq.map {|u| u.map {|v| abbr(v).inspect}.join(",")} error("Found '#{tok}' when parsing a #{abbr(cur_prod)}. expected #{expected.join(' | ')}") end - #log_debug("sequence") {sequence.inspect} + #log_debug("sequence", depth: depth) {sequence.inspect} todo_stack.last[:terms] += sequence end - - #log_debug("parse") {todo_stack.last.inspect} + + #log_debug("parse", depth: depth) {todo_stack.last.inspect} while !todo_stack.last[:terms].to_a.empty? term = todo_stack.last[:terms].shift if term.is_a?(String) - log_debug("parse term(string)") {term.to_s} + log_debug("parse term(string)", depth: depth) {term.to_s} word = buffer[0, term.length] if word == term onToken(term, word) @@ -60,7 +60,7 @@ def parse(prod) string = '"""' consume(3) next_line = buffer - #log_debug("ml-str(start)") {next_line.dump} + #log_debug("ml-str(start)", depth: depth) {next_line.dump} until md = R_MLSTRING.match(next_line) begin string += next_line @@ -72,23 +72,23 @@ def parse(prod) string += md[0].to_s consume(md[0].to_s.length) onToken('string', string) - #log_debug("ml-str now") {buffer.dump} + #log_debug("ml-str now", depth: depth) {buffer.dump} else md = regexp.match(buffer) error("Token(#{abbr(term)}) '#{buffer[0, 10]}...' should match #{regexp}") unless md - log_debug("parse") {"term(#{abbr(term)}:regexp): #{term}, #{regexp}.match('#{buffer[0, 10]}...') => '#{md.inspect.force_encoding(Encoding::UTF_8)}'"} + log_debug("parse", depth: depth) {"term(#{abbr(term)}:regexp): #{term}, #{regexp}.match('#{buffer[0, 10]}...') => '#{md.inspect.force_encoding(Encoding::UTF_8)}'"} onToken(abbr(term), md.to_s) consume(md[0].length) end else - log_debug("parse term(push)") {term} + log_debug("parse term(push)", depth: depth) {term} todo_stack << {prod: term, terms: nil} pushed = true break end self.token end - + while !pushed && todo_stack.last[:terms].to_a.empty? todo_stack.pop self.onFinish @@ -105,26 +105,26 @@ def token unless @memo.has_key?(@pos) tok = self.get_token @memo[@pos] = tok - log_debug("token") {"'#{tok}'('#{buffer[0, 10]}...')"} if buffer + log_debug("token", depth: depth) {"'#{tok}'('#{buffer[0, 10]}...')"} if buffer end @memo[@pos] end def get_token whitespace - + return nil if buffer.nil? - + ch2 = buffer[0, 2] return ch2 if %w(=> <= ^^).include?(ch2) - + ch = buffer[0, 1] @keyword_mode = false if ch == '.' && @keyword_mode - + return ch if SINGLE_CHARACTER_SELECTORS.include?(ch) return ":" if ch == ":" return "0" if "+-0123456789".include?(ch) - + if ch == '@' return '@' if @pos > 0 && @line[@pos-1, 1] == '"' @@ -154,43 +154,45 @@ def get_token 'a' end - + def whitespace while buffer && md = R_WHITESPACE.match(buffer) return unless md[0].length > 0 consume(md[0].length) - #log_debug("ws") {"'#{md[0]}', pos=#{@pos}"} + #log_debug("ws", depth: depth) {"'#{md[0]}', pos=#{@pos}"} end end - + def readline @line = @input.readline @lineno += 1 @line.force_encoding(Encoding::UTF_8) - log_debug("readline[#{@lineno}]") {@line.dump} + log_debug("readline[#{@lineno}]", depth: depth) {@line.dump} @pos = 0 @line rescue EOFError @line, @pos = nil, 0 end - + # Return data from current off set to end of line def buffer @line[@pos, @line.length - @pos] unless @line.nil? end - + # Cause n characters of line to be consumed. Read new line while line is empty or until eof def consume(n) @memo = {} @pos += n readline while @line && @line.length <= @pos - #log_debug("consume[#{n}]") {buffer} + #log_debug("consume[#{n}]", depth: depth) {buffer} end - + def abbr(prodURI) prodURI.to_s.split('#').last end - + + def depth; (@productions || []).length; end + def onStart(prod) $stdout.puts ' ' * @productions.length + prod @productions << prod @@ -204,7 +206,7 @@ def onFinish def onToken(prod, tok) $stdout.puts ' ' * @productions.length + "#{prod}(#{tok})" end - + def dump_stack(stack) STDERR.puts "\nstack trace:" stack.reverse.each do |se| @@ -216,14 +218,14 @@ def dump_stack(stack) end end end - + def test(input, branches, regexps) # FIXME: for now, read in entire doc, eventually, process as stream @input = input.respond_to?(:read) ? (input.rewind; input) : StringIO.new(input.to_s) @lineno = 0 readline # Prime the pump $stdout ||= STDOUT - + @memo = {} @keyword_mode = false @keywords = %w(a is of this has) From 0cb11cc021202916d53efe66023e5a4e76ae7286 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 31 Dec 2018 13:43:43 -0800 Subject: [PATCH 10/40] Progress towards supporting formulae. * Output both statements and patterns, converting patterns to statements in each_statement. * Use non-distinguished variables for existentials, instead of bnodes. --- README.md | 1 - lib/rdf/n3/reader.rb | 125 +++++++++++++++++++++++++++++++------------ spec/reader_spec.rb | 20 ++++--- spec/swap_spec.rb | 18 +++---- 4 files changed, 111 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index ad96fc4..c856a0c 100755 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ RDF::N3 parses [Notation-3][N3], [Turtle][Turtle] and [N-Triples][N-Triples] int Install with `gem install rdf-n3` ## Limitations -* Full support of Unicode input requires Ruby version 2.0 or greater. * Support for Variables in Formulae dependent on underlying repository. Existential variables are quantified to RDF::Node instances, Universals to RDF::Query::Variable, with the URI of the variable target used as the variable name. * No support for N3 Reification. If there were, it would be through a :reify option to the reader. diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 1850868..bac2989 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -23,6 +23,19 @@ class Reader < RDF::Reader N3_KEYWORDS = %w(a is of has keywords prefix base true false forSome forAny) + # The Blank nodes allocated for formula + # @return [Array] + attr_reader :formulae + + # The Variables allocated, along with their in-scope formula + VarInfo = Struct.new(:var, :formula) + + # The allocated variables, including their formulae + # + # Variables are indexed by variable name + # @return [Hash{String => {Hash{Symbol => RDF::Term}}}] + attr_reader :variables + ## # Initializes the N3 reader instance. # @@ -61,7 +74,7 @@ def initialize(input = $stdin, options = {}, &block) @branches = BRANCHES # Get from meta class @regexps = REGEXPS # Get from meta class - @formulae = [] # Nodes used as Formluae graph names + @formulae = [] # Nodes used as Formulae graph names @formulae_nodes = {} @variables = {} # variable definitions along with defining formula @@ -87,12 +100,11 @@ def inspect end ## - # Iterates the given block for each RDF statement in the input. - # - # @yield [statement] - # @yieldparam [RDF::Statement] statement + # Iterates over both statements and patterns. + # @yield [pattern] + # @yieldparam [RDF::Pattern] pattern # @return [void] - def each_statement(&block) + def each_pattern(&block) if block_given? @callback = block @@ -102,7 +114,44 @@ def each_statement(&block) raise RDF::ReaderError, "Errors found during processing" end end - enum_for(:each_triple) + enum_for(:each_pattern) + end + + ## + # Iterates the given block for each RDF statement in the input. + # + # @yield [statement] + # @yieldparam [RDF::Statement] statement + # @return [void] + def each_statement + if block_given? + each_pattern do |pattern| + # Turn patterns into statements through simple BNode replacement + if pattern.is_a?(RDF::Query::Pattern) || pattern.variable? + yield RDF::Statement.new( + *pattern.to_triple.map do |r| + # If bound, replace variable with it's value, otherwise + # create a URI. + if r.variable? + if r.bound? + r.value + elsif r.distinguished? + process_uri(r.name.to_s) + else + bnode(r.name) + end + else + r + end + end, + graph_name: pattern.graph_name + ) + else + yield pattern + end + end + end + enum_for(:each_statement) end ## @@ -113,10 +162,10 @@ def each_statement(&block) # @yieldparam [RDF::URI] predicate # @yieldparam [RDF::Value] object # @return [void] - def each_triple(&block) + def each_triple if block_given? each_statement do |statement| - block.call(*statement.to_triple) + yield(*statement.to_triple) end end enum_for(:each_triple) @@ -228,7 +277,8 @@ def existentialFinish pd = @prod_data.pop forSome = Array(pd[:symbol]) forSome.each do |term| - @variables[term.to_s] = {formula: @formulae.last, var: RDF::Node.new(term.to_s.split(/[\/#]/).last)} + # Blank nodes are scoped to the document + @variables[term.to_s] = VarInfo.new(univar(term, distinguished: false)) end end @@ -311,7 +361,7 @@ def pathitemToken(prod, tok) # There is a also a shorthand syntax ?x which is the same as :x except that it implies that x is # universally quantified not in the formula but in its parent formula uri = process_qname(tok.sub('?', ':')) - @variables[uri.to_s] = { formula: @formulae[-2], var: univar(uri) } + @variables[uri.to_s] = VarInfo.new(univar(uri), @formulae[-2]) add_prod_data(:symbol, uri) when "boolean" lit = RDF::Literal.new(tok.delete("@"), datatype: RDF::XSD.boolean, validate: validate?, canonicalize: canonicalize?) @@ -329,9 +379,8 @@ def pathitemToken(prod, tok) @formulae << node @formulae_nodes[node] = true when "}" - # Pop off the formula, and remove any variables defined in this graph + # Pop off the formula formula = @formulae.pop - @variables.delete_if {|k, v| v[:formula] == formula} add_prod_data(:symbol, formula) else error("pathitemToken(#{prod}, #{tok}): FIXME") @@ -412,9 +461,9 @@ def simpleStatementFinish objects = Array(p[:object]) objects.each do |object| if p[:invert] - add_triple("simpleStatementFinish", object, predicate, subject) + add_statement("simpleStatementFinish", object, predicate, subject) else - add_triple("simpleStatementFinish", subject, predicate, object) + add_statement("simpleStatementFinish", subject, predicate, object) end end end @@ -461,7 +510,7 @@ def universalFinish pd = @prod_data.pop forAll = Array(pd[:symbol]) forAll.each do |term| - @variables[term.to_s] = { formula: @formulae.last, var: univar(term) } + @variables[term.to_s] = VarInfo.new(univar(term), @formulae.last) end end @@ -519,7 +568,7 @@ def process_anonnode(anonnode) predicate = p[:verb] log_debug("process_anonnode(verb)", depth: depth) {predicate.inspect} objects = Array(p[:object]) - objects.each { |object| add_triple("anonnode", bnode, predicate, object) } + objects.each { |object| add_statement("anonnode", bnode, predicate, object) } end bnode elsif anonnode[:pathlist] @@ -527,7 +576,7 @@ def process_anonnode(anonnode) list = RDF::List[*objects] list.each_statement do |statement| next if statement.predicate == RDF.type && statement.object == RDF.List - add_triple("anonnode(list)", statement.subject, statement.predicate, statement.object) + add_statement("anonnode(list)", statement.subject, statement.predicate, statement.object) end list.subject end @@ -551,9 +600,9 @@ def process_path(expression) direction = direction_list.shift bnode = RDF::Node.new if direction == :reverse - add_triple("process_path(reverse)", bnode, pred, pathitem) + add_statement("process_path(reverse)", bnode, pred, pathitem) else - add_triple("process_path(forward)", pathitem, pred, bnode) + add_statement("process_path(forward)", pathitem, pred, bnode) end pathitem = bnode end @@ -587,8 +636,13 @@ def process_qname(tok) log_debug('process_qname(ns)', depth: depth) {"#{prefix(prefix)}, #{name}"} ns(prefix, name) elsif prefix == '_' - log_debug('process_qname(bnode)', name) - bnode(name) + log_debug('process_qname(bnode)', name, depth: depth) + # If we're in a formula, create a non-distigushed variable instead + if @formulae.last + univar(name, distinguished: false) + else + bnode(name) + end else log_debug('process_qname(default_ns)', name, depth: depth) namespace(nil, uri("#{base_uri}#")) unless prefix(nil) @@ -616,26 +670,31 @@ def bnode(value = nil) @bnode_cache[value.to_s] ||= RDF::Node.new(value) end - def univar(label) + def univar(label, distinguished: true) unless label @unnamed_label ||= "var0" label = @unnamed_label = @unnamed_label.succ end - RDF::Query::Variable.new(label.to_s) + v = RDF::Query::Variable.new(label.to_s) + v.distinguished = distinguished + v end - # add a statement, object can be literal or URI or bnode + # add a pattern or statement # # @param [any] node string for showing graph_name - # @param [URI, Node] subject the subject of the statement - # @param [URI] predicate the predicate of the statement - # @param [URI, Node, Literal] object the object of the statement + # @param [RDF::Term] subject the subject of the statement + # @param [RDF::URI] predicate the predicate of the statement + # @param [RDF::Term] object the object of the statement # @return [Statement] Added statement # @raise [RDF::ReaderError] Checks parameter types and raises if they are incorrect if parsing mode is _validate_. - def add_triple(node, subject, predicate, object) - graph_name_opts = @formulae.last ? {graph_name: @formulae.last} : {} - - statement = RDF::Statement(subject, predicate, object, graph_name_opts) + def add_statement(node, subject, predicate, object) + statement = if @formulae.last + # It's a pattern in a formula + RDF::Query::Pattern.new(subject, predicate, object, graph_name: @formulae.last) + else + RDF::Statement(subject, predicate, object) + end log_debug(node, depth: depth) {statement.to_s} @callback.call(statement) end @@ -667,7 +726,7 @@ def uri(value, append = nil) # Variable substitution for in-scope variables. Variables are in scope if they are defined in anthing other than # the current formula var = @variables[value.to_s] - value = var[:var] if var + value = var.var if var value end diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index ff1269b..e1bee97 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -1,6 +1,7 @@ # coding: utf-8 require_relative 'spec_helper' require 'rdf/spec/reader' +require 'rdf/trig' describe "RDF::N3::Reader" do let!(:doap) {File.expand_path("../../etc/doap.n3", __FILE__)} @@ -868,17 +869,22 @@ describe "formulae" do before(:each) { @repo = RDF::Repository.new } - it "creates an RDF::Graph instance for formula" do + it "creates an RDF::Node instance for formula" do n3 = %(:a :b {}) - parse(n3, graph: @repo, base_uri: "http://a/b") - statement = @repo.statements.first - expect(statement.object).to be_node + nq = %(:a :b _:c .) + result = parse(n3, graph: @repo, base_uri: "http://a/b") + expected = parse(nq, graph: @repo, base_uri: "http://a/b") + expect(result).to be_equivalent_graph(expected, logger: logger) end - it "adds statements with graph_name" + it "adds statements with graph_name" do + n3 = %(:a :b {[:c :d]}) + trig = %(<#a> <#b> _:c . _:c {[<#c> <#d>] .}) + result = parse(n3, graph: @repo, base_uri: "http://a/b") + expected = RDF::Repository.new {|r| r << RDF::TriG::Reader.new(trig, base_uri: "http://a/b")} + expect(result).to be_equivalent_graph(expected, logger: logger) + end - it "creates variables with ?" - context "contexts" do before(:each) do n3 = %( diff --git a/spec/swap_spec.rb b/spec/swap_spec.rb index 0b92891..7280965 100644 --- a/spec/swap_spec.rb +++ b/spec/swap_spec.rb @@ -6,29 +6,23 @@ describe "w3c swap tests" do require_relative 'suite_helper' - %w(n3parser.tests n3/n3-full.tests n3/n3-rdf.tests).each do |man| + %w(n3parser.tests).each do |man| + # Note, n3/n3-full.tests and n3/n3-rdf.tests is essentially the same as n3parser.tests describe man do Fixtures::SuiteTest::Entry.open(Fixtures::SuiteTest::BASE + man) do |t| - #next unless t.subject.to_s =~ /rdfms-rdf-names-use/ - #next unless t.name =~ /11/ - #puts t.inspect - next if %w(keywords1 keywords2 n3parser.tests strquot - numbers qvars1 qvars2 lists too-nested equals1).include?(t.name) specify "#{t.name}: #{t.description}" do case t.name - when *%w(n3_10006 n3_100009) - pending("needs checking") - when *%w(n3_10004 n3_10007 n3_10014) + when *%w(n3_10004 n3_10007 n3_10014 n3_10015 n3_10017) pending("Formulae inferrence not supported") - when *%w(n3_10009 n3_10015 n3_10017) + when *%w(n3_10006 n3_10009) pending("Verified test results are incorrect") when *%w(n3_10013) - pending("Isomorphic compare issue") + pending("numeric representation") end t.logger = RDF::Spec.logger t.logger.info t.inspect - t.logger.info "source:\n#{t.input}" + t.logger.info "source:\n#{t.input.read}" reader = RDF::N3::Reader.new(t.input, base_uri: t.base, From 6f3a73f64a56ef21a2e21892b0ae3bb7a362bb52 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 31 Dec 2018 15:36:25 -0800 Subject: [PATCH 11/40] Version of Notation3 in EBNF. --- etc/notation3.ebnf | 106 ++++++++ etc/notation3.ll1.sxp | 587 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 693 insertions(+) create mode 100644 etc/notation3.ebnf create mode 100644 etc/notation3.ll1.sxp diff --git a/etc/notation3.ebnf b/etc/notation3.ebnf new file mode 100644 index 0000000..13a5a6a --- /dev/null +++ b/etc/notation3.ebnf @@ -0,0 +1,106 @@ +# Notation3 Grammar updated with some Turtle poductions and terminals. +# From swap/notation3/notation3.bnf + +[1] document ::= (statement ".")* + +/* Formula does NOT need period on last statement */ + +[2] formulacontent ::= (statement ("." statement)*)? + +[3] statement ::= declaration + | universal + | existential + | simpleStatement + +[4] universal ::= "@forAll" varlist + +[5] existential ::= "@forSome" varlist + +[6] varlist ::= (symbol ("," symbol)*)? + +[7] declaration ::= "@prefix" PNAME_NS IRIREF + | "@keywords" (barename ("," barename)*)? + +[8] barename ::= PNAME_LN +/* barename constraint: no colon */ + +[9] simpleStatement ::= term propertylist + +[10] propertylist ::= (property (";" property)*)? +[11] property ::= (verb | inverb) term ("," term)* + +[12] verb ::= "@has"? term + | "@a" + | "=" + | "=>" + | "<=" + +[12a] inverb ::= "@is" term "@of" + +[13] term ::= pathitem pathtail? + +[14] pathtail ::= ("!" | "^") term + +[15] pathitem ::= symbol + | BLANK_NODE_LABEL + | UVAR + | literal + | "{" formulacontent "}" + | "[" propertylist "]" + | "(" term* ")" + +[13] literal ::= RDFLiteral | NumericLiteral | BooleanLiteral +[16] NumericLiteral ::= INTEGER | DECIMAL | DOUBLE +[128s] RDFLiteral ::= String ( LANGTAG | ( "^^" iri ) )? +[133s] BooleanLiteral ::= "@true" | "@false" +[17t] String ::= STRING_LITERAL_QUOTE | STRING_LITERAL_SINGLE_QUOTE | STRING_LITERAL_LONG_SINGLE_QUOTE | + STRING_LITERAL_LONG_QUOTE + +[18] symbol ::= IRIREF | PNAME_LN + +/***********/ + +@terminals + +[35] UVAR ::= "?" PN_LOCAL + +/* borrowed from SPARQL spec, which excludes newlines and other nastiness */ +[18] IRIREF ::= '<' ([^#x00-#x20<>"{}|^`\] | UCHAR)* '>' +[139s] PNAME_NS ::= PN_PREFIX? ":" +[140s] PNAME_LN ::= PNAME_NS PN_LOCAL +[141s] BLANK_NODE_LABEL ::= '_:' ( PN_CHARS_U | [0-9] ) ((PN_CHARS|'.')* PN_CHARS)? +[144s] LANGTAG ::= "@" [a-zA-Z]+ ( "-" [a-zA-Z0-9]+ )* +[19] INTEGER ::= [+-]? [0-9]+ +[20] DECIMAL ::= [+-]? ( ([0-9])* '.' ([0-9])+ ) +[21] DOUBLE ::= [+-]? ( [0-9]+ '.' [0-9]* EXPONENT | '.' ([0-9])+ EXPONENT | ([0-9])+ EXPONENT ) +[154s] EXPONENT ::= [eE] [+-]? [0-9]+ +[22] STRING_LITERAL_QUOTE ::= '"' ( [^#x22#x5C#xA#xD] | ECHAR | UCHAR )* '"' +[23] STRING_LITERAL_SINGLE_QUOTE ::= "'" ( [^#x27#x5C#xA#xD] | ECHAR | UCHAR )* "'" +[24] STRING_LITERAL_LONG_SINGLE_QUOTE ::= "'''" ( ( "'" | "''" )? ( [^'\] | ECHAR | UCHAR ) )* "'''" +[25] STRING_LITERAL_LONG_QUOTE ::= '"""' ( ( '"' | '""' )? ( [^"\] | ECHAR | UCHAR ) )* '"""' +[26] UCHAR ::= ( "\u" HEX HEX HEX HEX ) | ( "\U" HEX HEX HEX HEX HEX HEX HEX HEX ) +[159s] ECHAR ::= "\" [tbnrf\"'] + +[163s] PN_CHARS_BASE ::= [A-Z] + | [a-z] + | [#x00C0-#x00D6] + | [#x00D8-#x00F6] + | [#x00F8-#x02FF] + | [#x0370-#x037D] + | [#x037F-#x1FFF] + | [#x200C-#x200D] + | [#x2070-#x218F] + | [#x2C00-#x2FEF] + | [#x3001-#xD7FF] + | [#xF900-#xFDCF] + | [#xFDF0-#xFFFD] + | [#x10000-#xEFFFF] +[164s] PN_CHARS_U ::= PN_CHARS_BASE | '_' +[166s] PN_CHARS ::= PN_CHARS_U | "-" | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040] +[167s] PN_PREFIX ::= PN_CHARS_BASE ( ( PN_CHARS | "." )* PN_CHARS )? +[168s] PN_LOCAL ::= ( PN_CHARS_U | ':' | [0-9] | PLX ) ( ( PN_CHARS | '.' | ':' | PLX )* ( PN_CHARS | ':' | PLX ) ) ? +[169s] PLX ::= PERCENT | PN_LOCAL_ESC +[170s] PERCENT ::= '%' HEX HEX +[42] HEX ::= [0-9] | [A-F] | [a-f] +[172s] PN_LOCAL_ESC ::= '\' ( '_' | '~' | '.' | '-' | '!' | '$' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | ';' | '=' + | '/' | '?' | '#' | '@' | '%' ) \ No newline at end of file diff --git a/etc/notation3.ll1.sxp b/etc/notation3.ll1.sxp new file mode 100644 index 0000000..8ef9a3e --- /dev/null +++ b/etc/notation3.ll1.sxp @@ -0,0 +1,587 @@ +( + (rule _empty "0" (first _eps) (seq)) + (rule document "1" + (start #t) + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow _eof) + (cleanup star) + (alt _empty _document_2)) + (rule _document_1 "1.1" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eof "{" ) + (seq statement ".")) + (rule _document_2 "1.2" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow _eof) + (cleanup merge) + (seq _document_1 document)) + (rule _document_3 "1.3" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow _eof) + (seq document)) + (rule _document_4 "1.4" + (first ".") + (follow "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eof "{" ) + (seq ".")) + (rule formulacontent "2" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow "}") + (cleanup opt) + (alt _empty _formulacontent_1)) + (rule _formulacontent_1 "2.1" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "}") + (seq statement _formulacontent_2)) + (rule _formulacontent_2 "2.2" + (first "." _eps) + (follow "}") + (cleanup star) + (alt _empty _formulacontent_4)) + (rule _formulacontent_3 "2.3" (first ".") (follow "." "}") (seq "." statement)) + (rule _formulacontent_4 "2.4" + (first ".") + (follow "}") + (cleanup merge) + (seq _formulacontent_3 _formulacontent_2)) + (rule _formulacontent_5 "2.5" (first "." _eps) (follow "}") (seq _formulacontent_2)) + (rule _formulacontent_6 "2.6" (first "." _eps) (follow "}") (seq _formulacontent_2)) + (rule _formulacontent_7 "2.7" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." "}") + (seq statement)) + (rule statement "3" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." "}") + (alt declaration universal existential simpleStatement)) + (rule universal "4" (first "@forAll") (follow "." "}") (seq "@forAll" varlist)) + (rule _universal_1 "4.1" (first IRIREF PNAME_LN _eps) (follow "." "}") (seq varlist)) + (rule existential "5" (first "@forSome") (follow "." "}") (seq "@forSome" varlist)) + (rule _existential_1 "5.1" (first IRIREF PNAME_LN _eps) (follow "." "}") (seq varlist)) + (rule varlist "6" + (first IRIREF PNAME_LN _eps) + (follow "." "}") + (cleanup opt) + (alt _empty _varlist_1)) + (rule _varlist_1 "6.1" (first IRIREF PNAME_LN) (follow "." "}") (seq symbol _varlist_2)) + (rule _varlist_2 "6.2" + (first "," _eps) + (follow "." "}") + (cleanup star) + (alt _empty _varlist_4)) + (rule _varlist_3 "6.3" (first ",") (follow "," "." "}") (seq "," symbol)) + (rule _varlist_4 "6.4" + (first ",") + (follow "." "}") + (cleanup merge) + (seq _varlist_3 _varlist_2)) + (rule _varlist_5 "6.5" (first "," _eps) (follow "." "}") (seq _varlist_2)) + (rule _varlist_6 "6.6" (first "," _eps) (follow "." "}") (seq _varlist_2)) + (rule _varlist_7 "6.7" (first IRIREF PNAME_LN) (follow "," "." "}") (seq symbol)) + (rule declaration "7" + (first "@keywords" "@prefix") + (follow "." "}") + (alt _declaration_1 _declaration_2)) + (rule _declaration_1 "7.1" + (first "@prefix") + (follow "." "}") + (seq "@prefix" PNAME_NS IRIREF)) + (rule _declaration_10 "7.10" (first "," _eps) (follow "." "}") (seq _declaration_5)) + (rule _declaration_11 "7.11" (first "," _eps) (follow "." "}") (seq _declaration_5)) + (rule _declaration_12 "7.12" (first PNAME_LN) (follow "," "." "}") (seq barename)) + (rule _declaration_13 "7.13" (first IRIREF) (follow "." "}") (seq IRIREF)) + (rule _declaration_2 "7.2" + (first "@keywords") + (follow "." "}") + (seq "@keywords" _declaration_3)) + (rule _declaration_3 "7.3" + (first PNAME_LN _eps) + (follow "." "}") + (cleanup opt) + (alt _empty _declaration_4)) + (rule _declaration_4 "7.4" + (first PNAME_LN) + (follow "." "}") + (seq barename _declaration_5)) + (rule _declaration_5 "7.5" + (first "," _eps) + (follow "." "}") + (cleanup star) + (alt _empty _declaration_7)) + (rule _declaration_6 "7.6" (first ",") (follow "," "." "}") (seq "," barename)) + (rule _declaration_7 "7.7" + (first ",") + (follow "." "}") + (cleanup merge) + (seq _declaration_6 _declaration_5)) + (rule _declaration_8 "7.8" (first PNAME_NS) (follow "." "}") (seq PNAME_NS IRIREF)) + (rule _declaration_9 "7.9" (first PNAME_LN _eps) (follow "." "}") (seq _declaration_3)) + (rule barename "8" (first PNAME_LN) (follow "," "." "}") (seq PNAME_LN)) + (rule simpleStatement "9" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." "}") + (seq term propertylist)) + (rule _simpleStatement_1 "9.1" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow "." "}") + (seq propertylist)) + (rule propertylist "10" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow "." "]" "}") + (cleanup opt) + (alt _empty _propertylist_1)) + (rule _propertylist_1 "10.1" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." "]" "}") + (seq property _propertylist_2)) + (rule _propertylist_2 "10.2" + (first ";" _eps) + (follow "." "]" "}") + (cleanup star) + (alt _empty _propertylist_4)) + (rule _propertylist_3 "10.3" (first ";") (follow "." ";" "]" "}") (seq ";" property)) + (rule _propertylist_4 "10.4" + (first ";") + (follow "." "]" "}") + (cleanup merge) + (seq _propertylist_3 _propertylist_2)) + (rule _propertylist_5 "10.5" + (first ";" _eps) + (follow "." "]" "}") + (seq _propertylist_2)) + (rule _propertylist_6 "10.6" + (first ";" _eps) + (follow "." "]" "}") + (seq _propertylist_2)) + (rule _propertylist_7 "10.7" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." ";" "]" "}") + (seq property)) + (rule property "11" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." ";" "]" "}") + (seq _property_1 term _property_2)) + (rule _property_1 "11.1" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (alt verb inverb)) + (rule _property_2 "11.2" + (first "," _eps) + (follow "." ";" "]" "}") + (cleanup star) + (alt _empty _property_4)) + (rule _property_3 "11.3" (first ",") (follow "," "." ";" "]" "}") (seq "," term)) + (rule _property_4 "11.4" + (first ",") + (follow "." ";" "]" "}") + (cleanup merge) + (seq _property_3 _property_2)) + (rule _property_5 "11.5" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." ";" "]" "}") + (seq term _property_2)) + (rule _property_6 "11.6" (first "," _eps) (follow "." ";" "]" "}") (seq _property_2)) + (rule _property_7 "11.7" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "," "." ";" "]" "}") + (seq term)) + (rule verb "12" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (alt _verb_1 "@a" "=" "=>" "<=")) + (rule inverb "12a" + (first "@is") + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (seq "@is" term "@of")) + (rule _inverb_2 "12a.2" + (first "@of") + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (seq "@of")) + (rule _inverb_1 "12a.1" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (seq term "@of")) + (rule _verb_1 "12.1" + (first "(" "@false" "@has" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER + IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (seq _verb_2 term)) + (rule _verb_2 "12.2" + (first "@has" _eps) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (cleanup opt) + (alt _empty "@has")) + (rule _verb_3 "12.3" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (seq term)) + (rule literal "13" + (first "@false" "@true" DECIMAL DOUBLE INTEGER STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt RDFLiteral NumericLiteral BooleanLiteral)) + (rule term "13" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@of" + "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" "}" ) + (seq pathitem _term_1)) + (rule _term_1 "13.1" + (first "!" "^" _eps) + (follow "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@of" + "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" "}" ) + (cleanup opt) + (alt _empty pathtail)) + (rule _term_2 "13.2" + (first "!" "^" _eps) + (follow "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@of" + "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" "}" ) + (seq _term_1)) + (rule pathtail "14" + (first "!" "^") + (follow "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@of" + "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" "}" ) + (seq _pathtail_1 term)) + (rule _pathtail_1 "14.1" + (first "!" "^") + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (alt "!" "^")) + (rule _pathtail_2 "14.2" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@of" + "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" "}" ) + (seq term)) + (rule pathitem "15" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt symbol BLANK_NODE_LABEL UVAR literal _pathitem_1 _pathitem_2 _pathitem_3)) + (rule _pathitem_1 "15.1" + (first "{") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "{" formulacontent "}")) + (rule _pathitem_10 "15.10" + (first "}") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "}")) + (rule _pathitem_11 "15.11" + (first "]") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "]")) + (rule _pathitem_12 "15.12" + (first ")") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq ")")) + (rule _pathitem_2 "15.2" + (first "[") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "[" propertylist "]")) + (rule _pathitem_3 "15.3" + (first "(") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "(" _pathitem_4 ")")) + (rule _pathitem_4 "15.4" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow ")") + (cleanup star) + (alt _empty _pathitem_5)) + (rule _pathitem_5 "15.5" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow ")") + (cleanup merge) + (seq term _pathitem_4)) + (rule _pathitem_6 "15.6" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" "}" ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq formulacontent "}")) + (rule _pathitem_7 "15.7" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq propertylist "]")) + (rule _pathitem_8 "15.8" + (first "(" ")" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER + IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq _pathitem_4 ")")) + (rule _pathitem_9 "15.9" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow ")") + (seq _pathitem_4)) + (rule NumericLiteral "16" + (first DECIMAL DOUBLE INTEGER) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt INTEGER DECIMAL DOUBLE)) + (rule String "17t" + (first STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF LANGTAG + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "^^" "{" "}" ) + (alt STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_LONG_QUOTE )) + (rule symbol "18" + (first IRIREF PNAME_LN) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt IRIREF PNAME_LN)) + (terminal IRIREF "18" (seq "<" (range "^#x00-#x20<>\"{}|^`] | UCHAR)* '>'"))) + (terminal INTEGER "19" (seq (opt (range "+-")) (plus (range "0-9")))) + (terminal DECIMAL "20" + (seq (opt (range "+-")) (seq (star (range "0-9")) "." (plus (range "0-9"))))) + (terminal DOUBLE "21" + (seq + (opt (range "+-")) + (alt + (seq (plus (range "0-9")) "." (star (range "0-9")) EXPONENT) + (seq "." (plus (range "0-9")) EXPONENT) + (seq (plus (range "0-9")) EXPONENT)) )) + (terminal STRING_LITERAL_QUOTE "22" + (seq "\"" (star (alt (range "^#x22#x5C#xA#xD") ECHAR UCHAR)) "\"")) + (terminal STRING_LITERAL_SINGLE_QUOTE "23" + (seq "'" (star (alt (range "^#x27#x5C#xA#xD") ECHAR UCHAR)) "'")) + (terminal STRING_LITERAL_LONG_SINGLE_QUOTE "24" + (seq "'''" (seq (opt (alt "'" "''")) (range "^'] | ECHAR | UCHAR ))* \"'''\"")))) + (terminal STRING_LITERAL_LONG_QUOTE "25" + (seq "\"\"\"" (seq (opt (alt "\"" "\"\"")) (range "^\"] | ECHAR | UCHAR ))* '\"\"\"'")))) + (terminal UCHAR "26" + (alt (seq "u" HEX HEX HEX HEX) (seq "U" HEX HEX HEX HEX HEX HEX HEX HEX))) + (terminal HEX "42" (alt (range "0-9") (range "A-F") (range "a-f"))) + (rule _RDFLiteral_5 "128s.5" + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq iri)) + (rule _RDFLiteral_4 "128s.4" + (first LANGTAG "^^" _eps) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq _RDFLiteral_1)) + (rule RDFLiteral "128s" + (first STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq String _RDFLiteral_1)) + (rule _RDFLiteral_1 "128s.1" + (first LANGTAG "^^" _eps) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (cleanup opt) + (alt _empty _RDFLiteral_2)) + (rule _RDFLiteral_3 "128s.3" + (first "^^") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "^^" iri)) + (rule _RDFLiteral_2 "128s.2" + (first LANGTAG "^^") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt LANGTAG _RDFLiteral_3)) + (rule BooleanLiteral "133s" + (first "@false" "@true") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt "@true" "@false")) + (terminal PNAME_NS "139s" (seq (opt PN_PREFIX) ":")) + (terminal PNAME_LN "140s" (seq PNAME_NS PN_LOCAL)) + (terminal BLANK_NODE_LABEL "141s" + (seq "_:" (alt PN_CHARS_U (range "0-9")) (opt (seq (star (alt PN_CHARS ".")) PN_CHARS)))) + (terminal LANGTAG "144s" + (seq "@" (plus (range "a-zA-Z")) (star (seq "-" (plus (range "a-zA-Z0-9")))))) + (terminal EXPONENT "154s" (seq (range "eE") (opt (range "+-")) (plus (range "0-9")))) + (terminal ECHAR "159s" (seq "\\" (range "tbnrf\"'"))) + (terminal PN_CHARS_BASE "163s" + (alt + (range "A-Z") + (range "a-z") + (range "#x00C0-#x00D6") + (range "#x00D8-#x00F6") + (range "#x00F8-#x02FF") + (range "#x0370-#x037D") + (range "#x037F-#x1FFF") + (range "#x200C-#x200D") + (range "#x2070-#x218F") + (range "#x2C00-#x2FEF") + (range "#x3001-#xD7FF") + (range "#xF900-#xFDCF") + (range "#xFDF0-#xFFFD") + (range "#x10000-#xEFFFF")) ) + (terminal PN_CHARS_U "164s" (alt PN_CHARS_BASE "_")) + (terminal PN_CHARS "166s" + (alt PN_CHARS_U "-" + (range "0-9") + (hex "#x00B7") + (range "#x0300-#x036F") + (range "#x203F-#x2040")) ) + (terminal PN_PREFIX "167s" + (seq PN_CHARS_BASE (opt (seq (star (alt PN_CHARS ".")) PN_CHARS)))) + (terminal PN_LOCAL "168s" + (seq + (alt PN_CHARS_U ":" (range "0-9") PLX) + (opt (seq (star (alt PN_CHARS "." ":" PLX)) (alt PN_CHARS ":" PLX)))) ) + (terminal PLX "169s" (alt PERCENT PN_LOCAL_ESC)) + (terminal PERCENT "170s" (seq "%" HEX HEX)) + (terminal PN_LOCAL_ESC "172s" + (seq "\\" + (alt "_" "~" "." "-" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" "/" "?" "#" + "@" "%" )) )) From cfc8750634cba4607d983b718231ccf1c08404f2 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 13 Jan 2019 15:31:38 -0800 Subject: [PATCH 12/40] Start work on reasoner with a simple implication test case working. --- Gemfile | 12 +- README.md | 23 +- lib/rdf/n3.rb | 14 +- lib/rdf/n3/algebra.rb | 51 +++++ lib/rdf/n3/algebra/formula.rb | 127 +++++++++++ lib/rdf/n3/algebra/listAppend.rb | 9 + lib/rdf/n3/algebra/listIn.rb | 9 + lib/rdf/n3/algebra/listLast.rb | 9 + lib/rdf/n3/algebra/listMember.rb | 7 + lib/rdf/n3/algebra/logChaff.rb | 7 + lib/rdf/n3/algebra/logConclusion.rb | 9 + lib/rdf/n3/algebra/logConjunction.rb | 9 + lib/rdf/n3/algebra/logEqualTo.rb | 7 + lib/rdf/n3/algebra/logImplies.rb | 56 +++++ lib/rdf/n3/algebra/logIncludes.rb | 13 ++ lib/rdf/n3/algebra/logNotEqualTo.rb | 7 + lib/rdf/n3/algebra/logNotIncludes.rb | 12 + lib/rdf/n3/algebra/logOutputString.rb | 7 + lib/rdf/n3/algebra/logRawType.rb | 9 + lib/rdf/n3/extensions.rb | 16 ++ lib/rdf/n3/reader.rb | 252 ++++++++++++++------- lib/rdf/n3/reader/parser.rb | 2 +- lib/rdf/n3/reasoner.rb | 99 ++++++++ lib/rdf/n3/vocab.rb | 11 +- rdf-n3.gemspec | 3 + script/parse | 100 +++++--- spec/reader_spec.rb | 61 ++++- spec/spec_helper.rb | 11 +- spec/test-files/append-ref.n3 | 5 + spec/test-files/append.n3 | 40 ++++ spec/test-files/bnode-conclude-ref.n3 | 4 + spec/test-files/bnodeConclude.n3 | 32 +++ spec/test-files/builtin_generated_match.n3 | 14 ++ spec/test-files/example-1.n3 | 16 ++ spec/test-files/example-2.n3 | 19 ++ spec/test-files/example-3.n3 | 2 + spec/test-files/foo.n3 | 14 ++ spec/test-files/last.n3 | 30 +++ spec/test-files/list-bug1.n3 | 13 ++ spec/test-files/list-bug2.n3 | 21 ++ spec/test-files/list-in-ref.n3 | 21 ++ spec/test-files/list-in.n3 | 13 ++ spec/test-files/manifest.n3 | 153 +++++++++++++ spec/test-files/path-1.n3 | 5 + spec/test-files/r1.n3 | 19 ++ spec/test-files/unify2.n3 | 21 ++ spec/test-files/unify3.n3 | 21 ++ spec/test-files/unify4.n3 | 25 ++ spec/test-files/unify5.n3 | 21 ++ spec/test_file_spec.rb | 139 ++++++++++++ 50 files changed, 1448 insertions(+), 152 deletions(-) create mode 100644 lib/rdf/n3/algebra.rb create mode 100644 lib/rdf/n3/algebra/formula.rb create mode 100644 lib/rdf/n3/algebra/listAppend.rb create mode 100644 lib/rdf/n3/algebra/listIn.rb create mode 100644 lib/rdf/n3/algebra/listLast.rb create mode 100644 lib/rdf/n3/algebra/listMember.rb create mode 100644 lib/rdf/n3/algebra/logChaff.rb create mode 100644 lib/rdf/n3/algebra/logConclusion.rb create mode 100644 lib/rdf/n3/algebra/logConjunction.rb create mode 100644 lib/rdf/n3/algebra/logEqualTo.rb create mode 100644 lib/rdf/n3/algebra/logImplies.rb create mode 100644 lib/rdf/n3/algebra/logIncludes.rb create mode 100644 lib/rdf/n3/algebra/logNotEqualTo.rb create mode 100644 lib/rdf/n3/algebra/logNotIncludes.rb create mode 100644 lib/rdf/n3/algebra/logOutputString.rb create mode 100644 lib/rdf/n3/algebra/logRawType.rb create mode 100644 lib/rdf/n3/reasoner.rb create mode 100644 spec/test-files/append-ref.n3 create mode 100644 spec/test-files/append.n3 create mode 100644 spec/test-files/bnode-conclude-ref.n3 create mode 100644 spec/test-files/bnodeConclude.n3 create mode 100644 spec/test-files/builtin_generated_match.n3 create mode 100644 spec/test-files/example-1.n3 create mode 100644 spec/test-files/example-2.n3 create mode 100644 spec/test-files/example-3.n3 create mode 100644 spec/test-files/foo.n3 create mode 100644 spec/test-files/last.n3 create mode 100644 spec/test-files/list-bug1.n3 create mode 100644 spec/test-files/list-bug2.n3 create mode 100644 spec/test-files/list-in-ref.n3 create mode 100644 spec/test-files/list-in.n3 create mode 100644 spec/test-files/manifest.n3 create mode 100644 spec/test-files/path-1.n3 create mode 100644 spec/test-files/r1.n3 create mode 100644 spec/test-files/unify2.n3 create mode 100644 spec/test-files/unify3.n3 create mode 100644 spec/test-files/unify4.n3 create mode 100644 spec/test-files/unify5.n3 create mode 100644 spec/test_file_spec.rb diff --git a/Gemfile b/Gemfile index 2b8358d..cb68666 100644 --- a/Gemfile +++ b/Gemfile @@ -5,11 +5,13 @@ gemspec gem "rdf", github: "ruby-rdf/rdf", branch: "develop" group :development do - gem "rdf-spec", github: "ruby-rdf/rdf-spec", branch: "develop" - gem "rdf-isomorphic", github: "ruby-rdf/rdf-isomorphic", branch: "develop" - gem "rdf-trig", github: "ruby-rdf/rdf-trig", branch: "develop" - gem "rdf-xsd", github: "ruby-rdf/rdf-xsd", branch: "develop" - gem "json-ld", github: "ruby-rdf/json-ld", branch: "develop" + gem "rdf-spec", github: "ruby-rdf/rdf-spec", branch: "develop" + gem "rdf-isomorphic", github: "ruby-rdf/rdf-isomorphic", branch: "develop" + gem "rdf-trig", github: "ruby-rdf/rdf-trig", branch: "develop" + gem "rdf-xsd", github: "ruby-rdf/rdf-xsd", branch: "develop" + gem "json-ld", github: "ruby-rdf/json-ld", branch: "develop" + gem 'sparql', github: "ruby-rdf/sparql", branch: "develop" + gem 'sxp', github: "dryruby/sxp.rb", branch: "develop" end group :debug do diff --git a/README.md b/README.md index c856a0c..1fc15bd 100755 --- a/README.md +++ b/README.md @@ -42,17 +42,20 @@ Write a graph to a file: ### Formulae N3 Formulae are introduced with the { statement-list } syntax. A given formula is assigned an RDF::Node instance, which is also used as the graph_name for RDF::Statement instances provided to RDF::N3::Reader#each_statement. For example, the following N3 generates the associated statements: - { [ x:firstname "Ora" ] dc:wrote [ dc:title "Moby Dick" ] } a n3:falsehood . + @prefix x: . + @prefix log: . + @prefix dc: . + + { [ x:firstname "Ora" ] dc:wrote [ dc:title "Moby Dick" ] } a log:falsehood . -results in - - f = RDF::Node.new - s = RDF::Node.new - o = RDF::Node.new - RDF::Statement(f, rdf:type n3:falsehood) - RDF::Statement(s, x:firstname, "Ora", graph_name: f) - RDF::Statement(s, dc:wrote, o, graph_name: f) - RDF::Statement(o, dc:title, "Moby Dick", graph_name: f) +when turned into an RDF Repository results in the following quads + + _:form . + _:moby "Moby Dick" _:form . + _:ora _:moby _:form . + _:ora "Ora" _:form . + +Reasoning requires the use of the Notation3 Algebra, rather than an `RDF::Repository`. This implementation considers formulae to be patterns, which may be asserted on statements made in the default graph, possibly loaded from a separate file. The logical relationships are reduced to algebraic operators. So, for example, the following ### Variables N3 Variables are introduced with @forAll, @forEach, or ?x. Variables reference URIs described in formulae, typically defined in the default vocabulary (e.g., ":x"). Existential variables are replaced with an allocated RDF::Node instance. Universal variables are replaced with a RDF::Query::Variable instance. For example, the following N3 generates the associated statements: diff --git a/lib/rdf/n3.rb b/lib/rdf/n3.rb index c60c454..bc808ac 100644 --- a/lib/rdf/n3.rb +++ b/lib/rdf/n3.rb @@ -1,6 +1,5 @@ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..'))) require 'rdf' -require 'rdf/n3/extensions' module RDF ## @@ -21,13 +20,16 @@ module RDF # # @author [Gregg Kellogg](http://greggkellogg.net/) module N3 + require 'rdf/n3/algebra' require 'rdf/n3/format' require 'rdf/n3/vocab' + require 'rdf/n3/extensions' require 'rdf/n3/patches/array_hacks' - autoload :Meta, 'rdf/n3/reader/meta' - autoload :Parser, 'rdf/n3/reader/parser' - autoload :Reader, 'rdf/n3/reader' - autoload :VERSION, 'rdf/n3/version' - autoload :Writer, 'rdf/n3/writer' + autoload :Meta, 'rdf/n3/reader/meta' + autoload :Parser, 'rdf/n3/reader/parser' + autoload :Reader, 'rdf/n3/reader' + autoload :Reasoner, 'rdf/n3/reasoner' + autoload :VERSION, 'rdf/n3/version' + autoload :Writer, 'rdf/n3/writer' end end \ No newline at end of file diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb new file mode 100644 index 0000000..fd35b36 --- /dev/null +++ b/lib/rdf/n3/algebra.rb @@ -0,0 +1,51 @@ +$:.unshift(File.expand_path("../..", __FILE__)) +require 'sparql/algebra' +require 'sxp' + +module RDF::N3 + # Based on the SPARQL Algebra, operators for executing a patch + # + # @author [Gregg Kellogg](http://greggkellogg.net/) + module Algebra + autoload :Formula, 'rdf/n3/algebra/formula' + + autoload :ListAppend, 'rdf/n3/algebra/listAppend' + autoload :ListIn, 'rdf/n3/algebra/listIn' + autoload :ListLast, 'rdf/n3/algebra/listLast' + autoload :ListMember, 'rdf/n3/algebra/listMember' + + autoload :LogChaff, 'rdf/n3/algebra/logChaff' + autoload :LogConclusion, 'rdf/n3/algebra/logConclusion' + autoload :LogConjunction, 'rdf/n3/algebra/logConjunction' + autoload :LogEqualTo, 'rdf/n3/algebra/logEqualTo' + autoload :LogImplies, 'rdf/n3/algebra/logImplies' + autoload :LogIncludes, 'rdf/n3/algebra/logIncludes' + autoload :LogNotEqualTo, 'rdf/n3/algebra/logNotEqualTo' + autoload :LogNotIncludes, 'rdf/n3/algebra/logNotIncludes' + autoload :LogOutputString, 'rdf/n3/algebra/logOutputString' + autoload :LogRawType, 'rdf/n3/algebra/logRawType' + + def for(uri) + { + RDF::N3::List.append => ListAppend, + RDF::N3::List.in => ListIn, + RDF::N3::List.last => ListLast, + RDF::N3::List.member => ListMember, + + RDF::N3::Log.chaff => LogChaff, + RDF::N3::Log.conclusion => LogConclusion, + RDF::N3::Log.conjunction => LogConjunction, + RDF::N3::Log.equalTo => LogEqualTo, + RDF::N3::Log.implies => LogImplies, + RDF::N3::Log.includes => LogIncludes, + RDF::N3::Log.notEqualTo => LogNotEqualTo, + RDF::N3::Log.notIncludes => LogNotIncludes, + RDF::N3::Log.outputString => LogOutputString, + RDF::N3::Log.rawType => LogRawType, + }[uri] + end + module_function :for + end +end + + diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb new file mode 100644 index 0000000..dc38c02 --- /dev/null +++ b/lib/rdf/n3/algebra/formula.rb @@ -0,0 +1,127 @@ +require 'rdf' + +module RDF::N3::Algebra + # + # A Notation3 Formula combines a graph with a BGP query. + class Formula < SPARQL::Algebra::Operator + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + attr_accessor :query + + NAME = [:formula] + + ## + # Yields solutions from patterns and other operands. Solutions are created by evaluating each pattern and other sub-operand against `queryable`. + # + # @param [RDF::Queryable] queryable + # the graph or repository to query + # @param [Hash{Symbol => Object}] options + # any additional keyword options + # @option options [RDF::Query::Solutions] solutions + # optional initial solutions for chained queries + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + # @return [RDF::Solutions] distinct solutions + def execute(queryable, **options, &block) + log_debug {"formula #{graph_name} #{operands.to_sxp}"} + + patterns = operands.select {|op| op.is_a?(RDF::Statement) && op.variable?} + sub_ops = operands.reject {|op| op.is_a?(RDF::Statement)} + + query = RDF::Query.new(patterns) + @solutions = queryable.query(query, **options) + + # Join solutions from other operands + log_depth do + sub_ops.each do |op| + old_solutions, @solutions = @solutions, RDF::Query::Solutions.new + + ## XXX consider if this scope introduces new variables with same names + op.execute(queryable, bindings: old_solutions.bindings) do |soln| + log_debug {"(formula execute object) => #{soln.inspect}"} + @solutions << soln + end + end + end + @solutions.distinct! + log_debug {"(formula solutions) #{@solutions.inspect}"} + @solutions + end + + ## + # Yields each statement from this formula bound to previously determined solutions. + # + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + def each(&block) + log_debug {"formula #{graph_name} each #{@solutions.inspect}"} + constants = operands.select {|op| op.is_a?(RDF::Statement) && op.constant?} + patterns = operands.select {|op| op.is_a?(RDF::Statement) && op.variable?} + sub_ops = operands.reject {|op| op.is_a?(RDF::Statement)} + + # Yield constant statements/patterns + constants.each do |pattern| + log_debug {"(formula constant) #{pattern.to_sxp}"} + block.call(RDF::Statement.from(pattern)) + end + + # Yield patterns by binding variables + @solutions.each do |solution| + log_debug {"(formula apply) #{solution.inspect} to BGP"} + # Yield each variable statement which is constant after applying solution + patterns.each do |pattern| + terms = {} + [:subject, :predicate, :object].each do |r| + terms[r] = case o = pattern.send(r) + when RDF::Query::Variable then solution[o] + else o + end + end + + statement = RDF::Statement.from(terms) + + # Sanity checking on statement + if statement.subject.nil? || statement.predicate.nil? || statement.object.nil? || + statement.subject.literal? || statement.predicate.literal? || + statement.subject.is_a?(SPARQL::Algebra::Operator) || + statement.object.is_a?(SPARQL::Algebra::Operator) + log_debug {"(formula skip) #{statement.to_sxp}"} + next + end + + log_debug {"(formula add) #{statement.to_sxp}"} + block.call(statement) + end + end + + # statements from sub-operands + log_depth {sub_ops.each {|op| op.each(&block)}} + end + + # Set solutions + # @param [RDF::Query::Solutions] solutions + def solutions=(solutions) + @solutions = solutions + end + + # Graph name associated with this formula + # @return [RDF::Resource] + def graph_name; @options[:graph_name]; end + + def to_sxp_bin + @existentials = ndvars.uniq + @universals = vars.uniq - @existentials + [:formula, graph_name].compact + + (Array(universals).empty? ? [] : [universals.unshift(:universals)]) + + (Array(existentials).empty? ? [] : [existentials.unshift(:existentials)]) + + operands.map(&:to_sxp_bin) + end + end +end diff --git a/lib/rdf/n3/algebra/listAppend.rb b/lib/rdf/n3/algebra/listAppend.rb new file mode 100644 index 0000000..068aae7 --- /dev/null +++ b/lib/rdf/n3/algebra/listAppend.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra + ## + # Any statement mentioning anything in this class is considered boring and purged by the cwm --purge option. This is a convenience, and does not have any value when published as a general fact on the web. + class ListAppend < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :listAppend + end +end diff --git a/lib/rdf/n3/algebra/listIn.rb b/lib/rdf/n3/algebra/listIn.rb new file mode 100644 index 0000000..c5a0fd4 --- /dev/null +++ b/lib/rdf/n3/algebra/listIn.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra + ## + # Any statement mentioning anything in this class is considered boring and purged by the cwm --purge option. This is a convenience, and does not have any value when published as a general fact on the web. + class ListIn < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :listIn + end +end diff --git a/lib/rdf/n3/algebra/listLast.rb b/lib/rdf/n3/algebra/listLast.rb new file mode 100644 index 0000000..f6c4541 --- /dev/null +++ b/lib/rdf/n3/algebra/listLast.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra + ## + # Any statement mentioning anything in this class is considered boring and purged by the cwm --purge option. This is a convenience, and does not have any value when published as a general fact on the web. + class ListLast < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :listLast + end +end diff --git a/lib/rdf/n3/algebra/listMember.rb b/lib/rdf/n3/algebra/listMember.rb new file mode 100644 index 0000000..3723eb3 --- /dev/null +++ b/lib/rdf/n3/algebra/listMember.rb @@ -0,0 +1,7 @@ +module RDF::N3::Algebra + ## + # Any statement mentioning anything in this class is considered boring and purged by the cwm --purge option. This is a convenience, and does not have any value when published as a general fact on the web. + class ListMember < SPARQL::Algebra::Operator::Binary + NAME = :listMember + end +end diff --git a/lib/rdf/n3/algebra/logChaff.rb b/lib/rdf/n3/algebra/logChaff.rb new file mode 100644 index 0000000..66a9176 --- /dev/null +++ b/lib/rdf/n3/algebra/logChaff.rb @@ -0,0 +1,7 @@ +module RDF::N3::Algebra + ## + # Any statement mentioning anything in this class is considered boring and purged by the cwm --purge option. This is a convenience, and does not have any value when published as a general fact on the web. + class LogChaff < SPARQL::Algebra::Operator::Binary + NAME = :logChaff + end +end diff --git a/lib/rdf/n3/algebra/logConclusion.rb b/lib/rdf/n3/algebra/logConclusion.rb new file mode 100644 index 0000000..29f036b --- /dev/null +++ b/lib/rdf/n3/algebra/logConclusion.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra + ## + # All possible conclusions which can be drawn from a formula. + # + # The object of this function, a formula, is the set of conclusions which can be drawn from the subject formula, by successively applying any rules it contains to the data it contains. This is equivalent to cwm's "--think" command line function. It does use built-ins, so it may for example indirectly invoke other documents, validate signatures, etc. + class LogConclusion < SPARQL::Algebra::Operator::Binary + NAME = :logConclusion + end +end diff --git a/lib/rdf/n3/algebra/logConjunction.rb b/lib/rdf/n3/algebra/logConjunction.rb new file mode 100644 index 0000000..f6c1c1e --- /dev/null +++ b/lib/rdf/n3/algebra/logConjunction.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra + ## + # A function to merge formulae: logical AND. + # + # The subject is a list of formulae. The object, which can be generated, is a formula containing a copy of each of the formulae in the list on the left. A cwm built-in function. + class LogConjunction < SPARQL::Algebra::Operator::Binary + NAME = :logConjunction + end +end diff --git a/lib/rdf/n3/algebra/logEqualTo.rb b/lib/rdf/n3/algebra/logEqualTo.rb new file mode 100644 index 0000000..6e96d1a --- /dev/null +++ b/lib/rdf/n3/algebra/logEqualTo.rb @@ -0,0 +1,7 @@ +module RDF::N3::Algebra + ## + # True if the subject and object are the same RDF node (symbol or literal). Do not confuse with owl:sameAs. A cwm built-in logical operator, RDF graph level. + class LogEqualTo < SPARQL::Algebra::Operator::Binary + NAME = :logEqualTo + end +end diff --git a/lib/rdf/n3/algebra/logImplies.rb b/lib/rdf/n3/algebra/logImplies.rb new file mode 100644 index 0000000..8647199 --- /dev/null +++ b/lib/rdf/n3/algebra/logImplies.rb @@ -0,0 +1,56 @@ +module RDF::N3::Algebra + ## + # Logical implication. + # + # This is the relation between the antecedent (subject) and conclusion (object) of a rule. The application of a rule to a knowledge-base is as follows. For every substitution which, applied to the antecedent, gives a formula which is a subset of the knowledge-base, then the result of applying that same substitution to the conclusion may be added to the knowledge-base. + # + # related: See log:conclusion. + class LogImplies < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :logImplies + + # Yields solutions from subject. Solutions are created by evaluating subject against `queryable`. + # + # @param [RDF::Queryable] queryable + # the graph or repository to query + # @param [Hash{Symbol => Object}] options + # any additional keyword options + # @option options [RDF::Query::Solutions] solutions + # optional initial solutions for chained queries + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + # @return [RDF::Solutions] distinct solutions + def execute(queryable, **options, &block) + log_debug {"logImplies #{operands.to_sxp}"} + @solutions = log_depth {operands.first.execute(queryable, **options, &block)} + log_debug {"(logImplies solutions) #{@solutions.inspect}"} + @solutions + end + + ## + # Yields statements from the object based on solutions determined from the subject. Each solution formed by querying `queryable` from the subject is used to create a graph, which must be a subgraph of `queryable`. If so, that solution is used to generate triples from the object formula which are yielded. + # + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + def each(&block) + log_debug {"logImplies each #{@solutions.inspect}"} + _, object = operands + + # Use solutions from subject for object + log_depth do + object.solutions = @solutions + + # Yield statements + object.each(&block) + end + end + end +end diff --git a/lib/rdf/n3/algebra/logIncludes.rb b/lib/rdf/n3/algebra/logIncludes.rb new file mode 100644 index 0000000..f3633b4 --- /dev/null +++ b/lib/rdf/n3/algebra/logIncludes.rb @@ -0,0 +1,13 @@ +module RDF::N3::Algebra + ## + # The subject formula includes the object formula. + # + # Formula A includes formula B if there exists some substitution which when applied to B creates a formula B' such that for every statement in B' is also in A, every variable universally (or existentially) quantified in B' is quantified in the same way in A. + # + # Variable substitution is applied recursively to nested compound terms such as formulae, lists and sets. + # + # (Understood natively by cwm when in in the antecedent of a rule. You can use this to peer inside nested formulae.) + class LogIncludes < SPARQL::Algebra::Operator::Binary + NAME = :logIncludes + end +end diff --git a/lib/rdf/n3/algebra/logNotEqualTo.rb b/lib/rdf/n3/algebra/logNotEqualTo.rb new file mode 100644 index 0000000..5aa4b2e --- /dev/null +++ b/lib/rdf/n3/algebra/logNotEqualTo.rb @@ -0,0 +1,7 @@ +module RDF::N3::Algebra + ## + # Equality in this sense is actually the same URI. A cwm built-in logical operator. + class LogNotEqualTo < SPARQL::Algebra::Operator::Binary + NAME = :logNotEqualTo + end +end diff --git a/lib/rdf/n3/algebra/logNotIncludes.rb b/lib/rdf/n3/algebra/logNotIncludes.rb new file mode 100644 index 0000000..a3260c8 --- /dev/null +++ b/lib/rdf/n3/algebra/logNotIncludes.rb @@ -0,0 +1,12 @@ +module RDF::N3::Algebra + ## + # The object formula is NOT a subset of subject. True iff log:includes is false. The converse of log:includes. + # (Understood natively by cwm. The subject formula may contain variables.) + # + # (In cwm, variables must of course end up getting bound before the log:include test can be done, or an infinite result set would result) + # + # Related: See includes + class LogNotIncludes < SPARQL::Algebra::Operator::Binary + NAME = :logNotIncludes + end +end diff --git a/lib/rdf/n3/algebra/logOutputString.rb b/lib/rdf/n3/algebra/logOutputString.rb new file mode 100644 index 0000000..cd51786 --- /dev/null +++ b/lib/rdf/n3/algebra/logOutputString.rb @@ -0,0 +1,7 @@ +module RDF::N3::Algebra + ## + # The subject is a key and the object is a string, where the strings are to be output in the order of the keys. + class LogOutputString < SPARQL::Algebra::Operator::Binary + NAME = :logOutputString + end +end diff --git a/lib/rdf/n3/algebra/logRawType.rb b/lib/rdf/n3/algebra/logRawType.rb new file mode 100644 index 0000000..43b5e12 --- /dev/null +++ b/lib/rdf/n3/algebra/logRawType.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra + ## + # This is a low-level language type, one of log:Formula, log:Literal, log:List, log:Set or log:Other. + # + # Example: log:semanticsOrError returns either a formula or a string, and you can check which using log:rawType. + class LogRawType < SPARQL::Algebra::Operator::Binary + NAME = :logRawType + end +end diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index e349fcd..185e90e 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -12,4 +12,20 @@ module Enumerable # @return [Array] attr_accessor :universals end + + class Statement + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + [(variable? ? :pattern : :triple), subject, predicate, object] + end + + ## + # Returns an S-Expression (SXP) representation + # + # @return [String] + def to_sxp + to_sxp_bin.to_sxp + end + end end diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index bac2989..8caae1f 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -20,22 +20,13 @@ class Reader < RDF::Reader include RDF::Util::Logger include Meta include Parser - + N3_KEYWORDS = %w(a is of has keywords prefix base true false forSome forAny) # The Blank nodes allocated for formula # @return [Array] attr_reader :formulae - # The Variables allocated, along with their in-scope formula - VarInfo = Struct.new(:var, :formula) - - # The allocated variables, including their formulae - # - # Variables are indexed by variable name - # @return [Hash{String => {Hash{Symbol => RDF::Term}}}] - attr_reader :variables - ## # Initializes the N3 reader instance. # @@ -59,15 +50,14 @@ class Reader < RDF::Reader # @raise [Error]:: Raises RDF::ReaderError if validating and an error is found def initialize(input = $stdin, options = {}, &block) super do - @options = {log_depth: 0}.merge(@options) input.rewind if input.respond_to?(:rewind) @input = input.respond_to?(:read) ? input : StringIO.new(input.to_s) @lineno = 0 readline # Prime the pump - + @memo = {} @keyword_mode = false - @keywords = %w(a is of this has) + @keywords = %w(a is of this has).map(&:freeze).freeze @productions = [] @prod_data = [] @@ -76,15 +66,26 @@ def initialize(input = $stdin, options = {}, &block) @formulae = [] # Nodes used as Formulae graph names @formulae_nodes = {} - @variables = {} # variable definitions along with defining formula + @variables = {} # allocated variables by formula if options[:base_uri] - log_debug("@uri") { base_uri.inspect} + log_info("@uri") { base_uri.inspect} namespace(nil, uri("#{base_uri}#")) end - log_debug("validate") {validate?.inspect} - log_debug("canonicalize") {canonicalize?.inspect} - log_debug("intern") {intern?.inspect} + + # Prepopulate operator namespaces unless validating + unless validate? + namespace(:crypto, RDF::N3::Crypto) + namespace(:list, RDF::N3::List) + namespace(:log, RDF::N3::Log) + namespace(:math, RDF::N3::Math) + namespace(:rei, RDF::N3::Rei) + #namespace(:string, RDF::N3::String) + namespace(:time, RDF::N3::Time) + end + log_info("validate") {validate?.inspect} + log_info("canonicalize") {canonicalize?.inspect} + log_info("intern") {intern?.inspect} if block_given? case block.arity @@ -136,7 +137,7 @@ def each_statement if r.bound? r.value elsif r.distinguished? - process_uri(r.name.to_s) + RDF::URI(r.name.to_s) else bnode(r.name) end @@ -153,7 +154,7 @@ def each_statement end enum_for(:each_statement) end - + ## # Iterates the given block for each RDF triple in the input. # @@ -170,21 +171,75 @@ def each_triple end enum_for(:each_triple) end - + + ## + # Returns the top-level formula for this file + # + # @return [RDF::N3::Algebra::Formula] + def formula + # SPARQL used for SSE and algebra functionality + require 'sparql' unless defined?(:SPARQL) + + @formula ||= begin + formulae = {} + + # Add patterns to appropiate formula based on graph_name, + # and replace subject and object bnodes which identify + # named graphs with those formula + each_pattern do |pattern| + # A graph name indicates a formula. If not already allocated, create a new formula and use that for inserting statements or other operators + form = formulae[pattern.graph_name] ||= begin + Algebra::Formula.new(graph_name: pattern.graph_name, **@options) + end + + # Formulae may be the subject or object of a known operator + if klass = Algebra.for(pattern.predicate) + fs = formulae.fetch(pattern.subject, pattern.subject) + fo = formulae.fetch(pattern.object, pattern.object) + form.operands << klass.new(fs, fo, **@options) + else + # Add formulae as direct operators + if formulae.has_key?(pattern.subject) + form.operands << formulae[pattern.subject] + end + if formulae.has_key?(pattern.object) + form.operands << formulae[pattern.object] + end + pattern.graph_name = nil + form.operands << pattern + end + end + end + + # Formula is that without a graph name + formulae[nil] + end + + ## + # Returns the SPARQL S-Expression (SSE) representation of the parsed dataset. + # Formulae are represented as subjects and objects in the containing graph, along with their universals and existentials + # + # @return [Array] `self` + # @see http://openjena.org/wiki/SSE + def to_sxp_bin + formula.to_sxp_bin + end + protected # Start of production def onStart(prod) handler = "#{prod}Start".to_sym - log_debug("#{handler}(#{respond_to?(handler, true)})", prod, depth: depth) + log_info("#{handler}(#{respond_to?(handler, true)})", prod, depth: depth) @productions << prod send(handler, prod) if respond_to?(handler, true) + end # End of production def onFinish prod = @productions.pop() handler = "#{prod}Finish".to_sym - log_debug("#{handler}(#{respond_to?(handler, true)})", depth: depth) {"#{prod}: #{@prod_data.last.inspect}"} + log_info("#{handler}(#{respond_to?(handler, true)})", depth: depth) {"#{prod}: #{@prod_data.last.inspect}"} send(handler) if respond_to?(handler, true) end @@ -193,22 +248,22 @@ def onToken(prod, tok) unless @productions.empty? parentProd = @productions.last handler = "#{parentProd}Token".to_sym - log_debug("#{handler}(#{respond_to?(handler, true)})", depth: depth) {"#{prod}, #{tok}: #{@prod_data.last.inspect}"} + log_info("#{handler}(#{respond_to?(handler, true)})", depth: depth) {"#{prod}, #{tok}: #{@prod_data.last.inspect}"} send(handler, prod, tok) if respond_to?(handler, true) else error("Token has no parent production") end end - + def booleanToken(prod, tok) lit = RDF::Literal.new(tok.delete("@"), datatype: RDF::XSD.boolean, validate: validate?, canonicalize: canonicalize?) add_prod_data(:literal, lit) end - + def declarationStart(prod) @prod_data << {} end - + def declarationToken(prod, tok) case prod when "@prefix", "@base", "@keywords" @@ -232,13 +287,13 @@ def declarationFinish # Base, set or update document URI uri = decl[:explicituri] options[:base_uri] = process_uri(uri) - + # The empty prefix "" is by default , bound to "#" -- the local namespace of the file. # The parser behaves as though there were a # @prefix : <#>. # just before the file. # This means that <#foo> can be written :foo and using @keywords one can reduce that to foo. - + namespace(nil, uri.match(/[\/\#]$/) ? base_uri : process_uri("#{uri}#")) log_debug("declarationFinish[@base]", depth: depth) {"@base=#{base_uri}"} when "@keywords" @@ -252,17 +307,17 @@ def declarationFinish error("declarationFinish: FIXME #{decl.inspect}") end end - + # Document start, instantiate def documentStart(prod) @formulae.push(nil) @prod_data << {} end - + def dtlangToken(prod, tok) add_prod_data(:langcode, tok) if prod == "langcode" end - + def existentialStart(prod) @prod_data << {} end @@ -277,19 +332,19 @@ def existentialFinish pd = @prod_data.pop forSome = Array(pd[:symbol]) forSome.each do |term| - # Blank nodes are scoped to the document - @variables[term.to_s] = VarInfo.new(univar(term, distinguished: false)) + var = univar(term, distinguished: false) + add_var_to_formula(@formulae.last, var) end end - + def expressionStart(prod) @prod_data << {} end - + # Process path items, and push on the last object for parent processing def expressionFinish expression = @prod_data.pop - + # If we're in teh middle of a pathtail, append if @prod_data.last[:pathtail] && expression[:pathitem] && expression[:pathtail] path_list = [expression[:pathitem]] + expression[:pathtail] @@ -307,31 +362,31 @@ def expressionFinish error("expressionFinish: FIXME #{expression.inspect}") end end - + def literalStart(prod) @prod_data << {} end - + def literalToken(prod, tok) tok = tok[0, 3] == '"""' ? tok[3..-4] : tok[1..-2] add_prod_data(:string, tok) end - + def literalFinish lit = @prod_data.pop content = RDF::NTriples.unescape(lit[:string]) language = lit[:langcode] if lit[:langcode] language = language.downcase if language && canonicalize? datatype = lit[:symbol] - + lit = RDF::Literal.new(content, language: language, datatype: datatype, validate: validate?, canonicalize: canonicalize?) add_prod_data(:literal, lit) end - + def objectStart(prod) @prod_data << {} end - + def objectFinish object = @prod_data.pop if object[:expression] @@ -340,11 +395,11 @@ def objectFinish error("objectFinish: FIXME #{object.inspect}") end end - + def pathitemStart(prod) @prod_data << {} end - + def pathitemToken(prod, tok) case prod when "numericliteral" @@ -354,15 +409,19 @@ def pathitemToken(prod, tok) when /\./ then RDF::XSD.decimal else RDF::XSD.integer end - + lit = RDF::Literal.new(nl, datatype: datatype, validate: validate?, canonicalize: canonicalize?) add_prod_data(:literal, lit) when "quickvariable" # There is a also a shorthand syntax ?x which is the same as :x except that it implies that x is # universally quantified not in the formula but in its parent formula uri = process_qname(tok.sub('?', ':')) - @variables[uri.to_s] = VarInfo.new(univar(uri), @formulae[-2]) - add_prod_data(:symbol, uri) + var = uri.variable? ? uri : univar(uri) + add_var_to_formula(@formulae[-2], var) + # Also add var to this formula + add_var_to_formula(@formulae.last, var) + + add_prod_data(:symbol, var) when "boolean" lit = RDF::Literal.new(tok.delete("@"), datatype: RDF::XSD.boolean, validate: validate?, canonicalize: canonicalize?) add_prod_data(:literal, lit) @@ -378,6 +437,12 @@ def pathitemToken(prod, tok) node = RDF::Node.new @formulae << node @formulae_nodes[node] = true + + # Promote variables defined on the earlier formula to this formula + @variables[node] = {} + @variables[@formulae[-2]].each do |name, vars| + @variables[node][name] = vars.dup + end when "}" # Pop off the formula formula = @formulae.pop @@ -399,11 +464,11 @@ def pathitemFinish error("pathitemFinish: FIXME #{pathitem.inspect}") end end - + def pathlistStart(prod) @prod_data << {pathlist: []} end - + def pathlistFinish pathlist = @prod_data.pop # Flatten propertylist into an array @@ -411,11 +476,11 @@ def pathlistFinish add_prod_data(:pathlist, expr) if expr add_prod_data(:pathlist, pathlist[:pathlist]) if pathlist[:pathlist] end - + def pathtailStart(prod) @prod_data << {pathtail: []} end - + def pathtailToken(prod, tok) case tok when "!", "." @@ -424,33 +489,33 @@ def pathtailToken(prod, tok) add_prod_data(:direction, :reverse) end end - + def pathtailFinish pathtail = @prod_data.pop add_prod_data(:pathtail, pathtail[:pathtail]) add_prod_data(:direction, pathtail[:direction]) if pathtail[:direction] add_prod_data(:directiontail, pathtail[:directiontail]) if pathtail[:directiontail] end - + def propertylistStart(prod) @prod_data << {} end - + def propertylistFinish propertylist = @prod_data.pop # Flatten propertylist into an array ary = [propertylist, propertylist.delete(:propertylist)].flatten.compact @prod_data.last[:propertylist] = ary end - + def simpleStatementStart(prod) @prod_data << {} end - + # Completion of Simple Statement, all productions include :subject, and :propertyList def simpleStatementFinish statement = @prod_data.pop - + subject = statement[:subject] properties = Array(statement[:propertylist]) properties.each do |p| @@ -472,17 +537,17 @@ def simpleStatementFinish def subjectStart(prod) @prod_data << {} end - + def subjectFinish subject = @prod_data.pop - + if subject[:expression] add_prod_data(:subject, subject[:expression]) else error("unknown expression type") end end - + def symbolToken(prod, tok) term = case prod when 'explicituri' @@ -492,7 +557,7 @@ def symbolToken(prod, tok) else error("symbolToken(#{prod}, #{tok}): FIXME #{term.inspect}") end - + add_prod_data(:symbol, term) end @@ -510,21 +575,21 @@ def universalFinish pd = @prod_data.pop forAll = Array(pd[:symbol]) forAll.each do |term| - @variables[term.to_s] = VarInfo.new(univar(term), @formulae.last) + add_var_to_formula(@formulae.last, univar(term)) end end def verbStart(prod) @prod_data << {} end - + def verbToken(prod, tok) term = case prod when '<=' - add_prod_data(:expression, RDF::LOG.implies) + add_prod_data(:expression, RDF::N3::Log.implies) add_prod_data(:invert, true) when '=>' - add_prod_data(:expression, RDF::LOG.implies) + add_prod_data(:expression, RDF::N3::Log.implies) when '=' add_prod_data(:expression, RDF::OWL.sameAs) when '@a' @@ -536,7 +601,7 @@ def verbToken(prod, tok) else error("verbToken(#{prod}, #{tok}): FIXME #{term.inspect}") end - + add_prod_data(:symbol, term) end @@ -551,16 +616,16 @@ def verbFinish error("verbFinish: FIXME #{verb.inspect}") end end - + private - + ################### # Utility Functions ################### def process_anonnode(anonnode) log_debug("process_anonnode", depth: depth) {anonnode.inspect} - + if anonnode[:propertylist] properties = anonnode[:propertylist] bnode = RDF::Node.new @@ -568,7 +633,13 @@ def process_anonnode(anonnode) predicate = p[:verb] log_debug("process_anonnode(verb)", depth: depth) {predicate.inspect} objects = Array(p[:object]) - objects.each { |object| add_statement("anonnode", bnode, predicate, object) } + objects.each do |object| + if p[:invert] + add_statement("anonnode", object, predicate, bnode) + else + add_statement("anonnode", bnode, predicate, object) + end + end end bnode elsif anonnode[:pathlist] @@ -593,7 +664,7 @@ def process_path(expression) pathitem = expression[:pathitem] pathtail = expression[:pathtail] - + direction_list = [expression[:direction], expression[:directiontail]].flatten.compact pathtail.each do |pred| @@ -612,7 +683,7 @@ def process_path(expression) def process_uri(uri) uri(base_uri, RDF::NTriples.unescape(uri)) end - + def process_qname(tok) if tok.include?(":") prefix, name = tok.split(":") @@ -639,7 +710,8 @@ def process_qname(tok) log_debug('process_qname(bnode)', name, depth: depth) # If we're in a formula, create a non-distigushed variable instead if @formulae.last - univar(name, distinguished: false) + var = find_var(@formulae.last, name) || univar(term) + add_var_to_formula(formulae.last, var) else bnode(name) end @@ -651,7 +723,7 @@ def process_qname(tok) log_debug('process_qname', depth: depth) {uri.inspect} uri end - + # Add values to production data, values aranged as an array def add_prod_data(sym, value) case @prod_data.last[sym] @@ -714,7 +786,7 @@ def keyword_check(kw) raise RDF::ReaderError, "unqualified keyword '#{kw}' used without @keyword directive" if validate? end end - + # Create URIs def uri(value, append = nil) value = RDF::URI(value) @@ -722,20 +794,36 @@ def uri(value, append = nil) value.validate! if validate? && value.respond_to?(:validate) value.canonicalize! if canonicalize? value = RDF::URI.intern(value, {}) if intern? - - # Variable substitution for in-scope variables. Variables are in scope if they are defined in anthing other than - # the current formula - var = @variables[value.to_s] - value = var.var if var + + # Variable substitution for in-scope variables. Variables are in scope if they are defined in anthing other than the current formula + var = find_var(@formulae.last, value) + value = var if var value end - + def ns(prefix, suffix) base = prefix(prefix).to_s suffix = suffix.to_s.sub(/^\#/, "") if base.index("#") log_debug("ns", depth: depth) {"base: '#{base}', suffix: '#{suffix}'"} uri(base + suffix.to_s) end + + # Find any variable that may be defined in the formula identified by `bn` + # @param [RDF::Node] bn + # @param [#to_s] name + # @return [RDF::Query::Variable] + def find_var(bn, name) + ((@variables[bn] ||= {})[name.to_s] ||= []).last + end + + # Add a variable to the formula identified by `bn`, returning the formula + # @param [RDF::Node] bn + # @param [RDF::Query::Variable] var + # @return [RDF::Query::Variable] + def add_var_to_formula(bn, var) + ((@variables[bn] ||= {})[var.name.to_s] ||= []) << var + var + end end end diff --git a/lib/rdf/n3/reader/parser.rb b/lib/rdf/n3/reader/parser.rb index a502622..903ea32 100644 --- a/lib/rdf/n3/reader/parser.rb +++ b/lib/rdf/n3/reader/parser.rb @@ -20,7 +20,7 @@ def parse(prod) if todo_stack.last[:terms].nil? todo_stack.last[:terms] = [] tok = self.token - #log_debug("parse tok: '#{tok}'", depth: depth) {"prod #{todo_stack.last[:prod]}"} + log_debug("parse tok: '#{tok}'", depth: depth) {"prod #{todo_stack.last[:prod]}"} # Got an opened production onStart(abbr(todo_stack.last[:prod])) diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb new file mode 100644 index 0000000..d00f6a4 --- /dev/null +++ b/lib/rdf/n3/reasoner.rb @@ -0,0 +1,99 @@ +# coding: utf-8 +module RDF::N3 + ## + # A Notation-3/Turtle reasoner in Ruby + # + # Takes a parsed Notation-3 input and performs reasoning to implement CWM-like interface + # + # @author [Gregg Kellogg](http://greggkellogg.net/) + class Reasoner + include RDF::Util::Logger + # The top-level parsed formula + # @return [RDF::N3::Algebra::Formula] + attr_reader :formula + + # Opens a Notation-3 file, and parses it to initialize the reasoner + # + # @param [String, #to_s] filename + # @yield [reasoner] `self` + # @yieldparam [RDF::N3::Reasoner] reasoner + # @yieldreturn [void] ignored + # @return [RDF::N3::Reasoner] + def self.open(file) + RDF::N3::Reader.open(file, **options) do |reader| + RDF::N3::Reasoner.new(reader.to_sxp_bin, **options, &block) + end + end + + ## + # Initializes a new reasoner. If input is an IO or string, it is taken as n3 source and parsed first. Otherwise, it is a parsed formula. + # + # It returns the evaluated formula, or yields triples. + # + # @param [String, IO, StringIO, RDF::N3::Algebra::Formula, #to_s] input + # @param [Hash{Symbol => Object}] options + # @option options [#to_s] :base_uri (nil) + # the base URI to use when resolving relative URIs (for acessing intermediate parser productions) + # @yield [reasoner] `self` + # @yieldparam [RDF::N3::Reasoner] reasoner + # @yieldreturn [void] ignored + # @return [RDF::N3::Reasoner] + def initialize(input, **options, &block) + @formula = case input + when RDF::N3::Algebra::Formula then input + else RDF::N3::Reader.new(input, **options).formula + end + + log_debug("reasoner: expression", options) {@formula.to_sxp} + + if block_given? + case block.arity + when 0 then instance_eval(&block) + else block.call(self) + end + end + end + + ## + # Reasons over the formula, yielding each statement + # + # @param [Hash{Symbol => Object}] options + # @option options [Boolean] :apply + # @option options [Boolean] :think + # @yield [statement] + # @yieldparam [RDF::Statement] statement + # @return [RDF::Enumerable] + def execute(**options, &block) + results = RDF::Graph.new + + # Evaluate once to create initial triples for reasoning + log_info("reasoner: seed") + formula.execute(results, **options) + results << formula + + # If thinking, continuously execute until results stop growing + if options[:think] + count = 0 + log_info("reasoner: think start", options) { "count: #{results.count}"} + while results.count > count + log_depth {formula.execute(results, **options)} + results << formula + count = results.count + end + log_info("reasoner: think end") { "count: #{results.count}"} + else + # Run one iteration + log_info("reasoner: apply start") { "count: #{results.count}"} + log_depth {formula.execute(results, **options)} + results << formula + log_info("reasoner: apply end") { "count: #{results.count}"} + end + + log_debug("reasoner: results") {results.to_sxp} + + results.each(&block) if block_given? + results + end + end +end + diff --git a/lib/rdf/n3/vocab.rb b/lib/rdf/n3/vocab.rb index a89fccb..90ccd48 100644 --- a/lib/rdf/n3/vocab.rb +++ b/lib/rdf/n3/vocab.rb @@ -1,4 +1,9 @@ -module RDF - class LOG < Vocabulary("http://www.w3.org/2000/10/swap/log#"); end - class REI < Vocabulary("http://www.w3.org/2004/06/rei#"); end +module RDF::N3 + class Crypto < RDF::Vocabulary("http://www.w3.org/2000/10/swap/crypto#"); end + class List < RDF::Vocabulary("http://www.w3.org/2000/10/swap/list#"); end + class Log < RDF::Vocabulary("http://www.w3.org/2000/10/swap/log#"); end + class Math < RDF::Vocabulary("http://www.w3.org/2000/10/swap/math#"); end + class Rei < RDF::Vocabulary("http://www.w3.org/2000/10/swap/reify#"); end + #class String < RDF::Vocabulary("http://www.w3.org/2000/10/swap/string#"); end + class Time < RDF::Vocabulary("http://www.w3.org/2000/10/swap/time#"); end end diff --git a/rdf-n3.gemspec b/rdf-n3.gemspec index f8fe8ed..32ebe8e 100755 --- a/rdf-n3.gemspec +++ b/rdf-n3.gemspec @@ -22,6 +22,9 @@ Gem::Specification.new do |gem| gem.requirements = [] gem.add_dependency 'rdf', '~> 3.0' + gem.add_dependency 'sparql', '~> 3.0' + gem.add_runtime_dependency 'sxp', '~> 1.0' + gem.add_development_dependency 'json-ld', '~> 3.0' gem.add_development_dependency 'rspec', '~> 3.8' gem.add_development_dependency 'rspec-its', '~> 1.2' diff --git a/script/parse b/script/parse index 7736049..4ad74c1 100755 --- a/script/parse +++ b/script/parse @@ -5,28 +5,26 @@ require "bundler/setup" require 'logger' require 'rdf/n3' require 'rdf/ntriples' +require 'rdf/trig' require 'getoptlong' require 'open-uri' def run(input, options) + require 'profiler' if options[:profile] + reader_class = RDF::Reader.for(options[:input_format].to_sym) raise "Reader not found for #{options[:input_format]}" unless reader_class - prefixes = {} start = Time.new num = 0 - if options[:parse_only] - include RDF::N3::Meta - include RDF::N3::Parser - puts "\nparse #{input.read}---\n\n" unless options[:quiet] - input.rewind - if options[:quiet] - $stdout = StringIO.new - end - test(input, BRANCHES, REGEXPS) - if options[:quiet] - $stdout = STDOUT - print "." + Profiler__::start_profile if options[:profile] + if options[:think] + # Parse into a new reasoner and evaluate + reader_class.new(input, options[:parser_options]) do |reader| + reasoner = RDF::N3::Reasoner.new(input, options[:parser_options]) + repo = reasoner.execute(options) + num = repo.count + options[:output].puts repo.dump(options[:output_format], base_uri: options[:base_uri], prefixes: reader.prefixes, standard_prefixes: true) end elsif options[:output_format] == :ntriples || options[:quiet] reader_class.new(input, options[:parser_options]).each do |statement| @@ -39,6 +37,10 @@ def run(input, options) options[:output].puts statement.to_ntriples end end + elsif options[:output_format] == :sxp + reader_class.new(input, options[:parser_options]) do |reader| + SXP::Generator.print(reader.to_sxp_bin) + end elsif options[:output_format] == :inspect reader_class.new(input, options[:parser_options]).each do |statement| num += 1 @@ -50,6 +52,10 @@ def run(input, options) num = g.count options[:output].puts g.dump(options[:output_format], base_uri: options[:base_uri], prefixes: r.prefixes, standard_prefixes: true) end + if options[:profile] + Profiler__::stop_profile + Profiler__::print_profile($stderr) + end puts secs = Time.new - start puts "Parsed #{num} statements in #{secs} seconds @ #{num/secs} statements/second." @@ -72,41 +78,75 @@ parser_options = { options = { parser_options: parser_options, + logger: logger, output: STDOUT, output_format: :ntriples, input_format: :n3, } input = nil -opts = GetoptLong.new( - ["--dbg", GetoptLong::NO_ARGUMENT], - ["--errors", GetoptLong::NO_ARGUMENT], - ["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT], - ["--canonicalize", GetoptLong::NO_ARGUMENT], - ["--format", GetoptLong::REQUIRED_ARGUMENT], - ["--input-format", GetoptLong::REQUIRED_ARGUMENT], - ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT], - ["--profile", GetoptLong::NO_ARGUMENT], - ["--progress", GetoptLong::NO_ARGUMENT], - ["--quiet", GetoptLong::NO_ARGUMENT], - ["--uri", GetoptLong::REQUIRED_ARGUMENT], - ["--validate", GetoptLong::NO_ARGUMENT], - ["--verbose", GetoptLong::NO_ARGUMENT] -) +OPT_ARGS = [ + ["--apply", GetoptLong::REQUIRED_ARGUMENT, "Apply rules from specified file"], + ["--canonicalize", GetoptLong::NO_ARGUMENT, "Canonize all terms"], + ["--data", GetoptLong::NO_ARGUMENT, "Remove all except plain RDF triples (formulae, forAll, etc)v"], + ["--debug", GetoptLong::NO_ARGUMENT, "Debugging output"], + ["--errors", GetoptLong::NO_ARGUMENT, "Display invalid statements"], + ["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT, "Run against source in argument"], + ["--format", GetoptLong::REQUIRED_ARGUMENT, "Output format, any RDF format symbol, sxp, or inspect"], + ["--help", "-?", GetoptLong::NO_ARGUMENT, "print this message"], + ["--input-format", GetoptLong::REQUIRED_ARGUMENT, "Format of the input file, defaults to n3"], + ["--info", GetoptLong::NO_ARGUMENT, "Show progress on execution"], + ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT, "Save output to file"], + ["--profile", GetoptLong::NO_ARGUMENT, "Show an execution profile"], + ["--quiet", GetoptLong::NO_ARGUMENT, "Do not show parser output"], + ["--rules", GetoptLong::NO_ARGUMENT, "Run rules adding to the store"], + ["--think", GetoptLong::NO_ARGUMENT, "Run rules until until no more triples generated"], + ["--uri", GetoptLong::REQUIRED_ARGUMENT, "Default base URI"], + ["--validate", GetoptLong::NO_ARGUMENT, "Run parser in strict validation mode"], + #["--verbose", GetoptLong::NO_ARGUMENT, "Verbose output"], +] + +def usage + STDERR.puts %{ + n3 version #{RDF::N3::VERSION} + Exersize N3 parser/reasoner + + Usage: #{$0} [options] file ... + }.gsub(/^ /, '') + width = OPT_ARGS.map do |o| + l = o.first.length + l += o[1].length + 2 if o[1].is_a?(String) + l + end.max + OPT_ARGS.each do |o| + s = " %-*s " % [width, (o[1].is_a?(String) ? "#{o[0,2].join(', ')}" : o[0])] + s += o.last + STDERR.puts s + end + exit(1) +end + +opts = GetoptLong.new(*OPT_ARGS.map {|o| o[0..-2]}) + opts.each do |opt, arg| case opt - when '--dbg' then logger.level = Logger::DEBUG + when '--apply' then # Read rules + when "--data" then options[:data] = true + when '--debug' then logger.level = Logger::DEBUG when '--canonicalize' then parser_options[:canonicalize] = true when '--errors' then options[:errors] = true when '--execute' then input = arg when '--format' then options[:output_format] = arg.to_sym + when "--help" then usage() + when '--info' then logger.level = Logger::INFO when '--input-format' then options[:input_format] = arg.to_sym when '--output' then options[:output] = File.open(arg, "w") when '--profile' then options[:profile] = true - when '--progress' then logger.level = Logger::INFO when '--quiet' options[:quiet] = true logger.level = Logger::FATAL + when '--rules' then options[:rules] = true + when '--think' then options[:think] = true when '--uri' then parser_options[:base_uri] = arg when '--validate' then parser_options[:debug] ||= 1 when '--verbose' then $verbose = true diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index e1bee97..74e3637 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -395,6 +395,22 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end + + it "should generate inverse predicate for 'is xxx of' with blankNodePropertyList" do + n3 = %([ is :prop of :George]) + nt = %( + _:bn . + ) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + end + + it "should generate inverse predicate for 'is xxx of' with bnode" do + n3 = %(_:bn is :prop of :George.) + nt = %( + _:bn . + ) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + end it "should generate predicate for 'has xxx'" do n3 = %(@prefix a: . a:b has :pred a:c .) @@ -481,7 +497,7 @@ n3 = %(@forSome :x . :x :y :z .) g = parse(n3, base_uri: "http://a/b") statement = g.statements.first - expect(statement.subject).to be_node + expect(statement.subject).to be_variable expect(statement.predicate.to_s).to eq "http://a/b#y" expect(statement.object.to_s).to eq "http://a/b#z" end @@ -490,9 +506,9 @@ n3 = %(@forSome :x, :y, :z . :x :y :z .) g = parse(n3, base_uri: "http://a/b") statement = g.statements.first - expect(statement.subject).to be_node - expect(statement.predicate).to be_node - expect(statement.object).to be_node + expect(statement.subject).to be_variable + expect(statement.predicate).to be_variable + expect(statement.object).to be_variable expect(statement.subject).not_to equal statement.predicate expect(statement.object).not_to equal statement.predicate expect(statement.predicate).not_to equal statement.object @@ -580,7 +596,7 @@ :bar :d :c. :a :d :c. ) - reader = RDF::N3::Reader.new(n3) + reader = RDF::N3::Reader.new(n3, validate: true) reader.each {|statement|} expect(reader.prefixes).to eq({ rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", @@ -872,15 +888,36 @@ it "creates an RDF::Node instance for formula" do n3 = %(:a :b {}) nq = %(:a :b _:c .) - result = parse(n3, graph: @repo, base_uri: "http://a/b") - expected = parse(nq, graph: @repo, base_uri: "http://a/b") + result = parse(n3, repo: @repo, base_uri: "http://a/b") + expected = parse(nq, repo: @repo, base_uri: "http://a/b") expect(result).to be_equivalent_graph(expected, logger: logger) end it "adds statements with graph_name" do n3 = %(:a :b {[:c :d]}) trig = %(<#a> <#b> _:c . _:c {[<#c> <#d>] .}) - result = parse(n3, graph: @repo, base_uri: "http://a/b") + result = parse(n3, repo: @repo, base_uri: "http://a/b") + expected = RDF::Repository.new {|r| r << RDF::TriG::Reader.new(trig, base_uri: "http://a/b")} + expect(result).to be_equivalent_graph(expected, logger: logger) + end + + it "creates quads for patterns when added to a repository" do + n3 = %( + @prefix x: . + @prefix log: . + @prefix dc: . + { [ x:firstname "Ora" ] dc:wrote [ dc:title "Moby Dick" ] } a log:falsehood . + ) + trig = %( + @prefix x: . + @prefix log: . + @prefix dc: . + _:f a log:falsehood . + _:f { + [ x:firstname "Ora" ] dc:wrote [ dc:title "Moby Dick" ] . + } + ) + result = parse(n3, repo: @repo, base_uri: "http://a/b") expected = RDF::Repository.new {|r| r << RDF::TriG::Reader.new(trig, base_uri: "http://a/b")} expect(result).to be_equivalent_graph(expected, logger: logger) end @@ -906,7 +943,7 @@ # ENDS ) @repo = RDF::Repository.new - parse(n3, graph: @repo, base_uri: "http://a/b") + parse(n3, repo: @repo, base_uri: "http://a/b") end it "assumption graph has 2 statements" do @@ -1234,9 +1271,9 @@ def parse(input, options = {}) validate: false, canonicalize: false, }.merge(options) - graph = options[:graph] || RDF::Graph.new - RDF::N3::Reader.new(input, options).each do |statement| - graph << statement + graph = options[:repo] || RDF::Repository.new + RDF::N3::Reader.new(input, options).each_pattern do |pattern| + graph << pattern end graph end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index dc0974e..1df5aa0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,13 +3,12 @@ require "bundler/setup" require 'rspec' -require 'bigdecimal' # XXX Remove Me +require 'matchers' +require 'rdf/isomorphic' require 'rdf/n3' require 'rdf/ntriples' require 'rdf/spec' require 'rdf/spec/matchers' -require 'matchers' -require 'rdf/isomorphic' require 'yaml' # XXX should be in open-uri/cached ::RSpec.configure do |c| @@ -20,6 +19,12 @@ } end +module RDF + module Isomorphic + alias_method :==, :isomorphic_with? + end +end + # Heuristically detect the input stream def detect_format(stream) # Got to look into the file to see diff --git a/spec/test-files/append-ref.n3 b/spec/test-files/append-ref.n3 new file mode 100644 index 0000000..7dccc2b --- /dev/null +++ b/spec/test-files/append-ref.n3 @@ -0,0 +1,5 @@ +@prefix : <#> . + +:A0 :RESULT [ :append ( () "only" ) ]. +:A1 :RESULT [ :append ((1) 2 ) ] . +:B0 :append ( () "onlyB" ) . \ No newline at end of file diff --git a/spec/test-files/append.n3 b/spec/test-files/append.n3 new file mode 100644 index 0000000..f19b6af --- /dev/null +++ b/spec/test-files/append.n3 @@ -0,0 +1,40 @@ +# $Id: append.n3,v 1.1 2003-08-30 18:04:16 timbl Exp $ + +@prefix rdf: . +@prefix rdfs: . +@prefix log: . +@prefix : <#> . + +#{ ?A :append ( ?L ?X ) => + +{ + ?A :append ( [ rdf:first ?H ; rdf:rest ?T ] ?S ) . +} + => +{ + ?A rdf:first ?H ; + rdf:rest [ :append ( ?T ?S ) ] . +} . + + +{ + ?A :append ( () ?S ) . +} + => +{ + ?A rdf:first ?S ; + rdf:rest () . +} . + + +# Test cases + + :A0 :RESULT [ :append ( () "only" ) ]. + :B0 :append ( () "onlyB" ) . + + :A1 :RESULT [ :append ( ( 1 ) 2 ) ] . + +#:A2 :append ( :A1 "L2" ) . + +#:A3 :append ( :A1 "L3" ) . + diff --git a/spec/test-files/bnode-conclude-ref.n3 b/spec/test-files/bnode-conclude-ref.n3 new file mode 100644 index 0000000..f884d9d --- /dev/null +++ b/spec/test-files/bnode-conclude-ref.n3 @@ -0,0 +1,4 @@ + @prefix : <#> . + + [ a :Result ]. + diff --git a/spec/test-files/bnodeConclude.n3 b/spec/test-files/bnodeConclude.n3 new file mode 100644 index 0000000..c2f6fca --- /dev/null +++ b/spec/test-files/bnodeConclude.n3 @@ -0,0 +1,32 @@ +@prefix log: . +@forAll :X . + +{ {:a :b []} log:includes {:a :b :X} } => {:X a :Result} . + +# Looks like the following: + +# (graph +# (universals ?X) +# (logImplies +# (universals ?X) +# (formula _:f0 +# (universals ?X) +# (logIncludes +# (formula _:f1 (universals ?X) (pattern :a :b _:bn0)) +# (formula _:f2 (universals ?X) (pattern :a :b ?X)) +# ) +# ) +# (formula _:f3 (universals ?X) (pattern ?X rdf:type :Result)) +# ) +# ) +# +# evaluate graph => evaluate all child operators +# evalutate log_implies => evaluate _:f0 +# evaluate _:f0 => evaluate log_includes +# evaluate log_includes => +# evaluate _:f1 +# evaluate _:f2 +# bind ?X against lhs +# evaluate _:f2 as bgp against _:f1 => true +# evaluate _:f3 +# => iterate over solutions \ No newline at end of file diff --git a/spec/test-files/builtin_generated_match.n3 b/spec/test-files/builtin_generated_match.n3 new file mode 100644 index 0000000..a9c0bba --- /dev/null +++ b/spec/test-files/builtin_generated_match.n3 @@ -0,0 +1,14 @@ +@keywords a, of, the, is, has, prefix . +@prefix rdf: . + +((q)) a Thing . + +{?X a Thing . + ?X rdf:rest ?Y} +=> +{?Y a Thing } . + +{?X a Thing; + rdf:first (?B)} +=> +{?B a GreatThing} . diff --git a/spec/test-files/example-1.n3 b/spec/test-files/example-1.n3 new file mode 100644 index 0000000..e039c35 --- /dev/null +++ b/spec/test-files/example-1.n3 @@ -0,0 +1,16 @@ +@prefix log: . +@keywords. +@forAll x, y, z. {x parent y. y sister z} log:implies {x aunt z}. + +# This N3 formula has three universally quantified variables and one statement. The subject of the statement, + +# {x parent y. y sister z} # is the antecedent of the rule and the object, + +# {x aunt z} # is the conclusion. Given data + +Joe parent Alan. +Alan sister Susie. + +# a rule engine would conclude + +# => Joe aunt Susie. \ No newline at end of file diff --git a/spec/test-files/example-2.n3 b/spec/test-files/example-2.n3 new file mode 100644 index 0000000..fdcc04d --- /dev/null +++ b/spec/test-files/example-2.n3 @@ -0,0 +1,19 @@ +@keywords. +@forAll x, y, z. +{ x wrote y. + y log:includes {z weather w}. + x livesIn z +} log:implies { + Boston weather y +}. + +# Here the rule fires when x is bound to a symbol denoting some person who is the author of a formula y, when the formula makes a statement about the weather in (presumably some place) z, and x's home is z. That is, we believe statements about the weather at a place only from people who live there. Given the data + +Bob livesIn Boston. +Bob wrote { Boston weather sunny }. +Alice livesIn Adelaide. +Alice wrote { Boston weather cold }. + +# a valid inference would be + +# => Boston weather sunny. diff --git a/spec/test-files/example-3.n3 b/spec/test-files/example-3.n3 new file mode 100644 index 0000000..7e55814 --- /dev/null +++ b/spec/test-files/example-3.n3 @@ -0,0 +1,2 @@ +:Woman = foo:FemaleAdult . +:Title a rdf:Property; = dc:title . diff --git a/spec/test-files/foo.n3 b/spec/test-files/foo.n3 new file mode 100644 index 0000000..32beed8 --- /dev/null +++ b/spec/test-files/foo.n3 @@ -0,0 +1,14 @@ +# Axiom for truth +# +@prefix log: . +@prefix : <#> . + +@forAll :x, :y, :z. + +{ { :x :y :z } a log:Truth . } log:implies { :x :y :z } . + +{ :sky :is :blue } a log:Truth . + +# ends + + diff --git a/spec/test-files/last.n3 b/spec/test-files/last.n3 new file mode 100644 index 0000000..db7821a --- /dev/null +++ b/spec/test-files/last.n3 @@ -0,0 +1,30 @@ +# test list built in +@prefix rdf: . +@prefix : <#>. +@prefix list: . +@keywords a. + +{ ( 1 2 3 4 5 6 ) list:last 6 } => { test1 a SUCCESS }. + +{ ( 1 2 3 4 5 "Z" ) list:last "Z" } => { test2 a SUCCESS }. + +{ ( "wrong" "WongAgain" "Success" ) list:last ?x }=>{ test3 isa ?x}. + +{ 1 list:in ( 1 2 3 4 5 ) } => { test4a a SUCCESS }. +{ 2 list:in ( 1 2 3 4 5 ) } => { test4b a SUCCESS }. +{ 3 list:in ( 1 2 3 4 5 ) } => { test4c a SUCCESS }. +{ 4 list:in ( 1 2 3 4 5 ) } => { test4d a SUCCESS }. + +{ 1 list:in () } => { trap1 a FAILURE}. +{ 0 list:in ( 1 2 3 4 5 ) } => { trap2 a FAILURE }. + +thing1 prop1 ( test5a test5b test5c ) . +{ ?item list:in [ @is prop1 @of thing1 ] } => { ?item a SUCCESS } . + +( foo bar baz ) prop2 test6a . +( foo ) prop2 test6b . +( bar baz ) prop2 trap4 . +{ foo list:in ?a_list . + ?a_list prop2 ?thing } => { ?thing a SUCCESS } . + +#ends diff --git a/spec/test-files/list-bug1.n3 b/spec/test-files/list-bug1.n3 new file mode 100644 index 0000000..efeda27 --- /dev/null +++ b/spec/test-files/list-bug1.n3 @@ -0,0 +1,13 @@ +@prefix : <#>. +@prefix log: . + +@forAll :x, :y. + +( ) a . + +{ (:x :y) a } log:implies { :x :RESULT :y }. + + +# Result should be +# :RESULT . +# and should NOT include :RESULT . diff --git a/spec/test-files/list-bug2.n3 b/spec/test-files/list-bug2.n3 new file mode 100644 index 0000000..11b7117 --- /dev/null +++ b/spec/test-files/list-bug2.n3 @@ -0,0 +1,21 @@ +# test the handling os lists which are bound as other things get calculated + +@prefix : <#>. +@prefix log: . +@prefix string: . + +@forAll :x, :y. + + +# Cause llyn to search the store for a list: +( "1" ) . + +{ +# log:uri ?u. + ( "fred" " whatever ") string:concatenation ?s +} => { ?s a :RESULT }. + + +# Result should be +# "fred whatever " a :RESULT . +# diff --git a/spec/test-files/list-in-ref.n3 b/spec/test-files/list-in-ref.n3 new file mode 100644 index 0000000..4f95d0c --- /dev/null +++ b/spec/test-files/list-in-ref.n3 @@ -0,0 +1,21 @@ + @prefix : <#> . + + 12 :a :Pythagorean . + + 13 :a :Pythagorean . + + 3 :a :Pythagorean, + :RESULT1 . + + 4 :a :Pythagorean, + :RESULT1 . + + 5 :a :Pythagorean, + :RESULT1 . + + :amy a :Friend . + + :fred a :Friend . + + :joe a :Friend . + diff --git a/spec/test-files/list-in.n3 b/spec/test-files/list-in.n3 new file mode 100644 index 0000000..2b0c966 --- /dev/null +++ b/spec/test-files/list-in.n3 @@ -0,0 +1,13 @@ +@prefix li: . +@keywords. + +{ ?x li:in (3 4 5) } => { ?x a RESULT1 }. + + +{ ( joe fred amy ) li:member ?y } => { ?y @a Friend }. + + + +{ ((3 4 5) (5 12 13))!li:member li:member ?z } => { ?z a Pythagorean }. + +# ends diff --git a/spec/test-files/manifest.n3 b/spec/test-files/manifest.n3 new file mode 100644 index 0000000..2ecaa4d --- /dev/null +++ b/spec/test-files/manifest.n3 @@ -0,0 +1,153 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix mf: . +@prefix qt: . +@prefix rdft: . +@prefix test: . +@prefix xsd: . +@prefix : <#>. +@prefix x: <#>. + +<> a mf:Manifest ; + rdfs:label "Notation3 tests" ; + mf:entries + ( + # From swap/test/includes + :listin :bnode + + # From swap/test/list + :t1018b1 :t1018b2 :t1031 :t2004u2 :t2004u3 :t2004u4 :t2004u5 :t2005 :t2006 :t2007 + ) . + +# List tests from swap/tests/includes + +:listin a test:CwmTest; + mf:name "list membership" ; + rdfs:comment "Builtins for list membership, binding multiple values." ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +:bnode a test:CwmTest; + mf:name "list membership" ; + rdfs:comment "Builtins for list membership, binding multiple values." ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +# List tests from swap/test/list + +:t1018b1 a test:CwmTest; + mf:name "list-bug1.n3" ; + rdfs:comment "List processing bug check 1" ; + mf:action ; + mf:result ; + test:options [] . + +:t1018b2 a test:CwmTest; + mf:name "list-bug2.n3" ; + rdfs:comment "List processing bug check 2" ; + mf:action ; + mf:result ; + test:options [] . + +:t1031 a test:CwmTest; + mf:name "li-r1.n3" ; + rdfs:comment "Inference using lists" ; + mf:action ; + mf:result ; + test:options [test:think true;] . + +:t2004u2 a test:CwmTest; + mf:name "list-unify2.n3" ; + rdfs:comment "List unification 2 - variable in list" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2004u3 a test:CwmTest; + mf:name "list-unify3.n3" ; + rdfs:comment "List unification 3 - nested lists" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2004u4 a test:CwmTest; + mf:name "list-unify4.n3" ; + rdfs:comment "List unification 4 - nested lists" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2004u5 a test:CwmTest; + mf:name "list-unify5.n3" ; + rdfs:comment "List unification 5 - multiple values" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2005 a test:CwmTest; + mf:name "append-out.n3" ; + rdfs:comment "Iterative ops on lists" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2006 a test:CwmTest; + mf:name "list-last.n3" ; + rdfs:comment "last, in builtins on lists" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2007 a test:CwmTest; + mf:name "list-builtin_generated_match.n3" ; + rdfs:comment "last, in builtins on lists" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +#### +# Test Vocabulary + +test:apply a rdf:Property; + rdfs:comment "Read rules from foo, apply to store, adding conclusions to store"; + rdfs:domain test:Options; + rdfs:range rdfs:Resource; + . + +test:data a rdf:Property; + rdfs:comment "Remove all except plain RDF triples (formulae, forAll, etc)"; + rdfs:domain test:Options; + rdfs:range xsd:boolean; + . + +test:filter a rdf:Property; + rdfs:comment "Read rules from foo, apply to store, REPLACING store with conclusions"; + rdfs:domain test:Options; + rdfs:range rdfs:Resource; + . + +test:patch a rdf:Property; + rdfs:comment "Read patches from foo, applying insertions and deletions to store"; + rdfs:domain test:Options; + rdfs:range rdfs:Resource; + . + +test:options a rdf:Property; + rdfs:domain test:CwmTest; + rdfs:range test:Options; + rdfs:comment "Options for running tests"; + . + +test:rules a rdf:Property; + rdfs:comment "Apply rules in store to store, adding conclusions to store"; + rdfs:domain test:Options; + rdfs:domain xsd:boolean; + . + +test:think a rdf:Property; + rdfs:comment "as test:rules but continue until no more rule matches (or forever!)"; + rdfs:domain test:Options; + rdfs:domain xsd:boolean; + . diff --git a/spec/test-files/path-1.n3 b/spec/test-files/path-1.n3 new file mode 100644 index 0000000..9c8a4d3 --- /dev/null +++ b/spec/test-files/path-1.n3 @@ -0,0 +1,5 @@ +[is con:zipcode of [ + is con:address of [ + is con:home of [ + is off:assistant of [ + is rel:mother of :George]]]]] diff --git a/spec/test-files/r1.n3 b/spec/test-files/r1.n3 new file mode 100644 index 0000000..ec4b8ef --- /dev/null +++ b/spec/test-files/r1.n3 @@ -0,0 +1,19 @@ +@prefix r: . +@prefix s: . + +@prefix doc: . +@prefix log: . +@prefix daml: . +@prefix xml: . +@prefix contact: . + +@prefix : . + +@forAll :a, :b, :c, :x, :y, :z. + +( "one" "two" ) a :whatever. + +{ (:a :b) a :whatever } log:implies { :a a :SUCCESS. :b a :SUCCESS }. + +# { :x a :whatever } log:implies { :x :is """ The list ( "one" "two" )""" }. + diff --git a/spec/test-files/unify2.n3 b/spec/test-files/unify2.n3 new file mode 100644 index 0000000..645dae7 --- /dev/null +++ b/spec/test-files/unify2.n3 @@ -0,0 +1,21 @@ +# test the handling os lists which are in the store +# +# use with --think + +@prefix : <#>. +@prefix log: . +@prefix math: . + +@forAll :x, :y. + + +# Cause llyn to search the store for a list: + +( 17 ) a :TestCase. + + +{ ( ?x ) a :TestCase} => { ?x a :RESULT }. + +# { 17 a :RESULT } => { :THIS_TEST a :SUCCESS }. + +# diff --git a/spec/test-files/unify3.n3 b/spec/test-files/unify3.n3 new file mode 100644 index 0000000..43565a5 --- /dev/null +++ b/spec/test-files/unify3.n3 @@ -0,0 +1,21 @@ +# test the handling os lists which are in the store +# +# use with --think + +@prefix : <#>. +@prefix log: . +@prefix math: . + +@forAll :x, :y. + + +# Cause llyn to search the store for a list: + +( ( 17 ) ) a :TestCase. + + +{ ( ( ?x ) ) a :TestCase} => { ?x a :RESULT }. + + { 17 a :RESULT } => { :THIS_TEST a :SUCCESS }. + +# diff --git a/spec/test-files/unify4.n3 b/spec/test-files/unify4.n3 new file mode 100644 index 0000000..04df4fd --- /dev/null +++ b/spec/test-files/unify4.n3 @@ -0,0 +1,25 @@ +# test the handling os lists which are in the store +# +# use with --think + +@prefix : <#>. +@prefix log: . +@prefix math: . + +@forAll :x, :y. + + + + +( 1 2 ) :crinks ( 7 2 ). + +( 1 3 ) :crinks ( 999 999 ) . + +( 1 13 ) :crinks ( 2 14 ). +( 1 14 ) :crinks ( 2 13 ). + +{ ( 1 ?x ) :crinks ( 7 ?x ) } => { ?x a :RESULT }. + + { 2 a :RESULT } => { :THIS_TEST a :SUCCESS }. + +# diff --git a/spec/test-files/unify5.n3 b/spec/test-files/unify5.n3 new file mode 100644 index 0000000..b4ec62e --- /dev/null +++ b/spec/test-files/unify5.n3 @@ -0,0 +1,21 @@ +# Two variables and some bnodes +# And multiple values +# +@keywords is, a . + +fred siblings ( [ parents (Zeus Juno), (Alice Bob), (Jane Joe) ] Aphrodite ). + + + +{ fred siblings ( [] ?x ) } => { ?x a ShouldBeAphrodite }. + +{ fred siblings ( ?y ?x ) } => { ?y a BnodeExtractedError }. + + +{ fred siblings ( [ parents ([] ?y) ] ?x ) } => + + { ?x a ShouldBeAphrodite2. ?y a ShouldBeJunoBobJoe }. + + +#ends + diff --git a/spec/test_file_spec.rb b/spec/test_file_spec.rb new file mode 100644 index 0000000..cca8154 --- /dev/null +++ b/spec/test_file_spec.rb @@ -0,0 +1,139 @@ +require_relative "spec_helper" +require 'rdf/spec' +require 'rdf/n3' +require 'json/ld' +require 'rdf/trig' + +module Fixtures + module Test + FRAME = JSON.parse(%q({ + "@context": { + "xsd": "http://www.w3.org/2001/XMLSchema#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "mf": "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#", + "mq": "http://www.w3.org/2001/sw/DataAccess/tests/test-query#", + "n3test": "http://www.w3.org/2000/10/swap/test.n3#", + "comment": "rdfs:comment", + "entries": {"@id": "mf:entries", "@container": "@list"}, + "label": "rdfs:label", + "name": "mf:name", + "action": {"@id": "mf:action", "@type": "@id"}, + "result": {"@id": "mf:result", "@type": "@id"}, + "options": {"@id": "n3test:options", "@type": "@vocab"}, + "data": {"@id": "n3test:data","@type": "xsd:boolean"}, + "think": {"@id": "n3test:think","@type": "xsd:boolean"} + }, + "@type": "mf:Manifest", + "entries": {"@type": "n3test:CwmTest"} + })) + + class Manifest < JSON::LD::Resource + attr_accessor :manifest_url + + def self.open(file) + g = RDF::Repository.load(file, format: :n3) + JSON::LD::API.fromRDF(g) do |expanded| + JSON::LD::API.frame(expanded, FRAME) do |framed| + yield Manifest.new(framed['@graph'].first, manifest_url: file) + end + end + end + + def initialize(json, manifest_url:) + @manifest_url = manifest_url + super + end + + def entries + # Map entries to resources + attributes['entries'].map do |e| + e.is_a?(String) ? Manifest.open(manifest_url.join(e).to_s) : Entry.new(e, manifest_url: manifest_url) + end + end + end + + class Entry < JSON::LD::Resource + attr_accessor :logger + + def base + action + end + + # Alias data and query + def input + @input ||= RDF::Util::File.open_file(action) {|f| f.read} + end + + def expected + @expected ||= RDF::Util::File.open_file(result) {|f| f.read} + end + + def positive_test? + !attributes['@type'].to_s.match(/Negative/) + end + + def negative_test? + !positive_test? + end + + def evaluate? + !syntax? + end + + def syntax? + !result + end + + def inspect + super.sub('>', "\n" + + " positive?: #{positive_test?.inspect}\n" + + ">" + ) + end + end + end +end + +describe RDF::N3::Reader do + Fixtures::Test::Manifest.open(File.expand_path("../test-files/manifest.n3", __FILE__)) do |m| + describe m.label do + m.entries.each do |t| + specify "#{t.id.split('#').last}: #{t.name} - #{t.comment}" do + t.logger = RDF::Spec.logger + t.logger.info t.inspect + t.logger.info "source:\n#{t.input}" + + case t.id.split('#').last + when *%w{listin bnode} + #pending "support for li:member" + end + + reader = RDF::N3::Reader.new(t.input, + base_uri: t.base, + logger: t.logger) + + repo = RDF::Repository.new + + if t.positive_test? + begin + repo << reader + rescue Exception => e + expect(e.message).to produce("Not exception #{e.inspect}", t) + end + if t.evaluate? + output_repo = RDF::Repository.load(t.result, format: :n3, base_uri: t.base) + repo = repo.project_graph(nil) if t.options['data'] + expect(repo).to be_equivalent_graph(output_repo, t) + else + end + else + expect { + graph << reader + expect(graph.dump(:ntriples)).to produce("not this", t) + }.to raise_error(RDF::ReaderError) + end + end + end + end + end +end \ No newline at end of file From 12928f3f4ae955ef00cb444f94f2d44a0d60b788 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 16 Jan 2019 10:42:41 -0800 Subject: [PATCH 13/40] General support for log:implies with a number of swap examples. TODO: * Disambiguate variables with the same name in different scopes. * Use Blank-nodes for representation in formulae and turn into non-distinctive variables at runtime. * Merge solutions up to top-level variable scope. --- Gemfile | 3 +- lib/rdf/n3/algebra/formula.rb | 5 +- lib/rdf/n3/algebra/logImplies.rb | 10 +- lib/rdf/n3/reader.rb | 66 +++++--- lib/rdf/n3/reasoner.rb | 12 +- spec/reader_spec.rb | 160 +++++++++--------- .../test-files/builtin_generated_match-ref.n3 | 17 ++ spec/test-files/list-bug1-ref.n3 | 16 ++ spec/test-files/list-bug2-ref.n3 | 12 ++ spec/test-files/manifest.n3 | 6 +- spec/test-files/r1-ref.n3 | 17 ++ spec/test-files/unify2-ref.n3 | 14 ++ spec/test-files/unify3-ref.n3 | 21 +++ spec/test-files/unify3.n3 | 3 +- spec/test-files/unify4-ref.n3 | 34 ++++ spec/test-files/unify5-ref.n3 | 33 ++++ spec/test_file_spec.rb | 18 +- 17 files changed, 333 insertions(+), 114 deletions(-) create mode 100644 spec/test-files/builtin_generated_match-ref.n3 create mode 100644 spec/test-files/list-bug1-ref.n3 create mode 100644 spec/test-files/list-bug2-ref.n3 create mode 100644 spec/test-files/r1-ref.n3 create mode 100644 spec/test-files/unify2-ref.n3 create mode 100644 spec/test-files/unify3-ref.n3 create mode 100644 spec/test-files/unify4-ref.n3 create mode 100644 spec/test-files/unify5-ref.n3 diff --git a/Gemfile b/Gemfile index cb68666..4133565 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,8 @@ source "https://rubygems.org" gemspec -gem "rdf", github: "ruby-rdf/rdf", branch: "develop" +#gem "rdf", github: "ruby-rdf/rdf", branch: "develop" +gem "rdf", path: '../rdf' group :development do gem "rdf-spec", github: "ruby-rdf/rdf-spec", branch: "develop" diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index dc38c02..e2a1cc6 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -61,6 +61,7 @@ def execute(queryable, **options, &block) # @yieldparam [RDF::Statement] solution # @yieldreturn [void] ignored def each(&block) + @solutions ||= RDF::Query::Solutions.new log_debug {"formula #{graph_name} each #{@solutions.inspect}"} constants = operands.select {|op| op.is_a?(RDF::Statement) && op.constant?} patterns = operands.select {|op| op.is_a?(RDF::Statement) && op.variable?} @@ -69,7 +70,7 @@ def each(&block) # Yield constant statements/patterns constants.each do |pattern| log_debug {"(formula constant) #{pattern.to_sxp}"} - block.call(RDF::Statement.from(pattern)) + block.call(RDF::Statement.from(pattern, graph_name: graph_name)) end # Yield patterns by binding variables @@ -89,7 +90,7 @@ def each(&block) # Sanity checking on statement if statement.subject.nil? || statement.predicate.nil? || statement.object.nil? || - statement.subject.literal? || statement.predicate.literal? || + statement.predicate.literal? || statement.subject.is_a?(SPARQL::Algebra::Operator) || statement.object.is_a?(SPARQL::Algebra::Operator) log_debug {"(formula skip) #{statement.to_sxp}"} diff --git a/lib/rdf/n3/algebra/logImplies.rb b/lib/rdf/n3/algebra/logImplies.rb index 8647199..e158027 100644 --- a/lib/rdf/n3/algebra/logImplies.rb +++ b/lib/rdf/n3/algebra/logImplies.rb @@ -41,6 +41,7 @@ def execute(queryable, **options, &block) # @yieldparam [RDF::Statement] solution # @yieldreturn [void] ignored def each(&block) + @solutions ||= RDF::Query::Solutions.new log_debug {"logImplies each #{@solutions.inspect}"} _, object = operands @@ -48,8 +49,13 @@ def each(&block) log_depth do object.solutions = @solutions - # Yield statements - object.each(&block) + # Nothing emitted if @solutions is not complete. Solutions are complete when all variables are bound. + if !object.solutions.empty? + # Yield statements into the default graph + object.each do |statement| + block.call(RDF::Statement.from(statement.to_triple)) + end + end end end end diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 8caae1f..e0e72d0 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -333,7 +333,7 @@ def existentialFinish forSome = Array(pd[:symbol]) forSome.each do |term| var = univar(term, distinguished: false) - add_var_to_formula(@formulae.last, var) + add_var_to_formula(@formulae.last, term, var) end end @@ -417,9 +417,9 @@ def pathitemToken(prod, tok) # universally quantified not in the formula but in its parent formula uri = process_qname(tok.sub('?', ':')) var = uri.variable? ? uri : univar(uri) - add_var_to_formula(@formulae[-2], var) + add_var_to_formula(@formulae[-2], uri, var) # Also add var to this formula - add_var_to_formula(@formulae.last, var) + add_var_to_formula(@formulae.last, uri, var) add_prod_data(:symbol, var) when "boolean" @@ -434,6 +434,7 @@ def pathitemToken(prod, tok) add_prod_data(:symbol, symbol) when "{" # A new formula, push on a node as a named graph + # XXX use existential if embedded? node = RDF::Node.new @formulae << node @formulae_nodes[node] = true @@ -575,7 +576,7 @@ def universalFinish pd = @prod_data.pop forAll = Array(pd[:symbol]) forAll.each do |term| - add_var_to_formula(@formulae.last, univar(term)) + add_var_to_formula(@formulae.last, term, univar(term)) end end @@ -628,7 +629,7 @@ def process_anonnode(anonnode) if anonnode[:propertylist] properties = anonnode[:propertylist] - bnode = RDF::Node.new + bnode = @formulae.last ? univar(nil, distinguished: false) : bnode() properties.each do |p| predicate = p[:verb] log_debug("process_anonnode(verb)", depth: depth) {predicate.inspect} @@ -645,11 +646,30 @@ def process_anonnode(anonnode) elsif anonnode[:pathlist] objects = Array(anonnode[:pathlist]) list = RDF::List[*objects] + list_subjects = {} list.each_statement do |statement| next if statement.predicate == RDF.type && statement.object == RDF.List - add_statement("anonnode(list)", statement.subject, statement.predicate, statement.object) + + # In formula, use variables instead of bnodes + subject = if @formulae.last && statement.subject.node? + list_subjects[statement.subject.id] ||= univar(statement.subject.id, distinguished: false) + else + statement.subject + end + + object = if @formulae.last && statement.object.node? && statement.predicate == RDF.rest + list_subjects[statement.object.id] ||= univar(statement.object.id, distinguished: false) + else + statement.object + end + add_statement("anonnode(list)", subject, statement.predicate, object) + end + + if @formulae.last && list.subject.node? + list_subjects[list.subject.id] ||= univar(list.subject.id, distinguished: false) + else + list.subject end - list.subject end end @@ -710,8 +730,8 @@ def process_qname(tok) log_debug('process_qname(bnode)', name, depth: depth) # If we're in a formula, create a non-distigushed variable instead if @formulae.last - var = find_var(@formulae.last, name) || univar(term) - add_var_to_formula(formulae.last, var) + var = find_var(@formulae.last, name) || univar(name, distinguished: false) + add_var_to_formula(formulae.last, name, var) else bnode(name) end @@ -738,8 +758,12 @@ def add_prod_data(sym, value) # Keep track of allocated BNodes def bnode(value = nil) - @bnode_cache ||= {} - @bnode_cache[value.to_s] ||= RDF::Node.new(value) + if value + @bnode_cache ||= {} + @bnode_cache[value.to_s] ||= RDF::Node.new(value) + else + RDF::Node.new + end end def univar(label, distinguished: true) @@ -747,9 +771,8 @@ def univar(label, distinguished: true) @unnamed_label ||= "var0" label = @unnamed_label = @unnamed_label.succ end - v = RDF::Query::Variable.new(label.to_s) - v.distinguished = distinguished - v + #label = "_:#{label}" unless distinguished || label.start_with?('_:') + RDF::Query::Variable.new(label.to_s, distinguished: distinguished) end # add a pattern or statement @@ -767,7 +790,7 @@ def add_statement(node, subject, predicate, object) else RDF::Statement(subject, predicate, object) end - log_debug(node, depth: depth) {statement.to_s} + log_debug("statement(#{node})", depth: depth) {statement.to_s} @callback.call(statement) end @@ -810,19 +833,20 @@ def ns(prefix, suffix) end # Find any variable that may be defined in the formula identified by `bn` - # @param [RDF::Node] bn + # @param [RDF::Node] bn name of formula # @param [#to_s] name # @return [RDF::Query::Variable] - def find_var(bn, name) - ((@variables[bn] ||= {})[name.to_s] ||= []).last + def find_var(sym, name) + (@variables[sym] ||= {}).fetch(name.to_s, []).last end # Add a variable to the formula identified by `bn`, returning the formula - # @param [RDF::Node] bn + # @param [RDF::Node] bn name of formula + # @param [#to_s] name of variable for lookup # @param [RDF::Query::Variable] var # @return [RDF::Query::Variable] - def add_var_to_formula(bn, var) - ((@variables[bn] ||= {})[var.name.to_s] ||= []) << var + def add_var_to_formula(bn, name, var) + ((@variables[bn] ||= {})[name.to_s] ||= []) << var var end end diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index d00f6a4..097026d 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -39,6 +39,7 @@ def self.open(file) # @yieldreturn [void] ignored # @return [RDF::N3::Reasoner] def initialize(input, **options, &block) + @options = options @formula = case input when RDF::N3::Algebra::Formula then input else RDF::N3::Reader.new(input, **options).formula @@ -68,25 +69,24 @@ def execute(**options, &block) # Evaluate once to create initial triples for reasoning log_info("reasoner: seed") - formula.execute(results, **options) results << formula # If thinking, continuously execute until results stop growing if options[:think] count = 0 - log_info("reasoner: think start", options) { "count: #{results.count}"} + log_info("reasoner: think start") { "count: #{count}"} while results.count > count + count = results.count log_depth {formula.execute(results, **options)} results << formula - count = results.count end - log_info("reasoner: think end") { "count: #{results.count}"} + log_info("reasoner: think end") { "count: #{count}"} else # Run one iteration - log_info("reasoner: apply start") { "count: #{results.count}"} + log_info("reasoner: apply start") { "count: #{count}"} log_depth {formula.execute(results, **options)} results << formula - log_info("reasoner: apply end") { "count: #{results.count}"} + log_info("reasoner: apply end") { "count: #{count}"} end log_debug("reasoner: results") {results.to_sxp} diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 74e3637..e6dd5d9 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -66,7 +66,7 @@ #_:abcd a po:Episode. EOF end - + it "should yield reader" do inner = double("inner") expect(inner).to receive(:called).with(RDF::N3::Reader) @@ -74,11 +74,11 @@ inner.called(reader.class) end end - + it "should return reader" do expect(RDF::N3::Reader.new(@sampledoc)).to be_a(RDF::N3::Reader) end - + it "should yield statements" do inner = double("inner") expect(inner).to receive(:called).with(RDF::Statement).exactly(15) @@ -86,7 +86,7 @@ inner.called(statement.class) end end - + it "should yield triples" do inner = double("inner") expect(inner).to receive(:called).exactly(15) @@ -103,11 +103,11 @@ @graph = parse(n3_string) @statement = @graph.statements.first end - + it "should have a single triple" do expect(@graph.size).to eq 1 end - + it "should have subject" do expect(@statement.subject.to_s).to eq "http://example.org/" end @@ -118,7 +118,7 @@ expect(@statement.object.to_s).to eq "Gregg Kellogg" end end - + # NTriple tests from http://www.w3.org/2000/10/rdf-tests/rdfcore/ntriples/test.nt describe "with blank lines" do { @@ -153,7 +153,7 @@ expect(statement.object.value).to eq contents end end - + { 'Dürst' => ' "Dürst" .', "é" => ' "é" .', @@ -167,13 +167,13 @@ expect(statement.object.value).to eq contents end end - + it "should parse long literal with escape" do n3 = %(@prefix : . :a :b "\\U00015678another" .) statement = parse(n3).statements.first expect(statement.object.value).to eq "\u{15678}another" end - + context "string3 literals" do { "simple" => %q(foo), @@ -255,10 +255,10 @@ "XML Literals as Datatyped Literals (7)" => ' "a c"^^ .', "XML Literals as Datatyped Literals (8)" => ' "a\n\nc"^^ .', "XML Literals as Datatyped Literals (9)" => ' "chat"^^ .', - + "Plain literals with languages (1)" => ' "chat"@fr .', "Plain literals with languages (2)" => ' "chat"@en .', - + "Typed Literals" => ' "abc"^^ .', }.each_pair do |name, statement| specify "test #{name}" do @@ -319,7 +319,7 @@ end end end - + it "should create URIs" do n3doc = " ." statement = parse(n3doc).statements.first @@ -333,7 +333,7 @@ expect(statement.object).to be_literal end end - + describe "with n3 grammar" do describe "syntactic expressions" do it "should create typed literals with qname" do @@ -354,7 +354,7 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should use <#> as a prefix and as a triple node" do n3 = %(@prefix : <#> . <#> a :a.) nt = %( @@ -362,31 +362,31 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should generate rdf:type for 'a'" do n3 = %(@prefix a: . a:b a .) nt = %( .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should generate rdf:type for '@a'" do n3 = %(@prefix a: . a:b @a .) nt = %( .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should generate inverse predicate for 'is xxx of'" do n3 = %("value" is :prop of :b . :b :prop "value" .) nt = %( "value" .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should generate inverse predicate for '@is xxx @of'" do n3 = %("value" @is :prop @of :b . :b :prop "value" .) nt = %( "value" .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should generate inverse predicate for 'is xxx of' with object list" do n3 = %("value" is :prop of :b, :c . ) nt = %( @@ -411,37 +411,37 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should generate predicate for 'has xxx'" do n3 = %(@prefix a: . a:b has :pred a:c .) nt = %( .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should generate predicate for '@has xxx'" do n3 = %(@prefix a: . a:b @has :pred a:c .) nt = %( .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should create log:implies predicate for '=>'" do n3 = %(@prefix a: . _:a => a:something .) nt = %(_:a .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should create log:implies inverse predicate for '<='" do n3 = %(@prefix a: . _:a <= a:something .) nt = %( _:a .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should create owl:sameAs predicate for '='" do n3 = %(@prefix a: . _:a = a:something .) nt = %(_:a .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + { %(:a :b @true) => %( "true"^^ .), %(:a :b @false) => %( "false"^^ .), @@ -459,22 +459,24 @@ expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end end - + it "should accept empty localname" do n3 = %(: : : .) nt = %( .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should accept prefix with empty local name" do n3 = %(@prefix foo: . foo: foo: foo: .) nt = %( .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + end + + context "patterns" do it "substitutes variable for URI with @forAll" do n3 = %(@forAll :x . :x :y :z .) - g = parse(n3, base_uri: "http://a/b") + g = parse(n3, base_uri: "http://a/b", patterns: true) statement = g.statements.first expect(statement.subject).to be_variable expect(statement.predicate.to_s).to eq "http://a/b#y" @@ -483,7 +485,7 @@ it "substitutes variable for URIs with @forAll" do n3 = %(@forAll :x, :y, :z . :x :y :z .) - g = parse(n3, base_uri: "http://a/b") + g = parse(n3, base_uri: "http://a/b", patterns: true) statement = g.statements.first expect(statement.subject).to be_variable expect(statement.predicate).to be_variable @@ -495,16 +497,16 @@ it "substitutes node for URI with @forEach" do n3 = %(@forSome :x . :x :y :z .) - g = parse(n3, base_uri: "http://a/b") + g = parse(n3, base_uri: "http://a/b", patterns: true) statement = g.statements.first - expect(statement.subject).to be_variable + expect(statement.subject).to be_variable, logger.to_s expect(statement.predicate.to_s).to eq "http://a/b#y" expect(statement.object.to_s).to eq "http://a/b#z" end it "substitutes node for URIs with @forEach" do n3 = %(@forSome :x, :y, :z . :x :y :z .) - g = parse(n3, base_uri: "http://a/b") + g = parse(n3, base_uri: "http://a/b", patterns: true) statement = g.statements.first expect(statement.subject).to be_variable expect(statement.predicate).to be_variable @@ -514,7 +516,7 @@ expect(statement.predicate).not_to equal statement.object end end - + describe "prefixes" do it "should not append # for http://foo/bar" do n3 = %(@prefix : . :a : :b .) @@ -548,7 +550,7 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should set absolute base (trailing /)" do n3 = %(@base . <> :a . <#c> :d .) nt = %( @@ -557,7 +559,7 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should set absolute base (trailing #)" do n3 = %(@base . <> :a . <#c> :d .) nt = %( @@ -566,7 +568,7 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should set relative base" do n3 = %( @base . @@ -586,7 +588,7 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "returns defined prefixes" do n3 = %( @prefix rdf: . @@ -605,7 +607,7 @@ }) end end - + describe "keywords" do [ %(base <>.), @@ -621,7 +623,7 @@ end.to raise_error(RDF::ReaderError) end end - + [ %(prefix :<>.), ].each do |n3| @@ -657,7 +659,7 @@ expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end end - + it "should raise error if unknown keyword set" do n3 = %(@keywords foo.) expect do @@ -665,7 +667,7 @@ end.to raise_error(RDF::ReaderError, /Undefined keywords used: foo/) end end - + describe "declaration ordering" do it "should process _ namespace binding after an initial use as a BNode" do n3 = %( @@ -679,7 +681,7 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should allow a prefix to be redefined" do n3 = %( @prefix a: . @@ -709,7 +711,7 @@ expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end end - + describe "BNodes" do it "should create BNode for identifier with '_' prefix" do n3 = %(@prefix a: . _:a a:p a:v .) @@ -717,39 +719,39 @@ g = parse(n3, base_uri: "http://a/b") expect(g).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should create BNode for [] as subject" do n3 = %(@prefix a: . [] a:p a:v .) nt = %(_:bnode0 .) g = parse(n3, base_uri: "http://a/b") expect(g).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should create BNode for [] as predicate" do n3 = %(@prefix a: . a:s [] a:o .) g = parse(n3, base_uri: "http://a/b") st = g.statements.first expect(st.predicate).to be_a_node end - + it "should create BNode for [] as object" do n3 = %(@prefix a: . a:s a:p [] .) nt = %( _:bnode0 .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should create BNode for [] as statement" do n3 = %([:a :b] .) nt = %(_:bnode0 .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should create statements with BNode subjects using [ pref obj]" do n3 = %(@prefix a: . [ a:p a:v ] .) nt = %(_:bnode0 .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should create BNode as a single object" do n3 = %(@prefix a: . a:b a:oneRef [ a:pp "1" ; a:qq "2" ] .) nt = %( @@ -759,7 +761,7 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should create a shared BNode" do n3 = %( @prefix a: . @@ -779,7 +781,7 @@ expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end - + it "should create nested BNodes" do n3 = %( @prefix a: . @@ -804,14 +806,14 @@ expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end - + it "should create bnode for path x^p" do n3 = %(:x2^:y2 :p2 "3" .) nt = %(_:bnode0 :y2 :x2 . _:bnode0 :p2 "3" .) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end - + it "should decode :joe!fam:mother!loc:office!loc:zip as Joe's mother's office's zipcode" do n3 = %( @prefix fam: . @@ -881,7 +883,7 @@ end end end - + describe "formulae" do before(:each) { @repo = RDF::Repository.new } @@ -926,7 +928,7 @@ before(:each) do n3 = %( # Test data in notation3 http://www.w3.org/DesignIssues/Notation3.html - # + # @prefix u: . @prefix : <#> . @@ -968,7 +970,7 @@ end end - + describe "object lists" do it "should create 2 statements for simple list" do n3 = %(:a :b :c, :d) @@ -977,7 +979,7 @@ expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) end end - + describe "property lists" do it "should parse property list" do n3 = %( @@ -995,7 +997,7 @@ expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end end - + describe "lists" do it "should parse empty list" do n3 = %(@prefix :. :empty :set ().) @@ -1003,7 +1005,7 @@ .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should parse list with single element" do n3 = %(@prefix :. :gregg :wrote ("RdfContext").) nt = %( @@ -1013,7 +1015,7 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should parse list with multiple elements" do n3 = %(@prefix :. :gregg :name ("Gregg" "Barnum" "Kellogg").) nt = %( @@ -1027,7 +1029,7 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should parse unattached lists" do n3 = %( @prefix a: . @@ -1046,7 +1048,7 @@ ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end - + it "should add property to nil list" do n3 = %(@prefix a: . () a:prop "nilProp" .) nt = %( "nilProp" .) @@ -1057,7 +1059,7 @@ n3 = %( @prefix a: . a:a a:p ( - [ a:p2 "v1" ] + [ a:p2 "v1" ] ("inner list") @@ -1090,9 +1092,9 @@ expect(seq.third).to eq RDF::URI.new("http://resource2") expect(seq.fourth).to be_node end - + end - + # n3p tests taken from http://inamidst.com/n3p/test/ describe "with real data tests" do dirs = %w(misc lcsh rdflib n3p) @@ -1125,7 +1127,7 @@ expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end end - + describe "with blank clause" do it "should have 4 namespaces" do n3 = %( @@ -1146,7 +1148,7 @@ expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) end end - + describe "with empty subject" do before(:each) do @graph = parse(%( @@ -1157,14 +1159,14 @@ <> a log:N3Document. ), base_uri: "http://test/") end - + it "should have 4 namespaces" do nt = %( . ) expect(@graph).to be_equivalent_graph(nt, about: "http://test/", logger: logger) end - + it "should have default subject" do expect(@graph.size).to eq 1 expect(@graph.statements.first.subject.to_s).to eq "http://test/" @@ -1226,7 +1228,7 @@ end end end - + describe "validation" do { %(:y :p1 "xyz"^^xsd:integer .) => %r("xyz" is not a valid .*), @@ -1245,7 +1247,7 @@ end end end - + it "should parse rdf_core testcase" do sampledoc = <<-EOF; . @@ -1264,7 +1266,7 @@ logger: logger ) end - + def parse(input, options = {}) options = { logger: logger, @@ -1272,8 +1274,14 @@ def parse(input, options = {}) canonicalize: false, }.merge(options) graph = options[:repo] || RDF::Repository.new - RDF::N3::Reader.new(input, options).each_pattern do |pattern| - graph << pattern + if options[:patterns] + RDF::N3::Reader.new(input, options).each_pattern do |statement| + graph << statement + end + else + RDF::N3::Reader.new(input, options).each_statement do |statement| + graph << statement + end end graph end diff --git a/spec/test-files/builtin_generated_match-ref.n3 b/spec/test-files/builtin_generated_match-ref.n3 new file mode 100644 index 0000000..e601907 --- /dev/null +++ b/spec/test-files/builtin_generated_match-ref.n3 @@ -0,0 +1,17 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/builtin_generated_match.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/builtin_generated_match.n3 + @prefix : <#> . + + () a :Thing . + ( ( + :q ) ) + a :Thing . + + :q a :GreatThing . + +#ENDS diff --git a/spec/test-files/list-bug1-ref.n3 b/spec/test-files/list-bug1-ref.n3 new file mode 100644 index 0000000..ca924a2 --- /dev/null +++ b/spec/test-files/list-bug1-ref.n3 @@ -0,0 +1,16 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/list-bug1.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/list-bug1.n3 + @prefix : <#> . + + ( + ) + a . + + :RESULT . + +#ENDS diff --git a/spec/test-files/list-bug2-ref.n3 b/spec/test-files/list-bug2-ref.n3 new file mode 100644 index 0000000..4d0fc6b --- /dev/null +++ b/spec/test-files/list-bug2-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/list-bug2.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/list-bug2.n3 + @prefix : <#> . + + "fred whatever " a :RESULT . + +#ENDS diff --git a/spec/test-files/manifest.n3 b/spec/test-files/manifest.n3 index 2ecaa4d..a1e534f 100644 --- a/spec/test-files/manifest.n3 +++ b/spec/test-files/manifest.n3 @@ -42,21 +42,21 @@ rdfs:comment "List processing bug check 1" ; mf:action ; mf:result ; - test:options [] . + test:options [test:think true; test:data true;] . :t1018b2 a test:CwmTest; mf:name "list-bug2.n3" ; rdfs:comment "List processing bug check 2" ; mf:action ; mf:result ; - test:options [] . + test:options [test:think true; test:data true;] . :t1031 a test:CwmTest; mf:name "li-r1.n3" ; rdfs:comment "Inference using lists" ; mf:action ; mf:result ; - test:options [test:think true;] . + test:options [test:think true; test:data true;] . :t2004u2 a test:CwmTest; mf:name "list-unify2.n3" ; diff --git a/spec/test-files/r1-ref.n3 b/spec/test-files/r1-ref.n3 new file mode 100644 index 0000000..69ffbfb --- /dev/null +++ b/spec/test-files/r1-ref.n3 @@ -0,0 +1,17 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/r1.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/r1.n3 + @prefix : . + + "one" a :SUCCESS . + + "two" a :SUCCESS . + ( "one" + "two" ) + a :whatever . + +#ENDS diff --git a/spec/test-files/unify2-ref.n3 b/spec/test-files/unify2-ref.n3 new file mode 100644 index 0000000..d7a801a --- /dev/null +++ b/spec/test-files/unify2-ref.n3 @@ -0,0 +1,14 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify2.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify2.n3 + @prefix : <#> . + + 17 a :RESULT . + ( 17 ) + a :TestCase . + +#ENDS diff --git a/spec/test-files/unify3-ref.n3 b/spec/test-files/unify3-ref.n3 new file mode 100644 index 0000000..b17ebf5 --- /dev/null +++ b/spec/test-files/unify3-ref.n3 @@ -0,0 +1,21 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify3.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify3.n3 + @prefix : <#> . + + 17 a :RESULT . + ( + ( + + 17 + ) + ) + a :TestCase . + + :THIS_TEST a :SUCCESS . + +#ENDS diff --git a/spec/test-files/unify3.n3 b/spec/test-files/unify3.n3 index 43565a5..28db498 100644 --- a/spec/test-files/unify3.n3 +++ b/spec/test-files/unify3.n3 @@ -3,6 +3,7 @@ # use with --think @prefix : <#>. +@prefix rdf: . @prefix log: . @prefix math: . @@ -11,8 +12,8 @@ # Cause llyn to search the store for a list: -( ( 17 ) ) a :TestCase. +( ( 17 ) ) a :TestCase. { ( ( ?x ) ) a :TestCase} => { ?x a :RESULT }. diff --git a/spec/test-files/unify4-ref.n3 b/spec/test-files/unify4-ref.n3 new file mode 100644 index 0000000..a7847d3 --- /dev/null +++ b/spec/test-files/unify4-ref.n3 @@ -0,0 +1,34 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify4.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify4.n3 + @prefix : <#> . + + 2 a :RESULT . + ( 1 + 13 ) + :crinks ( + 2 + 14 ) . + ( 1 + 14 ) + :crinks ( + 2 + 13 ) . + ( 1 + 2 ) + :crinks ( + 7 + 2 ) . + ( 1 + 3 ) + :crinks ( + 999 + 999 ) . + + :THIS_TEST a :SUCCESS . + +#ENDS diff --git a/spec/test-files/unify5-ref.n3 b/spec/test-files/unify5-ref.n3 new file mode 100644 index 0000000..a662e62 --- /dev/null +++ b/spec/test-files/unify5-ref.n3 @@ -0,0 +1,33 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify5.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify5.n3 + @prefix : <#> . + + :Aphrodite a :ShouldBeAphrodite, + :ShouldBeAphrodite2 . + + :Bob a :ShouldBeJunoBobJoe . + + :Joe a :ShouldBeJunoBobJoe . + + :Juno a :ShouldBeJunoBobJoe . + + :fred :siblings ( + [ + a :BnodeExtractedError; + :parents ( + :Alice + :Bob ), + ( + :Jane + :Joe ), + ( + :Zeus + :Juno ) ] + :Aphrodite ) . + +#ENDS diff --git a/spec/test_file_spec.rb b/spec/test_file_spec.rb index cca8154..5d82913 100644 --- a/spec/test_file_spec.rb +++ b/spec/test_file_spec.rb @@ -105,18 +105,32 @@ def inspect case t.id.split('#').last when *%w{listin bnode} - #pending "support for li:member" + pending "support for li:member" + when *%w{t2006} + pending "support for li:last" + when *%w{t1018b2} + pending "support for string:concat" + when *%w{t2005} + pending "understanding output filtering" end reader = RDF::N3::Reader.new(t.input, base_uri: t.base, logger: t.logger) + reasoner = RDF::N3::Reasoner.new(t.input, + base_uri: t.base, + logger: t.logger) + repo = RDF::Repository.new if t.positive_test? begin - repo << reader + if t.options["think"] + repo = reasoner.execute(logger: t.logger, think: t.options['think']) + else + repo << reader + end rescue Exception => e expect(e.message).to produce("Not exception #{e.inspect}", t) end From c258a9348e09f24d2cc198d5253d70942d86b6b1 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 16 Jan 2019 12:33:07 -0800 Subject: [PATCH 14/40] Do bnode to non-distinctive variable substitution when creating query, not when parsing. If no solutions bound, when generating triples, bind non-distinguished variables to new blank nodes. --- lib/rdf/n3/algebra/formula.rb | 60 +++++++++++++++++++++++++++++------ lib/rdf/n3/reader.rb | 31 +++--------------- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index e2a1cc6..c1f265d 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -30,11 +30,8 @@ class Formula < SPARQL::Algebra::Operator def execute(queryable, **options, &block) log_debug {"formula #{graph_name} #{operands.to_sxp}"} - patterns = operands.select {|op| op.is_a?(RDF::Statement) && op.variable?} - sub_ops = operands.reject {|op| op.is_a?(RDF::Statement)} - - query = RDF::Query.new(patterns) - @solutions = queryable.query(query, **options) + @query ||= RDF::Query.new(patterns) + @solutions = queryable.query(@query, **options) # Join solutions from other operands log_depth do @@ -61,11 +58,15 @@ def execute(queryable, **options, &block) # @yieldparam [RDF::Statement] solution # @yieldreturn [void] ignored def each(&block) - @solutions ||= RDF::Query::Solutions.new + @solutions ||= begin + # If there are no solutions, create bindings for all existential variables using the variable name as the bnode identifier + RDF::Query::Solutions.new( + [RDF::Query::Solution.new( + patterns.ndvars.inject({}) {|memo, v| memo.merge(v.name => RDF::Node.intern(v.name))} + )] + ) + end log_debug {"formula #{graph_name} each #{@solutions.inspect}"} - constants = operands.select {|op| op.is_a?(RDF::Statement) && op.constant?} - patterns = operands.select {|op| op.is_a?(RDF::Statement) && op.variable?} - sub_ops = operands.reject {|op| op.is_a?(RDF::Statement)} # Yield constant statements/patterns constants.each do |pattern| @@ -116,6 +117,47 @@ def solutions=(solutions) # @return [RDF::Resource] def graph_name; @options[:graph_name]; end + ## + # Statements memoizer + def statements + # BNodes in statements are existential variables + @statements ||= operands. + select {|op| op.is_a?(RDF::Statement)}. + map do |pattern| + + terms = {} + [:subject, :predicate, :object].each do |r| + terms[r] = case o = pattern.send(r) + when RDF::Node then RDF::Query::Variable.new(o.id, distinguished: false) + else o + end + end + + RDF::Query::Pattern.from(terms) + end + end + + ## + # Constants memoizer + def constants + # BNodes in statements are existential variables + @constants ||= statements.select(&:constant?) + end + + ## + # Patterns memoizer + def patterns + # BNodes in statements are existential variables + @patterns ||= statements.reject(&:constant?) + end + + ## + # Non-statement operands memoizer + def sub_ops + # BNodes in statements are existential variables + @sub_ops ||= operands.reject {|op| op.is_a?(RDF::Statement)} + end + def to_sxp_bin @existentials = ndvars.uniq @universals = vars.uniq - @existentials diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index e0e72d0..99a2bda 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -629,7 +629,7 @@ def process_anonnode(anonnode) if anonnode[:propertylist] properties = anonnode[:propertylist] - bnode = @formulae.last ? univar(nil, distinguished: false) : bnode() + bnode = bnode() properties.each do |p| predicate = p[:verb] log_debug("process_anonnode(verb)", depth: depth) {predicate.inspect} @@ -649,27 +649,9 @@ def process_anonnode(anonnode) list_subjects = {} list.each_statement do |statement| next if statement.predicate == RDF.type && statement.object == RDF.List - - # In formula, use variables instead of bnodes - subject = if @formulae.last && statement.subject.node? - list_subjects[statement.subject.id] ||= univar(statement.subject.id, distinguished: false) - else - statement.subject - end - - object = if @formulae.last && statement.object.node? && statement.predicate == RDF.rest - list_subjects[statement.object.id] ||= univar(statement.object.id, distinguished: false) - else - statement.object - end - add_statement("anonnode(list)", subject, statement.predicate, object) - end - - if @formulae.last && list.subject.node? - list_subjects[list.subject.id] ||= univar(list.subject.id, distinguished: false) - else - list.subject + add_statement("anonnode(list)", statement.subject, statement.predicate, statement.object) end + list.subject end end @@ -729,12 +711,7 @@ def process_qname(tok) elsif prefix == '_' log_debug('process_qname(bnode)', name, depth: depth) # If we're in a formula, create a non-distigushed variable instead - if @formulae.last - var = find_var(@formulae.last, name) || univar(name, distinguished: false) - add_var_to_formula(formulae.last, name, var) - else - bnode(name) - end + bnode(name) else log_debug('process_qname(default_ns)', name, depth: depth) namespace(nil, uri("#{base_uri}#")) unless prefix(nil) From 69ed7a30dc9835a4909f53eb2afb4623ab8bdaec Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 16 Jan 2019 15:28:12 -0800 Subject: [PATCH 15/40] Remove each_pattern, and just use each_statement again. --- lib/rdf/n3/reader.rb | 47 +++++--------------------------------------- spec/reader_spec.rb | 18 ++++++----------- 2 files changed, 11 insertions(+), 54 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 99a2bda..56e4a59 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -101,11 +101,11 @@ def inspect end ## - # Iterates over both statements and patterns. - # @yield [pattern] - # @yieldparam [RDF::Pattern] pattern + # Iterates the given block for each RDF statement in the input. + # @yield [statement] + # @yieldparam [RDF::Statement] statement # @return [void] - def each_pattern(&block) + def each_statement(&block) if block_given? @callback = block @@ -115,43 +115,6 @@ def each_pattern(&block) raise RDF::ReaderError, "Errors found during processing" end end - enum_for(:each_pattern) - end - - ## - # Iterates the given block for each RDF statement in the input. - # - # @yield [statement] - # @yieldparam [RDF::Statement] statement - # @return [void] - def each_statement - if block_given? - each_pattern do |pattern| - # Turn patterns into statements through simple BNode replacement - if pattern.is_a?(RDF::Query::Pattern) || pattern.variable? - yield RDF::Statement.new( - *pattern.to_triple.map do |r| - # If bound, replace variable with it's value, otherwise - # create a URI. - if r.variable? - if r.bound? - r.value - elsif r.distinguished? - RDF::URI(r.name.to_s) - else - bnode(r.name) - end - else - r - end - end, - graph_name: pattern.graph_name - ) - else - yield pattern - end - end - end enum_for(:each_statement) end @@ -186,7 +149,7 @@ def formula # Add patterns to appropiate formula based on graph_name, # and replace subject and object bnodes which identify # named graphs with those formula - each_pattern do |pattern| + each_statement do |pattern| # A graph name indicates a formula. If not already allocated, create a new formula and use that for inserting statements or other operators form = formulae[pattern.graph_name] ||= begin Algebra::Formula.new(graph_name: pattern.graph_name, **@options) diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index e6dd5d9..296ae7a 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -476,7 +476,7 @@ context "patterns" do it "substitutes variable for URI with @forAll" do n3 = %(@forAll :x . :x :y :z .) - g = parse(n3, base_uri: "http://a/b", patterns: true) + g = parse(n3, base_uri: "http://a/b") statement = g.statements.first expect(statement.subject).to be_variable expect(statement.predicate.to_s).to eq "http://a/b#y" @@ -485,7 +485,7 @@ it "substitutes variable for URIs with @forAll" do n3 = %(@forAll :x, :y, :z . :x :y :z .) - g = parse(n3, base_uri: "http://a/b", patterns: true) + g = parse(n3, base_uri: "http://a/b") statement = g.statements.first expect(statement.subject).to be_variable expect(statement.predicate).to be_variable @@ -497,7 +497,7 @@ it "substitutes node for URI with @forEach" do n3 = %(@forSome :x . :x :y :z .) - g = parse(n3, base_uri: "http://a/b", patterns: true) + g = parse(n3, base_uri: "http://a/b") statement = g.statements.first expect(statement.subject).to be_variable, logger.to_s expect(statement.predicate.to_s).to eq "http://a/b#y" @@ -506,7 +506,7 @@ it "substitutes node for URIs with @forEach" do n3 = %(@forSome :x, :y, :z . :x :y :z .) - g = parse(n3, base_uri: "http://a/b", patterns: true) + g = parse(n3, base_uri: "http://a/b") statement = g.statements.first expect(statement.subject).to be_variable expect(statement.predicate).to be_variable @@ -1274,14 +1274,8 @@ def parse(input, options = {}) canonicalize: false, }.merge(options) graph = options[:repo] || RDF::Repository.new - if options[:patterns] - RDF::N3::Reader.new(input, options).each_pattern do |statement| - graph << statement - end - else - RDF::N3::Reader.new(input, options).each_statement do |statement| - graph << statement - end + RDF::N3::Reader.new(input, options).each_statement do |statement| + graph << statement end graph end From 7f79aaca6ff187e52d2855423702e8aa951138ea Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 16 Jan 2019 15:28:25 -0800 Subject: [PATCH 16/40] Add more SWAP tests. --- .../{ => includes}/bnode-conclude-ref.n3 | 0 .../{ => includes}/bnodeConclude.n3 | 0 spec/test-files/{ => includes}/list-in-ref.n3 | 0 spec/test-files/{ => includes}/list-in.n3 | 0 spec/test-files/{ => list}/append-ref.n3 | 0 spec/test-files/{ => list}/append.n3 | 0 .../{ => list}/builtin_generated_match-ref.n3 | 0 .../{ => list}/builtin_generated_match.n3 | 0 spec/test-files/{ => list}/last.n3 | 0 spec/test-files/{ => list}/list-bug1-ref.n3 | 0 spec/test-files/{ => list}/list-bug1.n3 | 0 spec/test-files/{ => list}/list-bug2-ref.n3 | 0 spec/test-files/{ => list}/list-bug2.n3 | 0 spec/test-files/{ => list}/r1-ref.n3 | 0 spec/test-files/{ => list}/r1.n3 | 0 spec/test-files/{ => list}/unify2-ref.n3 | 0 spec/test-files/{ => list}/unify2.n3 | 0 spec/test-files/{ => list}/unify3-ref.n3 | 0 spec/test-files/{ => list}/unify3.n3 | 0 spec/test-files/{ => list}/unify4-ref.n3 | 0 spec/test-files/{ => list}/unify4.n3 | 0 spec/test-files/{ => list}/unify5-ref.n3 | 0 spec/test-files/{ => list}/unify5.n3 | 0 spec/test-files/manifest.n3 | 213 ++++++++++++++++-- spec/test-files/norm/av-ref.n3 | 98 ++++++++ spec/test-files/norm/av.n3 | 89 ++++++++ spec/test-files/reason/double-ref.n3 | 15 ++ spec/test-files/reason/double.n3 | 10 + spec/test-files/reason/single_gen.n3 | 3 + spec/test-files/reason/socrates-ref.n3 | 13 ++ spec/test-files/reason/socrates.n3 | 7 + spec/test-files/reason/t1-ref.n3 | 11 + spec/test-files/reason/t1.n3 | 1 + spec/test-files/reason/t2-ref.n3 | 13 ++ spec/test-files/reason/t2.n3 | 4 + spec/test-files/reason/t3-ref.n3 | 14 ++ spec/test-files/reason/t3.n3 | 6 + spec/test-files/reason/t4-ref.n3 | 14 ++ spec/test-files/reason/t4.n3 | 6 + spec/test-files/reason/t5-ref.n3 | 14 ++ spec/test-files/reason/t5.n3 | 7 + spec/test-files/reason/t6-ref.n3 | 14 ++ spec/test-files/reason/t6.n3 | 15 ++ spec/test-files/reason/t8-ref.n3 | 12 + spec/test-files/reason/t8.n3 | 6 + spec/test-files/reason/t9-ref.n3 | 12 + spec/test-files/reason/t9.n3 | 8 + spec/test-files/string/endsWith-out.n3 | 27 +++ spec/test-files/string/endsWith.n3 | 37 +++ spec/test-files/string/roughly-out.n3 | 30 +++ spec/test-files/string/roughly.n3 | 42 ++++ spec/test-files/string/uriEncode-out.n3 | 46 ++++ spec/test-files/string/uriEncode.n3 | 61 +++++ spec/test-files/supports/simple-ref.n3 | 12 + spec/test-files/supports/simple.n3 | 5 + spec/test-files/unify/reflexive-ref.n3 | 12 + spec/test-files/unify/reflexive.n3 | 6 + spec/test-files/unify/unify1-ref.n3 | 12 + spec/test-files/unify/unify1.n3 | 13 ++ spec/test-files/unify/unify2-ref.n3 | 12 + spec/test-files/unify/unify2.n3 | 11 + spec/test_file_spec.rb | 12 +- 62 files changed, 916 insertions(+), 27 deletions(-) rename spec/test-files/{ => includes}/bnode-conclude-ref.n3 (100%) rename spec/test-files/{ => includes}/bnodeConclude.n3 (100%) rename spec/test-files/{ => includes}/list-in-ref.n3 (100%) rename spec/test-files/{ => includes}/list-in.n3 (100%) rename spec/test-files/{ => list}/append-ref.n3 (100%) rename spec/test-files/{ => list}/append.n3 (100%) rename spec/test-files/{ => list}/builtin_generated_match-ref.n3 (100%) rename spec/test-files/{ => list}/builtin_generated_match.n3 (100%) rename spec/test-files/{ => list}/last.n3 (100%) rename spec/test-files/{ => list}/list-bug1-ref.n3 (100%) rename spec/test-files/{ => list}/list-bug1.n3 (100%) rename spec/test-files/{ => list}/list-bug2-ref.n3 (100%) rename spec/test-files/{ => list}/list-bug2.n3 (100%) rename spec/test-files/{ => list}/r1-ref.n3 (100%) rename spec/test-files/{ => list}/r1.n3 (100%) rename spec/test-files/{ => list}/unify2-ref.n3 (100%) rename spec/test-files/{ => list}/unify2.n3 (100%) rename spec/test-files/{ => list}/unify3-ref.n3 (100%) rename spec/test-files/{ => list}/unify3.n3 (100%) rename spec/test-files/{ => list}/unify4-ref.n3 (100%) rename spec/test-files/{ => list}/unify4.n3 (100%) rename spec/test-files/{ => list}/unify5-ref.n3 (100%) rename spec/test-files/{ => list}/unify5.n3 (100%) create mode 100644 spec/test-files/norm/av-ref.n3 create mode 100644 spec/test-files/norm/av.n3 create mode 100644 spec/test-files/reason/double-ref.n3 create mode 100644 spec/test-files/reason/double.n3 create mode 100644 spec/test-files/reason/single_gen.n3 create mode 100644 spec/test-files/reason/socrates-ref.n3 create mode 100644 spec/test-files/reason/socrates.n3 create mode 100644 spec/test-files/reason/t1-ref.n3 create mode 100644 spec/test-files/reason/t1.n3 create mode 100644 spec/test-files/reason/t2-ref.n3 create mode 100644 spec/test-files/reason/t2.n3 create mode 100644 spec/test-files/reason/t3-ref.n3 create mode 100644 spec/test-files/reason/t3.n3 create mode 100644 spec/test-files/reason/t4-ref.n3 create mode 100644 spec/test-files/reason/t4.n3 create mode 100644 spec/test-files/reason/t5-ref.n3 create mode 100644 spec/test-files/reason/t5.n3 create mode 100644 spec/test-files/reason/t6-ref.n3 create mode 100644 spec/test-files/reason/t6.n3 create mode 100644 spec/test-files/reason/t8-ref.n3 create mode 100644 spec/test-files/reason/t8.n3 create mode 100644 spec/test-files/reason/t9-ref.n3 create mode 100644 spec/test-files/reason/t9.n3 create mode 100644 spec/test-files/string/endsWith-out.n3 create mode 100644 spec/test-files/string/endsWith.n3 create mode 100644 spec/test-files/string/roughly-out.n3 create mode 100644 spec/test-files/string/roughly.n3 create mode 100644 spec/test-files/string/uriEncode-out.n3 create mode 100644 spec/test-files/string/uriEncode.n3 create mode 100644 spec/test-files/supports/simple-ref.n3 create mode 100644 spec/test-files/supports/simple.n3 create mode 100644 spec/test-files/unify/reflexive-ref.n3 create mode 100644 spec/test-files/unify/reflexive.n3 create mode 100644 spec/test-files/unify/unify1-ref.n3 create mode 100644 spec/test-files/unify/unify1.n3 create mode 100644 spec/test-files/unify/unify2-ref.n3 create mode 100644 spec/test-files/unify/unify2.n3 diff --git a/spec/test-files/bnode-conclude-ref.n3 b/spec/test-files/includes/bnode-conclude-ref.n3 similarity index 100% rename from spec/test-files/bnode-conclude-ref.n3 rename to spec/test-files/includes/bnode-conclude-ref.n3 diff --git a/spec/test-files/bnodeConclude.n3 b/spec/test-files/includes/bnodeConclude.n3 similarity index 100% rename from spec/test-files/bnodeConclude.n3 rename to spec/test-files/includes/bnodeConclude.n3 diff --git a/spec/test-files/list-in-ref.n3 b/spec/test-files/includes/list-in-ref.n3 similarity index 100% rename from spec/test-files/list-in-ref.n3 rename to spec/test-files/includes/list-in-ref.n3 diff --git a/spec/test-files/list-in.n3 b/spec/test-files/includes/list-in.n3 similarity index 100% rename from spec/test-files/list-in.n3 rename to spec/test-files/includes/list-in.n3 diff --git a/spec/test-files/append-ref.n3 b/spec/test-files/list/append-ref.n3 similarity index 100% rename from spec/test-files/append-ref.n3 rename to spec/test-files/list/append-ref.n3 diff --git a/spec/test-files/append.n3 b/spec/test-files/list/append.n3 similarity index 100% rename from spec/test-files/append.n3 rename to spec/test-files/list/append.n3 diff --git a/spec/test-files/builtin_generated_match-ref.n3 b/spec/test-files/list/builtin_generated_match-ref.n3 similarity index 100% rename from spec/test-files/builtin_generated_match-ref.n3 rename to spec/test-files/list/builtin_generated_match-ref.n3 diff --git a/spec/test-files/builtin_generated_match.n3 b/spec/test-files/list/builtin_generated_match.n3 similarity index 100% rename from spec/test-files/builtin_generated_match.n3 rename to spec/test-files/list/builtin_generated_match.n3 diff --git a/spec/test-files/last.n3 b/spec/test-files/list/last.n3 similarity index 100% rename from spec/test-files/last.n3 rename to spec/test-files/list/last.n3 diff --git a/spec/test-files/list-bug1-ref.n3 b/spec/test-files/list/list-bug1-ref.n3 similarity index 100% rename from spec/test-files/list-bug1-ref.n3 rename to spec/test-files/list/list-bug1-ref.n3 diff --git a/spec/test-files/list-bug1.n3 b/spec/test-files/list/list-bug1.n3 similarity index 100% rename from spec/test-files/list-bug1.n3 rename to spec/test-files/list/list-bug1.n3 diff --git a/spec/test-files/list-bug2-ref.n3 b/spec/test-files/list/list-bug2-ref.n3 similarity index 100% rename from spec/test-files/list-bug2-ref.n3 rename to spec/test-files/list/list-bug2-ref.n3 diff --git a/spec/test-files/list-bug2.n3 b/spec/test-files/list/list-bug2.n3 similarity index 100% rename from spec/test-files/list-bug2.n3 rename to spec/test-files/list/list-bug2.n3 diff --git a/spec/test-files/r1-ref.n3 b/spec/test-files/list/r1-ref.n3 similarity index 100% rename from spec/test-files/r1-ref.n3 rename to spec/test-files/list/r1-ref.n3 diff --git a/spec/test-files/r1.n3 b/spec/test-files/list/r1.n3 similarity index 100% rename from spec/test-files/r1.n3 rename to spec/test-files/list/r1.n3 diff --git a/spec/test-files/unify2-ref.n3 b/spec/test-files/list/unify2-ref.n3 similarity index 100% rename from spec/test-files/unify2-ref.n3 rename to spec/test-files/list/unify2-ref.n3 diff --git a/spec/test-files/unify2.n3 b/spec/test-files/list/unify2.n3 similarity index 100% rename from spec/test-files/unify2.n3 rename to spec/test-files/list/unify2.n3 diff --git a/spec/test-files/unify3-ref.n3 b/spec/test-files/list/unify3-ref.n3 similarity index 100% rename from spec/test-files/unify3-ref.n3 rename to spec/test-files/list/unify3-ref.n3 diff --git a/spec/test-files/unify3.n3 b/spec/test-files/list/unify3.n3 similarity index 100% rename from spec/test-files/unify3.n3 rename to spec/test-files/list/unify3.n3 diff --git a/spec/test-files/unify4-ref.n3 b/spec/test-files/list/unify4-ref.n3 similarity index 100% rename from spec/test-files/unify4-ref.n3 rename to spec/test-files/list/unify4-ref.n3 diff --git a/spec/test-files/unify4.n3 b/spec/test-files/list/unify4.n3 similarity index 100% rename from spec/test-files/unify4.n3 rename to spec/test-files/list/unify4.n3 diff --git a/spec/test-files/unify5-ref.n3 b/spec/test-files/list/unify5-ref.n3 similarity index 100% rename from spec/test-files/unify5-ref.n3 rename to spec/test-files/list/unify5-ref.n3 diff --git a/spec/test-files/unify5.n3 b/spec/test-files/list/unify5.n3 similarity index 100% rename from spec/test-files/unify5.n3 rename to spec/test-files/list/unify5.n3 diff --git a/spec/test-files/manifest.n3 b/spec/test-files/manifest.n3 index a1e534f..a17b2e2 100644 --- a/spec/test-files/manifest.n3 +++ b/spec/test-files/manifest.n3 @@ -12,11 +12,42 @@ rdfs:label "Notation3 tests" ; mf:entries ( + # From swap/test/crypto + + # From swap/test/cwm + + # From swap/test/delta + + # From swap/test/i18n + # From swap/test/includes :listin :bnode + # From swap/test/math + + # From swap/test/norm + :norm10 + # From swap/test/list :t1018b1 :t1018b2 :t1031 :t2004u2 :t2004u3 :t2004u4 :t2004u5 :t2005 :t2006 :t2007 + + # From swap/test/paw + + # From swap/test/ql + + # From swap/test/reason + :t01proof :t02proof :t03proof :t04proof :t05proof :t06proof :socrates :t08proof + :t09proof :t12 + + # From swap/test/string + :t103 :t104 :t105 + + # From swap/test/supports + :t01 + + # From swap/test/unify + :t553 :t554 :t555 + ) . # List tests from swap/tests/includes @@ -24,15 +55,23 @@ :listin a test:CwmTest; mf:name "list membership" ; rdfs:comment "Builtins for list membership, binding multiple values." ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; test:options [test:think true; test:data true;] . :bnode a test:CwmTest; mf:name "list membership" ; rdfs:comment "Builtins for list membership, binding multiple values." ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +# Tests from swap/test/norm +:norm10 a test:CwmTest; + mf:name "norm-av1.n3"; + rdfs:comment "Bug with RDF output in bySubject mode"; + mf:action ; + mf:result ; test:options [test:think true; test:data true;] . # List tests from swap/test/list @@ -40,73 +79,199 @@ :t1018b1 a test:CwmTest; mf:name "list-bug1.n3" ; rdfs:comment "List processing bug check 1" ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; test:options [test:think true; test:data true;] . :t1018b2 a test:CwmTest; mf:name "list-bug2.n3" ; rdfs:comment "List processing bug check 2" ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; test:options [test:think true; test:data true;] . :t1031 a test:CwmTest; mf:name "li-r1.n3" ; rdfs:comment "Inference using lists" ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; test:options [test:think true; test:data true;] . :t2004u2 a test:CwmTest; mf:name "list-unify2.n3" ; rdfs:comment "List unification 2 - variable in list" ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; test:options [test:think true; test:data true] . :t2004u3 a test:CwmTest; mf:name "list-unify3.n3" ; rdfs:comment "List unification 3 - nested lists" ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; test:options [test:think true; test:data true] . :t2004u4 a test:CwmTest; mf:name "list-unify4.n3" ; rdfs:comment "List unification 4 - nested lists" ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; test:options [test:think true; test:data true] . :t2004u5 a test:CwmTest; mf:name "list-unify5.n3" ; rdfs:comment "List unification 5 - multiple values" ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; test:options [test:think true; test:data true] . :t2005 a test:CwmTest; mf:name "append-out.n3" ; rdfs:comment "Iterative ops on lists" ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; test:options [test:think true; test:data true] . :t2006 a test:CwmTest; mf:name "list-last.n3" ; rdfs:comment "last, in builtins on lists" ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; test:options [test:think true; test:data true] . :t2007 a test:CwmTest; mf:name "list-builtin_generated_match.n3" ; rdfs:comment "last, in builtins on lists" ; - mf:action ; - mf:result ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +# List tests from swap/test/reason + +:t01proof a test:CwmTest; + mf:name "reason-t1.n3" ; + rdfs:comment "Proof for just loading a file" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t02proof a test:CwmTest; + mf:name "reason-t2.n3" ; + rdfs:comment "Proof for just loading a file" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t03proof a test:CwmTest; + mf:name "reason-t3.n3" ; + rdfs:comment "Proof for just loading a file" ; + mf:action ; + mf:result ; test:options [test:think true; test:data true] . +:t04proof a test:CwmTest; + mf:name "reason-t4.n3" ; + rdfs:comment "Proof for just loading a file" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t05proof a test:CwmTest; + mf:name "reason-t5.n3" ; + rdfs:comment "Proof for a little inference" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t06proof a test:CwmTest; + mf:name "reason-t5.n3" ; + rdfs:comment "Proof for a little inference" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +# This is equiv of others but easier to explain with famous example +:socrates a test:CwmTest; + mf:name "reason-socrates.n3" ; + rdfs:comment "Proof for a little inference" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t08proof a test:CwmTest; + mf:name "reason-t8.n3" ; + rdfs:comment "Proof for a little inference, --n3=B to name BNodes" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t09proof a test:CwmTest; + mf:name "reason-t9.n3" ; + rdfs:comment "Proof for a little inference - binding Bnode to symbol" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t12 a test:CwmTest; # too unstable! + mf:name "reason-double.n3" ; + rdfs:comment "Proof for a little inference - binding Bnode to symbol" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +# List tests from swap/test/string + +:t103 a test:CwmTest; + mf:name "string-endsWith.n3" ; + rdfs:comment "string:endsWith" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +:t104 a test:CwmTest; + mf:name "string-roughly.n3" ; + rdfs:comment "string:containsRoughly ignores case smart whitespace" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +:t108 a test:CwmTest; + mf:name "string-uriEncode.n3" ; + rdfs:comment "string:encodeForURI and encodeForFragID" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +# List tests from swap/test/supports +:t01 a test:CwmTest; + mf:name "supports-simple.n3" ; + rdfs:comment "A very simple use of log:supports" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +# List tests from swap/test/unify +:t553 a test:CwmTest; + mf:name "unify-unify1.n3" ; + rdfs:comment "log:includes looking for @forAll" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +:t554 a test:CwmTest; + mf:name "unify-unify2.n3" ; + rdfs:comment "Query looking for @forAll" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +:t555 a test:CwmTest; + mf:name "unify-reflexive.n3" ; + rdfs:comment "Include using the same var twice" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + #### # Test Vocabulary diff --git a/spec/test-files/norm/av-ref.n3 b/spec/test-files/norm/av-ref.n3 new file mode 100644 index 0000000..1521a92 --- /dev/null +++ b/spec/test-files/norm/av-ref.n3 @@ -0,0 +1,98 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/norm/av.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/norm/av.n3 + @prefix : . + @prefix gv: . + @prefix rdfs: . + + <> gv:digraph [ + gv:label "AV Diagram"; + gv:subgraph [ + gv:hasEdgeProperty :dottedEdge, + :solidEdge; + gv:hasNode :ab, + :cable, + :catv, + :dvd, + :splitter, + :tv, + :vcr; + gv:label "" ] ] . + + :AV a rdfs:Class . + + :ab a :AV; + :output :selector; + :solidEdge :selector; + :title "A/B Switch"; + gv:color "black"; + gv:label "A/B Switch"; + gv:shape "box" . + + :cable a :AV; + :output :vcr; + :solidEdge :vcr; + :title "Cable Box"; + gv:color "black"; + gv:label "Cable Box"; + gv:shape "box" . + + :catv a :AV; + :title "CATV"; + gv:color "black"; + gv:label "CATV"; + gv:shape "box" . + + :dottedEdge gv:style "dotted" . + + :dvd a :AV; + :output :selector; + :solidEdge :selector; + :title "DVD"; + gv:color "black"; + gv:label "DVD"; + gv:shape "box" . + + :input a rdfs:Property . + + :output a rdfs:Property . + + :selector a :AV; + :output :tv; + :solidEdge :tv; + :title "Signal Selector"; + gv:color "black"; + gv:label "Signal Selector"; + gv:shape "box" . + + :solidEdge gv:style "solid" . + + :splitter a :AV; + :output :ab; + :solidEdge :ab; + :title "Splitter"; + gv:color "black"; + gv:label "Splitter"; + gv:shape "box" . + + :title a rdfs:Property . + + :tv a :AV; + :title "TV"; + gv:color "black"; + gv:label "TV"; + gv:shape "box" . + + :vcr a :AV; + :output :ab; + :solidEdge :ab; + :title "VCR"; + gv:color "black"; + gv:label "VCR"; + gv:shape "box" . + +#ENDS diff --git a/spec/test-files/norm/av.n3 b/spec/test-files/norm/av.n3 new file mode 100644 index 0000000..1010cd1 --- /dev/null +++ b/spec/test-files/norm/av.n3 @@ -0,0 +1,89 @@ +@prefix : . +@prefix rdfs: . +@prefix log: . +@prefix gv: . + +# The root + +:AV a rdfs:Class . +:input a rdfs:Property . +:output a rdfs:Property . +:title a rdfs:Property . + +# Rules + +:catv a :AV; + :title "CATV" . + +:splitter a :AV; + :title "Splitter"; + :output :ab . + +:selector a :AV; + :title "Signal Selector"; + :output :tv . + +:cable a :AV; + :title "Cable Box"; + :output :vcr . + +:vcr a :AV; + :title "VCR"; + :output :ab . + +:ab a :AV; + :title "A/B Switch"; + :output :selector . + +:tv a :AV; + :title "TV" . + +:dvd a :AV; + :title "DVD"; + :output :selector . + +@forAll :p, :s, :o, :t . + +# Graph rules + +{ :p :output :s } log:implies { :p :solidEdge :s } . + +{ :p :title :s } log:implies { :p gv:label :s } . + +# Style rules + +{ :s a :AV } log:implies { :s gv:color "black"; + gv:shape "box" } . + +#{ :s a :DocumentSet } log:implies { :s gv:color "black"; +# gv:shape "polygon"; +# gv:sides "5" } . +# +#{ :s a :Result } log:implies { :s gv:color "blue"; +# gv:style "filled"; +# gv:fontcolor "white" } . +# +#{ :s a :Source } log:implies { :s gv:shape "Mdiamond" } . +# +#{ :s a :Optional } log:implies { :s gv:shape "diamond" } . +# +#{ :s a :Temporary } log:implies { :s gv:color "gray"; +# gv:style "filled"; +# gv:fontcolor "white" } . + +:solidEdge gv:style "solid" . +:dottedEdge gv:style "dotted" . + +# The graph information + +<> gv:digraph [ + gv:label "AV Diagram"; + + gv:subgraph [ + gv:label "" ; + gv:hasNode :catv, :splitter, :cable, :vcr, :ab, :tv, :dvd; + gv:hasEdgeProperty :solidEdge ; + gv:hasEdgeProperty :dottedEdge ; + ] +] . + diff --git a/spec/test-files/reason/double-ref.n3 b/spec/test-files/reason/double-ref.n3 new file mode 100644 index 0000000..82fabde --- /dev/null +++ b/spec/test-files/reason/double-ref.n3 @@ -0,0 +1,15 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/double.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/double.n3 + @prefix : . + + :dan a :Man; + :home [ + :in :Texas ]; + :homeRegion :Texas . + +#ENDS diff --git a/spec/test-files/reason/double.n3 b/spec/test-files/reason/double.n3 new file mode 100644 index 0000000..bbda1c9 --- /dev/null +++ b/spec/test-files/reason/double.n3 @@ -0,0 +1,10 @@ +@prefix : . +@keywords is, of, a. + +dan a Man; home []. + +{ ?WHO home ?WHERE. + ?WHERE in ?REGION } => { ?WHO homeRegion ?REGION }. + + +{ dan home ?WHERE} => {?WHERE in Texas} . diff --git a/spec/test-files/reason/single_gen.n3 b/spec/test-files/reason/single_gen.n3 new file mode 100644 index 0000000..a69f513 --- /dev/null +++ b/spec/test-files/reason/single_gen.n3 @@ -0,0 +1,3 @@ +:a :b "A noun", 3.14159265359 . + +{:a :b ?X} => { [ a :Thing ] } . diff --git a/spec/test-files/reason/socrates-ref.n3 b/spec/test-files/reason/socrates-ref.n3 new file mode 100644 index 0000000..db0bc01 --- /dev/null +++ b/spec/test-files/reason/socrates-ref.n3 @@ -0,0 +1,13 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/socrates.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/socrates.n3 + @prefix : . + + :socrates a :Man, + :Mortal . + +#ENDS diff --git a/spec/test-files/reason/socrates.n3 b/spec/test-files/reason/socrates.n3 new file mode 100644 index 0000000..c2936ed --- /dev/null +++ b/spec/test-files/reason/socrates.n3 @@ -0,0 +1,7 @@ +@prefix : . +@prefix vars: . +:socrates a :Man. +{ ?who a :Man } => { ?who a :Mortal }. + +#ends + diff --git a/spec/test-files/reason/t1-ref.n3 b/spec/test-files/reason/t1-ref.n3 new file mode 100644 index 0000000..4a262d7 --- /dev/null +++ b/spec/test-files/reason/t1-ref.n3 @@ -0,0 +1,11 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t1.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t1.n3 + + . + +#ENDS diff --git a/spec/test-files/reason/t1.n3 b/spec/test-files/reason/t1.n3 new file mode 100644 index 0000000..5d932fc --- /dev/null +++ b/spec/test-files/reason/t1.n3 @@ -0,0 +1 @@ + . diff --git a/spec/test-files/reason/t2-ref.n3 b/spec/test-files/reason/t2-ref.n3 new file mode 100644 index 0000000..303b9da --- /dev/null +++ b/spec/test-files/reason/t2-ref.n3 @@ -0,0 +1,13 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t2.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t2.n3 + + . + + . + +#ENDS diff --git a/spec/test-files/reason/t2.n3 b/spec/test-files/reason/t2.n3 new file mode 100644 index 0000000..b72adfb --- /dev/null +++ b/spec/test-files/reason/t2.n3 @@ -0,0 +1,4 @@ + . + . +# ends + diff --git a/spec/test-files/reason/t3-ref.n3 b/spec/test-files/reason/t3-ref.n3 new file mode 100644 index 0000000..ca27386 --- /dev/null +++ b/spec/test-files/reason/t3-ref.n3 @@ -0,0 +1,14 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t3.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t3.n3 + @prefix : <#> . + + :a :b :c . + + :a2 :b2 :c2 . + +#ENDS diff --git a/spec/test-files/reason/t3.n3 b/spec/test-files/reason/t3.n3 new file mode 100644 index 0000000..d41a2c0 --- /dev/null +++ b/spec/test-files/reason/t3.n3 @@ -0,0 +1,6 @@ +@prefix : <#>. + +:a :b :c. +{:a :b :c} => { :a2 :b2 :c2 }. +# ends + diff --git a/spec/test-files/reason/t4-ref.n3 b/spec/test-files/reason/t4-ref.n3 new file mode 100644 index 0000000..59b0709 --- /dev/null +++ b/spec/test-files/reason/t4-ref.n3 @@ -0,0 +1,14 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t4.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t4.n3 + @prefix : <#> . + + :a :b :c . + + :c :d :e . + +#ENDS diff --git a/spec/test-files/reason/t4.n3 b/spec/test-files/reason/t4.n3 new file mode 100644 index 0000000..7016375 --- /dev/null +++ b/spec/test-files/reason/t4.n3 @@ -0,0 +1,6 @@ +@prefix : <#>. + +:a :b :c. +{:a :b ?x} => { ?x :d :e }. +# ends + diff --git a/spec/test-files/reason/t5-ref.n3 b/spec/test-files/reason/t5-ref.n3 new file mode 100644 index 0000000..25765a8 --- /dev/null +++ b/spec/test-files/reason/t5-ref.n3 @@ -0,0 +1,14 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t5.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t5.n3 + @prefix : <#> . + + :a :b :c . + + :c :b . + +#ENDS diff --git a/spec/test-files/reason/t5.n3 b/spec/test-files/reason/t5.n3 new file mode 100644 index 0000000..78ccb66 --- /dev/null +++ b/spec/test-files/reason/t5.n3 @@ -0,0 +1,7 @@ +@prefix log: . +@prefix foo: <#>. +@prefix : <#>. +:a :b :c. +{:a ?y ?x} => { ?x ?y }. +# ends + diff --git a/spec/test-files/reason/t6-ref.n3 b/spec/test-files/reason/t6-ref.n3 new file mode 100644 index 0000000..c82ceee --- /dev/null +++ b/spec/test-files/reason/t6-ref.n3 @@ -0,0 +1,14 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t6.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t6.n3 + @prefix : <#> . + + :joe a :player; + :friend :kev; + :height "1.6" . + +#ENDS diff --git a/spec/test-files/reason/t6.n3 b/spec/test-files/reason/t6.n3 new file mode 100644 index 0000000..e43afd5 --- /dev/null +++ b/spec/test-files/reason/t6.n3 @@ -0,0 +1,15 @@ +@prefix log: . +@prefix math: . +@prefix foo: <#>. +@prefix : <#>. +:joe :friend :kev; :height "1.6". + +# Currently (2002/12) the checker can't compare things with bnodes +# with different identifiers and find them equal :-( + +#{?x :friend :kev; :height [math:greaterThan "1.3"]} => { ?x a :player }. + +{?x :friend :kev; :height ?h. ?h math:greaterThan "1.3"} => { ?x a :player }. + +# ends + diff --git a/spec/test-files/reason/t8-ref.n3 b/spec/test-files/reason/t8-ref.n3 new file mode 100644 index 0000000..93270b6 --- /dev/null +++ b/spec/test-files/reason/t8-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t8.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t8.n3 + + [ + ] . + +#ENDS diff --git a/spec/test-files/reason/t8.n3 b/spec/test-files/reason/t8.n3 new file mode 100644 index 0000000..5cfc21f --- /dev/null +++ b/spec/test-files/reason/t8.n3 @@ -0,0 +1,6 @@ +# Testing proof generation and checking. + + []. +{ ?x} => { ?x }. +# ends + diff --git a/spec/test-files/reason/t9-ref.n3 b/spec/test-files/reason/t9-ref.n3 new file mode 100644 index 0000000..4bd1414 --- /dev/null +++ b/spec/test-files/reason/t9-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t9.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t9.n3 + + a ; + . + +#ENDS diff --git a/spec/test-files/reason/t9.n3 b/spec/test-files/reason/t9.n3 new file mode 100644 index 0000000..4ec27e9 --- /dev/null +++ b/spec/test-files/reason/t9.n3 @@ -0,0 +1,8 @@ +# Testing proof generation and checking. + + . + +{ []} => { a }. + +# ends + diff --git a/spec/test-files/string/endsWith-out.n3 b/spec/test-files/string/endsWith-out.n3 new file mode 100644 index 0000000..cf67adc --- /dev/null +++ b/spec/test-files/string/endsWith-out.n3 @@ -0,0 +1,27 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/endsWith.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/endsWith.n3 + @prefix : <#> . + @prefix contact: . + @prefix doc: . + @prefix rcs: . + + <> doc:creator [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox ]; + rcs:id "$Id: endsWith.n3,v 1.3 2004-06-25 01:27:00 timbl Exp $" . + + :test1 a :success . + + :test3 a :success . + + :test6 a :success . + + :test7 a :success . + +#ENDS diff --git a/spec/test-files/string/endsWith.n3 b/spec/test-files/string/endsWith.n3 new file mode 100644 index 0000000..628bf91 --- /dev/null +++ b/spec/test-files/string/endsWith.n3 @@ -0,0 +1,37 @@ + +@prefix contact: . +@prefix rcs: . +@prefix doc: . + + +<> rcs:id "$Id: endsWith.n3,v 1.3 2004-06-25 01:27:00 timbl Exp $"; + + doc:creator [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox + ]. + + +@prefix log: . +@prefix string: . +@prefix : <#>. + +@forAll :x, :y, :z, :p, :q. + +{ "asdfghjkl" string:endsWith "jkl" } log:implies { :test1 a :success }. + +{ "asdfghjkl" string:endsWith "jkk" } log:implies { :test2 a :FAILURE }. + +{ "jkl" string:endsWith "jkl" } log:implies { :test3 a :success }. + +{ "asdfghjkl" string:endsWith "aaajkl" } log:implies { :test4 a :FAILURE }. + +{ "asdfjhkh" string:endsWith "asd" } log:implies { :test5 a :FAILURE }. + +{ "foobar" string:endsWith "" } log:implies { :test6 a :success }. + +{ "" string:endsWith "" } log:implies { :test7 a :success }. + +#ends + diff --git a/spec/test-files/string/roughly-out.n3 b/spec/test-files/string/roughly-out.n3 new file mode 100644 index 0000000..750f2f4 --- /dev/null +++ b/spec/test-files/string/roughly-out.n3 @@ -0,0 +1,30 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/roughly.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/roughly.n3 + @prefix : <#> . + @prefix contact: . + @prefix rcs: . + + <> [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox ]; + "Test string:containsRoughly"; + "copyright (c) 2001 W3C (MIT, Keio, INRIA)"; + rcs:id "$Id: roughly.n3,v 1.2 2004-06-25 01:27:00 timbl Exp $" . + + :test1 a :success . + + :test3 a :success . + + :test6 a :success . + + :test7 a :success . + + :test8 a :success . + +#ENDS diff --git a/spec/test-files/string/roughly.n3 b/spec/test-files/string/roughly.n3 new file mode 100644 index 0000000..060e443 --- /dev/null +++ b/spec/test-files/string/roughly.n3 @@ -0,0 +1,42 @@ +@prefix dc: . +@prefix contact: . +@prefix rcs: . + +<> dc:description """Test string:containsRoughly"""; + rcs:id "$Id: roughly.n3,v 1.2 2004-06-25 01:27:00 timbl Exp $"; + dc:creator [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox + ]; + dc:rights "copyright (c) 2001 W3C (MIT, Keio, INRIA)". + + +@prefix log: . +@prefix string: . +@prefix : <#>. + +@forAll :x, :y, :z, :p, :q. + +{ "A green party" string:containsRoughly "green Party" } log:implies { :test1 a :success }. + +{ "all good people to come to" string:containsRoughly "gooood" } log:implies { :test2 a :FAILURE }. + +{ "jkl" string:containsRoughly "jkl" } log:implies { :test3 a :success }. + +{ "foo" string:containsRoughly "foo bar" } log:implies { :test4 a :FAILURE }. + +{ "asd" string:containsRoughly "asdfjhkjhasd" } log:implies { :test5 a :FAILURE }. + +{ "" string:containsRoughly "" } log:implies { :test6 a :success }. + +{ "supercalifragilisticexpialidocious" string:containsRoughly "" } log:implies { :test7 a :success }. +{ """THE + WIDE + AND + THE + narrowEST + OF PLACES""" string:containsRoughly "wide and the" } log:implies { :test8 a :success }. + +#ends + diff --git a/spec/test-files/string/uriEncode-out.n3 b/spec/test-files/string/uriEncode-out.n3 new file mode 100644 index 0000000..a8cea96 --- /dev/null +++ b/spec/test-files/string/uriEncode-out.n3 @@ -0,0 +1,46 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/uriEncode.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/uriEncode.n3 + @prefix : <#> . + @prefix contact: . + @prefix doc: . + @prefix rcs: . + + "asd#jkl" :AS_FragID "asd%23jkl"; + :AS_URI "asd#jkl" . + + "asd'jkl" :AS_FragID "asd%27jkl"; + :AS_URI "asd'jkl" . + + "asd(jkl" :AS_FragID "asd%28jkl"; + :AS_URI "asd(jkl" . + + "asd)jkl" :AS_FragID "asd%29jkl"; + :AS_URI "asd)jkl" . + + "asd-jkl" :AS_FragID "asd-jkl"; + :AS_URI "asd-jkl" . + + "asd.jkl" :AS_FragID "asd.jkl"; + :AS_URI "asd.jkl" . + + "asd/jkl" :AS_FragID "asd/jkl"; + :AS_URI "asd%2Fjkl" . + + "asd_jkl" :AS_FragID "asd_jkl"; + :AS_URI "asd_jkl" . + + "asd~jkl" :AS_FragID "asd%7Ejkl"; + :AS_URI "asd~jkl" . + + <> doc:creator [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox ]; + rcs:id "$Id: uriEncode.n3,v 1.1 2006-01-05 16:02:13 timbl Exp $" . + +#ENDS diff --git a/spec/test-files/string/uriEncode.n3 b/spec/test-files/string/uriEncode.n3 new file mode 100644 index 0000000..b9e3266 --- /dev/null +++ b/spec/test-files/string/uriEncode.n3 @@ -0,0 +1,61 @@ + +@prefix contact: . +@prefix rcs: . +@prefix doc: . + + +<> rcs:id "$Id: uriEncode.n3,v 1.1 2006-01-05 16:02:13 timbl Exp $"; + + doc:creator [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox + ]. + + +@prefix log: . +@prefix string: . +@prefix : <#>. + +{ "asd#jkl" string:encodeForURI ?x } log:implies { "asd#jkl" :AS_URI ?x }. + +{ "asd/jkl" string:encodeForURI ?x } log:implies { "asd/jkl" :AS_URI ?x }. + +{ "asd(jkl" string:encodeForURI ?x } log:implies { "asd(jkl" :AS_URI ?x }. + +{ "asd'jkl" string:encodeForURI ?x } log:implies { "asd'jkl" :AS_URI ?x }. + +{ "asd)jkl" string:encodeForURI ?x } log:implies { "asd)jkl" :AS_URI ?x }. + +{ "asd_jkl" string:encodeForURI ?x } log:implies { "asd_jkl" :AS_URI ?x }. + +{ "asd~jkl" string:encodeForURI ?x } log:implies { "asd~jkl" :AS_URI ?x }. + +{ "asd-jkl" string:encodeForURI ?x } log:implies { "asd-jkl" :AS_URI ?x }. + +{ "asd.jkl" string:encodeForURI ?x } log:implies { "asd.jkl" :AS_URI ?x }. + +### + + +{ "asd#jkl" string:encodeForFragID ?x } log:implies { "asd#jkl" :AS_FragID ?x }. + +{ "asd/jkl" string:encodeForFragID ?x } log:implies { "asd/jkl" :AS_FragID ?x }. + +{ "asd(jkl" string:encodeForFragID ?x } log:implies { "asd(jkl" :AS_FragID ?x }. + +{ "asd'jkl" string:encodeForFragID ?x } log:implies { "asd'jkl" :AS_FragID ?x }. + +{ "asd)jkl" string:encodeForFragID ?x } log:implies { "asd)jkl" :AS_FragID ?x }. + +{ "asd_jkl" string:encodeForFragID ?x } log:implies { "asd_jkl" :AS_FragID ?x }. + +{ "asd~jkl" string:encodeForFragID ?x } log:implies { "asd~jkl" :AS_FragID ?x }. + +{ "asd-jkl" string:encodeForFragID ?x } log:implies { "asd-jkl" :AS_FragID ?x }. + +{ "asd.jkl" string:encodeForFragID ?x } log:implies { "asd.jkl" :AS_FragID ?x }. + + +#ends + diff --git a/spec/test-files/supports/simple-ref.n3 b/spec/test-files/supports/simple-ref.n3 new file mode 100644 index 0000000..7a401ad --- /dev/null +++ b/spec/test-files/supports/simple-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/supports/simple.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/supports/simple.n3 + @prefix : <#> . + + :Q :Q :Q . + +#ENDS diff --git a/spec/test-files/supports/simple.n3 b/spec/test-files/supports/simple.n3 new file mode 100644 index 0000000..38ba23e --- /dev/null +++ b/spec/test-files/supports/simple.n3 @@ -0,0 +1,5 @@ +@prefix log: . + +@forAll :X . + +{ {:a :b :c . :r :e :g . {:a :b :c} => {:d :e :f} } log:supports {:a :b :c. :d :e :f} } => {:Q :Q :Q} . diff --git a/spec/test-files/unify/reflexive-ref.n3 b/spec/test-files/unify/reflexive-ref.n3 new file mode 100644 index 0000000..ae135e6 --- /dev/null +++ b/spec/test-files/unify/reflexive-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/reflexive.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/reflexive.n3 + @prefix : <#> . + + :Steve :talksTo :Joe . + +#ENDS diff --git a/spec/test-files/unify/reflexive.n3 b/spec/test-files/unify/reflexive.n3 new file mode 100644 index 0000000..3e205d9 --- /dev/null +++ b/spec/test-files/unify/reflexive.n3 @@ -0,0 +1,6 @@ +@prefix log: . +@prefix : <#>. + +:Steve :talksTo :Joe . + +{ ?s :talksTo ?s } log:implies {?s :admits "I talk to myself"} . diff --git a/spec/test-files/unify/unify1-ref.n3 b/spec/test-files/unify/unify1-ref.n3 new file mode 100644 index 0000000..9a8ad20 --- /dev/null +++ b/spec/test-files/unify/unify1-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/unify1.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/unify1.n3 + @prefix : <#> . + + :test a :Successful . + +#ENDS diff --git a/spec/test-files/unify/unify1.n3 b/spec/test-files/unify/unify1.n3 new file mode 100644 index 0000000..c73a3cf --- /dev/null +++ b/spec/test-files/unify/unify1.n3 @@ -0,0 +1,13 @@ +# Two variables and some bnodes +# +@keywords is, a . + +Juno says { Mars too Successful }. + +@forAll x. + +{ Juno says { Mars too x } } => { test a x }. + + +#ends + diff --git a/spec/test-files/unify/unify2-ref.n3 b/spec/test-files/unify/unify2-ref.n3 new file mode 100644 index 0000000..47dad6d --- /dev/null +++ b/spec/test-files/unify/unify2-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/unify2.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/unify2.n3 + @prefix : <#> . + + :test a :SUCCESS . + +#ENDS diff --git a/spec/test-files/unify/unify2.n3 b/spec/test-files/unify/unify2.n3 new file mode 100644 index 0000000..d7fb77f --- /dev/null +++ b/spec/test-files/unify/unify2.n3 @@ -0,0 +1,11 @@ +# For All in the template for unification +# +@keywords is, a . + +Juno says {@forAll y. Mars fights y}. + +{ Juno says {@forAll y. Mars fights y } } => { test a SUCCESS }. + + +#ends + diff --git a/spec/test_file_spec.rb b/spec/test_file_spec.rb index 5d82913..7a9f262 100644 --- a/spec/test_file_spec.rb +++ b/spec/test_file_spec.rb @@ -108,10 +108,16 @@ def inspect pending "support for li:member" when *%w{t2006} pending "support for li:last" - when *%w{t1018b2} - pending "support for string:concat" - when *%w{t2005} + when *%w{t1018b2 t103 t104 t105} + pending "support for string" + when *%w{t2005 t555} pending "understanding output filtering" + when *%w{t06proof} + pending "support for math" + when *%w{t01} + pending "support for log:supports" + when *%w{t553 t554} + pending "support for inference over quoted graphs" end reader = RDF::N3::Reader.new(t.input, From 68de237d503817e74c70b4231473fad21999ddf6 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 16 Jan 2019 15:46:34 -0800 Subject: [PATCH 17/40] Update Gemfile. --- Gemfile | 3 +-- spec/swap_spec.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 4133565..cb68666 100644 --- a/Gemfile +++ b/Gemfile @@ -2,8 +2,7 @@ source "https://rubygems.org" gemspec -#gem "rdf", github: "ruby-rdf/rdf", branch: "develop" -gem "rdf", path: '../rdf' +gem "rdf", github: "ruby-rdf/rdf", branch: "develop" group :development do gem "rdf-spec", github: "ruby-rdf/rdf-spec", branch: "develop" diff --git a/spec/swap_spec.rb b/spec/swap_spec.rb index 7280965..8dcc9b4 100644 --- a/spec/swap_spec.rb +++ b/spec/swap_spec.rb @@ -14,7 +14,7 @@ case t.name when *%w(n3_10004 n3_10007 n3_10014 n3_10015 n3_10017) pending("Formulae inferrence not supported") - when *%w(n3_10006 n3_10009) + when *%w(n3_10003 n3_10006 n3_10009) pending("Verified test results are incorrect") when *%w(n3_10013) pending("numeric representation") From ce1d510229fa385042c391c7302718be6d611695 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 17 Jan 2019 15:53:41 -0800 Subject: [PATCH 18/40] Use N3 writer for test debug output. --- spec/reader_spec.rb | 132 +++++++++++++++++++++-------------------- spec/suite_helper.rb | 3 + spec/swap_spec.rb | 2 +- spec/test_file_spec.rb | 2 + 4 files changed, 73 insertions(+), 66 deletions(-) diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 296ae7a..21711b8 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -264,7 +264,7 @@ specify "test #{name}" do graph = parse([statement].flatten.first) g2 = RDF::NTriples::Reader.new([statement].flatten.last) - expect(graph).to be_equivalent_graph(g2, about: "http://a/b", logger: logger) + expect(graph).to be_equivalent_graph(g2, about: "http://a/b", logger: logger, format: :n3) end end @@ -295,7 +295,7 @@ %(<#D%C3%BCrst> a "URI percent ^encoded as C3, BC".) => %( "URI percent ^encoded as C3, BC" .), }.each_pair do |n3, nt| it "for '#{n3}'" do - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end @@ -306,7 +306,7 @@ %(:alice :resumé "Alice's normalized resumé".) => ' "Alice\'s normalized resumé" .', }.each_pair do |n3, nt| it "for '#{n3}'" do - expect(parse(n3, base_uri: "http://a/b", logger: false)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b", logger: false)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end @@ -315,7 +315,7 @@ #%(:a :related :ひらがな .) => %( .), }.each_pair do |n3, nt| it "for '#{n3}'" do - expect(parse(n3, base_uri: "http://a/b", logger: false)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b", logger: false)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end end @@ -352,7 +352,7 @@ nt = %( . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should use <#> as a prefix and as a triple node" do @@ -360,31 +360,31 @@ nt = %( . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate rdf:type for 'a'" do n3 = %(@prefix a: . a:b a .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate rdf:type for '@a'" do n3 = %(@prefix a: . a:b @a .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate inverse predicate for 'is xxx of'" do n3 = %("value" is :prop of :b . :b :prop "value" .) nt = %( "value" .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate inverse predicate for '@is xxx @of'" do n3 = %("value" @is :prop @of :b . :b :prop "value" .) nt = %( "value" .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate inverse predicate for 'is xxx of' with object list" do @@ -393,7 +393,7 @@ "value" . "value" . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate inverse predicate for 'is xxx of' with blankNodePropertyList" do @@ -401,7 +401,7 @@ nt = %( _:bn . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate inverse predicate for 'is xxx of' with bnode" do @@ -409,37 +409,37 @@ nt = %( _:bn . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate predicate for 'has xxx'" do n3 = %(@prefix a: . a:b has :pred a:c .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate predicate for '@has xxx'" do n3 = %(@prefix a: . a:b @has :pred a:c .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should create log:implies predicate for '=>'" do n3 = %(@prefix a: . _:a => a:something .) nt = %(_:a .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should create log:implies inverse predicate for '<='" do n3 = %(@prefix a: . _:a <= a:something .) nt = %( _:a .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should create owl:sameAs predicate for '='" do n3 = %(@prefix a: . _:a = a:something .) nt = %(_:a .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end { @@ -456,20 +456,20 @@ }.each_pair do |n3, nt| it "should create typed literal for '#{n3}'" do expected = RDF::NTriples::Reader.new(nt) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end end it "should accept empty localname" do n3 = %(: : : .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should accept prefix with empty local name" do n3 = %(@prefix foo: . foo: foo: foo: .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end @@ -523,7 +523,7 @@ nt = %( . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should not append # for http://foo/bar/" do @@ -531,7 +531,7 @@ nt = %( . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should not append # for http://foo/bar#" do @@ -539,7 +539,7 @@ nt = %( . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should set absolute base" do @@ -548,7 +548,7 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should set absolute base (trailing /)" do @@ -557,7 +557,7 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should set absolute base (trailing #)" do @@ -566,7 +566,7 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should set relative base" do @@ -586,7 +586,7 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "returns defined prefixes" do @@ -642,7 +642,7 @@ %(:c :a t) => %( .), }.each_pair do |n3, nt| it "should use default_ns for '#{n3}'" do - expect(parse("@keywords . #{n3}", base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse("@keywords . #{n3}", base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end @@ -656,7 +656,7 @@ } .each_pair do |n3, nt| it "should use keyword for '#{n3}'" do expected = RDF::NTriples::Reader.new(nt) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end end @@ -679,7 +679,7 @@ . _:a . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should allow a prefix to be redefined" do @@ -694,7 +694,7 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should process sequential @base declarations (swap base.n3)" do @@ -708,7 +708,7 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end @@ -717,14 +717,14 @@ n3 = %(@prefix a: . _:a a:p a:v .) nt = %(_:bnode0 .) g = parse(n3, base_uri: "http://a/b") - expect(g).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(g).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should create BNode for [] as subject" do n3 = %(@prefix a: . [] a:p a:v .) nt = %(_:bnode0 .) g = parse(n3, base_uri: "http://a/b") - expect(g).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(g).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should create BNode for [] as predicate" do @@ -737,19 +737,19 @@ it "should create BNode for [] as object" do n3 = %(@prefix a: . a:s a:p [] .) nt = %( _:bnode0 .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should create BNode for [] as statement" do n3 = %([:a :b] .) nt = %(_:bnode0 .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should create statements with BNode subjects using [ pref obj]" do n3 = %(@prefix a: . [ a:p a:v ] .) nt = %(_:bnode0 .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should create BNode as a single object" do @@ -759,7 +759,7 @@ _:bnode0 "2" . _:bnode0 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should create a shared BNode" do @@ -779,7 +779,7 @@ _:a :pred _:bnode0 . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end it "should create nested BNodes" do @@ -796,7 +796,7 @@ _:bnode1 "v4" . _:bnode1 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end describe "from paths" do @@ -804,14 +804,14 @@ n3 = %(:x2!:y2 :p2 "3" .) nt = %(:x2 :y2 _:bnode0 ._:bnode0 :p2 "3" .) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end it "should create bnode for path x^p" do n3 = %(:x2^:y2 :p2 "3" .) nt = %(_:bnode0 :y2 :x2 . _:bnode0 :p2 "3" .) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end it "should decode :joe!fam:mother!loc:office!loc:zip as Joe's mother's office's zipcode" do @@ -827,7 +827,7 @@ _:bnode1 _:bnode2 . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end it "should decode :joe!fam:mother^fam:mother Anyone whose mother is Joe's mother." do @@ -842,7 +842,7 @@ _:bnode1 _:bnode0 . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end it "should decode path with property list." do @@ -858,7 +858,7 @@ _:bnode1 :q2 "5" . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end it "should decode path as object(1)" do @@ -868,7 +868,7 @@ _:bnode :c "lit" . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end it "should decode path as object(2)" do @@ -879,7 +879,7 @@ :r :p _:bnode1 . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end end end @@ -892,7 +892,7 @@ nq = %(:a :b _:c .) result = parse(n3, repo: @repo, base_uri: "http://a/b") expected = parse(nq, repo: @repo, base_uri: "http://a/b") - expect(result).to be_equivalent_graph(expected, logger: logger) + expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end it "adds statements with graph_name" do @@ -900,7 +900,7 @@ trig = %(<#a> <#b> _:c . _:c {[<#c> <#d>] .}) result = parse(n3, repo: @repo, base_uri: "http://a/b") expected = RDF::Repository.new {|r| r << RDF::TriG::Reader.new(trig, base_uri: "http://a/b")} - expect(result).to be_equivalent_graph(expected, logger: logger) + expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end it "creates quads for patterns when added to a repository" do @@ -921,7 +921,7 @@ ) result = parse(n3, repo: @repo, base_uri: "http://a/b") expected = RDF::Repository.new {|r| r << RDF::TriG::Reader.new(trig, base_uri: "http://a/b")} - expect(result).to be_equivalent_graph(expected, logger: logger) + expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end context "contexts" do @@ -976,7 +976,7 @@ n3 = %(:a :b :c, :d) nt = %( . .) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end end @@ -994,7 +994,7 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end @@ -1003,7 +1003,7 @@ n3 = %(@prefix :. :empty :set ().) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should parse list with single element" do @@ -1013,7 +1013,7 @@ _:bnode0 . _:bnode0 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should parse list with multiple elements" do @@ -1027,7 +1027,7 @@ _:bnode2 . _:bnode0 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should parse unattached lists" do @@ -1046,13 +1046,13 @@ _:bnode2 "3" . _:bnode2 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should add property to nil list" do n3 = %(@prefix a: . () a:prop "nilProp" .) nt = %( "nilProp" .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should parse with compound items" do @@ -1124,7 +1124,7 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end @@ -1145,7 +1145,7 @@ _:g2160128180 . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end @@ -1164,7 +1164,7 @@ nt = %( . ) - expect(@graph).to be_equivalent_graph(nt, about: "http://test/", logger: logger) + expect(@graph).to be_equivalent_graph(nt, about: "http://test/", logger: logger, format: :n3) end it "should have default subject" do @@ -1210,7 +1210,7 @@ # it "returns subject #{result} given #{input}" do # n3 = %(#{input} a :b) # nt = %(#{result} .) -# parse(n3, base_uri: "http://a/b", canonicalize: true).should be_equivalent_graph(nt, about: "http://a/b", logger: logger) +# parse(n3, base_uri: "http://a/b", canonicalize: true).should be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) # end # end @@ -1224,7 +1224,7 @@ n3 = %(@prefix xsd: . :a :b #{input} .) nt = %( #{result} .) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b", canonicalize: true)).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b", canonicalize: true)).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end end end @@ -1263,7 +1263,8 @@ expect(graph).to be_equivalent_graph(sampledoc, about: "http://www.w3.org/2000/10/rdf-tests/rdfcore/amp-in-url/Manifest.rdf", - logger: logger + logger: logger, + format: :n3 ) end @@ -1287,6 +1288,7 @@ def test_file(filepath) nt_string = File.read(filepath.sub('.n3', '.nt')) expect(@graph).to be_equivalent_graph(nt_string, about: "file:#{filepath}", - logger: logger) + logger: logger, + format: :n3) end end diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 5ea7418..135d7bf 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -91,6 +91,9 @@ def self.open(file) end attr_accessor :logger + # For debug output formatting + def format; :n3; end + def base inputDocument end diff --git a/spec/swap_spec.rb b/spec/swap_spec.rb index 8dcc9b4..000937b 100644 --- a/spec/swap_spec.rb +++ b/spec/swap_spec.rb @@ -13,7 +13,7 @@ specify "#{t.name}: #{t.description}" do case t.name when *%w(n3_10004 n3_10007 n3_10014 n3_10015 n3_10017) - pending("Formulae inferrence not supported") + pending("Reification not supported") when *%w(n3_10003 n3_10006 n3_10009) pending("Verified test results are incorrect") when *%w(n3_10013) diff --git a/spec/test_file_spec.rb b/spec/test_file_spec.rb index 7a9f262..9b60b44 100644 --- a/spec/test_file_spec.rb +++ b/spec/test_file_spec.rb @@ -54,6 +54,8 @@ def entries class Entry < JSON::LD::Resource attr_accessor :logger + # For debug output formatting + def format; :n3; end def base action From 532485cc8cad290781559e117a98f154a3ee8724 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 17 Jan 2019 15:53:57 -0800 Subject: [PATCH 19/40] Update writer based on Turtle writer. --- Gemfile | 17 +- lib/rdf/n3/writer.rb | 361 +++++++++++++------------ rdf-n3.gemspec | 1 + spec/writer_spec.rb | 624 +++++++++++++++++++++++-------------------- 4 files changed, 532 insertions(+), 471 deletions(-) diff --git a/Gemfile b/Gemfile index cb68666..60de3c8 100644 --- a/Gemfile +++ b/Gemfile @@ -2,16 +2,17 @@ source "https://rubygems.org" gemspec -gem "rdf", github: "ruby-rdf/rdf", branch: "develop" +gem "rdf", git: "https://github.com/ruby-rdf/rdf", branch: "develop" group :development do - gem "rdf-spec", github: "ruby-rdf/rdf-spec", branch: "develop" - gem "rdf-isomorphic", github: "ruby-rdf/rdf-isomorphic", branch: "develop" - gem "rdf-trig", github: "ruby-rdf/rdf-trig", branch: "develop" - gem "rdf-xsd", github: "ruby-rdf/rdf-xsd", branch: "develop" - gem "json-ld", github: "ruby-rdf/json-ld", branch: "develop" - gem 'sparql', github: "ruby-rdf/sparql", branch: "develop" - gem 'sxp', github: "dryruby/sxp.rb", branch: "develop" + gem "rdf-spec", git: "https://github.com/ruby-rdf/rdf-spec", branch: "develop" + gem "rdf-isomorphic", git: "https://github.com/ruby-rdf/rdf-isomorphic", branch: "develop" + gem "rdf-trig", git: "https://github.com/ruby-rdf/rdf-trig", branch: "develop" + gem 'rdf-vocab', git: "https://github.com/ruby-rdf/rdf-vocab", branch: "develop" + gem "rdf-xsd", git: "https://github.com/ruby-rdf/rdf-xsd", branch: "develop" + gem "json-ld", git: "https://github.com/ruby-rdf/json-ld", branch: "develop" + gem 'sparql', git: "https://github.com/ruby-rdf/sparql", branch: "develop" + gem 'sxp', git: "https://github.com/dryruby/sxp.rb", branch: "develop" end group :debug do diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index a0eedf2..5d891c1 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -1,42 +1,39 @@ # coding: utf-8 module RDF::N3 ## - # A Turtle serialiser in Ruby + # A Notation-3 serialiser in Ruby # # Note that the natural interface is to write a whole graph at a time. # Writing statements or Triples will create a graph to add them to # and then serialize the graph. # - # @example Obtaining a Turtle writer class + # @example Obtaining a N3 writer class # RDF::Writer.for(:n3) #=> RDF::N3::Writer # RDF::Writer.for("etc/test.n3") - # RDF::Writer.for("etc/test.ttl") # RDF::Writer.for(file_name: "etc/test.n3") - # RDF::Writer.for(file_name: "etc/test.ttl") # RDF::Writer.for(file_extension: "n3") - # RDF::Writer.for(file_extension: "ttl") # RDF::Writer.for(content_type: "text/n3") # - # @example Serializing RDF graph into an Turtle file + # @example Serializing RDF graph into an N3 file # RDF::N3::Writer.open("etc/test.n3") do |writer| # writer << graph # end # - # @example Serializing RDF statements into an Turtle file + # @example Serializing RDF statements into an N3 file # RDF::N3::Writer.open("etc/test.n3") do |writer| # graph.each_statement do |statement| # writer << statement # end # end # - # @example Serializing RDF statements into an Turtle string + # @example Serializing RDF statements into an N3 string # RDF::N3::Writer.buffer do |writer| # graph.each_statement do |statement| # writer << statement # end # end # - # The writer will add prefix definitions, and use them for creating @prefix definitions, and minting QNames + # The writer will add prefix definitions, and use them for creating @prefix definitions, and minting pnames # # @example Creating @base and @prefix definitions in output # RDF::N3::Writer.buffer(base_uri: "http://example.com/", prefixes: { @@ -56,8 +53,6 @@ class Writer < RDF::Writer # @return [Graph] Graph of statements serialized attr_accessor :graph - # @return [URI] Base URI used for relativizing URIs - attr_accessor :base_uri ## # N3 Writer options @@ -78,7 +73,7 @@ def self.options end ## - # Initializes the Turtle writer instance. + # Initializes the N3 writer instance. # # @param [IO, File] output # the output stream @@ -106,10 +101,11 @@ def self.options # @yield [writer] # @yieldparam [RDF::Writer] writer def initialize(output = $stdout, options = {}, &block) + @graph = RDF::Graph.new + @uri_to_pname = {} + @uri_to_prefix = {} super do - @graph = RDF::Graph.new - @uri_to_qname = {} - @uri_to_prefix = {} + reset if block_given? case block.arity when 0 then instance_eval(&block) @@ -138,15 +134,21 @@ def write_triple(subject, predicate, object) # @see #write_triple def write_epilogue @max_depth = @options[:max_depth] || 3 - @base_uri = RDF::URI(@options[:base_uri]) self.reset log_debug {"\nserialize: graph: #{@graph.size}"} preprocess + start_document + # Remove lists that are referenced and have non-list properties; + # these are legal, but can't be serialized as lists + @lists.reject! do |node, list| + ref_count(node) > 0 && prop_count(node) > 0 + end + order_subjects.each do |subject| unless is_done?(subject) statement(subject) @@ -155,11 +157,11 @@ def write_epilogue super end - - # Return a QName for the URI, or nil. Adds namespace of QName to defined prefixes + + # Return a pname for the URI, or nil. Adds namespace of pname to defined prefixes # @param [RDF::Resource] resource # @return [String, nil] value to use to identify URI - def get_qname(resource) + def get_pname(resource) case resource when RDF::Node return options[:unique_bnodes] ? resource.to_unique_base : resource.to_base @@ -169,37 +171,39 @@ def get_qname(resource) return nil end - log_debug {"get_qname(#{resource}), std?}"} - qname = case - when @uri_to_qname.has_key?(uri) - return @uri_to_qname[uri] + log_debug {"get_pname(#{resource}), std?}"} + pname = case + when @uri_to_pname.has_key?(uri) + return @uri_to_pname[uri] when u = @uri_to_prefix.keys.detect {|u| uri.index(u.to_s) == 0} # Use a defined prefix prefix = @uri_to_prefix[u] - prefix(prefix, u) unless u.to_s.empty? # Define for output - log_debug {"get_qname: add prefix #{prefix.inspect} => #{u}"} - uri.sub(u.to_s, "#{prefix}:") + unless u.to_s.empty? + prefix(prefix, u) unless u.to_s.empty? + log_debug("get_pname") {"add prefix #{prefix.inspect} => #{u}"} + uri.sub(u.to_s, "#{prefix}:") + end when @options[:standard_prefixes] && vocab = RDF::Vocabulary.each.to_a.detect {|v| uri.index(v.to_uri.to_s) == 0} prefix = vocab.__name__.to_s.split('::').last.downcase @uri_to_prefix[vocab.to_uri.to_s] = prefix prefix(prefix, vocab.to_uri) # Define for output - log_debug {"get_qname: add standard prefix #{prefix.inspect} => #{vocab.to_uri}"} + log_debug {"get_pname: add standard prefix #{prefix.inspect} => #{vocab.to_uri}"} uri.sub(vocab.to_uri.to_s, "#{prefix}:") else nil end - - # Make sure qname is a valid qname - if qname - md = QNAME.match(qname) - qname = nil unless md.to_s.length == qname.length + + # Make sure pname is a valid pname + if pname + md = QNAME.match(pname) + pname = nil unless md.to_s.length == pname.length end - @uri_to_qname[uri] = qname + @uri_to_pname[uri] = pname rescue Addressable::URI::InvalidURIError => e raise RDF::WriterError, "Invalid URI #{resource.inspect}: #{e.message}" end - + # Take a hash from predicate uris to lists of values. # Sort the lists of values. Return a sorted list of properties. # @param [Hash{String => Array}] properties A hash of Property to Resource mappings @@ -207,17 +211,17 @@ def get_qname(resource) def sort_properties(properties) # Make sorted list of properties prop_list = [] - + predicate_order.each do |prop| next unless properties[prop.to_s] prop_list << prop.to_s end - + properties.keys.sort.each do |prop| next if prop_list.include?(prop.to_s) prop_list << prop.to_s end - + log_debug {"sort_properties: #{prop_list.join(', ')}"} prop_list end @@ -232,11 +236,11 @@ def format_literal(literal, options = {}) literal = literal.dup.canonicalize! if @options[:canonicalize] case literal when RDF::Literal - case literal.datatype + case literal.valid? ? literal.datatype : false when RDF::XSD.boolean, RDF::XSD.integer, RDF::XSD.decimal - literal.to_s + literal.canonicalize.to_s when RDF::XSD.double - literal.to_s.sub('E', 'e') # Favor lower case exponent + literal.canonicalize.to_s.sub('E', 'e') # Favor lower case exponent else text = quoted(literal.value) text << "@#{literal.language}" if literal.has_language? @@ -247,21 +251,21 @@ def format_literal(literal, options = {}) quoted(literal.to_s) end end - + ## - # Returns the Turtle/N3 representation of a URI reference. + # Returns the N3 representation of a URI reference. # # @param [RDF::URI] uri # @param [Hash{Symbol => Object}] options # @return [String] def format_uri(uri, options = {}) - md = relativize(uri) - log_debug {"relativize(#{uri.inspect}) => #{md.inspect}"} if md != uri.to_s - md != uri.to_s ? "<#{md}>" : (get_qname(uri) || "<#{uri}>") + md = uri.relativize(base_uri) + log_debug("relativize") {"#{uri.to_ntriples} => #{md.inspect}"} if md != uri.to_s + md != uri.to_s ? "<#{md}>" : (get_pname(uri) || "<#{uri}>") end - + ## - # Returns the Turtle/N3 representation of a blank node. + # Returns the N3 representation of a blank node. # # @param [RDF::Node] node # @param [Hash{Symbol => Object}] options @@ -269,25 +273,17 @@ def format_uri(uri, options = {}) def format_node(node, options = {}) options[:unique_bnodes] ? node.to_unique_base : node.to_base end - + protected # Output @base and @prefix definitions def start_document @output.write("#{indent}@base <#{base_uri}> .\n") unless base_uri.to_s.empty? - + log_debug {"start_document: #{prefixes.inspect}"} prefixes.keys.sort_by(&:to_s).each do |prefix| @output.write("#{indent}@prefix #{prefix}: <#{prefixes[prefix]}> .\n") end end - - # If base_uri is defined, use it to try to make uri relative - # @param [#to_s] uri - # @return [String] - def relativize(uri) - uri = uri.to_s - base_uri ? uri.sub(base_uri.to_s, "") : uri - end # Defines rdf:type of subjects to be emitted at the beginning of the graph. Defaults to rdfs:Class # @return [Array] @@ -297,7 +293,7 @@ def top_classes; [RDF::RDFS.Class]; end # [rdf:type, rdfs:label, dc:title] # @return [Array] def predicate_order; [RDF.type, RDF::RDFS.label, RDF::URI("http://purl.org/dc/terms/title")]; end - + # Order subjects for output. Override this to output subjects in another order. # # Uses #top_classes and #base_uri. @@ -305,14 +301,13 @@ def predicate_order; [RDF.type, RDF::RDFS.label, RDF::URI("http://purl.org/dc/te def order_subjects seen = {} subjects = [] - + # Start with base_uri if base_uri && @subjects.keys.include?(base_uri) subjects << base_uri seen[base_uri] = true end - log_debug {"subjects1: #{subjects.inspect}"} - + # Add distinguished classes top_classes.each do |class_uri| graph.query(predicate: RDF.type, object: class_uri). @@ -325,7 +320,6 @@ def order_subjects seen[subject] = true end end - log_debug {"subjects2: #{subjects.inspect}"} # Mark as seen lists that are part of another list @lists.values.map(&:statements). @@ -333,18 +327,18 @@ def order_subjects seen[st.object] = true if @lists.has_key?(st.object) end + # List elements should not be targets for top-level serialization + list_elements = @lists.values.map(&:to_a).flatten.compact + # Sort subjects by resources over bnodes, ref_counts and the subject URI itself - recursable = @subjects.keys. + recursable = (@subjects.keys - list_elements). select {|s| !seen.include?(s)}. map {|r| [r.node? ? 1 : 0, ref_count(r), r]}. sort - - log_debug {"subjects3: #{subjects.inspect}"} + subjects += recursable.map{|r| r.last} - log_debug {"subjects4: #{subjects.inspect}"} - subjects end - + # Perform any preprocessing of statements required def preprocess # Load defined prefixes @@ -355,21 +349,24 @@ def preprocess prefix(nil, @options[:default_namespace]) if @options[:default_namespace] + @options[:prefixes] = {} # Will define actual used when matched @graph.each {|statement| preprocess_statement(statement)} end - + # Perform any statement preprocessing required. This is used to perform reference counts and determine required # prefixes. # @param [Statement] statement def preprocess_statement(statement) #log_debug {"preprocess: #{statement.inspect}"} - references = ref_count(statement.object) + 1 - @references[statement.object] = references - @subjects[statement.subject] = true + bump_reference(statement.object) + # Count properties of this subject + (@subjects[statement.subject] ||= {})[statement.predicate] ||= 0 + @subjects[statement.subject][statement.predicate] += 1 # Collect lists if statement.predicate == RDF.first - @lists[statement.subject] = RDF::List.new(subject: statement.subject, graph: graph) + l = RDF::List.new(subject: statement.subject, graph: graph) + @lists[statement.subject] = l if l.valid? end if statement.object == RDF.nil || statement.subject == RDF.nil @@ -377,31 +374,24 @@ def preprocess_statement(statement) @lists[RDF.nil] ||= RDF::List[] end - # Pre-fetch qnames, to fill prefixes - get_qname(statement.subject) - get_qname(statement.predicate) - get_qname(statement.object) - get_qname(statement.object.datatype) if statement.object.literal? && statement.object.datatype + # Pre-fetch pnames, to fill prefixes + get_pname(statement.subject) + get_pname(statement.predicate) + get_pname(statement.object) + get_pname(statement.object.datatype) if statement.object.literal? && statement.object.datatype - @references[statement.predicate] = ref_count(statement.predicate) + 1 - end - - # Return the number of times this node has been referenced in the object position - # @return [Integer] - def ref_count(node) - @references.fetch(node, 0) + bump_reference(statement.predicate) # XXX legacy end # Returns indent string multiplied by the depth # @param [Integer] modifier Increase depth by specified amount # @return [String] A number of spaces, depending on current depth def indent(modifier = 0) - " " * (@depth + modifier) + " " * (@options.fetch(:log_depth, log_depth) * 2 + modifier) end # Reset internal helper instance variables def reset - @depth = 0 @lists = {} @references = {} @@ -424,17 +414,17 @@ def quoted(string) end private - + # Checks if l is a valid RDF list, i.e. no nodes have other properties. - def is_valid_list(l) - #log_debug {"is_valid_list: #{l.inspect}"} + def is_valid_list?(l) + #log_debug("is_valid_list?") {l.inspect} return @lists[l] && @lists[l].valid? end - - def do_list(l) + + def do_list(l, position) list = @lists[l] - log_debug {"do_list: #{list.inspect}"} - position = :subject + log_debug("do_list") {list.inspect} + subject_done(RDF.nil) list.each_statement do |st| next unless st.predicate == RDF.first log_debug {" list this: #{st.subject} first: #{st.object}[#{position}]"} @@ -443,132 +433,149 @@ def do_list(l) position = :object end end - - def p_list(node, position) - return false if !is_valid_list(node) - #log_debug {"p_list: #{node.inspect}, #{position}"} + + def collection(node, position) + return false if !is_valid_list?(node) + return false if position == :subject && ref_count(node) > 0 + return false if position == :object && prop_count(node) > 0 + #log_debug("collection") {"#{node.to_ntriples}, #{position}"} @output.write(position == :subject ? "(" : " (") - @depth += 2 - do_list(node) - @depth -= 2 + log_depth {do_list(node, position)} @output.write(')') end - - def p_squared?(node, position) - node.node? && - !@serialized.has_key?(node) && - ref_count(node) <= 1 - end - - def p_squared(node, position) - return false unless p_squared?(node, position) - - #log_debug {"p_squared: #{node.inspect}, #{position}"} - subject_done(node) - @output.write(position == :subject ? '[' : ' [') - @depth += 2 - predicate_list(node) - @depth -= 2 - @output.write(']') - - true - end - - def p_default(node, position) - #log_debug {"p_default: #{node.inspect}, #{position}"} - l = (position == :subject ? "" : " ") + format_term(node, options) + + # Default singular resource representation. + def p_term(resource, position) + #log_debug("p_term") {"#{resource.to_ntriples}, #{position}"} + l = (position == :subject ? "" : " ") + format_term(resource, options) @output.write(l) end - - def path(node, position) - log_debug do - "path: #{node.inspect}, " + + + # Represent a resource in subject, predicate or object position. + # Use either collection, blankNodePropertyList or singular resource notation. + def path(resource, position) + log_debug("path") do + "#{resource.to_ntriples}, " + "pos: #{position}, " + - "[]: #{is_valid_list(node)}, " + - "p2?: #{p_squared?(node, position)}, " + - "rc: #{ref_count(node)}" + "()?: #{is_valid_list?(resource)}, " + + "[]?: #{blankNodePropertyList?(resource, position)}, " + + "rc: #{ref_count(resource)}" end - raise RDF::WriterError, "Cannot serialize node '#{node}'" unless p_list(node, position) || p_squared(node, position) || p_default(node, position) + raise RDF::WriterError, "Cannot serialize resource '#{resource}'" unless + collection(resource, position) || + blankNodePropertyList(resource, position) || + p_term(resource, position) end - - def verb(node) - log_debug {"verb: #{node.inspect}"} - if node == RDF.type + + def predicate(resource) + log_debug("predicate") {resource.to_ntriples} + if resource == RDF.type @output.write(" a") else - path(node, :predicate) + path(resource, :predicate) end end - - def object_list(objects) - log_debug {"object_list: #{objects.inspect}"} + + # Render an objectList having a common subject and predicate + def objectList(objects) + log_debug("objectList") {objects.inspect} return if objects.empty? objects.each_with_index do |obj, i| - @output.write(",\n#{indent(4)}") if i > 0 + if i > 0 && blankNodePropertyList?(obj, :object) + @output.write ", " + elsif i > 0 + @output.write ",\n#{indent(4)}" + end path(obj, :object) end end - - def predicate_list(subject) + + # Render a predicateObjectList having a common subject. + # @return [Integer] the number of properties serialized + def predicateObjectList(subject, from_bpl = false) properties = {} - @graph.query(subject: subject) do |st| - properties[st.predicate.to_s] ||= [] - properties[st.predicate.to_s] << st.object + @graph.query(subject: subject) do |st| + (properties[st.predicate.to_s] ||= []) << st.object end prop_list = sort_properties(properties) - prop_list -= [RDF.first.to_s, RDF.rest.to_s] if subject.node? - log_debug {"predicate_list: #{prop_list.inspect}"} - return if prop_list.empty? + prop_list -= [RDF.first.to_s, RDF.rest.to_s] if @lists.include?(subject) + log_debug("predicateObjectList") {prop_list.inspect} + return 0 if prop_list.empty? + @output.write("\n#{indent(2)}") if properties.keys.length > 1 && from_bpl prop_list.each_with_index do |prop, i| begin @output.write(";\n#{indent(2)}") if i > 0 - verb(prop[0, 2] == "_:" ? RDF::Node.intern(prop.split(':').last) : RDF::URI.intern(prop)) - object_list(properties[prop]) - rescue Addressable::URI::InvalidURIError => e - log_debug {"Predicate #{prop.inspect} is an invalid URI: #{e.message}"} + predicate(RDF::URI.intern(prop)) + objectList(properties[prop]) end end + properties.keys.length + end + + # Can subject be represented as a blankNodePropertyList? + def blankNodePropertyList?(resource, position) + resource.node? && + !is_valid_list?(resource) && + (!is_done?(resource) || position == :subject) && + ref_count(resource) == (position == :object ? 1 : 0) end - - def s_squared?(subject) - ref_count(subject) == 0 && subject.node? && !is_valid_list(subject) - end - - def s_squared(subject) - return false unless s_squared?(subject) - - log_debug {"s_squared: #{subject.inspect}"} - @output.write("\n#{indent} [") - @depth += 1 - predicate_list(subject) - @depth -= 1 - @output.write("] .") + + def blankNodePropertyList(resource, position) + return false unless blankNodePropertyList?(resource, position) + + log_debug("blankNodePropertyList") {resource.to_ntriples} + subject_done(resource) + @output.write(position == :subject ? "\n#{indent} [" : ' [') + num_props = log_depth {predicateObjectList(resource, true)} + @output.write((num_props > 1 ? "\n#{indent}" : "") + (position == :object ? ']' : '] .')) true end - - def s_default(subject) + + # Render triples having the same subject using an explicit subject + def triples(subject) @output.write("\n#{indent}") path(subject, :subject) - predicate_list(subject) + predicateObjectList(subject) @output.write(" .") true end - + def statement(subject) - log_debug {"statement: #{subject.inspect}, s2?: #{s_squared?(subject)}"} + log_debug("statement") {"#{subject.to_ntriples}, bnodePL?: #{blankNodePropertyList?(subject, :subject)}"} subject_done(subject) - s_squared(subject) || s_default(subject) - @output.write("\n") + blankNodePropertyList(subject, :subject) || triples(subject) + @output.puts + end + + # Return the number of statements having this resource as a subject other than for list properties + # @return [Integer] + def prop_count(subject) + @subjects.fetch(subject, {}). + reject {|k, v| [RDF.type, RDF.first, RDF.rest].include?(k)}. + values.reduce(:+) || 0 end - + + # Return the number of times this node has been referenced in the object position + # @return [Integer] + def ref_count(node) + @references.fetch(node, 0) + end + + # Increase the reference count of this resource + # @param [RDF::Resource] resource + # @return [Integer] resulting reference count + def bump_reference(resource) + @references[resource] = ref_count(resource) + 1 + end + def is_done?(subject) @serialized.include?(subject) end - + # Mark a subject as done. def subject_done(subject) @serialized[subject] = true diff --git a/rdf-n3.gemspec b/rdf-n3.gemspec index 32ebe8e..a38c4b2 100755 --- a/rdf-n3.gemspec +++ b/rdf-n3.gemspec @@ -31,6 +31,7 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'rdf-spec', '~> 3.0' gem.add_development_dependency 'rdf-isomorphic', '~> 3.0' gem.add_development_dependency 'rdf-trig', '~> 3.0' + gem.add_development_dependency 'rdf-vocab', '~> 3.0' gem.add_development_dependency 'yard' , '~> 0.9.16' gem.post_install_message = nil diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index c3d46b1..821abdb 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -1,6 +1,7 @@ # coding: utf-8 require_relative 'spec_helper' require 'rdf/spec/writer' +require 'rdf/vocab' describe RDF::N3::Writer do let(:logger) {RDF::Spec.logger} @@ -26,301 +27,283 @@ end describe "simple tests" do - it "should use full URIs without base" do - input = %( .) - serialize(input, nil, [%r(^ \.$)]) - end - - it "should use relative URIs with base" do - input = %( .) - serialize(input, "http://a/", - [ %r(^@base \.$), - %r(^ \.$)] - ) - end - - it "should use qname URIs with prefix" do - input = %( .) - serialize(input, nil, - [%r(^@prefix foaf: \.$), - %r(^foaf:b foaf:c foaf:d \.$)], - prefixes: { foaf: "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should use qname URIs with empty prefix" do - input = %( .) - serialize(input, nil, - [%r(^@prefix : \.$), - %r(^:b :c :d \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - # see example-files/arnau-registered-vocab.rb - it "should use qname URIs with empty suffix" do - input = %( .) - serialize(input, nil, - [%r(^@prefix foaf: \.$), - %r(^foaf: foaf: foaf: \.$)], + { + "full URIs without base" => { + input: %( .), + regexp: [%r(^ \.$)], + }, + "relative URIs with base" => { + input: %( .), + regexp: [ %r(^@base \.$), %r(^ \.$)], + base_uri: "http://a/" + }, + "qname URIs with prefix" => { + input: %( .), + regexp: [ + %r(^@prefix ex: \.$), + %r(^ex:b ex:c ex:d \.$) + ], + prefixes: {ex: "http://example.com/"} + }, + "qname URIs with empty prefix" => { + input: %( .), + regexp: [ + %r(^@prefix : \.$), + %r(^:b :c :d \.$) + ], + prefixes: {"" => "http://example.com/"} + }, + # see example-files/arnau-registered-vocab.rb + "qname URIs with empty suffix" => { + input: %( .), + regexp: [ + %r(^@prefix foaf: \.$), + %r(^foaf: foaf: foaf: \.$) + ], prefixes: { "foaf" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should not use qname with illegal local part" do - input = %( - @prefix db: . - @prefix dbo: . - db:Michael_Jackson dbo:artistOf . - ) - - serialize(input, nil, - [%r(^@prefix db: \.$), - %r(^db:Michael_Jackson dbo:artistOf \.$)], + }, + "order properties" => { + input: %( + @prefix ex: . + @prefix dc: . + @prefix rdfs: . + ex:b ex:c ex:d . + ex:b dc:title "title" . + ex:b a ex:class . + ex:b rdfs:label "label" . + ), + regexp: [ + %r(^ex:b a ex:class;$), + %r(ex:class;\s+rdfs:label "label")m, + %r("label";\s+ex:c ex:d)m, + %r(ex:d;\s+dc:title "title" \.$)m + ], + }, + "object list" => { + input: %(@prefix ex: . ex:b ex:c ex:d, ex:e .), + regexp: [ + %r(^@prefix ex: \.$), + %r(^ex:b ex:c ex:[de],\s+ex:[de] \.$)m, + ], + }, + "property list" => { + input: %(@prefix ex: . ex:b ex:c ex:d; ex:e ex:f .), + regexp: [ + %r(^@prefix ex: \.$), + %r(^ex:b ex:c ex:d;$), + %r(^\s+ex:e ex:f \.$) + ], + }, + "bare anon" => { + input: %(@prefix ex: . [ex:a ex:b] .), + regexp: [%r(^\s*\[ ex:a ex:b\] \.$)], + }, + "anon as subject" => { + input: %(@prefix ex: . [ex:a ex:b] ex:c ex:d .), + regexp: [ + %r(^\s*\[\s*ex:a ex:b;$)m, + %r(^\s+ex:c ex:d\s*\] \.$)m + ], + }, + "anon as object" => { + input: %(@prefix ex: . ex:a ex:b [ex:c ex:d] .), + regexp: [%r(^ex:a ex:b \[ ex:c ex:d\] \.$)], + }, + "reuses BNode labels by default" => { + input: %(@prefix ex: . _:a ex:b _:a .), + regexp: [%r(^\s*_:a ex:b _:a \.$)] + }, + "generated BNodes with :unique_bnodes" => { + input: %(@prefix ex: . _:a ex:b _:a .), + regexp: [%r(^\s*_:g\w+ ex:b _:g\w+ \.$)], + unique_bnodes: true + }, + "standard prefixes" => { + input: %( + a ; + "Person" . + ), + regexp: [ + %r(^@prefix foaf: \.$), + %r(^@prefix dc: \.$), + %r(^ a foaf:Person;$), + %r(dc:title "Person" \.$), + ], + standard_prefixes: true, prefixes: {} + }, + "should not use qname with illegal local part" => { + input: %( + @prefix db: . + @prefix dbo: . + db:Michael_Jackson dbo:artistOf . + ), + regexp: [ + %r(^@prefix db: \.$), + %r(^db:Michael_Jackson dbo:artistOf \.$) + ], prefixes: { "db" => RDF::URI("http://dbpedia.org/resource/"), "dbo" => RDF::URI("http://dbpedia.org/ontology/")} - ) - end - - it "should order properties" do - input = %( - @prefix : . - @prefix dc: . - @prefix rdfs: . - :b :c :d . - :b dc:title "title" . - :b a :class . - :b rdfs:label "label" . - ) - serialize(input, nil, - [ - %r(^:b a :class;$), - %r(:class;\s+rdfs:label "label")m, - %r("label";\s+dc:title "title")m, - %r("title";\s+:c :d \.$)m - ], - prefixes: { "" => "http://xmlns.com/foaf/0.1/", dc: "http://purl.org/dc/elements/1.1/", rdfs: RDF::RDFS} - ) - end - - it "should generate object list" do - input = %(@prefix : . :b :c :d, :e .) - serialize(input, nil, - [%r(^@prefix : \.$), - %r(^:b :c :[de],\s+:[de] \.$)m], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate property list" do - input = %(@prefix : . :b :c :d; :e :f .) - serialize(input, nil, - [%r(^@prefix : \.$), - %r(^:b :[ce] :[df];\s+:[ce] :[df] \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - end - - describe "anons" do - it "should generate bare anon" do - input = %(@prefix : . [:a :b] .) - serialize(input, nil, - [%r(^\s*\[ :a :b\] \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate anon as subject" do - input = %(@prefix : . [:a :b] :c :d .) - serialize(input, nil, - [%r(^\s*\[ :a :b;$), - %r(^\s+:c :d\] \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate anon as object" do - input = %(@prefix : . :a :b [:c :d] .) - serialize(input, nil, - [%r(^\s*\:a :b \[ :c :d\] \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - end - - describe "BNodes" do - let(:input) {%(@prefix : . _:a :b _:a .)} - it "reuses BNode labels by default" do - serialize(input, nil, - [%r(^\s*_:a :b _:a \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - it "uses generated BNodes with :unique_bnodes" do - serialize(input, nil, - [%r(^\s*_:g\w+ :b _:g\w+ \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"}, - unique_bnodes: true - ) + } + }.each do |name, params| + it name do + serialize(params[:input], params[:regexp], params) + end end end describe "lists" do - it "should generate bare list" do - input = %(@prefix : . (:a :b) .) - serialize(input, nil, - [%r(^\(:a :b\) \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate literal list" do - input = %(@prefix : . :a :b ( "apple" "banana" ) .) - serialize(input, nil, - [%r(^:a :b \("apple" "banana"\) \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate empty list" do - input = %(@prefix : . :a :b () .) - serialize(input, nil, - [%r(^:a :b \(\) \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate empty list(2)" do - input = %(@prefix : . :emptyList = () .) - serialize(input, nil, - [%r(^:emptyList (<.*sameAs>|owl:sameAs) \(\) \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate empty list as subject" do - input = %(@prefix : . () :a :b .) - serialize(input, nil, - [%r(^\(\) :a :b \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate list as subject" do - input = %(@prefix : . (:a) :b :c .) - serialize(input, nil, - [%r(^\(:a\) :b :c \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate list of empties" do - input = %(@prefix : . :listOf2Empties = (() ()) .) - serialize(input, nil, - [%r(^:listOf2Empties (<.*sameAs>|owl:sameAs) \(\(\) \(\)\) \.$)], + { + "bare list": { + input: %(@prefix ex: . (ex:a ex:b) .), + regexp: [%r(^\(\s*ex:a ex:b\s*\) \.$)] + }, + "literal list": { + input: %(@prefix ex: . ex:a ex:b ( "apple" "banana" ) .), + regexp: [%r(^ex:a ex:b \(\s*"apple" "banana"\s*\) \.$)] + }, + "empty list": { + input: %(@prefix ex: . ex:a ex:b () .), + regexp: [%r(^ex:a ex:b \(\s*\) \.$)], + prefixes: { "" => RDF::Vocab::FOAF} + }, + "should generate empty list(2)" => { + input: %(@prefix : . :emptyList = () .), + regexp: [%r(^:emptyList (<.*sameAs>|owl:sameAs) \(\) \.$)], prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate list anon" do - input = %(@prefix : . :twoAnons = ([a :mother] [a :father]) .) - serialize(input, nil, - [%r(^:twoAnons (<.*sameAs>|owl:sameAs) \(\[\s*a :(mother|father)\] \[\s*a :(mother|father)\]\) \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate owl:unionOf list" do - input = %( - @prefix : . - @prefix owl: . - @prefix rdf: . - @prefix rdfs: . - :a rdfs:domain [ - a owl:Class; - owl:unionOf [ + }, + "empty list as subject": { + input: %(@prefix ex: . () ex:a ex:b .), + regexp: [%r(^\(\s*\) ex:a ex:b \.$)] + }, + "list as subject": { + input: %(@prefix ex: . (ex:a) ex:b ex:c .), + regexp: [%r(^\(\s*ex:a\s*\) ex:b ex:c \.$)] + }, + "list of empties": { + input: %(@prefix ex: . [ex:listOf2Empties (() ())] .), + regexp: [%r(\[\s*ex:listOf2Empties \(\s*\(\s*\) \(\s*\)\s*\)\s*\] \.$)] + }, + "list anon": { + input: %(@prefix ex: . [ex:twoAnons ([a ex:mother] [a ex:father])] .), + regexp: [%r(\[\s*ex:twoAnons \(\s*\[\s*a ex:mother\s*\] \[\s*a ex:father\s*\]\)\] \.$)] + }, + "owl:unionOf list": { + input: %( + @prefix ex: . + @prefix owl: . + @prefix rdf: . + @prefix rdfs: . + ex:a rdfs:domain [ a owl:Class; - rdf:first :b; - rdf:rest [ + owl:unionOf [ a owl:Class; - rdf:first :c; - rdf:rest rdf:nil + rdf:first ex:b; + rdf:rest [ + a owl:Class; + rdf:first ex:c; + rdf:rest rdf:nil + ] ] - ] - ] . - ) - #$verbose = true - serialize(input, nil, - [ - %r(:a rdfs:domain \[\s*a owl:Class;\s+owl:unionOf\s+\(:b\s+:c\)\]\s*\.$)m, - %r(@prefix : \.), + ] . + ), + regexp: [ + %r(ex:a rdfs:domain \[\s*a owl:Class;\s+owl:unionOf\s+\(\s*ex:b\s+ex:c\s*\)\s*\]\s*\.$)m, + %r(@prefix ex: \.), %r(@prefix rdf: \.), - ], - prefixes: { "" => "http://xmlns.com/foaf/0.1/", dfs: RDF::RDFS, owl: RDF::OWL, rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#"} - ) - #$verbose = false - end - - it "should generate list with first subject a URI" do - input = %( - "1"^^ . - _:g47006741228480 . - _:g47006741228480 "2"^^ . - _:g47006741228480 _:g47006737917560 . - _:g47006737917560 "3"^^ . - _:g47006737917560 . - ) - #$verbose = true - serialize(input, nil, - [ + ] + }, + "list with first subject a URI": { + input: %( + "1"^^ . + _:g47006741228480 . + _:g47006741228480 "2"^^ . + _:g47006741228480 _:g47006737917560 . + _:g47006737917560 "3"^^ . + _:g47006737917560 . + ), + regexp: [ %r(@prefix rdf: \.), %r( rdf:first 1;), - %r(rdf:rest \(2 3\) \.), + %r(rdf:rest \(\s*2 3\s*\) \.), + ], + standard_prefixes: true + }, + "list pattern without rdf:nil": { + input: %( + _:a . + _:a "a" . + _:a _:b . + _:b "b" . + _:b _:c . + _:c "c" . + ), + regexp: [%r( \[), + %r(rdf:first "a";), + %r(rdf:rest \[), + %r(rdf:first "b";), + %r(rdf:rest \[\s*rdf:first "c"\s*\]), + ], + standard_prefixes: true + }, + "list pattern with extra properties": { + input: %( + _:a . + _:a "a" . + _:a _:b . + _:b "b" . + _:a "This list node has also properties other than rdf:first and rdf:rest" . + _:b _:c . + _:c "c" . + _:c . + ), + regexp: [%r( \[), + %r( "This list node has also properties other than rdf:first and rdf:rest";), + %r(rdf:first "a";), + %r(rdf:rest \(\s*"b" "c"\s*\)), + ], + standard_prefixes: true + }, + "list with empty list": { + input: %( + _:l1 . + _:l1 . + _:l1 . + ), + regexp: [ + %r( \(\s*\(\)\) .) ], standard_prefixes: true - ) - #$verbose = false + }, + "list with multiple lists": { + input: %( + _:l1 . + _:a "a" . + _:a . + _:b "b" . + _:b . + _:l1 _:a . + _:l1 _:l2 . + _:l2 _:b . + _:l2 . + ), + regexp: [ + %r( \(\s*\(\s*"a"\) \(\s*"b"\)\) .) + ], + standard_prefixes: true + }, + }.each do |name, params| + it name do + serialize(params[:input], params[:regexp], params) + end end end describe "literals" do - describe "plain" do - it "encodes embedded \"\"\"" do - n3 = %(:a :b """testing string parsing in N3. - """ .) - serialize(n3, nil, [/testing string parsing in N3.\n/]) - end - - it "encodes embedded \"" do - n3 = %(:a :b """string with " escaped quote marks""" .) - serialize(n3, nil, [/string with \\" escaped quote mark/]) - end - - it "encodes embedded \\" do - n3 = %(:a :b """string with \\\\ escaped quote marks""" .) - serialize(n3, nil, [/string with \\\\ escaped quote mark/]) - end - - it "encodes embedded \\ multi-line" do - n3 = %(:a :b """string with \\\\ escaped quote marks - """ .) - serialize(n3, nil, [/string with \\\\ escaped quote mark/]) - end - end - - describe "with language" do - it "specifies language for literal with language" do - ttl = %q(:a :b "string"@en .) - serialize(ttl, nil, [%r("string"@en)]) - end - end - describe "xsd:anyURI" do it "uses xsd namespace for datatype" do - ttl = %q(@prefix xsd: . :a :b "http://foo/"^^xsd:anyURI .) - serialize(ttl, nil, [ + ttl = %q(@prefix xsd: . "http://foo/"^^xsd:anyURI .) + serialize(ttl, [ %r(@prefix xsd: \.), %r("http://foo/"\^\^xsd:anyURI \.), ]) @@ -339,13 +322,29 @@ [%q(false), /false ./], ].each do |(l,r)| it "uses token for #{l.inspect}" do - ttl = %(@prefix : . @prefix xsd: . :a :b #{l} .) - serialize(ttl, nil, [ + ttl = %(@prefix xsd: . #{l} .) + serialize(ttl, [ %r(@prefix xsd: \.), r, ], canonicalize: true) end end + + [ + [true, "true"], + [false, "false"], + [1, "true"], + [0, "false"], + ["true", "true"], + ["false", "false"], + ["1", "true"], + ["0", "false"], + ["string", %{"string"^^}], + ].each do |(l,r)| + it "serializes #{l.inspect} to #{r.inspect}" do + expect(subject.format_literal(RDF::Literal::Boolean.new(l))).to eql r + end + end end describe "xsd:integer" do @@ -358,13 +357,27 @@ [%q(10), /10 ./], ].each do |(l,r)| it "uses token for #{l.inspect}" do - ttl = %(@prefix : . @prefix xsd: . :a :b #{l} .) - serialize(ttl, nil, [ + ttl = %(@prefix xsd: . #{l} .) + serialize(ttl, [ %r(@prefix xsd: \.), r, ], canonicalize: true) end end + + [ + [0, "0"], + [10, "10"], + [-1, "-1"], + ["0", "0"], + ["true", %{"true"^^}], + ["false", %{"false"^^}], + ["string", %{"string"^^}], + ].each do |(l,r)| + it "serializes #{l.inspect} to #{r.inspect}" do + expect(subject.format_literal(RDF::Literal::Integer.new(l))).to eql r + end + end end describe "xsd:int" do @@ -374,8 +387,8 @@ [%q("10"^^xsd:int), /"10"\^\^xsd:int ./], ].each do |(l,r)| it "uses token for #{l.inspect}" do - ttl = %(@prefix : . @prefix xsd: . :a :b #{l} .) - serialize(ttl, nil, [ + ttl = %(@prefix xsd: . #{l} .) + serialize(ttl, [ %r(@prefix xsd: \.), r, ], canonicalize: true) @@ -393,13 +406,32 @@ [%q(10.02), /10.02 ./], ].each do |(l,r)| it "uses token for #{l.inspect}" do - ttl = %(@prefix : . @prefix xsd: . :a :b #{l} .) - serialize(ttl, nil, [ + ttl = %(@prefix xsd: . #{l} .) + serialize(ttl, [ %r(@prefix xsd: \.), r, ], canonicalize: true) end end + + [ + [0, "0.0"], + [10, "10.0"], + [-1, "-1.0"], + ["0", "0.0"], + ["10", "10.0"], + ["-1", "-1.0"], + ["1.0", "1.0"], + ["0.1", "0.1"], + ["10.01", "10.01"], + ["true", %{"true"^^}], + ["false", %{"false"^^}], + ["string", %{"string"^^}], + ].each do |(l,r)| + it "serializes #{l.inspect} to #{r.inspect}" do + expect(subject.format_literal(RDF::Literal::Decimal.new(l))).to eql r + end + end end describe "xsd:double" do @@ -410,15 +442,35 @@ [%q(0.1e1), /1.0e0 ./], [%q("10.02e1"^^xsd:double), /1.002e2 ./], [%q(10.02e1), /1.002e2 ./], + [%q("14"^^xsd:double), /1.4e1 ./], ].each do |(l,r)| it "uses token for #{l.inspect}" do - ttl = %(@prefix : . @prefix xsd: . :a :b #{l} .) - serialize(ttl, nil, [ + ttl = %(@prefix xsd: . #{l} .) + serialize(ttl, [ %r(@prefix xsd: \.), r, ], canonicalize: true) end end + + [ + [0, "0.0e0"], + [10, "1.0e1"], + [-1, "-1.0e0"], + ["0", "0.0e0"], + ["10", "1.0e1"], + ["-1", "-1.0e0"], + ["1.0", "1.0e0"], + ["0.1", "1.0e-1"], + ["10.01", "1.001e1"], + ["true", %{"true"^^}], + ["false", %{"false"^^}], + ["string", %{"string"^^}], + ].each do |(l,r)| + it "serializes #{l.inspect} to #{r.inspect}" do + expect(subject.format_literal(RDF::Literal::Double.new(l))).to eql r + end + end end end @@ -431,10 +483,10 @@ def parse(input, options = {}) end # Serialize ntstr to a string and compare against regexps - def serialize(ntstr, base = nil, regexps = [], options = {}) + def serialize(ntstr, regexps = [], base_uri: nil, **options) prefixes = options[:prefixes] || {} - g = ntstr.is_a?(RDF::Enumerable) ? ntstr : parse(ntstr, base_uri: base, prefixes: prefixes, validate: false, logger: []) - result = RDF::N3::Writer.buffer(options.merge(logger: logger, base_uri: base, prefixes: prefixes)) do |writer| + g = ntstr.is_a?(RDF::Enumerable) ? ntstr : parse(ntstr, base_uri: base_uri, prefixes: prefixes, validate: false, logger: []) + result = RDF::N3::Writer.buffer(options.merge(logger: logger, base_uri: base_uri, prefixes: prefixes)) do |writer| writer << g end if $verbose @@ -445,7 +497,7 @@ def serialize(ntstr, base = nil, regexps = [], options = {}) logger.info "result: #{result}" regexps.each do |re| logger.info "match: #{re.inspect}" - expect(result).to match_re(re, about: base, logger: logger, input: ntstr), logger.to_s + expect(result).to match_re(re, about: base_uri, logger: logger, input: ntstr), logger.to_s end result From 6d44dcc56b3f9814d145e9adf80b681bf46b7e5f Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 18 Jan 2019 15:43:53 -0800 Subject: [PATCH 20/40] Write formulae without variables. --- lib/rdf/n3/writer.rb | 144 ++++++++++++++++++++++++++++++++++--------- script/parse | 6 +- spec/writer_spec.rb | 75 ++++++++++++++++++++-- 3 files changed, 187 insertions(+), 38 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 5d891c1..e939433 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -51,7 +51,10 @@ class Writer < RDF::Writer include RDF::Util::Logger QNAME = Meta::REGEXPS[:"http://www.w3.org/2000/10/swap/grammar/n3#qname"] - # @return [Graph] Graph of statements serialized + # @return [RDF::Repository] Repository of statements serialized + attr_accessor :repo + + # @return [RDF::Graph] Graph being serialized attr_accessor :graph ## @@ -101,7 +104,7 @@ def self.options # @yield [writer] # @yieldparam [RDF::Writer] writer def initialize(output = $stdout, options = {}, &block) - @graph = RDF::Graph.new + @repo = RDF::Repository.new @uri_to_pname = {} @uri_to_prefix = {} super do @@ -124,7 +127,19 @@ def initialize(output = $stdout, options = {}, &block) # @raise [NotImplementedError] unless implemented in subclass # @abstract def write_triple(subject, predicate, object) - @graph.insert(RDF::Statement(subject, predicate, object)) + repo.insert(RDF::Statement(subject, predicate, object)) + end + + ## + # Adds a quad to be serialized + # @param [RDF::Resource] subject + # @param [RDF::URI] predicate + # @param [RDF::Value] object + # @param [RDF::Resource] graph_name + # @return [void] + def write_quad(subject, predicate, object, graph_name) + statement = RDF::Statement.new(subject, predicate, object, graph_name: graph_name) + repo.insert(statement) end ## @@ -137,21 +152,25 @@ def write_epilogue self.reset - log_debug {"\nserialize: graph: #{@graph.size}"} + log_debug {"\nserialize: repo: #{repo.size}"} preprocess start_document - # Remove lists that are referenced and have non-list properties; - # these are legal, but can't be serialized as lists - @lists.reject! do |node, list| - ref_count(node) > 0 && prop_count(node) > 0 - end + with_graph(nil) do + graph.each {|statement| preprocess_graph_statement(statement)} - order_subjects.each do |subject| - unless is_done?(subject) - statement(subject) + # Remove lists that are referenced and have non-list properties; + # these are legal, but can't be serialized as lists + @lists.reject! do |node, list| + ref_count(node) > 0 && prop_count(node) > 0 || + list.subjects.any? {|elt| !resource_in_single_graph?(elt)} + end + order_subjects.each do |subject| + unless is_done?(subject) + statement(subject) + end end end @@ -310,7 +329,7 @@ def order_subjects # Add distinguished classes top_classes.each do |class_uri| - graph.query(predicate: RDF.type, object: class_uri). + repo.query(predicate: RDF.type, object: class_uri, graph_name: false). map {|st| st.subject}. sort. uniq. @@ -350,7 +369,7 @@ def preprocess prefix(nil, @options[:default_namespace]) if @options[:default_namespace] @options[:prefixes] = {} # Will define actual used when matched - @graph.each {|statement| preprocess_statement(statement)} + repo.each {|statement| preprocess_statement(statement)} end # Perform any statement preprocessing required. This is used to perform reference counts and determine required @@ -358,9 +377,21 @@ def preprocess # @param [Statement] statement def preprocess_statement(statement) #log_debug {"preprocess: #{statement.inspect}"} + + # Pre-fetch pnames, to fill prefixes + get_pname(statement.subject) + get_pname(statement.predicate) + get_pname(statement.object) + get_pname(statement.object.datatype) if statement.object.literal? && statement.object.datatype + end + + # Perform graph-specific preprocessing + # @param [Statement] + def preprocess_graph_statement(statement) bump_reference(statement.object) # Count properties of this subject - (@subjects[statement.subject] ||= {})[statement.predicate] ||= 0 + @subjects[statement.subject] ||= {} + @subjects[statement.subject][statement.predicate] ||= 0 @subjects[statement.subject][statement.predicate] += 1 # Collect lists @@ -373,14 +404,6 @@ def preprocess_statement(statement) # Add an entry for the list tail @lists[RDF.nil] ||= RDF::List[] end - - # Pre-fetch pnames, to fill prefixes - get_pname(statement.subject) - get_pname(statement.predicate) - get_pname(statement.object) - get_pname(statement.object.datatype) if statement.object.literal? && statement.object.datatype - - bump_reference(statement.predicate) # XXX legacy end # Returns indent string multiplied by the depth @@ -393,7 +416,6 @@ def indent(modifier = 0) # Reset internal helper instance variables def reset @lists = {} - @references = {} @serialized = {} @subjects = {} @@ -458,11 +480,13 @@ def path(resource, position) log_debug("path") do "#{resource.to_ntriples}, " + "pos: #{position}, " + + "{}?: #{formula?(resource, position)}, " + "()?: #{is_valid_list?(resource)}, " + "[]?: #{blankNodePropertyList?(resource, position)}, " + "rc: #{ref_count(resource)}" end raise RDF::WriterError, "Cannot serialize resource '#{resource}'" unless + formula(resource, position) || collection(resource, position) || blankNodePropertyList(resource, position) || p_term(resource, position) @@ -470,8 +494,13 @@ def path(resource, position) def predicate(resource) log_debug("predicate") {resource.to_ntriples} - if resource == RDF.type + case resource + when RDF.type @output.write(" a") + when RDF::OWL.sameAs + @output.write(" =") + when RDF::N3::Log.implies + @output.write(" =>") else path(resource, :predicate) end @@ -483,7 +512,7 @@ def objectList(objects) return if objects.empty? objects.each_with_index do |obj, i| - if i > 0 && blankNodePropertyList?(obj, :object) + if i > 0 && (formula?(obj, :object) || blankNodePropertyList?(obj, :object)) @output.write ", " elsif i > 0 @output.write ",\n#{indent(4)}" @@ -496,7 +525,7 @@ def objectList(objects) # @return [Integer] the number of properties serialized def predicateObjectList(subject, from_bpl = false) properties = {} - @graph.query(subject: subject) do |st| + @graph.query(subject: subject) do |st| (properties[st.predicate.to_s] ||= []) << st.object end @@ -519,9 +548,12 @@ def predicateObjectList(subject, from_bpl = false) # Can subject be represented as a blankNodePropertyList? def blankNodePropertyList?(resource, position) resource.node? && + !formula?(resource, position) && !is_valid_list?(resource) && (!is_done?(resource) || position == :subject) && - ref_count(resource) == (position == :object ? 1 : 0) + ref_count(resource) == (position == :object ? 1 : 0) && + resource_in_single_graph?(resource) && + !repo.has_graph?(resource) end def blankNodePropertyList(resource, position) @@ -529,12 +561,48 @@ def blankNodePropertyList(resource, position) log_debug("blankNodePropertyList") {resource.to_ntriples} subject_done(resource) - @output.write(position == :subject ? "\n#{indent} [" : ' [') + @output.write(position == :subject ? "\n#{indent}[" : ' [') num_props = log_depth {predicateObjectList(resource, true)} @output.write((num_props > 1 ? "\n#{indent}" : "") + (position == :object ? ']' : '] .')) true end + # Can subject be represented as a formula? + def formula?(resource, position) + resource.node? && + repo.has_graph?(resource) && + !is_valid_list?(resource) && + (!is_done?(resource) || position == :subject) && + ref_count(resource) == (position == :object ? 1 : 0) && + resource_in_single_graph?(resource) + end + + def formula(resource, position) + return false unless formula?(resource, position) + + log_debug("formula") {resource.to_ntriples} + subject_done(resource) + @output.write(position == :subject ? "\n#{indent}{" : ' {') + log_depth do + with_graph(resource) do + graph.each {|statement| preprocess_graph_statement(statement)} + + # Remove lists that are referenced and have non-list properties; + # these are legal, but can't be serialized as lists + @lists.reject! do |node, list| + ref_count(node) > 0 && prop_count(node) > 0 + end + order_subjects.each do |subject| + unless is_done?(subject) + statement(subject) + end + end + end + end + @output.write((graph.count > 1 ? "\n#{indent}" : "") + '}') + true + end + # Render triples having the same subject using an explicit subject def triples(subject) @output.write("\n#{indent}") @@ -580,5 +648,23 @@ def is_done?(subject) def subject_done(subject) @serialized[subject] = true end + + def resource_in_single_graph?(resource) + graph_names = @repo.query(subject: resource).map(&:graph_name) + graph_names += @repo.query(object: resource).map(&:graph_name) + graph_names.uniq.length <= 1 + end + + # Process a graph projection + def with_graph(graph_name) + old_lists, @lists = @lists, {} + old_references, @references = @references, {} + old_serialized, @serialized = @serialized, {} + old_subjects, @subjects = @subjects, {} + old_graph, @graph = @graph, repo.project_graph(graph_name) + yield + ensure + @graph, @lists, @references, @serialized, @subjects = old_graph, old_lists, old_references, old_serialized, old_subjects + end end end diff --git a/script/parse b/script/parse index 4ad74c1..2dddd62 100755 --- a/script/parse +++ b/script/parse @@ -48,9 +48,9 @@ def run(input, options) end else r = reader_class.new(input, options[:parser_options]) - g = RDF::Graph.new << r - num = g.count - options[:output].puts g.dump(options[:output_format], base_uri: options[:base_uri], prefixes: r.prefixes, standard_prefixes: true) + repo = RDF::Repository.new << r + num = repo.count + options[:output].puts repo.dump(options[:output_format], base_uri: options[:base_uri], prefixes: r.prefixes, standard_prefixes: true, logger: options[:logger]) end if options[:profile] Profiler__::stop_profile diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 821abdb..1cc08cc 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -169,7 +169,7 @@ }, "should generate empty list(2)" => { input: %(@prefix : . :emptyList = () .), - regexp: [%r(^:emptyList (<.*sameAs>|owl:sameAs) \(\) \.$)], + regexp: [%r(^:emptyList (<.*sameAs>|owl:sameAs|=) \(\) \.$)], prefixes: { "" => "http://xmlns.com/foaf/0.1/"} }, "empty list as subject": { @@ -474,12 +474,75 @@ end end - def parse(input, options = {}) - graph = RDF::Graph.new - RDF::N3::Reader.new(input, options).each do |statement| - graph << statement + describe "formulae" do + { + "empty subject" => { + input: %({} .), + regexp: [ + %r(\[ \] \.) + ] + }, + "empty object" => { + input: %( {} .), + regexp: [ + %r( \[\] \.) + ] + }, + "as subject with constant content" => { + input: %({ } .), + regexp: [ + %r({\s+ \.\s+} \.)m + ] + }, + "as object with constant content" => { + input: %( { } .), + regexp: [ + %r( {\s+ \.\s+} \.)m + ] + }, + "implies" => { + input: %({ _:x :is :happy } => {_:x :is :happy } .), + regexp: [ + %r({\s+_:x :is :happy \.\s+} => {\s+_:x :is :happy \.\s+} \.)m + ] + }, + "formula simple" => { + input: %(<> :about { :c :d :e }.), + regexp: [ + %r(<> :about {\s+:c :d :e \.\s+} \.) + ] + }, + "nested" => { + input: %( + @prefix doc: . + @prefix ex: . + @prefix contact: . + [] + doc:creator [ contact:email ]; + ex:says { + [] doc:title "Huckleberry Finn"; + doc:creator [ contact:knownAs "Mark Twain"] + }. + ), + regexp: [ + %r(\[\s+ex:says {\s+\[)m, + %r(doc:creator \[ contact:knownAs "Mark Twain"\];), + %r(doc:title "Huckleberry Finn"), + %r(\] \.\s+};)m, + %r(doc:creator \[ contact:email ) + ] + } + }.each do |name, params| + it name do + serialize(params[:input], params[:regexp], params) + end end - graph + end + + def parse(input, options = {}) + repo = RDF::Repository.new + repo << RDF::N3::Reader.new(input, options) + repo end # Serialize ntstr to a string and compare against regexps From 0a07eeb1dac7271b81cbf151ef6191fb1fd31e23 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 19 Jan 2019 17:41:13 -0800 Subject: [PATCH 21/40] Format variables using `@forAll` and `@forSome`. --- README.md | 2 +- lib/rdf/n3/writer.rb | 75 +++++++++++++++++++++++++++++++++----------- spec/reader_spec.rb | 4 +-- spec/writer_spec.rb | 64 +++++++++++++++++++++++++++++++++++-- 4 files changed, 121 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 1fc15bd..c7d5d5c 100755 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ when turned into an RDF Repository results in the following quads Reasoning requires the use of the Notation3 Algebra, rather than an `RDF::Repository`. This implementation considers formulae to be patterns, which may be asserted on statements made in the default graph, possibly loaded from a separate file. The logical relationships are reduced to algebraic operators. So, for example, the following ### Variables -N3 Variables are introduced with @forAll, @forEach, or ?x. Variables reference URIs described in formulae, typically defined in the default vocabulary (e.g., ":x"). Existential variables are replaced with an allocated RDF::Node instance. Universal variables are replaced with a RDF::Query::Variable instance. For example, the following N3 generates the associated statements: +N3 Variables are introduced with @forAll, @forSome, or ?x. Variables reference URIs described in formulae, typically defined in the default vocabulary (e.g., ":x"). Existential variables are replaced with an allocated RDF::Node instance. Universal variables are replaced with a RDF::Query::Variable instance. For example, the following N3 generates the associated statements: @forAll <#h>. @forSome <#g>. <#g> <#loves> <#h> . diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index e939433..7261040 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -159,19 +159,22 @@ def write_epilogue start_document with_graph(nil) do - graph.each {|statement| preprocess_graph_statement(statement)} - - # Remove lists that are referenced and have non-list properties; - # these are legal, but can't be serialized as lists - @lists.reject! do |node, list| - ref_count(node) > 0 && prop_count(node) > 0 || - list.subjects.any? {|elt| !resource_in_single_graph?(elt)} - end order_subjects.each do |subject| unless is_done?(subject) statement(subject) end end + + # Output any formulae not already serialized using owl:sameAs + repo.graph_names.each do |graph_name| + next if graph_done?(graph_name) + + log_debug {"named graph(#{graph_name})"} + p_term(graph_name, :subject) + predicate(RDF::OWL.sameAs) + formula(graph_name, :graph_name) + @output.write(" .\n") + end end super @@ -298,10 +301,22 @@ def format_node(node, options = {}) def start_document @output.write("#{indent}@base <#{base_uri}> .\n") unless base_uri.to_s.empty? - log_debug {"start_document: #{prefixes.inspect}"} + log_debug {"start_document: prefixes #{prefixes.inspect}"} prefixes.keys.sort_by(&:to_s).each do |prefix| @output.write("#{indent}@prefix #{prefix}: <#{prefixes[prefix]}> .\n") end + + unless @universals.empty? + log_debug {"start_document: universals #{@universals.inspect}"} + terms = @universals.map {|v| format_uri(RDF::URI(v.name.to_s))} + @output.write("#{indent}@forAll #{terms.join(', ')} .\n") + end + + unless @existentials.empty? + log_debug {"start_document: universals #{@existentials.inspect}"} + terms = @existentials.map {|v| format_uri(RDF::URI(v.name.to_s))} + @output.write("#{indent}@forSome #{terms.join(', ')} .\n") + end end # Defines rdf:type of subjects to be emitted at the beginning of the graph. Defaults to rdfs:Class @@ -370,6 +385,9 @@ def preprocess @options[:prefixes] = {} # Will define actual used when matched repo.each {|statement| preprocess_statement(statement)} + + @universals = repo.enum_term.to_a.select {|r| r.is_a?(RDF::Query::Variable) && r.distinguished?}.uniq + @existentials = repo.enum_term.to_a.select {|r| r.is_a?(RDF::Query::Variable) && !r.distinguished?}.uniq end # Perform any statement preprocessing required. This is used to perform reference counts and determine required @@ -415,9 +433,11 @@ def indent(modifier = 0) # Reset internal helper instance variables def reset + @universals, @existentials = [], [] @lists = {} @references = {} @serialized = {} + @graphs = {} @subjects = {} end @@ -470,7 +490,12 @@ def collection(node, position) # Default singular resource representation. def p_term(resource, position) #log_debug("p_term") {"#{resource.to_ntriples}, #{position}"} - l = (position == :subject ? "" : " ") + format_term(resource, options) + l = (position == :subject ? "" : " ") + + if resource.is_a?(RDF::Query::Variable) + format_term(RDF::URI(resource.name.to_s)) + else + format_term(resource, options) + end @output.write(l) end @@ -569,7 +594,7 @@ def blankNodePropertyList(resource, position) # Can subject be represented as a formula? def formula?(resource, position) - resource.node? && + (resource.node? || position == :graph_name) && repo.has_graph?(resource) && !is_valid_list?(resource) && (!is_done?(resource) || position == :subject) && @@ -585,13 +610,6 @@ def formula(resource, position) @output.write(position == :subject ? "\n#{indent}{" : ' {') log_depth do with_graph(resource) do - graph.each {|statement| preprocess_graph_statement(statement)} - - # Remove lists that are referenced and have non-list properties; - # these are legal, but can't be serialized as lists - @lists.reject! do |node, list| - ref_count(node) > 0 && prop_count(node) > 0 - end order_subjects.each do |subject| unless is_done?(subject) statement(subject) @@ -649,6 +667,15 @@ def subject_done(subject) @serialized[subject] = true end + def graph_done?(subject) + @graphs.include?(subject) + end + + # Mark a graph as done. + def graph_done(graph_name) + @graphs[graph_name] = true + end + def resource_in_single_graph?(resource) graph_names = @repo.query(subject: resource).map(&:graph_name) graph_names += @repo.query(object: resource).map(&:graph_name) @@ -662,6 +689,18 @@ def with_graph(graph_name) old_serialized, @serialized = @serialized, {} old_subjects, @subjects = @subjects, {} old_graph, @graph = @graph, repo.project_graph(graph_name) + + graph_done(graph_name) + + graph.each {|statement| preprocess_graph_statement(statement)} + + # Remove lists that are referenced and have non-list properties; + # these are legal, but can't be serialized as lists + @lists.reject! do |node, list| + ref_count(node) > 0 && prop_count(node) > 0 || + list.subjects.any? {|elt| !resource_in_single_graph?(elt)} + end + yield ensure @graph, @lists, @references, @serialized, @subjects = old_graph, old_lists, old_references, old_serialized, old_subjects diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 21711b8..9e81890 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -495,7 +495,7 @@ expect(statement.predicate).not_to equal statement.object end - it "substitutes node for URI with @forEach" do + it "substitutes node for URI with @forSome" do n3 = %(@forSome :x . :x :y :z .) g = parse(n3, base_uri: "http://a/b") statement = g.statements.first @@ -504,7 +504,7 @@ expect(statement.object.to_s).to eq "http://a/b#z" end - it "substitutes node for URIs with @forEach" do + it "substitutes node for URIs with @forSome" do n3 = %(@forSome :x, :y, :z . :x :y :z .) g = parse(n3, base_uri: "http://a/b") statement = g.statements.first diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 1cc08cc..2967e64 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -2,6 +2,7 @@ require_relative 'spec_helper' require 'rdf/spec/writer' require 'rdf/vocab' +require 'rdf/trig' describe RDF::N3::Writer do let(:logger) {RDF::Spec.logger} @@ -531,6 +532,32 @@ %r(\] \.\s+};)m, %r(doc:creator \[ contact:email ) ] + }, + "named with URI" => { + input: %q( + . + { .} + ), + regexp: [ + %r( \.), + %r( = {), + %r( \.), + %r(} \.), + ], + input_format: :trig + }, + "named with BNode" => { + input: %q( + . + _:C { .} + ), + regexp: [ + %r( \.), + %r(_:C = {), + %r( \.), + %r(} \.), + ], + input_format: :trig } }.each do |name, params| it name do @@ -539,16 +566,47 @@ end end - def parse(input, options = {}) + describe "variables" do + { + "@forAll": { + input: %(@forAll :o. :s :p :o .), + regexp: [ + %r(@forAll :o \.), + %r(:s :p :o \.), + ] + }, + "@forSome": { + input: %(@forSome :o. :s :p :o .), + regexp: [ + %r(@forSome :o \.), + %r(:s :p :o \.), + ] + }, + "?o": { + input: %(:s :p ?o .), + regexp: [ + %r(@forAll :o \.), + %r(:s :p :o \.), + ] + }, + }.each do |name, params| + it name do + serialize(params[:input], params[:regexp], params) + end + end + end + + def parse(input, format: :n3, **options) repo = RDF::Repository.new - repo << RDF::N3::Reader.new(input, options) + reader = RDF::Reader.for(format) + repo << reader.new(input, options) repo end # Serialize ntstr to a string and compare against regexps def serialize(ntstr, regexps = [], base_uri: nil, **options) prefixes = options[:prefixes] || {} - g = ntstr.is_a?(RDF::Enumerable) ? ntstr : parse(ntstr, base_uri: base_uri, prefixes: prefixes, validate: false, logger: []) + g = ntstr.is_a?(RDF::Enumerable) ? ntstr : parse(ntstr, base_uri: base_uri, prefixes: prefixes, validate: false, logger: [], format: options.fetch(:input_format, :n3)) result = RDF::N3::Writer.buffer(options.merge(logger: logger, base_uri: base_uri, prefixes: prefixes)) do |writer| writer << g end From 71ecab8c8efd8efc98cbdb5d7318a12849d5a221 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 19 Jan 2019 18:28:32 -0800 Subject: [PATCH 22/40] Remove log:Chaff and log:rawType. --- lib/rdf/n3/algebra.rb | 4 ---- lib/rdf/n3/algebra/logChaff.rb | 7 ------- lib/rdf/n3/algebra/logRawType.rb | 9 --------- 3 files changed, 20 deletions(-) delete mode 100644 lib/rdf/n3/algebra/logChaff.rb delete mode 100644 lib/rdf/n3/algebra/logRawType.rb diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index fd35b36..d37a63b 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -14,7 +14,6 @@ module Algebra autoload :ListLast, 'rdf/n3/algebra/listLast' autoload :ListMember, 'rdf/n3/algebra/listMember' - autoload :LogChaff, 'rdf/n3/algebra/logChaff' autoload :LogConclusion, 'rdf/n3/algebra/logConclusion' autoload :LogConjunction, 'rdf/n3/algebra/logConjunction' autoload :LogEqualTo, 'rdf/n3/algebra/logEqualTo' @@ -23,7 +22,6 @@ module Algebra autoload :LogNotEqualTo, 'rdf/n3/algebra/logNotEqualTo' autoload :LogNotIncludes, 'rdf/n3/algebra/logNotIncludes' autoload :LogOutputString, 'rdf/n3/algebra/logOutputString' - autoload :LogRawType, 'rdf/n3/algebra/logRawType' def for(uri) { @@ -32,7 +30,6 @@ def for(uri) RDF::N3::List.last => ListLast, RDF::N3::List.member => ListMember, - RDF::N3::Log.chaff => LogChaff, RDF::N3::Log.conclusion => LogConclusion, RDF::N3::Log.conjunction => LogConjunction, RDF::N3::Log.equalTo => LogEqualTo, @@ -41,7 +38,6 @@ def for(uri) RDF::N3::Log.notEqualTo => LogNotEqualTo, RDF::N3::Log.notIncludes => LogNotIncludes, RDF::N3::Log.outputString => LogOutputString, - RDF::N3::Log.rawType => LogRawType, }[uri] end module_function :for diff --git a/lib/rdf/n3/algebra/logChaff.rb b/lib/rdf/n3/algebra/logChaff.rb deleted file mode 100644 index 66a9176..0000000 --- a/lib/rdf/n3/algebra/logChaff.rb +++ /dev/null @@ -1,7 +0,0 @@ -module RDF::N3::Algebra - ## - # Any statement mentioning anything in this class is considered boring and purged by the cwm --purge option. This is a convenience, and does not have any value when published as a general fact on the web. - class LogChaff < SPARQL::Algebra::Operator::Binary - NAME = :logChaff - end -end diff --git a/lib/rdf/n3/algebra/logRawType.rb b/lib/rdf/n3/algebra/logRawType.rb deleted file mode 100644 index 43b5e12..0000000 --- a/lib/rdf/n3/algebra/logRawType.rb +++ /dev/null @@ -1,9 +0,0 @@ -module RDF::N3::Algebra - ## - # This is a low-level language type, one of log:Formula, log:Literal, log:List, log:Set or log:Other. - # - # Example: log:semanticsOrError returns either a formula or a string, and you can check which using log:rawType. - class LogRawType < SPARQL::Algebra::Operator::Binary - NAME = :logRawType - end -end From 2d3b32b0fcfbc0356e8e085028106dc5ded66c48 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 19 Jan 2019 18:28:48 -0800 Subject: [PATCH 23/40] Add more tests to eventually work on. --- spec/test-files/andy/D-ref.n3 | 0 spec/test-files/andy/D.n3 | 11 +++ spec/test-files/includes/concat-ref.n3 | 0 spec/test-files/includes/concat.n3 | 75 +++++++++++++++++++ .../includes/conclusion-simple-ref.n3 | 0 spec/test-files/includes/conclusion-simple.n3 | 29 +++++++ spec/test-files/includes/conjunction-ref.n3 | 27 +++++++ spec/test-files/includes/conjunction.n3 | 56 ++++++++++++++ spec/test-files/manifest.n3 | 23 +++++- spec/test_file_spec.rb | 12 +-- 10 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 spec/test-files/andy/D-ref.n3 create mode 100644 spec/test-files/andy/D.n3 create mode 100644 spec/test-files/includes/concat-ref.n3 create mode 100644 spec/test-files/includes/concat.n3 create mode 100644 spec/test-files/includes/conclusion-simple-ref.n3 create mode 100644 spec/test-files/includes/conclusion-simple.n3 create mode 100644 spec/test-files/includes/conjunction-ref.n3 create mode 100644 spec/test-files/includes/conjunction.n3 diff --git a/spec/test-files/andy/D-ref.n3 b/spec/test-files/andy/D-ref.n3 new file mode 100644 index 0000000..e69de29 diff --git a/spec/test-files/andy/D.n3 b/spec/test-files/andy/D.n3 new file mode 100644 index 0000000..454fc9d --- /dev/null +++ b/spec/test-files/andy/D.n3 @@ -0,0 +1,11 @@ +@prefix log: . +@prefix d: . + +:doc :is { + d:a d:p d:b . + d:a d:q d:c . +} . + +{ :doc :is ?d . + ?d log:notIncludes { d:a d:p d:xxx } . + } => { :doc a :Success . } . diff --git a/spec/test-files/includes/concat-ref.n3 b/spec/test-files/includes/concat-ref.n3 new file mode 100644 index 0000000..e69de29 diff --git a/spec/test-files/includes/concat.n3 b/spec/test-files/includes/concat.n3 new file mode 100644 index 0000000..eaa7db8 --- /dev/null +++ b/spec/test-files/includes/concat.n3 @@ -0,0 +1,75 @@ +# Test the string:concatenation function +# +# The earlier tests were written with a historical string:concat which is the INVERSE!! +# Please use string:concatenation -- or think of a better name for its inverse. + +#@prefix rdf: . +#@prefix s: . +#@prefix daml: . +#@prefix dpo: . +#@prefix ex: . + +@prefix log: . +@prefix string: . +@prefix : <#>. # Local stuff + +# Usage: cwm t13.n3 -think +# +# Output should conclude all test* a :success and no failures. +# + +#@forAll :x. + +@forAll :x, :y, :z. + +{ "" string:concat () } log:implies {:test13a a :success}. + +{ :x string:concat () } log:implies {:emptyString :is :x}. +{ :emptyString :is "" } log:implies { :test13b a :success }. + +{ :x string:concat ( "foo" ) } log:implies { :fooString :is :x }. +{ :fooString :is "foo" } log:implies { :test13c a :success }. + +{ :x string:concat ("World" [string:concat ("W" "i" "d" "e")] "Web") } log:implies {:www :is :x}. +{ :www :is "WorldWideWeb" } log:implies { :test13d a :success }. + +{ :x string:concat ("World" "Wide" "Web") } log:implies {:www2 :is :x}. +{ :www2 :is "WorldWideWeb" } log:implies { :test13e a :success }. + +{ :x string:concat ("World" :y "Web"). + :y string:concat ( "W" "I" "D""E"). + } log:implies {:www3:is :x}. +{ :www3 :is "WorldWIDEWeb" } log:implies { :test13f a :success }. + + +{ "" log:equalTo [ string:concat () ] } log:implies {:test13g a :success}. + +{ "one" log:equalTo [ string:concat () ] } log:implies {:test13a_bis a :FAILURE}. + +{ "" log:equalTo [ string:concat ( "one" ) ].} log:implies {:test13b_bis a :FAILURE}. + +{ "one" log:equalTo [ string:concat ( "World" "Wide" "Web" ) ]} + log:implies {:test13c_bis a :FAILURE}. + +{ :x is string:concatenation of ( + "World" + [is string:concatenation of ("W" "i" "d" "e")] + "Web") } log:implies {:www5 :is :x}. +{ :www5 :is "WorldWideWeb" } log:implies { :test13h a :success }. + +{ + :test13a a :success. + :test13b a :success. + :test13c a :success. + :test13d a :success. + :test13e a :success. + :test13f a :success. + :test13g a :success. + :test13h a :success. +} log:implies { :TEST13 a :success }. + +log:implies a log:Chaff. # Purge will remove rules + +# ends + + diff --git a/spec/test-files/includes/conclusion-simple-ref.n3 b/spec/test-files/includes/conclusion-simple-ref.n3 new file mode 100644 index 0000000..e69de29 diff --git a/spec/test-files/includes/conclusion-simple.n3 b/spec/test-files/includes/conclusion-simple.n3 new file mode 100644 index 0000000..5555d1b --- /dev/null +++ b/spec/test-files/includes/conclusion-simple.n3 @@ -0,0 +1,29 @@ +# Test the log:conclusion function + +# simple version for debugging + + +@prefix rdf: . +@prefix s: . +@prefix daml: . +@prefix dpo: . +@prefix ex: . +@prefix log: . + + +# Usage: cwm conclusion-simple.n3 -think +# +# + +@prefix : <#>. + + + +{{ }=>{ a }. + . +} a :TestRule. + + +{ ?x a :TestRule; log:conclusion ?y } => { ?y a :TestResult }. + +#ends diff --git a/spec/test-files/includes/conjunction-ref.n3 b/spec/test-files/includes/conjunction-ref.n3 new file mode 100644 index 0000000..0f05989 --- /dev/null +++ b/spec/test-files/includes/conjunction-ref.n3 @@ -0,0 +1,27 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/includes/conjunction.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/includes/conjunction.n3 + @prefix : . + @prefix log: . + + @forAll :F, + :G, + :d, + :x, + :y . + { + + ( {:sky :color :blue . + } + {:sky :color :green . + } ) + log:conjunction :F . + + } log:implies {:F a :result . + } . + +#ENDS diff --git a/spec/test-files/includes/conjunction.n3 b/spec/test-files/includes/conjunction.n3 new file mode 100644 index 0000000..036215a --- /dev/null +++ b/spec/test-files/includes/conjunction.n3 @@ -0,0 +1,56 @@ +# Test the log:includes function + +@prefix rdf: . +@prefix s: . +#@prefix daml: . +#@prefix dpo: . +#@prefix ex: . +@prefix log: . +#@prefix string: . + + +@prefix foo: . # Local stuff +@prefix : . # Local stuff + +@prefix local: . + +# Usage: cwm conclusion.n3 -think +# +# See also t10a.n3 foo.n3 +# +# Output should be (in "result" formula) the same as for the cwm command below. +# + + + +@forAll :d, :x, :y, :F, :G. + +#{ "z" string:greaterThan "a" } log#:implies { :result :is :sanity }. + +{ ( { :sky :color :blue } { :sky :color :green } ) log:conjunction :F } + log:implies { :F a :result} . + + +# "Conjunction junction, what's your're function?" - schoolhouse rock. +# The conjunction of a list of formulae is the logical AND of them, ie +# the set of statements which contains each of the statements in each of the +# conjoined formulae. + + + +# +#{ ( [ is log:semantics of <../daml-ex.n3> ] +# [ is log:semantics of <../invalid-ex.n3> ] +# [ is log:semantics of <../schema-rules.n3> ] ) +# log:conjunction [ log:conclusion :G]} +# log:implies { :result :is :G }. +# +# The above is a much more complicated way of writing the cwm +# command line "cwm daml-ex.n3 invalid-ex.n3 schema-rules.n3 --think". +# + +# (Maybe conjunction should automatically reference a resource with no "#" +# so that the log:semantics becomes unnecesary. Maybe N3 should just assume +# coersion from resource to formula where that is appropriate? :o/ ) + +#ends diff --git a/spec/test-files/manifest.n3 b/spec/test-files/manifest.n3 index a17b2e2..405efe7 100644 --- a/spec/test-files/manifest.n3 +++ b/spec/test-files/manifest.n3 @@ -21,7 +21,7 @@ # From swap/test/i18n # From swap/test/includes - :listin :bnode + :listin :bnode :concat :conclusion-simple :conjunction # From swap/test/math @@ -66,6 +66,27 @@ mf:result ; test:options [test:think true; test:data true;] . +:concat a test:CwmTest; + mf:name "list concatenation" ; + rdfs:comment "Builtins for list concatenation." ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +:conclusion-simple a test:CwmTest; + mf:name "includes-conclusion-simple.n3" ; + rdfs:comment "Builtins for log:conclusion." ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +:conjunction a test:CwmTest; + mf:name "includes-conjunction.n3" ; + rdfs:comment "Builtins for log:conjunction." ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + # Tests from swap/test/norm :norm10 a test:CwmTest; mf:name "norm-av1.n3"; diff --git a/spec/test_file_spec.rb b/spec/test_file_spec.rb index 9b60b44..1203e39 100644 --- a/spec/test_file_spec.rb +++ b/spec/test_file_spec.rb @@ -106,11 +106,9 @@ def inspect t.logger.info "source:\n#{t.input}" case t.id.split('#').last - when *%w{listin bnode} - pending "support for li:member" - when *%w{t2006} - pending "support for li:last" - when *%w{t1018b2 t103 t104 t105} + when *%w{listin bnode concat t2006} + pending "support for lists" + when *%w{t1018b2 t103 t104 t105 concat} pending "support for string" when *%w{t2005 t555} pending "understanding output filtering" @@ -118,6 +116,10 @@ def inspect pending "support for math" when *%w{t01} pending "support for log:supports" + when *%w{conclusion-simple conclusion} + pending "support for log:conclusion" + when *%w{conjunction} + pending "support for log:conjunction" when *%w{t553 t554} pending "support for inference over quoted graphs" end From ff8b6e6b5271d576598bb4652e88327a877e0f9c Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 21 Jan 2019 16:17:46 -0800 Subject: [PATCH 24/40] Move formula stuff out of reader and into reasoner. Implement `data` and `conclusions` from reasoner, which is an Enumerable and Mutable. Minor writer improvements. Reasoner tests are order dependent, and there are false positives. --- README.md | 35 ++- lib/rdf/n3/algebra/formula.rb | 2 +- lib/rdf/n3/algebra/logImplies.rb | 2 +- lib/rdf/n3/extensions.rb | 7 +- lib/rdf/n3/reader.rb | 53 ---- lib/rdf/n3/reasoner.rb | 237 ++++++++++++++++-- lib/rdf/n3/writer.rb | 66 +++-- script/parse | 31 ++- spec/test-files/includes/conjunction-ref.n3 | 2 +- .../list/builtin_generated_match-ref.n3 | 2 +- spec/test-files/list/list-bug1-ref.n3 | 2 +- spec/test-files/list/list-bug2-ref.n3 | 2 +- spec/test-files/list/r1-ref.n3 | 2 +- spec/test-files/list/unify2-ref.n3 | 2 +- spec/test-files/list/unify3-ref.n3 | 2 +- spec/test-files/list/unify4-ref.n3 | 2 +- spec/test-files/list/unify5-ref.n3 | 2 +- spec/test-files/manifest.n3 | 2 +- spec/test-files/norm/av-ref.n3 | 2 +- spec/test-files/reason/double-ref.n3 | 2 +- spec/test-files/reason/socrates-ref.n3 | 2 +- spec/test-files/reason/t1-ref.n3 | 2 +- spec/test-files/reason/t2-ref.n3 | 2 +- spec/test-files/reason/t3-ref.n3 | 2 +- spec/test-files/reason/t4-ref.n3 | 2 +- spec/test-files/reason/t5-ref.n3 | 2 +- spec/test-files/reason/t6-ref.n3 | 2 +- spec/test-files/reason/t8-ref.n3 | 2 +- spec/test-files/reason/t9-ref.n3 | 2 +- spec/test-files/string/endsWith-out.n3 | 2 +- spec/test-files/string/roughly-out.n3 | 2 +- spec/test-files/string/uriEncode-out.n3 | 2 +- spec/test-files/supports/simple-ref.n3 | 2 +- spec/test-files/unify/reflexive-ref.n3 | 2 +- spec/test-files/unify/unify1-ref.n3 | 2 +- spec/test-files/unify/unify2-ref.n3 | 2 +- spec/test_file_spec.rb | 21 +- 37 files changed, 362 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index c7d5d5c..b1ff475 100755 --- a/README.md +++ b/README.md @@ -39,7 +39,34 @@ Write a graph to a file: writer << graph end -### Formulae +### Reasoning +Partial N3 reasoning is supported. Instantiate a reasoner from a dataset: + + RDF::N3::Reasoner.new do |reasoner| + RDF::N3::Reader.open("etc/foaf.n3") {|reader| reasoner << reader} + + reader.each_statement do |statement| + puts statement.inspect + end + end + +Reasoning is performed by turning a repository containing formula and predicate operators into an executable set of operators (similar to the executable SPARQL Algebra). Reasoning adds statements to the base dataset, marked with `:inferred` (e.g. `statement.inferred?`). Predicate operators are defined from the following vocabularies: + +* RDF List vocabulary + * list:append (not implemented yet - See {RDF::N3::Algebra::ListAppend}) + * list:in (not implemented yet - See {RDF::N3::Algebra::ListIn}) + * list:last (not implemented yet - See {RDF::N3::Algebra::ListLast}) + * list:member (not implemented yet - See {RDF::N3::Algebra::ListMember}) +* RDF Log vocabulary + * log:conclusion (not implemented yet - See {RDF::N3::Algebra::LogConclusion}) + * log:conjunction (not implemented yet - See {RDF::N3::Algebra::LogConjunction}) + * log:equalTo (See {not implemented yet - RDF::N3::Algebra::LogEqualTo}) + * log:implies (See {RDF::N3::Algebra::LogImplies}) + * log:includes (not implemented yet - See {RDF::N3::Algebra::LogIncludes}) + * log:notEqualTo (not implemented yet - See {RDF::N3::Algebra::LogNotEqualTo}) + * log:notIncludes (not implemented yet - See {RDF::N3::Algebra::LogNotIncludes}) + * log:outputString (not implemented yet - See {RDF::N3::Algebra::LogOutputString}) + N3 Formulae are introduced with the { statement-list } syntax. A given formula is assigned an RDF::Node instance, which is also used as the graph_name for RDF::Statement instances provided to RDF::N3::Reader#each_statement. For example, the following N3 generates the associated statements: @prefix x: . @@ -55,7 +82,7 @@ when turned into an RDF Repository results in the following quads _:ora _:moby _:form . _:ora "Ora" _:form . -Reasoning requires the use of the Notation3 Algebra, rather than an `RDF::Repository`. This implementation considers formulae to be patterns, which may be asserted on statements made in the default graph, possibly loaded from a separate file. The logical relationships are reduced to algebraic operators. So, for example, the following +Reasoning requires the use of the Notation3 Algebra, rather than an `RDF::Repository`. This implementation considers formulae to be patterns, which may be asserted on statements made in the default graph, possibly loaded from a separate file. The logical relationships are reduced to algebraic operators. ### Variables N3 Variables are introduced with @forAll, @forSome, or ?x. Variables reference URIs described in formulae, typically defined in the default vocabulary (e.g., ":x"). Existential variables are replaced with an allocated RDF::Node instance. Universal variables are replaced with a RDF::Query::Variable instance. For example, the following N3 generates the associated statements: @@ -95,7 +122,11 @@ Full documentation available on [RubyDoc.info](http://rubydoc.info/github/ruby-r * {RDF::N3} * {RDF::N3::Format} * {RDF::N3::Reader} +* {RDF::N3::Reasoner} * {RDF::N3::Writer} +* {RDF::N3::Algebra} +* {RDF::N3::Algebra::Formula} +* {RDF::N3::Algebra::LogImplies} ### Additional vocabularies * {RDF::LOG} diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index c1f265d..103668c 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -90,7 +90,7 @@ def each(&block) statement = RDF::Statement.from(terms) # Sanity checking on statement - if statement.subject.nil? || statement.predicate.nil? || statement.object.nil? || + if statement.variable? || statement.predicate.literal? || statement.subject.is_a?(SPARQL::Algebra::Operator) || statement.object.is_a?(SPARQL::Algebra::Operator) diff --git a/lib/rdf/n3/algebra/logImplies.rb b/lib/rdf/n3/algebra/logImplies.rb index e158027..3d9c72b 100644 --- a/lib/rdf/n3/algebra/logImplies.rb +++ b/lib/rdf/n3/algebra/logImplies.rb @@ -53,7 +53,7 @@ def each(&block) if !object.solutions.empty? # Yield statements into the default graph object.each do |statement| - block.call(RDF::Statement.from(statement.to_triple)) + block.call(RDF::Statement.from(statement.to_triple, inferred: true)) end end end diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index 185e90e..a9623e9 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -14,10 +14,15 @@ module Enumerable end class Statement + # Override variable? + def variable? + to_a.any? {|term| !term.is_a?(RDF::Term) || term.variable?} || graph_name && graph_name.variable? + end + # Transform Statement into an SXP # @return [Array] def to_sxp_bin - [(variable? ? :pattern : :triple), subject, predicate, object] + [(variable? ? :pattern : :triple), subject, predicate, object, graph_name].compact end ## diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 56e4a59..58de08c 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -135,59 +135,6 @@ def each_triple enum_for(:each_triple) end - ## - # Returns the top-level formula for this file - # - # @return [RDF::N3::Algebra::Formula] - def formula - # SPARQL used for SSE and algebra functionality - require 'sparql' unless defined?(:SPARQL) - - @formula ||= begin - formulae = {} - - # Add patterns to appropiate formula based on graph_name, - # and replace subject and object bnodes which identify - # named graphs with those formula - each_statement do |pattern| - # A graph name indicates a formula. If not already allocated, create a new formula and use that for inserting statements or other operators - form = formulae[pattern.graph_name] ||= begin - Algebra::Formula.new(graph_name: pattern.graph_name, **@options) - end - - # Formulae may be the subject or object of a known operator - if klass = Algebra.for(pattern.predicate) - fs = formulae.fetch(pattern.subject, pattern.subject) - fo = formulae.fetch(pattern.object, pattern.object) - form.operands << klass.new(fs, fo, **@options) - else - # Add formulae as direct operators - if formulae.has_key?(pattern.subject) - form.operands << formulae[pattern.subject] - end - if formulae.has_key?(pattern.object) - form.operands << formulae[pattern.object] - end - pattern.graph_name = nil - form.operands << pattern - end - end - end - - # Formula is that without a graph name - formulae[nil] - end - - ## - # Returns the SPARQL S-Expression (SSE) representation of the parsed dataset. - # Formulae are represented as subjects and objects in the containing graph, along with their universals and existentials - # - # @return [Array] `self` - # @see http://openjena.org/wiki/SSE - def to_sxp_bin - formula.to_sxp_bin - end - protected # Start of production def onStart(prod) diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 097026d..8debc56 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -3,11 +3,14 @@ module RDF::N3 ## # A Notation-3/Turtle reasoner in Ruby # - # Takes a parsed Notation-3 input and performs reasoning to implement CWM-like interface + # Takes either a parsed formula or an `RDF::Queryable` and updates it by reasoning over formula defined within the queryable. # # @author [Gregg Kellogg](http://greggkellogg.net/) class Reasoner + include RDF::Enumerable + include RDF::Mutable include RDF::Util::Logger + # The top-level parsed formula # @return [RDF::N3::Algebra::Formula] attr_reader :formula @@ -21,7 +24,7 @@ class Reasoner # @return [RDF::N3::Reasoner] def self.open(file) RDF::N3::Reader.open(file, **options) do |reader| - RDF::N3::Reasoner.new(reader.to_sxp_bin, **options, &block) + RDF::N3::Reasoner.new(reader, **options, &block) end end @@ -30,7 +33,24 @@ def self.open(file) # # It returns the evaluated formula, or yields triples. # - # @param [String, IO, StringIO, RDF::N3::Algebra::Formula, #to_s] input + # @example Initializing from a reader + # reader = RDF::N3::Reader.new(":a :b :c .") + # reasoner = RDF::N3::Reasoner.new(reader) + # reasoner.each_triple {} + # + # @example Initializing as a mutable + # reasoner = RDF::N3::Reasoner.new do |r| + # r << RDF::N3::Reader.new(":a :b :c .") + # end + # reasoner.each_triple {} + # + # @example Initializing with multiple inputs + # reasoner = RDF::N3::Reasoner.new + # RDF::NTriples::Reader.open("example.nt") {|r| reasoner << r} + # RDF::N3::Reader.open("rules.n3") {|r| reasoner << r} + # reasoner.each_triple {} + # + # @param [RDF::Enumerable] input (nil) # @param [Hash{Symbol => Object}] options # @option options [#to_s] :base_uri (nil) # the base URI to use when resolving relative URIs (for acessing intermediate parser productions) @@ -40,12 +60,13 @@ def self.open(file) # @return [RDF::N3::Reasoner] def initialize(input, **options, &block) @options = options - @formula = case input - when RDF::N3::Algebra::Formula then input - else RDF::N3::Reader.new(input, **options).formula + @mutable = case input + when RDF::Mutable then input + when RDF::Enumerable then RDF::Repository.new {|r| r << input} + else RDF::Repository.new end - log_debug("reasoner: expression", options) {@formula.to_sxp} + log_debug("reasoner: expression", options) {SXP::Generator.string(formula.to_sxp_bin)} if block_given? case block.arity @@ -56,43 +77,213 @@ def initialize(input, **options, &block) end ## - # Reasons over the formula, yielding each statement + # Returns a copy of this reasoner + def dup + repo = RDF::Repository.new {|r| r << @mutable} + self.class.new(repo) do |reasoner| + reasoner.instance_variable_set(:@options, @options.dup) + reasoner.instance_variable_set(:@formula, @formula.dup) if @formula + end + end + + ## + # Inserts an RDF statement the datastore, resets `formula`. + # + # @param [RDF::Statement] statement + # @return [void] + def insert_statement(statement) + @mutable.insert_statement(statement) + end + + ## + # Updates the datastore by reasoning over the formula, optionally yielding each conclusion # # @param [Hash{Symbol => Object}] options # @option options [Boolean] :apply # @option options [Boolean] :think # @yield [statement] # @yieldparam [RDF::Statement] statement - # @return [RDF::Enumerable] + # @return [RDF::N3::Reasoner] `self` def execute(**options, &block) - results = RDF::Graph.new - - # Evaluate once to create initial triples for reasoning - log_info("reasoner: seed") - results << formula + @options[:logger] = options[:logger] if options.has_key?(:logger) # If thinking, continuously execute until results stop growing if options[:think] count = 0 log_info("reasoner: think start") { "count: #{count}"} - while results.count > count - count = results.count - log_depth {formula.execute(results, **options)} - results << formula + while @mutable.count > count + count = @mutable.count + log_depth {formula.execute(@mutable, **options)} + @mutable << formula end log_info("reasoner: think end") { "count: #{count}"} else # Run one iteration log_info("reasoner: apply start") { "count: #{count}"} - log_depth {formula.execute(results, **options)} - results << formula + log_depth {formula.execute(@mutable, **options)} + @mutable << formula log_info("reasoner: apply end") { "count: #{count}"} end - log_debug("reasoner: results") {results.to_sxp} + log_debug("reasoner: datastore") {@mutable.to_sxp} + + conclusions(&block) if block_given? + self + end + alias_method :reason!, :execute + + ## + # Reason with results in a duplicate datastore + # + # @see {execute} + def reason(**options, &block) + self.dup.reason!(**options, &block) + end + + ## + # Yields each statement in the datastore + # + # @yieldparam [RDF::Statement] statement + # @yieldreturn [void] ignored + # @return [void] + def each(&block) + @mutable.each(&block) + end - results.each(&block) if block_given? - results + ## + # Yields data, excluding formulae or variables and statements referencing formulae or variables + # + # @overload data + # @yield [statement] + # each statement + # @yieldparam [RDF::Statement] statement + # @yieldreturn [void] ignored + # @return [void] + # + # @overload data + # @return [Enumerator] + # @return [RDF::Enumerator] + # @yield [statement] + # @yieldparam [RDF::Statement] statement + def data(&block) + if block_given? + project_graph(nil) do |statement| + block.call(statement) unless statement.variable? || + has_graph?(statement.subject) || + has_graph?(statement.object) + end + end + enum_data + end + alias_method :each_datum, :data + + ## + # Returns an enumerator for {#conclusions}. + # FIXME: enum_for doesn't seem to be working properly + # in JRuby 1.7, so specs are marked pending + # + # @return [Enumerator] + # @see #each_statement + def enum_data + # Ensure that statements are queryable, countable and enumerable + this = self + RDF::Queryable::Enumerator.new do |yielder| + this.send(:each_datum) {|y| yielder << y} + end + end + + ## + # Yields conclusions, excluding formulae and those statements in the original dataset, or returns an enumerator over the conclusions + # + # @overload conclusions + # @yield [statement] + # each statement + # @yieldparam [RDF::Statement] statement + # @yieldreturn [void] ignored + # @return [void] + # + # @overload conclusions + # @return [Enumerator] + # @return [RDF::Enumerator] + # @yield [statement] + # @yieldparam [RDF::Statement] statement + def conclusions(&block) + if block_given? + # Invoke {#each} in the containing class: + each_statement {|s| block.call(s) if s.inferred?} + end + enum_conclusions + end + alias_method :each_conclusion, :conclusions + + ## + # Returns an enumerator for {#conclusions}. + # FIXME: enum_for doesn't seem to be working properly + # in JRuby 1.7, so specs are marked pending + # + # @return [Enumerator] + # @see #each_statement + def enum_conclusions + # Ensure that statements are queryable, countable and enumerable + this = self + RDF::Queryable::Enumerator.new do |yielder| + this.send(:each_conclusion) {|y| yielder << y} + end + end + + ## + # Returns the top-level formula for this file + # + # @return [RDF::N3::Algebra::Formula] + def formula + # SPARQL used for SSE and algebra functionality + require 'sparql' unless defined?(:SPARQL) + + @formula ||= begin + formulae = (@mutable.graph_names.unshift(nil)).inject({}) do |memo, graph_name| + memo.merge(graph_name => Algebra::Formula.new(graph_name: graph_name, **@options)) + end + + # Add patterns to appropiate formula based on graph_name, + # and replace subject and object bnodes which identify + # named graphs with those formula + @mutable.each_statement do |statement| + pattern = statement.variable? ? RDF::Query::Pattern.from(statement) : statement + + # A graph name indicates a formula. If not already allocated, create a new formula and use that for inserting statements or other operators + form = formulae[pattern.graph_name] + + # Formulae may be the subject or object of a known operator + if klass = Algebra.for(pattern.predicate) + fs = formulae.fetch(pattern.subject, pattern.subject) + fo = formulae.fetch(pattern.object, pattern.object) + form.operands << klass.new(fs, fo, **@options) + else + # Add formulae as direct operators + if formulae.has_key?(pattern.subject) + form.operands << formulae[pattern.subject] + end + if formulae.has_key?(pattern.object) + form.operands << formulae[pattern.object] + end + pattern.graph_name = nil + form.operands << pattern + end + end + + # Formula is that without a graph name + formulae[nil] + end + end + + ## + # Returns the SPARQL S-Expression (SSE) representation of the parsed formula. + # Formulae are represented as subjects and objects in the containing graph, along with their universals and existentials + # + # @return [Array] `self` + # @see http://openjena.org/wiki/SSE + def to_sxp_bin + formula.to_sxp_bin end end end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 7261040..5f3b535 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -159,9 +159,11 @@ def write_epilogue start_document with_graph(nil) do + count = 0 order_subjects.each do |subject| unless is_done?(subject) - statement(subject) + statement(subject, count) + count += 1 end end @@ -193,7 +195,7 @@ def get_pname(resource) return nil end - log_debug {"get_pname(#{resource}), std?}"} + #log_debug {"get_pname(#{resource}), std?}"} pname = case when @uri_to_pname.has_key?(uri) return @uri_to_pname[uri] @@ -202,14 +204,14 @@ def get_pname(resource) prefix = @uri_to_prefix[u] unless u.to_s.empty? prefix(prefix, u) unless u.to_s.empty? - log_debug("get_pname") {"add prefix #{prefix.inspect} => #{u}"} + #log_debug("get_pname") {"add prefix #{prefix.inspect} => #{u}"} uri.sub(u.to_s, "#{prefix}:") end when @options[:standard_prefixes] && vocab = RDF::Vocabulary.each.to_a.detect {|v| uri.index(v.to_uri.to_s) == 0} prefix = vocab.__name__.to_s.split('::').last.downcase @uri_to_prefix[vocab.to_uri.to_s] = prefix prefix(prefix, vocab.to_uri) # Define for output - log_debug {"get_pname: add standard prefix #{prefix.inspect} => #{vocab.to_uri}"} + #log_debug {"get_pname: add standard prefix #{prefix.inspect} => #{vocab.to_uri}"} uri.sub(vocab.to_uri.to_s, "#{prefix}:") else nil @@ -282,7 +284,7 @@ def format_literal(literal, options = {}) # @return [String] def format_uri(uri, options = {}) md = uri.relativize(base_uri) - log_debug("relativize") {"#{uri.to_ntriples} => #{md.inspect}"} if md != uri.to_s + log_debug("relativize") {"#{uri.to_sxp} => #{md.inspect}"} if md != uri.to_s md != uri.to_s ? "<#{md}>" : (get_pname(uri) || "<#{uri}>") end @@ -344,12 +346,12 @@ def order_subjects # Add distinguished classes top_classes.each do |class_uri| - repo.query(predicate: RDF.type, object: class_uri, graph_name: false). + graph.query(predicate: RDF.type, object: class_uri). map {|st| st.subject}. sort. uniq. each do |subject| - log_debug("order_subjects") {subject.to_ntriples} + log_debug("order_subjects") {subject.to_sxp} subjects << subject seen[subject] = true end @@ -361,8 +363,8 @@ def order_subjects seen[st.object] = true if @lists.has_key?(st.object) end - # List elements should not be targets for top-level serialization - list_elements = @lists.values.map(&:to_a).flatten.compact + # List elements which are bnodes should not be targets for top-level serialization + list_elements = @lists.values.map(&:to_a).flatten.select(&:node?).compact # Sort subjects by resources over bnodes, ref_counts and the subject URI itself recursable = (@subjects.keys - list_elements). @@ -480,7 +482,7 @@ def collection(node, position) return false if !is_valid_list?(node) return false if position == :subject && ref_count(node) > 0 return false if position == :object && prop_count(node) > 0 - #log_debug("collection") {"#{node.to_ntriples}, #{position}"} + #log_debug("collection") {"#{node.to_sxp}, #{position}"} @output.write(position == :subject ? "(" : " (") log_depth {do_list(node, position)} @@ -489,10 +491,12 @@ def collection(node, position) # Default singular resource representation. def p_term(resource, position) - #log_debug("p_term") {"#{resource.to_ntriples}, #{position}"} + #log_debug("p_term") {"#{resource.to_sxp}, #{position}"} l = (position == :subject ? "" : " ") + if resource.is_a?(RDF::Query::Variable) format_term(RDF::URI(resource.name.to_s)) + elsif resource == RDF.nil + "()" else format_term(resource, options) end @@ -503,7 +507,7 @@ def p_term(resource, position) # Use either collection, blankNodePropertyList or singular resource notation. def path(resource, position) log_debug("path") do - "#{resource.to_ntriples}, " + + "#{resource.to_sxp}, " + "pos: #{position}, " + "{}?: #{formula?(resource, position)}, " + "()?: #{is_valid_list?(resource)}, " + @@ -518,7 +522,7 @@ def path(resource, position) end def predicate(resource) - log_debug("predicate") {resource.to_ntriples} + log_debug("predicate") {resource.to_sxp} case resource when RDF.type @output.write(" a") @@ -550,8 +554,15 @@ def objectList(objects) # @return [Integer] the number of properties serialized def predicateObjectList(subject, from_bpl = false) properties = {} - @graph.query(subject: subject) do |st| - (properties[st.predicate.to_s] ||= []) << st.object + if subject.variable? + # Can't query on variable + @graph.enum_statement.select {|s| s.subject.equal?(subject)}.each do |st| + (properties[st.predicate.to_s] ||= []) << st.object + end + else + @graph.query(subject: subject) do |st| + (properties[st.predicate.to_s] ||= []) << st.object + end end prop_list = sort_properties(properties) @@ -584,7 +595,7 @@ def blankNodePropertyList?(resource, position) def blankNodePropertyList(resource, position) return false unless blankNodePropertyList?(resource, position) - log_debug("blankNodePropertyList") {resource.to_ntriples} + log_debug("blankNodePropertyList") {resource.to_sxp} subject_done(resource) @output.write(position == :subject ? "\n#{indent}[" : ' [') num_props = log_depth {predicateObjectList(resource, true)} @@ -605,14 +616,16 @@ def formula?(resource, position) def formula(resource, position) return false unless formula?(resource, position) - log_debug("formula") {resource.to_ntriples} + log_debug("formula") {resource.to_sxp} subject_done(resource) @output.write(position == :subject ? "\n#{indent}{" : ' {') log_depth do with_graph(resource) do + count = 0 order_subjects.each do |subject| unless is_done?(subject) - statement(subject) + statement(subject, count) + count += 1 end end end @@ -630,11 +643,11 @@ def triples(subject) true end - def statement(subject) - log_debug("statement") {"#{subject.to_ntriples}, bnodePL?: #{blankNodePropertyList?(subject, :subject)}"} + def statement(subject, count) + log_debug("statement") {"#{subject.to_sxp}, bnodePL?: #{blankNodePropertyList?(subject, :subject)}"} subject_done(subject) blankNodePropertyList(subject, :subject) || triples(subject) - @output.puts + @output.puts if count > 0 || graph.graph_name end # Return the number of statements having this resource as a subject other than for list properties @@ -677,8 +690,15 @@ def graph_done(graph_name) end def resource_in_single_graph?(resource) - graph_names = @repo.query(subject: resource).map(&:graph_name) - graph_names += @repo.query(object: resource).map(&:graph_name) + if resource.variable? + graph_names = @repo. + enum_statement. + select {|st| s.subject.equal?(resource) || st.object.equal?(resource)}. + map(&:graph_name) + else + graph_names = @repo.query(subject: resource).map(&:graph_name) + graph_names += @repo.query(object: resource).map(&:graph_name) + end graph_names.uniq.length <= 1 end diff --git a/script/parse b/script/parse index 2dddd62..053ad09 100755 --- a/script/parse +++ b/script/parse @@ -20,11 +20,19 @@ def run(input, options) Profiler__::start_profile if options[:profile] if options[:think] # Parse into a new reasoner and evaluate - reader_class.new(input, options[:parser_options]) do |reader| - reasoner = RDF::N3::Reasoner.new(input, options[:parser_options]) - repo = reasoner.execute(options) + reader_class.new(input, options[:parser_options].merge(logger: nil)) do |reader| + reasoner = RDF::N3::Reasoner.new(reader, options[:parser_options]) + reasoner.reason!(options) + repo = RDF::Repository.new + if options[:conclusions] + repo << reasoner.conclusions + elsif options[:data] + repo << reasoner.data + else + repo << reasoner + end num = repo.count - options[:output].puts repo.dump(options[:output_format], base_uri: options[:base_uri], prefixes: reader.prefixes, standard_prefixes: true) + options[:output].puts repo.dump(options[:output_format], prefixes: reader.prefixes, standard_prefixes: true, logger: options[:logger]) end elsif options[:output_format] == :ntriples || options[:quiet] reader_class.new(input, options[:parser_options]).each do |statement| @@ -39,7 +47,8 @@ def run(input, options) end elsif options[:output_format] == :sxp reader_class.new(input, options[:parser_options]) do |reader| - SXP::Generator.print(reader.to_sxp_bin) + reasoner = RDF::N3::Reasoner.new(reader) + SXP::Generator.print(reasoner.to_sxp_bin) end elsif options[:output_format] == :inspect reader_class.new(input, options[:parser_options]).each do |statement| @@ -47,10 +56,10 @@ def run(input, options) options[:output].puts statement.inspect end else - r = reader_class.new(input, options[:parser_options]) - repo = RDF::Repository.new << r + reader = reader_class.new(input, options[:parser_options]) + repo = RDF::Repository.new << reader num = repo.count - options[:output].puts repo.dump(options[:output_format], base_uri: options[:base_uri], prefixes: r.prefixes, standard_prefixes: true, logger: options[:logger]) + options[:output].puts repo.dump(options[:output_format], prefixes: reader.prefixes, standard_prefixes: true, logger: options[:logger]) end if options[:profile] Profiler__::stop_profile @@ -80,13 +89,14 @@ options = { parser_options: parser_options, logger: logger, output: STDOUT, - output_format: :ntriples, + output_format: :n3, input_format: :n3, } input = nil OPT_ARGS = [ ["--apply", GetoptLong::REQUIRED_ARGUMENT, "Apply rules from specified file"], + ["--conclusions", GetoptLong::NO_ARGUMENT, "Remove all except conclusions"], ["--canonicalize", GetoptLong::NO_ARGUMENT, "Canonize all terms"], ["--data", GetoptLong::NO_ARGUMENT, "Remove all except plain RDF triples (formulae, forAll, etc)v"], ["--debug", GetoptLong::NO_ARGUMENT, "Debugging output"], @@ -131,9 +141,10 @@ opts = GetoptLong.new(*OPT_ARGS.map {|o| o[0..-2]}) opts.each do |opt, arg| case opt when '--apply' then # Read rules + when '--conclusions' then options[:conclusions] = true + when '--canonicalize' then parser_options[:canonicalize] = true when "--data" then options[:data] = true when '--debug' then logger.level = Logger::DEBUG - when '--canonicalize' then parser_options[:canonicalize] = true when '--errors' then options[:errors] = true when '--execute' then input = arg when '--format' then options[:output_format] = arg.to_sym diff --git a/spec/test-files/includes/conjunction-ref.n3 b/spec/test-files/includes/conjunction-ref.n3 index 0f05989..14fbc97 100644 --- a/spec/test-files/includes/conjunction-ref.n3 +++ b/spec/test-files/includes/conjunction-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/includes/conjunction.n3 + # using base test-files/includes/conjunction.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/list/builtin_generated_match-ref.n3 b/spec/test-files/list/builtin_generated_match-ref.n3 index e601907..10d6fca 100644 --- a/spec/test-files/list/builtin_generated_match-ref.n3 +++ b/spec/test-files/list/builtin_generated_match-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/builtin_generated_match.n3 + # using base test-files/builtin_generated_match.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/list/list-bug1-ref.n3 b/spec/test-files/list/list-bug1-ref.n3 index ca924a2..8f99e96 100644 --- a/spec/test-files/list/list-bug1-ref.n3 +++ b/spec/test-files/list/list-bug1-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/list-bug1.n3 + # using base test-files/list-bug1.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/list/list-bug2-ref.n3 b/spec/test-files/list/list-bug2-ref.n3 index 4d0fc6b..c121833 100644 --- a/spec/test-files/list/list-bug2-ref.n3 +++ b/spec/test-files/list/list-bug2-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/list-bug2.n3 + # using base test-files/list-bug2.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/list/r1-ref.n3 b/spec/test-files/list/r1-ref.n3 index 69ffbfb..7b5cfa8 100644 --- a/spec/test-files/list/r1-ref.n3 +++ b/spec/test-files/list/r1-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/r1.n3 + # using base test-files/r1.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/list/unify2-ref.n3 b/spec/test-files/list/unify2-ref.n3 index d7a801a..4fbee5a 100644 --- a/spec/test-files/list/unify2-ref.n3 +++ b/spec/test-files/list/unify2-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify2.n3 + # using base test-files/unify2.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/list/unify3-ref.n3 b/spec/test-files/list/unify3-ref.n3 index b17ebf5..a6f1032 100644 --- a/spec/test-files/list/unify3-ref.n3 +++ b/spec/test-files/list/unify3-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify3.n3 + # using base test-files/unify3.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/list/unify4-ref.n3 b/spec/test-files/list/unify4-ref.n3 index a7847d3..729baad 100644 --- a/spec/test-files/list/unify4-ref.n3 +++ b/spec/test-files/list/unify4-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify4.n3 + # using base test-files/unify4.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/list/unify5-ref.n3 b/spec/test-files/list/unify5-ref.n3 index a662e62..4f7874d 100644 --- a/spec/test-files/list/unify5-ref.n3 +++ b/spec/test-files/list/unify5-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify5.n3 + # using base test-files/unify5.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/manifest.n3 b/spec/test-files/manifest.n3 index 405efe7..6933b80 100644 --- a/spec/test-files/manifest.n3 +++ b/spec/test-files/manifest.n3 @@ -205,7 +205,7 @@ test:options [test:think true; test:data true] . :t06proof a test:CwmTest; - mf:name "reason-t5.n3" ; + mf:name "reason-t6.n3" ; rdfs:comment "Proof for a little inference" ; mf:action ; mf:result ; diff --git a/spec/test-files/norm/av-ref.n3 b/spec/test-files/norm/av-ref.n3 index 1521a92..5ff1a20 100644 --- a/spec/test-files/norm/av-ref.n3 +++ b/spec/test-files/norm/av-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/norm/av.n3 + # using base test-files/norm/av.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/reason/double-ref.n3 b/spec/test-files/reason/double-ref.n3 index 82fabde..934c8cf 100644 --- a/spec/test-files/reason/double-ref.n3 +++ b/spec/test-files/reason/double-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/double.n3 + # using base test-files/reason/double.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/reason/socrates-ref.n3 b/spec/test-files/reason/socrates-ref.n3 index db0bc01..3181401 100644 --- a/spec/test-files/reason/socrates-ref.n3 +++ b/spec/test-files/reason/socrates-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/socrates.n3 + # using base test-files/reason/socrates.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/reason/t1-ref.n3 b/spec/test-files/reason/t1-ref.n3 index 4a262d7..fddcbb4 100644 --- a/spec/test-files/reason/t1-ref.n3 +++ b/spec/test-files/reason/t1-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t1.n3 + # using base test-files/reason/t1.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/reason/t2-ref.n3 b/spec/test-files/reason/t2-ref.n3 index 303b9da..0a78798 100644 --- a/spec/test-files/reason/t2-ref.n3 +++ b/spec/test-files/reason/t2-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t2.n3 + # using base test-files/reason/t2.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/reason/t3-ref.n3 b/spec/test-files/reason/t3-ref.n3 index ca27386..5cbaadb 100644 --- a/spec/test-files/reason/t3-ref.n3 +++ b/spec/test-files/reason/t3-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t3.n3 + # using base test-files/reason/t3.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/reason/t4-ref.n3 b/spec/test-files/reason/t4-ref.n3 index 59b0709..0ae8828 100644 --- a/spec/test-files/reason/t4-ref.n3 +++ b/spec/test-files/reason/t4-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t4.n3 + # using base test-files/reason/t4.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/reason/t5-ref.n3 b/spec/test-files/reason/t5-ref.n3 index 25765a8..e2e3ac8 100644 --- a/spec/test-files/reason/t5-ref.n3 +++ b/spec/test-files/reason/t5-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t5.n3 + # using base test-files/reason/t5.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/reason/t6-ref.n3 b/spec/test-files/reason/t6-ref.n3 index c82ceee..2432ecc 100644 --- a/spec/test-files/reason/t6-ref.n3 +++ b/spec/test-files/reason/t6-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t6.n3 + # using base test-files/reason/t6.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/reason/t8-ref.n3 b/spec/test-files/reason/t8-ref.n3 index 93270b6..bbf3efe 100644 --- a/spec/test-files/reason/t8-ref.n3 +++ b/spec/test-files/reason/t8-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t8.n3 + # using base test-files/reason/t8.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/reason/t9-ref.n3 b/spec/test-files/reason/t9-ref.n3 index 4bd1414..89ba6f7 100644 --- a/spec/test-files/reason/t9-ref.n3 +++ b/spec/test-files/reason/t9-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t9.n3 + # using base test-files/reason/t9.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/string/endsWith-out.n3 b/spec/test-files/string/endsWith-out.n3 index cf67adc..5903b75 100644 --- a/spec/test-files/string/endsWith-out.n3 +++ b/spec/test-files/string/endsWith-out.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/endsWith.n3 + # using base test-files/string/endsWith.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/string/roughly-out.n3 b/spec/test-files/string/roughly-out.n3 index 750f2f4..b31845d 100644 --- a/spec/test-files/string/roughly-out.n3 +++ b/spec/test-files/string/roughly-out.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/roughly.n3 + # using base test-files/string/roughly.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/string/uriEncode-out.n3 b/spec/test-files/string/uriEncode-out.n3 index a8cea96..e223d75 100644 --- a/spec/test-files/string/uriEncode-out.n3 +++ b/spec/test-files/string/uriEncode-out.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/uriEncode.n3 + # using base test-files/string/uriEncode.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/supports/simple-ref.n3 b/spec/test-files/supports/simple-ref.n3 index 7a401ad..93ec3d3 100644 --- a/spec/test-files/supports/simple-ref.n3 +++ b/spec/test-files/supports/simple-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/supports/simple.n3 + # using base test-files/supports/simple.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/unify/reflexive-ref.n3 b/spec/test-files/unify/reflexive-ref.n3 index ae135e6..be0eb88 100644 --- a/spec/test-files/unify/reflexive-ref.n3 +++ b/spec/test-files/unify/reflexive-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/reflexive.n3 + # using base test-files/unify/reflexive.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/unify/unify1-ref.n3 b/spec/test-files/unify/unify1-ref.n3 index 9a8ad20..bd93c4c 100644 --- a/spec/test-files/unify/unify1-ref.n3 +++ b/spec/test-files/unify/unify1-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/unify1.n3 + # using base test-files/unify/unify1.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test-files/unify/unify2-ref.n3 b/spec/test-files/unify/unify2-ref.n3 index 47dad6d..97b1e85 100644 --- a/spec/test-files/unify/unify2-ref.n3 +++ b/spec/test-files/unify/unify2-ref.n3 @@ -1,5 +1,5 @@ #Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp - # using base file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/unify2.n3 + # using base test-files/unify/unify2.n3 # Notation3 generation by # notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp diff --git a/spec/test_file_spec.rb b/spec/test_file_spec.rb index 1203e39..2a1d3e4 100644 --- a/spec/test_file_spec.rb +++ b/spec/test_file_spec.rb @@ -122,13 +122,15 @@ def inspect pending "support for log:conjunction" when *%w{t553 t554} pending "support for inference over quoted graphs" + when *%w{norm} + pending "something else" end reader = RDF::N3::Reader.new(t.input, base_uri: t.base, - logger: t.logger) + logger: (t.options["think"] ? nil : t.logger)) - reasoner = RDF::N3::Reasoner.new(t.input, + reasoner = RDF::N3::Reasoner.new(reader, base_uri: t.base, logger: t.logger) @@ -137,16 +139,23 @@ def inspect if t.positive_test? begin if t.options["think"] - repo = reasoner.execute(logger: t.logger, think: t.options['think']) + reasoner.execute(logger: t.logger, think: t.options['think']) + reasoner.reason!(logger: t.logger, think: t.options['think']) + if t.options["conclusions"] + repo << reasoner.conclusions + elsif t.options["data"] + repo << reasoner.data + else + repo << reasoner + end else repo << reader end rescue Exception => e - expect(e.message).to produce("Not exception #{e.inspect}", t) + expect(e.message).to produce("Not exception #{e.inspect}: #{e.backtrace.join("\n")}", t) end if t.evaluate? output_repo = RDF::Repository.load(t.result, format: :n3, base_uri: t.base) - repo = repo.project_graph(nil) if t.options['data'] expect(repo).to be_equivalent_graph(output_repo, t) else end @@ -160,4 +169,4 @@ def inspect end end end -end \ No newline at end of file +end unless ENV['CI'] \ No newline at end of file From 1c4108d7ee3fcedc9c5b73d9e5a65c11544f26fb Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 23 Jan 2019 12:23:14 -0800 Subject: [PATCH 25/40] Fix some whitespace issues in writer output. --- lib/rdf/n3/writer.rb | 37 ++++++++++++++++++++++--------------- spec/writer_spec.rb | 22 +++++++++++++++------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 5f3b535..f8328bd 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -172,8 +172,11 @@ def write_epilogue next if graph_done?(graph_name) log_debug {"named graph(#{graph_name})"} + @output.write("\n#{indent}") p_term(graph_name, :subject) + @output.write(" ") predicate(RDF::OWL.sameAs) + @output.write(" ") formula(graph_name, :graph_name) @output.write(" .\n") end @@ -301,23 +304,23 @@ def format_node(node, options = {}) protected # Output @base and @prefix definitions def start_document - @output.write("#{indent}@base <#{base_uri}> .\n") unless base_uri.to_s.empty? + @output.write("@base <#{base_uri}> .\n") unless base_uri.to_s.empty? log_debug {"start_document: prefixes #{prefixes.inspect}"} prefixes.keys.sort_by(&:to_s).each do |prefix| - @output.write("#{indent}@prefix #{prefix}: <#{prefixes[prefix]}> .\n") + @output.write("@prefix #{prefix}: <#{prefixes[prefix]}> .\n") end unless @universals.empty? log_debug {"start_document: universals #{@universals.inspect}"} terms = @universals.map {|v| format_uri(RDF::URI(v.name.to_s))} - @output.write("#{indent}@forAll #{terms.join(', ')} .\n") + @output.write("@forAll #{terms.join(', ')} .\n") end unless @existentials.empty? log_debug {"start_document: universals #{@existentials.inspect}"} terms = @existentials.map {|v| format_uri(RDF::URI(v.name.to_s))} - @output.write("#{indent}@forSome #{terms.join(', ')} .\n") + @output.write("@forSome #{terms.join(', ')} .\n") end end @@ -469,12 +472,15 @@ def do_list(l, position) list = @lists[l] log_debug("do_list") {list.inspect} subject_done(RDF.nil) + index = 0 list.each_statement do |st| next unless st.predicate == RDF.first log_debug {" list this: #{st.subject} first: #{st.object}[#{position}]"} + @output.write(" ") if index > 0 path(st.object, position) subject_done(st.subject) position = :object + index += 1 end end @@ -484,7 +490,7 @@ def collection(node, position) return false if position == :object && prop_count(node) > 0 #log_debug("collection") {"#{node.to_sxp}, #{position}"} - @output.write(position == :subject ? "(" : " (") + @output.write("(") log_depth {do_list(node, position)} @output.write(')') end @@ -492,8 +498,7 @@ def collection(node, position) # Default singular resource representation. def p_term(resource, position) #log_debug("p_term") {"#{resource.to_sxp}, #{position}"} - l = (position == :subject ? "" : " ") + - if resource.is_a?(RDF::Query::Variable) + l = if resource.is_a?(RDF::Query::Variable) format_term(RDF::URI(resource.name.to_s)) elsif resource == RDF.nil "()" @@ -525,11 +530,11 @@ def predicate(resource) log_debug("predicate") {resource.to_sxp} case resource when RDF.type - @output.write(" a") + @output.write("a") when RDF::OWL.sameAs - @output.write(" =") + @output.write("=") when RDF::N3::Log.implies - @output.write(" =>") + @output.write("=>") else path(resource, :predicate) end @@ -575,6 +580,7 @@ def predicateObjectList(subject, from_bpl = false) begin @output.write(";\n#{indent(2)}") if i > 0 predicate(RDF::URI.intern(prop)) + @output.write(" ") objectList(properties[prop]) end end @@ -597,9 +603,9 @@ def blankNodePropertyList(resource, position) log_debug("blankNodePropertyList") {resource.to_sxp} subject_done(resource) - @output.write(position == :subject ? "\n#{indent}[" : ' [') + @output.write(position == :subject ? "\n#{indent}[" : '[') num_props = log_depth {predicateObjectList(resource, true)} - @output.write((num_props > 1 ? "\n#{indent}" : "") + (position == :object ? ']' : '] .')) + @output.write((num_props > 1 ? "\n#{indent(2)}" : "") + (position == :object ? ']' : '] .')) true end @@ -618,7 +624,7 @@ def formula(resource, position) log_debug("formula") {resource.to_sxp} subject_done(resource) - @output.write(position == :subject ? "\n#{indent}{" : ' {') + @output.write('{') log_depth do with_graph(resource) do count = 0 @@ -638,8 +644,9 @@ def formula(resource, position) def triples(subject) @output.write("\n#{indent}") path(subject, :subject) - predicateObjectList(subject) - @output.write(" .") + @output.write(" ") + num_props = predicateObjectList(subject) + @output.write("#{num_props > 0 ? ' ' : ''}.") true end diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 2967e64..7c6e91a 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -97,7 +97,7 @@ }, "bare anon" => { input: %(@prefix ex: . [ex:a ex:b] .), - regexp: [%r(^\s*\[ ex:a ex:b\] \.$)], + regexp: [%r(^\s*\[ex:a ex:b\] \.$)], }, "anon as subject" => { input: %(@prefix ex: . [ex:a ex:b] ex:c ex:d .), @@ -108,7 +108,7 @@ }, "anon as object" => { input: %(@prefix ex: . ex:a ex:b [ex:c ex:d] .), - regexp: [%r(^ex:a ex:b \[ ex:c ex:d\] \.$)], + regexp: [%r(^ex:a ex:b \[ex:c ex:d\] \.$)], }, "reuses BNode labels by default" => { input: %(@prefix ex: . _:a ex:b _:a .), @@ -189,6 +189,14 @@ input: %(@prefix ex: . [ex:twoAnons ([a ex:mother] [a ex:father])] .), regexp: [%r(\[\s*ex:twoAnons \(\s*\[\s*a ex:mother\s*\] \[\s*a ex:father\s*\]\)\] \.$)] }, + "list subjects": { + input: %(@prefix ex: . (ex:a ex:b) . ex:a a ex:Thing . ex:b a ex:Thing .), + regexp: [ + %r(\(ex:a ex:b\) \.), + %r(ex:a a ex:Thing \.), + %r(ex:b a ex:Thing \.), + ] + }, "owl:unionOf list": { input: %( @prefix ex: . @@ -480,7 +488,7 @@ "empty subject" => { input: %({} .), regexp: [ - %r(\[ \] \.) + %r(\[ \] \.) ] }, "empty object" => { @@ -519,18 +527,18 @@ @prefix ex: . @prefix contact: . [] - doc:creator [ contact:email ]; + doc:creator [contact:email ]; ex:says { [] doc:title "Huckleberry Finn"; - doc:creator [ contact:knownAs "Mark Twain"] + doc:creator [contact:knownAs "Mark Twain"] }. ), regexp: [ %r(\[\s+ex:says {\s+\[)m, - %r(doc:creator \[ contact:knownAs "Mark Twain"\];), + %r(doc:creator \[contact:knownAs "Mark Twain"\];), %r(doc:title "Huckleberry Finn"), %r(\] \.\s+};)m, - %r(doc:creator \[ contact:email ) + %r(doc:creator \[contact:email ) ] }, "named with URI" => { From 122ae9f060fb23c73aeec2af94cad4f39d59141e Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 23 Jan 2019 16:41:39 -0800 Subject: [PATCH 26/40] Move test files from SWAP to examples/ --- {spec/test-files => examples}/andy/D-ref.n3 | 0 {spec/test-files => examples}/andy/D.n3 | 0 {example-files => examples}/arnau-registered-vocab.rb | 0 {example-files => examples}/arnau-stack-overflow.ttl | 0 {example-files => examples}/back-slash.nt | 0 {example-files => examples}/best-buy.nt | 0 {example-files => examples}/dwbutler-mj.n3 | 0 {example-files => examples}/dwbutler-mj.ttl | 0 {spec/test-files => examples}/example-1.n3 | 0 {spec/test-files => examples}/example-2.n3 | 0 {spec/test-files => examples}/example-3.n3 | 0 example.rb => examples/example.rb | 0 {spec/test-files => examples}/foo.n3 | 0 {spec/test-files => examples}/includes/bnode-conclude-ref.n3 | 0 {spec/test-files => examples}/includes/bnodeConclude.n3 | 0 {spec/test-files => examples}/includes/concat-ref.n3 | 0 {spec/test-files => examples}/includes/concat.n3 | 0 {spec/test-files => examples}/includes/conclusion-simple-ref.n3 | 0 {spec/test-files => examples}/includes/conclusion-simple.n3 | 0 {spec/test-files => examples}/includes/conjunction-ref.n3 | 0 {spec/test-files => examples}/includes/conjunction.n3 | 0 {spec/test-files => examples}/includes/list-in-ref.n3 | 0 {spec/test-files => examples}/includes/list-in.n3 | 0 {example-files => examples}/jeni-ice-cream.nt | 0 {example-files => examples}/lee-reilly-list.rb | 0 {spec/test-files => examples}/list/append-ref.n3 | 0 {spec/test-files => examples}/list/append.n3 | 0 {spec/test-files => examples}/list/builtin_generated_match-ref.n3 | 0 {spec/test-files => examples}/list/builtin_generated_match.n3 | 0 {spec/test-files => examples}/list/last.n3 | 0 {spec/test-files => examples}/list/list-bug1-ref.n3 | 0 {spec/test-files => examples}/list/list-bug1.n3 | 0 {spec/test-files => examples}/list/list-bug2-ref.n3 | 0 {spec/test-files => examples}/list/list-bug2.n3 | 0 {spec/test-files => examples}/list/r1-ref.n3 | 0 {spec/test-files => examples}/list/r1.n3 | 0 {spec/test-files => examples}/list/unify2-ref.n3 | 0 {spec/test-files => examples}/list/unify2.n3 | 0 {spec/test-files => examples}/list/unify3-ref.n3 | 0 {spec/test-files => examples}/list/unify3.n3 | 0 {spec/test-files => examples}/list/unify4-ref.n3 | 0 {spec/test-files => examples}/list/unify4.n3 | 0 {spec/test-files => examples}/list/unify5-ref.n3 | 0 {spec/test-files => examples}/list/unify5.n3 | 0 {spec/test-files => examples}/manifest.n3 | 0 {spec/test-files => examples}/norm/av-ref.n3 | 0 {spec/test-files => examples}/norm/av.n3 | 0 {spec/test-files => examples}/path-1.n3 | 0 {spec/test-files => examples}/reason/double-ref.n3 | 0 {spec/test-files => examples}/reason/double.n3 | 0 {spec/test-files => examples}/reason/single_gen.n3 | 0 {spec/test-files => examples}/reason/socrates-ref.n3 | 0 {spec/test-files => examples}/reason/socrates.n3 | 0 {spec/test-files => examples}/reason/t1-ref.n3 | 0 {spec/test-files => examples}/reason/t1.n3 | 0 {spec/test-files => examples}/reason/t2-ref.n3 | 0 {spec/test-files => examples}/reason/t2.n3 | 0 {spec/test-files => examples}/reason/t3-ref.n3 | 0 {spec/test-files => examples}/reason/t3.n3 | 0 {spec/test-files => examples}/reason/t4-ref.n3 | 0 {spec/test-files => examples}/reason/t4.n3 | 0 {spec/test-files => examples}/reason/t5-ref.n3 | 0 {spec/test-files => examples}/reason/t5.n3 | 0 {spec/test-files => examples}/reason/t6-ref.n3 | 0 {spec/test-files => examples}/reason/t6.n3 | 0 {spec/test-files => examples}/reason/t8-ref.n3 | 0 {spec/test-files => examples}/reason/t8.n3 | 0 {spec/test-files => examples}/reason/t9-ref.n3 | 0 {spec/test-files => examples}/reason/t9.n3 | 0 {example-files => examples}/recipe.ttl | 0 {example-files => examples}/sp2b.n3 | 0 {spec/test-files => examples}/string/endsWith-out.n3 | 0 {spec/test-files => examples}/string/endsWith.n3 | 0 {spec/test-files => examples}/string/roughly-out.n3 | 0 {spec/test-files => examples}/string/roughly.n3 | 0 {spec/test-files => examples}/string/uriEncode-out.n3 | 0 {spec/test-files => examples}/string/uriEncode.n3 | 0 {spec/test-files => examples}/supports/simple-ref.n3 | 0 {spec/test-files => examples}/supports/simple.n3 | 0 {spec/test-files => examples}/unify/reflexive-ref.n3 | 0 {spec/test-files => examples}/unify/reflexive.n3 | 0 {spec/test-files => examples}/unify/unify1-ref.n3 | 0 {spec/test-files => examples}/unify/unify1.n3 | 0 {spec/test-files => examples}/unify/unify2-ref.n3 | 0 {spec/test-files => examples}/unify/unify2.n3 | 0 85 files changed, 0 insertions(+), 0 deletions(-) rename {spec/test-files => examples}/andy/D-ref.n3 (100%) rename {spec/test-files => examples}/andy/D.n3 (100%) rename {example-files => examples}/arnau-registered-vocab.rb (100%) rename {example-files => examples}/arnau-stack-overflow.ttl (100%) rename {example-files => examples}/back-slash.nt (100%) rename {example-files => examples}/best-buy.nt (100%) rename {example-files => examples}/dwbutler-mj.n3 (100%) rename {example-files => examples}/dwbutler-mj.ttl (100%) rename {spec/test-files => examples}/example-1.n3 (100%) rename {spec/test-files => examples}/example-2.n3 (100%) rename {spec/test-files => examples}/example-3.n3 (100%) rename example.rb => examples/example.rb (100%) rename {spec/test-files => examples}/foo.n3 (100%) rename {spec/test-files => examples}/includes/bnode-conclude-ref.n3 (100%) rename {spec/test-files => examples}/includes/bnodeConclude.n3 (100%) rename {spec/test-files => examples}/includes/concat-ref.n3 (100%) rename {spec/test-files => examples}/includes/concat.n3 (100%) rename {spec/test-files => examples}/includes/conclusion-simple-ref.n3 (100%) rename {spec/test-files => examples}/includes/conclusion-simple.n3 (100%) rename {spec/test-files => examples}/includes/conjunction-ref.n3 (100%) rename {spec/test-files => examples}/includes/conjunction.n3 (100%) rename {spec/test-files => examples}/includes/list-in-ref.n3 (100%) rename {spec/test-files => examples}/includes/list-in.n3 (100%) rename {example-files => examples}/jeni-ice-cream.nt (100%) rename {example-files => examples}/lee-reilly-list.rb (100%) rename {spec/test-files => examples}/list/append-ref.n3 (100%) rename {spec/test-files => examples}/list/append.n3 (100%) rename {spec/test-files => examples}/list/builtin_generated_match-ref.n3 (100%) rename {spec/test-files => examples}/list/builtin_generated_match.n3 (100%) rename {spec/test-files => examples}/list/last.n3 (100%) rename {spec/test-files => examples}/list/list-bug1-ref.n3 (100%) rename {spec/test-files => examples}/list/list-bug1.n3 (100%) rename {spec/test-files => examples}/list/list-bug2-ref.n3 (100%) rename {spec/test-files => examples}/list/list-bug2.n3 (100%) rename {spec/test-files => examples}/list/r1-ref.n3 (100%) rename {spec/test-files => examples}/list/r1.n3 (100%) rename {spec/test-files => examples}/list/unify2-ref.n3 (100%) rename {spec/test-files => examples}/list/unify2.n3 (100%) rename {spec/test-files => examples}/list/unify3-ref.n3 (100%) rename {spec/test-files => examples}/list/unify3.n3 (100%) rename {spec/test-files => examples}/list/unify4-ref.n3 (100%) rename {spec/test-files => examples}/list/unify4.n3 (100%) rename {spec/test-files => examples}/list/unify5-ref.n3 (100%) rename {spec/test-files => examples}/list/unify5.n3 (100%) rename {spec/test-files => examples}/manifest.n3 (100%) rename {spec/test-files => examples}/norm/av-ref.n3 (100%) rename {spec/test-files => examples}/norm/av.n3 (100%) rename {spec/test-files => examples}/path-1.n3 (100%) rename {spec/test-files => examples}/reason/double-ref.n3 (100%) rename {spec/test-files => examples}/reason/double.n3 (100%) rename {spec/test-files => examples}/reason/single_gen.n3 (100%) rename {spec/test-files => examples}/reason/socrates-ref.n3 (100%) rename {spec/test-files => examples}/reason/socrates.n3 (100%) rename {spec/test-files => examples}/reason/t1-ref.n3 (100%) rename {spec/test-files => examples}/reason/t1.n3 (100%) rename {spec/test-files => examples}/reason/t2-ref.n3 (100%) rename {spec/test-files => examples}/reason/t2.n3 (100%) rename {spec/test-files => examples}/reason/t3-ref.n3 (100%) rename {spec/test-files => examples}/reason/t3.n3 (100%) rename {spec/test-files => examples}/reason/t4-ref.n3 (100%) rename {spec/test-files => examples}/reason/t4.n3 (100%) rename {spec/test-files => examples}/reason/t5-ref.n3 (100%) rename {spec/test-files => examples}/reason/t5.n3 (100%) rename {spec/test-files => examples}/reason/t6-ref.n3 (100%) rename {spec/test-files => examples}/reason/t6.n3 (100%) rename {spec/test-files => examples}/reason/t8-ref.n3 (100%) rename {spec/test-files => examples}/reason/t8.n3 (100%) rename {spec/test-files => examples}/reason/t9-ref.n3 (100%) rename {spec/test-files => examples}/reason/t9.n3 (100%) rename {example-files => examples}/recipe.ttl (100%) rename {example-files => examples}/sp2b.n3 (100%) rename {spec/test-files => examples}/string/endsWith-out.n3 (100%) rename {spec/test-files => examples}/string/endsWith.n3 (100%) rename {spec/test-files => examples}/string/roughly-out.n3 (100%) rename {spec/test-files => examples}/string/roughly.n3 (100%) rename {spec/test-files => examples}/string/uriEncode-out.n3 (100%) rename {spec/test-files => examples}/string/uriEncode.n3 (100%) rename {spec/test-files => examples}/supports/simple-ref.n3 (100%) rename {spec/test-files => examples}/supports/simple.n3 (100%) rename {spec/test-files => examples}/unify/reflexive-ref.n3 (100%) rename {spec/test-files => examples}/unify/reflexive.n3 (100%) rename {spec/test-files => examples}/unify/unify1-ref.n3 (100%) rename {spec/test-files => examples}/unify/unify1.n3 (100%) rename {spec/test-files => examples}/unify/unify2-ref.n3 (100%) rename {spec/test-files => examples}/unify/unify2.n3 (100%) diff --git a/spec/test-files/andy/D-ref.n3 b/examples/andy/D-ref.n3 similarity index 100% rename from spec/test-files/andy/D-ref.n3 rename to examples/andy/D-ref.n3 diff --git a/spec/test-files/andy/D.n3 b/examples/andy/D.n3 similarity index 100% rename from spec/test-files/andy/D.n3 rename to examples/andy/D.n3 diff --git a/example-files/arnau-registered-vocab.rb b/examples/arnau-registered-vocab.rb similarity index 100% rename from example-files/arnau-registered-vocab.rb rename to examples/arnau-registered-vocab.rb diff --git a/example-files/arnau-stack-overflow.ttl b/examples/arnau-stack-overflow.ttl similarity index 100% rename from example-files/arnau-stack-overflow.ttl rename to examples/arnau-stack-overflow.ttl diff --git a/example-files/back-slash.nt b/examples/back-slash.nt similarity index 100% rename from example-files/back-slash.nt rename to examples/back-slash.nt diff --git a/example-files/best-buy.nt b/examples/best-buy.nt similarity index 100% rename from example-files/best-buy.nt rename to examples/best-buy.nt diff --git a/example-files/dwbutler-mj.n3 b/examples/dwbutler-mj.n3 similarity index 100% rename from example-files/dwbutler-mj.n3 rename to examples/dwbutler-mj.n3 diff --git a/example-files/dwbutler-mj.ttl b/examples/dwbutler-mj.ttl similarity index 100% rename from example-files/dwbutler-mj.ttl rename to examples/dwbutler-mj.ttl diff --git a/spec/test-files/example-1.n3 b/examples/example-1.n3 similarity index 100% rename from spec/test-files/example-1.n3 rename to examples/example-1.n3 diff --git a/spec/test-files/example-2.n3 b/examples/example-2.n3 similarity index 100% rename from spec/test-files/example-2.n3 rename to examples/example-2.n3 diff --git a/spec/test-files/example-3.n3 b/examples/example-3.n3 similarity index 100% rename from spec/test-files/example-3.n3 rename to examples/example-3.n3 diff --git a/example.rb b/examples/example.rb similarity index 100% rename from example.rb rename to examples/example.rb diff --git a/spec/test-files/foo.n3 b/examples/foo.n3 similarity index 100% rename from spec/test-files/foo.n3 rename to examples/foo.n3 diff --git a/spec/test-files/includes/bnode-conclude-ref.n3 b/examples/includes/bnode-conclude-ref.n3 similarity index 100% rename from spec/test-files/includes/bnode-conclude-ref.n3 rename to examples/includes/bnode-conclude-ref.n3 diff --git a/spec/test-files/includes/bnodeConclude.n3 b/examples/includes/bnodeConclude.n3 similarity index 100% rename from spec/test-files/includes/bnodeConclude.n3 rename to examples/includes/bnodeConclude.n3 diff --git a/spec/test-files/includes/concat-ref.n3 b/examples/includes/concat-ref.n3 similarity index 100% rename from spec/test-files/includes/concat-ref.n3 rename to examples/includes/concat-ref.n3 diff --git a/spec/test-files/includes/concat.n3 b/examples/includes/concat.n3 similarity index 100% rename from spec/test-files/includes/concat.n3 rename to examples/includes/concat.n3 diff --git a/spec/test-files/includes/conclusion-simple-ref.n3 b/examples/includes/conclusion-simple-ref.n3 similarity index 100% rename from spec/test-files/includes/conclusion-simple-ref.n3 rename to examples/includes/conclusion-simple-ref.n3 diff --git a/spec/test-files/includes/conclusion-simple.n3 b/examples/includes/conclusion-simple.n3 similarity index 100% rename from spec/test-files/includes/conclusion-simple.n3 rename to examples/includes/conclusion-simple.n3 diff --git a/spec/test-files/includes/conjunction-ref.n3 b/examples/includes/conjunction-ref.n3 similarity index 100% rename from spec/test-files/includes/conjunction-ref.n3 rename to examples/includes/conjunction-ref.n3 diff --git a/spec/test-files/includes/conjunction.n3 b/examples/includes/conjunction.n3 similarity index 100% rename from spec/test-files/includes/conjunction.n3 rename to examples/includes/conjunction.n3 diff --git a/spec/test-files/includes/list-in-ref.n3 b/examples/includes/list-in-ref.n3 similarity index 100% rename from spec/test-files/includes/list-in-ref.n3 rename to examples/includes/list-in-ref.n3 diff --git a/spec/test-files/includes/list-in.n3 b/examples/includes/list-in.n3 similarity index 100% rename from spec/test-files/includes/list-in.n3 rename to examples/includes/list-in.n3 diff --git a/example-files/jeni-ice-cream.nt b/examples/jeni-ice-cream.nt similarity index 100% rename from example-files/jeni-ice-cream.nt rename to examples/jeni-ice-cream.nt diff --git a/example-files/lee-reilly-list.rb b/examples/lee-reilly-list.rb similarity index 100% rename from example-files/lee-reilly-list.rb rename to examples/lee-reilly-list.rb diff --git a/spec/test-files/list/append-ref.n3 b/examples/list/append-ref.n3 similarity index 100% rename from spec/test-files/list/append-ref.n3 rename to examples/list/append-ref.n3 diff --git a/spec/test-files/list/append.n3 b/examples/list/append.n3 similarity index 100% rename from spec/test-files/list/append.n3 rename to examples/list/append.n3 diff --git a/spec/test-files/list/builtin_generated_match-ref.n3 b/examples/list/builtin_generated_match-ref.n3 similarity index 100% rename from spec/test-files/list/builtin_generated_match-ref.n3 rename to examples/list/builtin_generated_match-ref.n3 diff --git a/spec/test-files/list/builtin_generated_match.n3 b/examples/list/builtin_generated_match.n3 similarity index 100% rename from spec/test-files/list/builtin_generated_match.n3 rename to examples/list/builtin_generated_match.n3 diff --git a/spec/test-files/list/last.n3 b/examples/list/last.n3 similarity index 100% rename from spec/test-files/list/last.n3 rename to examples/list/last.n3 diff --git a/spec/test-files/list/list-bug1-ref.n3 b/examples/list/list-bug1-ref.n3 similarity index 100% rename from spec/test-files/list/list-bug1-ref.n3 rename to examples/list/list-bug1-ref.n3 diff --git a/spec/test-files/list/list-bug1.n3 b/examples/list/list-bug1.n3 similarity index 100% rename from spec/test-files/list/list-bug1.n3 rename to examples/list/list-bug1.n3 diff --git a/spec/test-files/list/list-bug2-ref.n3 b/examples/list/list-bug2-ref.n3 similarity index 100% rename from spec/test-files/list/list-bug2-ref.n3 rename to examples/list/list-bug2-ref.n3 diff --git a/spec/test-files/list/list-bug2.n3 b/examples/list/list-bug2.n3 similarity index 100% rename from spec/test-files/list/list-bug2.n3 rename to examples/list/list-bug2.n3 diff --git a/spec/test-files/list/r1-ref.n3 b/examples/list/r1-ref.n3 similarity index 100% rename from spec/test-files/list/r1-ref.n3 rename to examples/list/r1-ref.n3 diff --git a/spec/test-files/list/r1.n3 b/examples/list/r1.n3 similarity index 100% rename from spec/test-files/list/r1.n3 rename to examples/list/r1.n3 diff --git a/spec/test-files/list/unify2-ref.n3 b/examples/list/unify2-ref.n3 similarity index 100% rename from spec/test-files/list/unify2-ref.n3 rename to examples/list/unify2-ref.n3 diff --git a/spec/test-files/list/unify2.n3 b/examples/list/unify2.n3 similarity index 100% rename from spec/test-files/list/unify2.n3 rename to examples/list/unify2.n3 diff --git a/spec/test-files/list/unify3-ref.n3 b/examples/list/unify3-ref.n3 similarity index 100% rename from spec/test-files/list/unify3-ref.n3 rename to examples/list/unify3-ref.n3 diff --git a/spec/test-files/list/unify3.n3 b/examples/list/unify3.n3 similarity index 100% rename from spec/test-files/list/unify3.n3 rename to examples/list/unify3.n3 diff --git a/spec/test-files/list/unify4-ref.n3 b/examples/list/unify4-ref.n3 similarity index 100% rename from spec/test-files/list/unify4-ref.n3 rename to examples/list/unify4-ref.n3 diff --git a/spec/test-files/list/unify4.n3 b/examples/list/unify4.n3 similarity index 100% rename from spec/test-files/list/unify4.n3 rename to examples/list/unify4.n3 diff --git a/spec/test-files/list/unify5-ref.n3 b/examples/list/unify5-ref.n3 similarity index 100% rename from spec/test-files/list/unify5-ref.n3 rename to examples/list/unify5-ref.n3 diff --git a/spec/test-files/list/unify5.n3 b/examples/list/unify5.n3 similarity index 100% rename from spec/test-files/list/unify5.n3 rename to examples/list/unify5.n3 diff --git a/spec/test-files/manifest.n3 b/examples/manifest.n3 similarity index 100% rename from spec/test-files/manifest.n3 rename to examples/manifest.n3 diff --git a/spec/test-files/norm/av-ref.n3 b/examples/norm/av-ref.n3 similarity index 100% rename from spec/test-files/norm/av-ref.n3 rename to examples/norm/av-ref.n3 diff --git a/spec/test-files/norm/av.n3 b/examples/norm/av.n3 similarity index 100% rename from spec/test-files/norm/av.n3 rename to examples/norm/av.n3 diff --git a/spec/test-files/path-1.n3 b/examples/path-1.n3 similarity index 100% rename from spec/test-files/path-1.n3 rename to examples/path-1.n3 diff --git a/spec/test-files/reason/double-ref.n3 b/examples/reason/double-ref.n3 similarity index 100% rename from spec/test-files/reason/double-ref.n3 rename to examples/reason/double-ref.n3 diff --git a/spec/test-files/reason/double.n3 b/examples/reason/double.n3 similarity index 100% rename from spec/test-files/reason/double.n3 rename to examples/reason/double.n3 diff --git a/spec/test-files/reason/single_gen.n3 b/examples/reason/single_gen.n3 similarity index 100% rename from spec/test-files/reason/single_gen.n3 rename to examples/reason/single_gen.n3 diff --git a/spec/test-files/reason/socrates-ref.n3 b/examples/reason/socrates-ref.n3 similarity index 100% rename from spec/test-files/reason/socrates-ref.n3 rename to examples/reason/socrates-ref.n3 diff --git a/spec/test-files/reason/socrates.n3 b/examples/reason/socrates.n3 similarity index 100% rename from spec/test-files/reason/socrates.n3 rename to examples/reason/socrates.n3 diff --git a/spec/test-files/reason/t1-ref.n3 b/examples/reason/t1-ref.n3 similarity index 100% rename from spec/test-files/reason/t1-ref.n3 rename to examples/reason/t1-ref.n3 diff --git a/spec/test-files/reason/t1.n3 b/examples/reason/t1.n3 similarity index 100% rename from spec/test-files/reason/t1.n3 rename to examples/reason/t1.n3 diff --git a/spec/test-files/reason/t2-ref.n3 b/examples/reason/t2-ref.n3 similarity index 100% rename from spec/test-files/reason/t2-ref.n3 rename to examples/reason/t2-ref.n3 diff --git a/spec/test-files/reason/t2.n3 b/examples/reason/t2.n3 similarity index 100% rename from spec/test-files/reason/t2.n3 rename to examples/reason/t2.n3 diff --git a/spec/test-files/reason/t3-ref.n3 b/examples/reason/t3-ref.n3 similarity index 100% rename from spec/test-files/reason/t3-ref.n3 rename to examples/reason/t3-ref.n3 diff --git a/spec/test-files/reason/t3.n3 b/examples/reason/t3.n3 similarity index 100% rename from spec/test-files/reason/t3.n3 rename to examples/reason/t3.n3 diff --git a/spec/test-files/reason/t4-ref.n3 b/examples/reason/t4-ref.n3 similarity index 100% rename from spec/test-files/reason/t4-ref.n3 rename to examples/reason/t4-ref.n3 diff --git a/spec/test-files/reason/t4.n3 b/examples/reason/t4.n3 similarity index 100% rename from spec/test-files/reason/t4.n3 rename to examples/reason/t4.n3 diff --git a/spec/test-files/reason/t5-ref.n3 b/examples/reason/t5-ref.n3 similarity index 100% rename from spec/test-files/reason/t5-ref.n3 rename to examples/reason/t5-ref.n3 diff --git a/spec/test-files/reason/t5.n3 b/examples/reason/t5.n3 similarity index 100% rename from spec/test-files/reason/t5.n3 rename to examples/reason/t5.n3 diff --git a/spec/test-files/reason/t6-ref.n3 b/examples/reason/t6-ref.n3 similarity index 100% rename from spec/test-files/reason/t6-ref.n3 rename to examples/reason/t6-ref.n3 diff --git a/spec/test-files/reason/t6.n3 b/examples/reason/t6.n3 similarity index 100% rename from spec/test-files/reason/t6.n3 rename to examples/reason/t6.n3 diff --git a/spec/test-files/reason/t8-ref.n3 b/examples/reason/t8-ref.n3 similarity index 100% rename from spec/test-files/reason/t8-ref.n3 rename to examples/reason/t8-ref.n3 diff --git a/spec/test-files/reason/t8.n3 b/examples/reason/t8.n3 similarity index 100% rename from spec/test-files/reason/t8.n3 rename to examples/reason/t8.n3 diff --git a/spec/test-files/reason/t9-ref.n3 b/examples/reason/t9-ref.n3 similarity index 100% rename from spec/test-files/reason/t9-ref.n3 rename to examples/reason/t9-ref.n3 diff --git a/spec/test-files/reason/t9.n3 b/examples/reason/t9.n3 similarity index 100% rename from spec/test-files/reason/t9.n3 rename to examples/reason/t9.n3 diff --git a/example-files/recipe.ttl b/examples/recipe.ttl similarity index 100% rename from example-files/recipe.ttl rename to examples/recipe.ttl diff --git a/example-files/sp2b.n3 b/examples/sp2b.n3 similarity index 100% rename from example-files/sp2b.n3 rename to examples/sp2b.n3 diff --git a/spec/test-files/string/endsWith-out.n3 b/examples/string/endsWith-out.n3 similarity index 100% rename from spec/test-files/string/endsWith-out.n3 rename to examples/string/endsWith-out.n3 diff --git a/spec/test-files/string/endsWith.n3 b/examples/string/endsWith.n3 similarity index 100% rename from spec/test-files/string/endsWith.n3 rename to examples/string/endsWith.n3 diff --git a/spec/test-files/string/roughly-out.n3 b/examples/string/roughly-out.n3 similarity index 100% rename from spec/test-files/string/roughly-out.n3 rename to examples/string/roughly-out.n3 diff --git a/spec/test-files/string/roughly.n3 b/examples/string/roughly.n3 similarity index 100% rename from spec/test-files/string/roughly.n3 rename to examples/string/roughly.n3 diff --git a/spec/test-files/string/uriEncode-out.n3 b/examples/string/uriEncode-out.n3 similarity index 100% rename from spec/test-files/string/uriEncode-out.n3 rename to examples/string/uriEncode-out.n3 diff --git a/spec/test-files/string/uriEncode.n3 b/examples/string/uriEncode.n3 similarity index 100% rename from spec/test-files/string/uriEncode.n3 rename to examples/string/uriEncode.n3 diff --git a/spec/test-files/supports/simple-ref.n3 b/examples/supports/simple-ref.n3 similarity index 100% rename from spec/test-files/supports/simple-ref.n3 rename to examples/supports/simple-ref.n3 diff --git a/spec/test-files/supports/simple.n3 b/examples/supports/simple.n3 similarity index 100% rename from spec/test-files/supports/simple.n3 rename to examples/supports/simple.n3 diff --git a/spec/test-files/unify/reflexive-ref.n3 b/examples/unify/reflexive-ref.n3 similarity index 100% rename from spec/test-files/unify/reflexive-ref.n3 rename to examples/unify/reflexive-ref.n3 diff --git a/spec/test-files/unify/reflexive.n3 b/examples/unify/reflexive.n3 similarity index 100% rename from spec/test-files/unify/reflexive.n3 rename to examples/unify/reflexive.n3 diff --git a/spec/test-files/unify/unify1-ref.n3 b/examples/unify/unify1-ref.n3 similarity index 100% rename from spec/test-files/unify/unify1-ref.n3 rename to examples/unify/unify1-ref.n3 diff --git a/spec/test-files/unify/unify1.n3 b/examples/unify/unify1.n3 similarity index 100% rename from spec/test-files/unify/unify1.n3 rename to examples/unify/unify1.n3 diff --git a/spec/test-files/unify/unify2-ref.n3 b/examples/unify/unify2-ref.n3 similarity index 100% rename from spec/test-files/unify/unify2-ref.n3 rename to examples/unify/unify2-ref.n3 diff --git a/spec/test-files/unify/unify2.n3 b/examples/unify/unify2.n3 similarity index 100% rename from spec/test-files/unify/unify2.n3 rename to examples/unify/unify2.n3 From 44f376c6b52a5db283273f223b4785799266c2c7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 23 Jan 2019 16:42:42 -0800 Subject: [PATCH 27/40] Run test suite from github.com/w3c/n3 instead of swap directory. --- spec/suite_helper.rb | 77 ++++++--- spec/{swap_spec.rb => suite_parser_spec.rb} | 36 ++-- spec/suite_reasoner_spec.rb | 84 ++++++++++ spec/test_file_spec.rb | 172 -------------------- spec/w3c-n3 | 1 + 5 files changed, 158 insertions(+), 212 deletions(-) rename spec/{swap_spec.rb => suite_parser_spec.rb} (61%) create mode 100644 spec/suite_reasoner_spec.rb delete mode 100644 spec/test_file_spec.rb create mode 120000 spec/w3c-n3 diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 135d7bf..46ed8a6 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -4,8 +4,8 @@ # For now, override RDF::Utils::File.open_file to look for the file locally before attempting to retrieve it module RDF::Util module File - REMOTE_PATH = "http://www.w3.org/2000/10/swap/" - LOCAL_PATH = ::File.expand_path("../swap", __FILE__) + '/' + REMOTE_PATH = "https://w3c.github.io/n3/" + LOCAL_PATH = ::File.expand_path("../w3c-n3", __FILE__) + '/' class << self alias_method :original_open_file, :open_file @@ -67,35 +67,61 @@ def self.open_file(filename_or_url, options = {}, &block) module Fixtures module SuiteTest - BASE = "http://www.w3.org/2000/10/swap/test/" - CONTEXT = JSON.parse(%q({ - "n3test": "http://www.w3.org/2004/11/n3test#", - - "inputDocument": {"@id": "n3test:inputDocument", "@type": "@id"}, - "outputDocument": {"@id": "n3test:outputDocument", "@type": "@id"}, - "description": "n3test:description" + FRAME = JSON.parse(%q({ + "@context": { + "xsd": "http://www.w3.org/2001/XMLSchema#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "mf": "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#", + "mq": "http://www.w3.org/2001/sw/DataAccess/tests/test-query#", + "rdft": "http://www.w3.org/ns/rdftest#", + "test": "https://w3c.github.io/n3/tests/test.n3#", + "comment": "rdfs:comment", + "entries": {"@id": "mf:entries", "@container": "@list"}, + "name": "mf:name", + "action": {"@id": "mf:action", "@type": "@id"}, + "result": {"@id": "mf:result", "@type": "@id"}, + "options": {"@id": "test:options", "@type": "@id"}, + "data": {"@id": "test:data", "@type": "xsd:boolean"}, + "think": {"@id": "test:think", "@type": "xsd:boolean"}, + "filter": {"@id": "test:filter", "@type": "xsd:boolean"}, + "rules": {"@id": "test:rules", "@type": "xsd:boolean"} + }, + "@type": "mf:Manifest", + "entries": { + "@type": [ + "test:TestN3Reason", + "test:TestN3Eval", + "test:TestN3PositiveSyntax", + "test:TestN3NegativeSyntax" + ] + } })) - - class Entry < JSON::LD::Resource + + class Manifest < JSON::LD::Resource def self.open(file) #puts "open: #{file}" - g = RDF::Repository.load(file, format: :n3) + g = RDF::Repository.load(file, format: :n3) JSON::LD::API.fromRDF(g) do |expanded| - JSON::LD::API.compact(expanded, CONTEXT) do |doc| - doc['@graph'].map {|r| Entry.new(r)}. - reject {|r| Array(r.attributes['@type']).empty?}. - sort_by(&:name). - each {|t| yield(t)} + JSON::LD::API.frame(expanded, FRAME) do |framed| + yield Manifest.new(framed['@graph'].first) end end end + + def entries + # Map entries to resources + attributes['entries'].map {|e| Entry.new(e)} + end + end + + class Entry < JSON::LD::Resource attr_accessor :logger # For debug output formatting def format; :n3; end def base - inputDocument + action end def name @@ -104,11 +130,11 @@ def name # Alias data and query def input - RDF::Util::File.open_file(inputDocument) + @input ||= RDF::Util::File.open_file(action) {|f| f.read} end def expected - RDF::Util::File.open_file(outputDocument) + @expected ||= RDF::Util::File.open_file(result) {|f| f.read} end def positive_test? @@ -120,16 +146,23 @@ def negative_test? end def evaluate? - !syntax? + !!attributes['@type'].to_s.match(/Eval/) end def syntax? - !outputDocument + !!attributes['@type'].to_s.match(/Syntax/) + end + + def reason? + !!attributes['@type'].to_s.match(/Reason/) end def inspect super.sub('>', "\n" + " positive?: #{positive_test?.inspect}\n" + + " syntax?: #{syntax?.inspect}\n" + + " eval?: #{evaluate?.inspect}\n" + + " reason?: #{reason?.inspect}\n" + ">" ) end diff --git a/spec/swap_spec.rb b/spec/suite_parser_spec.rb similarity index 61% rename from spec/swap_spec.rb rename to spec/suite_parser_spec.rb index 000937b..39d6169 100644 --- a/spec/swap_spec.rb +++ b/spec/suite_parser_spec.rb @@ -3,26 +3,25 @@ describe RDF::N3::Reader do # W3C N3 Test suite from http://www.w3.org/2000/10/swap/test/n3parser.tests - describe "w3c swap tests" do + describe "w3c n3 tests" do require_relative 'suite_helper' - %w(n3parser.tests).each do |man| - # Note, n3/n3-full.tests and n3/n3-rdf.tests is essentially the same as n3parser.tests - describe man do - Fixtures::SuiteTest::Entry.open(Fixtures::SuiteTest::BASE + man) do |t| + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-parser.n3") do |m| + describe m.label do + m.entries.each do |t| specify "#{t.name}: #{t.description}" do case t.name when *%w(n3_10004 n3_10007 n3_10014 n3_10015 n3_10017) pending("Reification not supported") - when *%w(n3_10003 n3_10006 n3_10009) - pending("Verified test results are incorrect") when *%w(n3_10013) pending("numeric representation") + when *%w(n3_10003 n3_10006) + pending("Verified test results are incorrect") end t.logger = RDF::Spec.logger t.logger.info t.inspect - t.logger.info "source:\n#{t.input.read}" + t.logger.info "source:\n#{t.input}" reader = RDF::N3::Reader.new(t.input, base_uri: t.base, @@ -30,11 +29,12 @@ validate: true, logger: t.logger) - graph = RDF::Repository.new - output_graph = if t.evaluate? + repo = RDF::Repository.new + + output_repo = if t.evaluate? begin - format = detect_format(t.outputDocument) - RDF::Repository.load(t.outputDocument, format: format, base_uri: t.inputDocument) + format = detect_format(t.expected) + RDF::Repository.load(t.result, format: format, base_uri: t.accept) rescue Exception => e expect(e.message).to produce("Exception loading output #{e.inspect}", t.logger) end @@ -42,23 +42,23 @@ if t.positive_test? begin - graph << reader + repo << reader rescue Exception => e expect(e.message).to produce("Not exception #{e.inspect}", t.logger) end if t.evaluate? - expect(graph).to be_equivalent_graph(output_graph, t) + expect(repo).to be_equivalent_graph(output_repo, t) else - expect(graph).to be_enumerable + expect(repo).to be_enumerable end elsif t.syntax? expect { - graph << reader - graph.dump(:ntriples).should produce("not this", t.logger) + repo << reader + repo.dump(:nquads).should produce("not this", t.logger) }.to raise_error(RDF::ReaderError) else - expect(graph).not_to be_equivalent_graph(output_graph, t) + expect(repo).not_to be_equivalent_graph(output_repo, t) end end end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb new file mode 100644 index 0000000..e99294a --- /dev/null +++ b/spec/suite_reasoner_spec.rb @@ -0,0 +1,84 @@ +require_relative 'spec_helper' +require 'rdf/trig' # For formatting error descriptions + +describe RDF::N3::Reader do + # W3C N3 Test suite from http://www.w3.org/2000/10/swap/test/n3parser.tests + describe "w3c n3 tests" do + require_relative 'suite_helper' + + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-reasoner.n3") do |m| + describe m.label do + m.entries.each do |t| + specify "#{t.name}: #{t.description}" do + case t.id.split('#').last + when *%w{listin bnode concat t2006} + pending "support for lists" + when *%w{t1018b2 t103 t104 t105 concat} + pending "support for string" + when *%w{t2005 t555} + pending "understanding output filtering" + when *%w{t06proof} + pending "support for math" + when *%w{t01} + pending "support for log:supports" + when *%w{conclusion-simple conclusion} + pending "support for log:conclusion" + when *%w{conjunction} + pending "support for log:conjunction" + when *%w{t553 t554} + pending "support for inference over quoted graphs" + when *%w{norm} + pending "something else" + end + + t.logger = RDF::Spec.logger + t.logger.info t.inspect + t.logger.info "source:\n#{t.input}" + + reader = RDF::N3::Reader.new(t.input, + base_uri: t.base, + canonicalize: false, + validate: false, + logger: t.logger) + + reasoner = RDF::N3::Reasoner.new(reader, + base_uri: t.base, + logger: t.logger) + + repo = RDF::Repository.new + + if t.positive_test? + begin + if t.options["think"] + reasoner.execute(logger: t.logger, think: t.options['think']) + reasoner.reason!(logger: t.logger, think: t.options['think']) + if t.options["conclusions"] + repo << reasoner.conclusions + elsif t.options["data"] + repo << reasoner.data + else + repo << reasoner + end + else + repo << reader + end + rescue Exception => e + expect(e.message).to produce("Not exception #{e.inspect}: #{e.backtrace.join("\n")}", t) + end + if t.evaluate? + output_repo = RDF::Repository.load(t.result, format: :n3, base_uri: t.base) + expect(repo).to be_equivalent_graph(output_repo, t) + else + end + else + expect { + graph << reader + expect(graph.dump(:nquads)).to produce("not this", t) + }.to raise_error(RDF::ReaderError) + end + end + end + end + end + end +end unless ENV['CI'] \ No newline at end of file diff --git a/spec/test_file_spec.rb b/spec/test_file_spec.rb deleted file mode 100644 index 2a1d3e4..0000000 --- a/spec/test_file_spec.rb +++ /dev/null @@ -1,172 +0,0 @@ -require_relative "spec_helper" -require 'rdf/spec' -require 'rdf/n3' -require 'json/ld' -require 'rdf/trig' - -module Fixtures - module Test - FRAME = JSON.parse(%q({ - "@context": { - "xsd": "http://www.w3.org/2001/XMLSchema#", - "rdfs": "http://www.w3.org/2000/01/rdf-schema#", - "mf": "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#", - "mq": "http://www.w3.org/2001/sw/DataAccess/tests/test-query#", - "n3test": "http://www.w3.org/2000/10/swap/test.n3#", - "comment": "rdfs:comment", - "entries": {"@id": "mf:entries", "@container": "@list"}, - "label": "rdfs:label", - "name": "mf:name", - "action": {"@id": "mf:action", "@type": "@id"}, - "result": {"@id": "mf:result", "@type": "@id"}, - "options": {"@id": "n3test:options", "@type": "@vocab"}, - "data": {"@id": "n3test:data","@type": "xsd:boolean"}, - "think": {"@id": "n3test:think","@type": "xsd:boolean"} - }, - "@type": "mf:Manifest", - "entries": {"@type": "n3test:CwmTest"} - })) - - class Manifest < JSON::LD::Resource - attr_accessor :manifest_url - - def self.open(file) - g = RDF::Repository.load(file, format: :n3) - JSON::LD::API.fromRDF(g) do |expanded| - JSON::LD::API.frame(expanded, FRAME) do |framed| - yield Manifest.new(framed['@graph'].first, manifest_url: file) - end - end - end - - def initialize(json, manifest_url:) - @manifest_url = manifest_url - super - end - - def entries - # Map entries to resources - attributes['entries'].map do |e| - e.is_a?(String) ? Manifest.open(manifest_url.join(e).to_s) : Entry.new(e, manifest_url: manifest_url) - end - end - end - - class Entry < JSON::LD::Resource - attr_accessor :logger - # For debug output formatting - def format; :n3; end - - def base - action - end - - # Alias data and query - def input - @input ||= RDF::Util::File.open_file(action) {|f| f.read} - end - - def expected - @expected ||= RDF::Util::File.open_file(result) {|f| f.read} - end - - def positive_test? - !attributes['@type'].to_s.match(/Negative/) - end - - def negative_test? - !positive_test? - end - - def evaluate? - !syntax? - end - - def syntax? - !result - end - - def inspect - super.sub('>', "\n" + - " positive?: #{positive_test?.inspect}\n" + - ">" - ) - end - end - end -end - -describe RDF::N3::Reader do - Fixtures::Test::Manifest.open(File.expand_path("../test-files/manifest.n3", __FILE__)) do |m| - describe m.label do - m.entries.each do |t| - specify "#{t.id.split('#').last}: #{t.name} - #{t.comment}" do - t.logger = RDF::Spec.logger - t.logger.info t.inspect - t.logger.info "source:\n#{t.input}" - - case t.id.split('#').last - when *%w{listin bnode concat t2006} - pending "support for lists" - when *%w{t1018b2 t103 t104 t105 concat} - pending "support for string" - when *%w{t2005 t555} - pending "understanding output filtering" - when *%w{t06proof} - pending "support for math" - when *%w{t01} - pending "support for log:supports" - when *%w{conclusion-simple conclusion} - pending "support for log:conclusion" - when *%w{conjunction} - pending "support for log:conjunction" - when *%w{t553 t554} - pending "support for inference over quoted graphs" - when *%w{norm} - pending "something else" - end - - reader = RDF::N3::Reader.new(t.input, - base_uri: t.base, - logger: (t.options["think"] ? nil : t.logger)) - - reasoner = RDF::N3::Reasoner.new(reader, - base_uri: t.base, - logger: t.logger) - - repo = RDF::Repository.new - - if t.positive_test? - begin - if t.options["think"] - reasoner.execute(logger: t.logger, think: t.options['think']) - reasoner.reason!(logger: t.logger, think: t.options['think']) - if t.options["conclusions"] - repo << reasoner.conclusions - elsif t.options["data"] - repo << reasoner.data - else - repo << reasoner - end - else - repo << reader - end - rescue Exception => e - expect(e.message).to produce("Not exception #{e.inspect}: #{e.backtrace.join("\n")}", t) - end - if t.evaluate? - output_repo = RDF::Repository.load(t.result, format: :n3, base_uri: t.base) - expect(repo).to be_equivalent_graph(output_repo, t) - else - end - else - expect { - graph << reader - expect(graph.dump(:ntriples)).to produce("not this", t) - }.to raise_error(RDF::ReaderError) - end - end - end - end - end -end unless ENV['CI'] \ No newline at end of file diff --git a/spec/w3c-n3 b/spec/w3c-n3 new file mode 120000 index 0000000..149ed6a --- /dev/null +++ b/spec/w3c-n3 @@ -0,0 +1 @@ +../../w3c-n3 \ No newline at end of file From 3b00112f7724c0ff2595d2e9f681028b216d1fd7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 23 Jan 2019 16:43:20 -0800 Subject: [PATCH 28/40] Run writer tests through reserializing the suite tests. --- lib/rdf/n3/writer.rb | 2 +- spec/writer_spec.rb | 49 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index f8328bd..7077605 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -700,7 +700,7 @@ def resource_in_single_graph?(resource) if resource.variable? graph_names = @repo. enum_statement. - select {|st| s.subject.equal?(resource) || st.object.equal?(resource)}. + select {|st| st.subject.equal?(resource) || st.object.equal?(resource)}. map(&:graph_name) else graph_names = @repo.query(subject: resource).map(&:graph_name) diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 7c6e91a..0446c49 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -604,6 +604,55 @@ end end + # W3C TriG Test suite + describe "w3c n3 parser tests" do + require_relative 'suite_helper' + + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-parser.n3") do |m| + describe m.comment do + m.entries.each do |t| + next unless t.positive_test? && t.evaluate? + specify "#{t.name}: #{t.comment} (action)" do + case t.name + when *%w(n3_10003 n3_10004 n3_10008) + skip "Blank Node predicates" + when *%w(n3_10012 n3_10016 n3_10017) + pending "Investigate" + when *%w(n3_10013) + pending "Number syntax" + end + logger.info t.inspect + logger.info "source: #{t.input}" + repo = parse(t.input, base_uri: t.base) + n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true) + logger.info "serialized: #{n3}" + g2 = parse(n3, base_uri: t.base) + expect(g2).to be_equivalent_graph(repo, logger: logger) + end + + specify "#{t.name}: #{t.comment} (result)" do + case t.name + when *%w(n3_10003 n3_10004 n3_10008) + skip "Blank Node predicates" + when *%w(n3_10012 n3_10016 n3_10017) + pending "Investigate" + when *%w(n3_10013) + pending "Number syntax" + end + logger.info t.inspect + logger.info "source: #{t.expected}" + format = detect_format(t.expected) + repo = parse(t.expected, base_uri: t.base, format: format) + n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true) + logger.info "serialized: #{n3}" + g2 = parse(n3, base_uri: t.base) + expect(g2).to be_equivalent_graph(repo, logger: logger) + end + end + end + end + end unless ENV['CI'] + def parse(input, format: :n3, **options) repo = RDF::Repository.new reader = RDF::Reader.for(format) From 5bd57a408ee0b59ada7ddefdadb117ff38d33cdf Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 23 Jan 2019 16:43:46 -0800 Subject: [PATCH 29/40] Update project information. --- README.md | 46 ++++++++++++++++++++++++++------------------- etc/doap.n3 | 2 +- etc/doap.nt | 2 +- lib/rdf/n3/vocab.rb | 2 +- rdf-n3.gemspec | 4 ++-- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b1ff475..38d2711 100755 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# RDF::N3 reader/writer +# RDF::N3 reader/writer and reasoner Notation-3 reader/writer for [RDF.rb][RDF.rb] . [![Gem Version](https://badge.fury.io/rb/rdf-n3.png)](http://badge.fury.io/rb/rdf-n3) [![Build Status](https://travis-ci.org/ruby-rdf/rdf-n3.png?branch=master)](http://travis-ci.org/ruby-rdf/rdf-n3) ## Description -RDF::N3 is an Notation-3 parser for Ruby using the [RDF.rb][RDF.rb] library suite. +RDF::N3 is an Notation-3 parser for Ruby using the [RDF.rb][RDF.rb] library suite. Also implements N3 Entailment. Reader inspired from TimBL predictiveParser and Python librdf implementation. @@ -14,13 +14,12 @@ Support for Turtle mime-types and specific format support has been deprecated fr as Turtle is now implemented using [RDF::Turtle][RDF::Turtle]. ## Features -RDF::N3 parses [Notation-3][N3], [Turtle][Turtle] and [N-Triples][N-Triples] into statements or triples. It also serializes to Turtle. +RDF::N3 parses [Notation-3][N3], [Turtle][Turtle] and [N-Triples][N-Triples] into statements or quads. It also performs reasoning and serializes to N3. Install with `gem install rdf-n3` ## Limitations -* Support for Variables in Formulae dependent on underlying repository. Existential variables are quantified to RDF::Node instances, Universals to RDF::Query::Variable, with the URI of the variable target used as the variable name. -* No support for N3 Reification. If there were, it would be through a :reify option to the reader. +* Support for Variables in Formulae. Existential variables are quantified to RDF::Node instances, Universals to RDF::Query::Variable, with the URI of the variable target used as the variable name. ## Usage Instantiate a reader from a local file: @@ -82,7 +81,7 @@ when turned into an RDF Repository results in the following quads _:ora _:moby _:form . _:ora "Ora" _:form . -Reasoning requires the use of the Notation3 Algebra, rather than an `RDF::Repository`. This implementation considers formulae to be patterns, which may be asserted on statements made in the default graph, possibly loaded from a separate file. The logical relationships are reduced to algebraic operators. +Reasoning uses a Notation3 Algebra, similar to [SPARQL S-Expressions](). This implementation considers formulae to be patterns, which may be asserted on statements made in the default graph, possibly loaded from a separate file. The logical relationships are reduced to algebraic operators. ### Variables N3 Variables are introduced with @forAll, @forSome, or ?x. Variables reference URIs described in formulae, typically defined in the default vocabulary (e.g., ":x"). Existential variables are replaced with an allocated RDF::Node instance. Universal variables are replaced with a RDF::Query::Variable instance. For example, the following N3 generates the associated statements: @@ -108,12 +107,8 @@ http://www.w3.org/2000/10/swap/grammar/n3.n3 (along with bnf-rules.n3) using cwm [n3-selectors.n3][file:lib/rdf/n3/reader/n3-selectors.rb] is itself used to generate meta.rb using script/build_meta. -## TODO -* Generate Formulae and solutions using BGP and SPARQL CONSTRUCT mechanisms -* Create equivalent to `--think` to iterate on solutions. - ## Dependencies -* [RDF.rb](http://rubygems.org/gems/rdf) (>= 3.0) +* [RDF.rb](http://rubygems.org/gems/rdf) (~> 3.0, >= 3.0.10) ## Documentation Full documentation available on [RubyDoc.info](http://rubydoc.info/github/ruby-rdf/rdf-n3/frames) @@ -125,16 +120,28 @@ Full documentation available on [RubyDoc.info](http://rubydoc.info/github/ruby-r * {RDF::N3::Reasoner} * {RDF::N3::Writer} * {RDF::N3::Algebra} -* {RDF::N3::Algebra::Formula} -* {RDF::N3::Algebra::LogImplies} + * {RDF::N3::Algebra::Formula} + * {RDF::N3::Algebra::ListAppend} + * {RDF::N3::Algebra::ListIn} + * {RDF::N3::Algebra::ListLast} + * {RDF::N3::Algebra::ListMember} + * {RDF::N3::Algebra::LogConclusion} + * {RDF::N3::Algebra::LogConjunction} + * {RDF::N3::Algebra::LogEqualTo} + * {RDF::N3::Algebra::LogImplies} + * {RDF::N3::Algebra::LogIncludes} + * {RDF::N3::Algebra::LogNotEqualTo} + * {RDF::N3::Algebra::LogNotIncludes} + * {RDF::N3::Algebra::LogOutputString} ### Additional vocabularies -* {RDF::LOG} -* {RDF::REI} - -### Patches -* `Array` -* `RDF::List` +* {RDF::N3::Log} +* {RDF::N3::Rei} +* {RDF::N3::Crypto} +* {RDF::N3::List} +* {RDF::N3::Math} +* {RDF::N3::Str} +* {RDF::N3::Time} ## Resources * [RDF.rb][RDF.rb] @@ -189,3 +196,4 @@ see or the accompanying {file:UNLICENSE} file. [YARD]: http://yardoc.org/ [YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md [PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html +[SPARQL S-Expressions]: https://jena.apache.org/documentation/notes/sse.html diff --git a/etc/doap.n3 b/etc/doap.n3 index 363c6cf..200ef1d 100644 --- a/etc/doap.n3 +++ b/etc/doap.n3 @@ -12,7 +12,7 @@ doap:homepage ; doap:license ; doap:shortdesc "N3 reader/writer for Ruby."@en ; - doap:description "RDF::N3 is an Notation-3 reader/writer for the RDF.rb library suite."@en ; + doap:description "RDF::N3 is an Notation-3 reader/writer and reasoner for the RDF.rb library suite."@en ; doap:created "2010-06-03"^^xsd:date; doap:programming-language "Ruby" ; doap:implements ; diff --git a/etc/doap.nt b/etc/doap.nt index 9a85eb8..cbc5a7f 100644 --- a/etc/doap.nt +++ b/etc/doap.nt @@ -2,7 +2,7 @@ "RDF::N3" . . . - "N3 reader/writer for Ruby."@en . + "N3 reader/writer reader/writer and reasoner for Ruby."@en . "RDF::N3 is an Notation-3 reader/writer for the RDF.rb library suite."@en . "2010-06-03"^^ . "Ruby" . diff --git a/lib/rdf/n3/vocab.rb b/lib/rdf/n3/vocab.rb index 90ccd48..bf0e009 100644 --- a/lib/rdf/n3/vocab.rb +++ b/lib/rdf/n3/vocab.rb @@ -4,6 +4,6 @@ class List < RDF::Vocabulary("http://www.w3.org/2000/10/swap/list#"); end class Log < RDF::Vocabulary("http://www.w3.org/2000/10/swap/log#"); end class Math < RDF::Vocabulary("http://www.w3.org/2000/10/swap/math#"); end class Rei < RDF::Vocabulary("http://www.w3.org/2000/10/swap/reify#"); end - #class String < RDF::Vocabulary("http://www.w3.org/2000/10/swap/string#"); end + class Str < RDF::Vocabulary("http://www.w3.org/2000/10/swap/string#"); end class Time < RDF::Vocabulary("http://www.w3.org/2000/10/swap/time#"); end end diff --git a/rdf-n3.gemspec b/rdf-n3.gemspec index a38c4b2..87866c3 100755 --- a/rdf-n3.gemspec +++ b/rdf-n3.gemspec @@ -8,8 +8,8 @@ Gem::Specification.new do |gem| gem.name = %q{rdf-n3} gem.homepage = %q{http://ruby-rdf.github.com/rdf-n3} gem.license = 'Unlicense' - gem.summary = %q{Notation3 reader/writer for RDF.rb.} - gem.description = %q{RDF::N3 is an Notation-3 reader/writer for the RDF.rb library suite.} + gem.summary = %q{Notation3 reader/writer and reasoner for RDF.rb.} + gem.description = %q{RDF::N3 is an Notation-3 reader/writer and reasoner for the RDF.rb library suite.} gem.authors = %w(Gregg Kellogg) gem.email = 'public-rdf-ruby@w3.org' From eccb7dd58ec105b31e57802412e6378967a40528 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 25 Jan 2019 16:54:04 -0800 Subject: [PATCH 30/40] Allocate all variables and blank nodes to the formula and use unique and increasing labels to avoid mismatch in querys. --- examples/dorthe2.n3 | 3 ++ examples/existential-universal-love.n3 | 8 +++++ examples/forAllSomeConfusion.n3 | 3 ++ lib/rdf/n3/reader.rb | 44 ++++++++++++++------------ spec/reader_spec.rb | 9 ++++++ spec/suite_helper.rb | 4 +++ spec/suite_parser_spec.rb | 2 +- spec/suite_reasoner_spec.rb | 5 ++- spec/writer_spec.rb | 23 ++++++-------- 9 files changed, 63 insertions(+), 38 deletions(-) create mode 100644 examples/dorthe2.n3 create mode 100644 examples/existential-universal-love.n3 create mode 100644 examples/forAllSomeConfusion.n3 diff --git a/examples/dorthe2.n3 b/examples/dorthe2.n3 new file mode 100644 index 0000000..6b31eff --- /dev/null +++ b/examples/dorthe2.n3 @@ -0,0 +1,3 @@ +_:x :p {_:x :b :c}. + +{?x :p {?x2 :b :c}}=>{:s :p :o}. diff --git a/examples/existential-universal-love.n3 b/examples/existential-universal-love.n3 new file mode 100644 index 0000000..6d4c368 --- /dev/null +++ b/examples/existential-universal-love.n3 @@ -0,0 +1,8 @@ +:Raymond a :Person . +:Julie a :Person; :loves :Raymond . +:Steve a :Person; :loves :Raymond . +:Nancy a :Person; :loves :Raymond . + +@forAll :g . +@forSome :h . +{:g :loves :h} => {:g a :Lover} . diff --git a/examples/forAllSomeConfusion.n3 b/examples/forAllSomeConfusion.n3 new file mode 100644 index 0000000..968d500 --- /dev/null +++ b/examples/forAllSomeConfusion.n3 @@ -0,0 +1,3 @@ +:s :p :o. +@forAll :x. {:x :p :o}=>{:x :b :c}. +@forSome :x. {:s :p :o}=>{:a :b :x}. diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 58de08c..2d49619 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -66,6 +66,8 @@ def initialize(input = $stdin, options = {}, &block) @formulae = [] # Nodes used as Formulae graph names @formulae_nodes = {} + @label_uniquifier ||= "#{Random.new_seed}_000000" + @bnodes = {} # allocated bnodes by formula @variables = {} # allocated variables by formula if options[:base_uri] @@ -344,15 +346,14 @@ def pathitemToken(prod, tok) add_prod_data(:symbol, symbol) when "{" # A new formula, push on a node as a named graph - # XXX use existential if embedded? - node = RDF::Node.new + node = RDF::Node.new(".form_#{unique_label}") @formulae << node @formulae_nodes[node] = true # Promote variables defined on the earlier formula to this formula @variables[node] = {} - @variables[@formulae[-2]].each do |name, vars| - @variables[node][name] = vars.dup + @variables[@formulae[-2]].each do |name, var| + @variables[node][name] = var end when "}" # Pop off the formula @@ -612,7 +613,7 @@ def process_qname(tok) # old parser encountering true or false naked or in a @keywords return RDF::Literal.new(tok, datatype: RDF::XSD.boolean) else - error("Set user @keywords to use barenames.") + error("Set user @keywords to use barenames (#{tok}).") end uri = if prefix(prefix) @@ -621,6 +622,7 @@ def process_qname(tok) elsif prefix == '_' log_debug('process_qname(bnode)', name, depth: depth) # If we're in a formula, create a non-distigushed variable instead + # Note from https://www.w3.org/TeamSubmission/n3/#bnodes, it seems the blank nodes are scoped to the formula, not the file. bnode(name) else log_debug('process_qname(default_ns)', name, depth: depth) @@ -643,23 +645,20 @@ def add_prod_data(sym, value) end end - # Keep track of allocated BNodes - def bnode(value = nil) - if value - @bnode_cache ||= {} - @bnode_cache[value.to_s] ||= RDF::Node.new(value) + # Keep track of allocated BNodes. Blank nodes are allocated to the formula. + def bnode(label = nil) + if label + value = "#{label}_#{unique_label}" + (@bnodes[@formulae.last] ||= {})[label.to_s] ||= RDF::Node.new(value) else RDF::Node.new end end def univar(label, distinguished: true) - unless label - @unnamed_label ||= "var0" - label = @unnamed_label = @unnamed_label.succ - end - #label = "_:#{label}" unless distinguished || label.start_with?('_:') - RDF::Query::Variable.new(label.to_s, distinguished: distinguished) + # Label using any provided label, followed by seed, followed by incrementing index + value = "#{label}_#{unique_label}" + RDF::Query::Variable.new(value, distinguished: distinguished) end # add a pattern or statement @@ -719,22 +718,27 @@ def ns(prefix, suffix) uri(base + suffix.to_s) end + # Returns a unique label + def unique_label + label, @label_uniquifier = @label_uniquifier, @label_uniquifier.succ + label + end + # Find any variable that may be defined in the formula identified by `bn` # @param [RDF::Node] bn name of formula # @param [#to_s] name # @return [RDF::Query::Variable] def find_var(sym, name) - (@variables[sym] ||= {}).fetch(name.to_s, []).last + (@variables[sym] ||= {})[name.to_s] end - # Add a variable to the formula identified by `bn`, returning the formula + # Add a variable to the formula identified by `bn`, returning the variable. Useful as an LRU for variable name lookups # @param [RDF::Node] bn name of formula # @param [#to_s] name of variable for lookup # @param [RDF::Query::Variable] var # @return [RDF::Query::Variable] def add_var_to_formula(bn, name, var) - ((@variables[bn] ||= {})[name.to_s] ||= []) << var - var + (@variables[bn] ||= {})[name.to_s] = var end end end diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 9e81890..4cf9e34 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -924,6 +924,15 @@ expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end + it "creates unique bnodes within different formula" do + n3 = %( + _:a a :Thing . + {_:a a :Thing} => {_:a a :Thing} + ) + result = parse(n3, repo: @repo, base_uri: "http://a/b") + expect(result.statements.uniq.length).to eq 4 + end + context "contexts" do before(:each) do n3 = %( diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 46ed8a6..18c826b 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -149,6 +149,10 @@ def evaluate? !!attributes['@type'].to_s.match(/Eval/) end + def reason? + !!attributes['@type'].to_s.match(/Reason/) + end + def syntax? !!attributes['@type'].to_s.match(/Syntax/) end diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index 39d6169..d3402f2 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -9,7 +9,7 @@ Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-parser.n3") do |m| describe m.label do m.entries.each do |t| - specify "#{t.name}: #{t.description}" do + specify "#{t.name}: #{t.comment}" do case t.name when *%w(n3_10004 n3_10007 n3_10014 n3_10015 n3_10017) pending("Reification not supported") diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index e99294a..1642113 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -9,7 +9,7 @@ Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-reasoner.n3") do |m| describe m.label do m.entries.each do |t| - specify "#{t.name}: #{t.description}" do + specify "#{t.name}: #{t.comment}" do case t.id.split('#').last when *%w{listin bnode concat t2006} pending "support for lists" @@ -51,7 +51,6 @@ begin if t.options["think"] reasoner.execute(logger: t.logger, think: t.options['think']) - reasoner.reason!(logger: t.logger, think: t.options['think']) if t.options["conclusions"] repo << reasoner.conclusions elsif t.options["data"] @@ -65,7 +64,7 @@ rescue Exception => e expect(e.message).to produce("Not exception #{e.inspect}: #{e.backtrace.join("\n")}", t) end - if t.evaluate? + if t.evaluate? || t.reason? output_repo = RDF::Repository.load(t.result, format: :n3, base_uri: t.base) expect(repo).to be_equivalent_graph(output_repo, t) else diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 0446c49..111c120 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -112,12 +112,7 @@ }, "reuses BNode labels by default" => { input: %(@prefix ex: . _:a ex:b _:a .), - regexp: [%r(^\s*_:a ex:b _:a \.$)] - }, - "generated BNodes with :unique_bnodes" => { - input: %(@prefix ex: . _:a ex:b _:a .), - regexp: [%r(^\s*_:g\w+ ex:b _:g\w+ \.$)], - unique_bnodes: true + regexp: [%r(^\s*_:a(_\d+_\d+) ex:b _:a\1 \.$)] }, "standard prefixes" => { input: %( @@ -510,9 +505,9 @@ ] }, "implies" => { - input: %({ _:x :is :happy } => {_:x :is :happy } .), + input: %({ _:x :is _:x } => {_:x :is _:x } .), regexp: [ - %r({\s+_:x :is :happy \.\s+} => {\s+_:x :is :happy \.\s+} \.)m + %r({\s+_:x(_\d+_\d+) :is _:x\1 \.\s+} => {\s+_:x(_\d+_\d+) :is _:x\2 \.\s+} \.)m ] }, "formula simple" => { @@ -579,22 +574,22 @@ "@forAll": { input: %(@forAll :o. :s :p :o .), regexp: [ - %r(@forAll :o \.), - %r(:s :p :o \.), + %r(@forAll :o_\d+_\d+ \.), + %r(:s :p :o_\d+_\d+ \.), ] }, "@forSome": { input: %(@forSome :o. :s :p :o .), regexp: [ - %r(@forSome :o \.), - %r(:s :p :o \.), + %r(@forSome :o_\d+_\d+ \.), + %r(:s :p :o_\d+_\d+ \.), ] }, "?o": { input: %(:s :p ?o .), regexp: [ - %r(@forAll :o \.), - %r(:s :p :o \.), + %r(@forAll :o_\d+_\d+ \.), + %r(:s :p :o_\d+_\d+ \.), ] }, }.each do |name, params| From e2dfc7af10d8ffc9395763c661b2eb2d71deaf2e Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 27 Jan 2019 11:57:04 -0800 Subject: [PATCH 31/40] Don't collection bindings from sub-operands of formula. --- Gemfile | 3 +- lib/rdf/n3/algebra/formula.rb | 50 ++++++++++++++++++-------------- lib/rdf/n3/algebra/logImplies.rb | 29 ++++++++++++------ lib/rdf/n3/extensions.rb | 43 +++++++++++++++++++++++++++ lib/rdf/n3/reasoner.rb | 14 +++++++-- spec/suite_reasoner_spec.rb | 25 ++++++---------- 6 files changed, 114 insertions(+), 50 deletions(-) diff --git a/Gemfile b/Gemfile index 60de3c8..71bcd52 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,8 @@ source "https://rubygems.org" gemspec -gem "rdf", git: "https://github.com/ruby-rdf/rdf", branch: "develop" +#gem "rdf", git: "https://github.com/ruby-rdf/rdf", branch: "develop" +gem "rdf", path: '../rdf' group :development do gem "rdf-spec", git: "https://github.com/ruby-rdf/rdf-spec", branch: "develop" diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 103668c..2126f0a 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -29,24 +29,21 @@ class Formula < SPARQL::Algebra::Operator # @return [RDF::Solutions] distinct solutions def execute(queryable, **options, &block) log_debug {"formula #{graph_name} #{operands.to_sxp}"} + log_debug {"(formula bindings) #{options.fetch(:bindings, {}).map {|k,v| RDF::Query::Variable.new(k,v)}.to_sxp}"} - @query ||= RDF::Query.new(patterns) - @solutions = queryable.query(@query, **options) + # Only query as patterns if this is an embedded formula + @query ||= RDF::Query.new(patterns).optimize! + @solutions = @query.patterns.empty? ? RDF::Query::Solutions.new : queryable.query(@query, **options) + @solutions.distinct! + # Use our solutions for sub-ops, without binding solutions from those sub-ops # Join solutions from other operands log_depth do sub_ops.each do |op| - old_solutions, @solutions = @solutions, RDF::Query::Solutions.new - - ## XXX consider if this scope introduces new variables with same names - op.execute(queryable, bindings: old_solutions.bindings) do |soln| - log_debug {"(formula execute object) => #{soln.inspect}"} - @solutions << soln - end + op.execute(queryable, bindings: @solutions.bindings) end end - @solutions.distinct! - log_debug {"(formula solutions) #{@solutions.inspect}"} + log_debug {"(formula solutions) #{@solutions.to_sxp}"} @solutions end @@ -66,7 +63,7 @@ def each(&block) )] ) end - log_debug {"formula #{graph_name} each #{@solutions.inspect}"} + log_debug {"formula #{graph_name} each #{@solutions.to_sxp}"} # Yield constant statements/patterns constants.each do |pattern| @@ -76,7 +73,7 @@ def each(&block) # Yield patterns by binding variables @solutions.each do |solution| - log_debug {"(formula apply) #{solution.inspect} to BGP"} + log_debug {"(formula apply) #{solution.to_sxp} to BGP"} # Yield each variable statement which is constant after applying solution patterns.each do |pattern| terms = {} @@ -125,15 +122,20 @@ def statements select {|op| op.is_a?(RDF::Statement)}. map do |pattern| - terms = {} - [:subject, :predicate, :object].each do |r| - terms[r] = case o = pattern.send(r) - when RDF::Node then RDF::Query::Variable.new(o.id, distinguished: false) - else o + # Map nodes to variables (except when in top-level formula) + if graph_name + terms = {} + [:subject, :predicate, :object].each do |r| + terms[r] = case o = pattern.send(r) + when RDF::Node then RDF::Query::Variable.new(o.id, distinguished: false) + else o + end end - end - RDF::Query::Pattern.from(terms) + RDF::Query::Pattern.from(terms) + else + RDF::Query::Pattern.from(pattern) + end end end @@ -154,10 +156,16 @@ def patterns ## # Non-statement operands memoizer def sub_ops - # BNodes in statements are existential variables + # operands that aren't statements, ordered by their graph_name @sub_ops ||= operands.reject {|op| op.is_a?(RDF::Statement)} end + ## + # Reorder operands by graph_name or subject + def reorder_operands! + @operands = @operands.sort_by {|op| op.graph_name || op.subject} + end + def to_sxp_bin @existentials = ndvars.uniq @universals = vars.uniq - @existentials diff --git a/lib/rdf/n3/algebra/logImplies.rb b/lib/rdf/n3/algebra/logImplies.rb index 3d9c72b..c12798c 100644 --- a/lib/rdf/n3/algebra/logImplies.rb +++ b/lib/rdf/n3/algebra/logImplies.rb @@ -27,9 +27,10 @@ class LogImplies < SPARQL::Algebra::Operator::Binary # @yieldreturn [void] ignored # @return [RDF::Solutions] distinct solutions def execute(queryable, **options, &block) - log_debug {"logImplies #{operands.to_sxp}"} + @queryable = queryable + log_debug {"logImplies #{graph_name}"} @solutions = log_depth {operands.first.execute(queryable, **options, &block)} - log_debug {"(logImplies solutions) #{@solutions.inspect}"} + log_debug {"(logImplies solutions) #{@solutions.to_sxp}"} @solutions end @@ -42,21 +43,31 @@ def execute(queryable, **options, &block) # @yieldreturn [void] ignored def each(&block) @solutions ||= RDF::Query::Solutions.new - log_debug {"logImplies each #{@solutions.inspect}"} - _, object = operands + log_debug {"logImplies #{graph_name} each #{@solutions.to_sxp}"} + subject, object = operands + + # Graph based on solutions from subject + subject_graph = log_depth {RDF::Graph.new {|g| g << subject}} # Use solutions from subject for object - log_depth do - object.solutions = @solutions + object.solutions = @solutions - # Nothing emitted if @solutions is not complete. Solutions are complete when all variables are bound. - if !object.solutions.empty? - # Yield statements into the default graph + # Nothing emitted if @solutions is not complete. Solutions are complete when all variables are bound. + if @queryable.contain?(subject_graph) + log_debug("(logImplies implication true)") + # Yield statements into the default graph + log_depth do object.each do |statement| block.call(RDF::Statement.from(statement.to_triple, inferred: true)) end end + else + log_debug("(logImplies implication false)") end end + + # Graph name associated with this operation, using the name of the first operand + # @return [RDF::Resource] + def graph_name; operands.first {|o| o.respond_to?(:graph_name)}.graph_name; end end end diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index a9623e9..ea366c4 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -11,6 +11,15 @@ module Enumerable # Universal quantifiers defined on this enumerable # @return [Array] attr_accessor :universals + + ## + # An enumerable contains another enumerable if every statement in other is a statement in self + # + # @param [RDF::Enumerable] other + # @return [Boolean] + def contain?(other) + other.all? {|statement| has_statement?(statement)} + end end class Statement @@ -33,4 +42,38 @@ def to_sxp to_sxp_bin.to_sxp end end + + class Query::Solution + + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + [:solution] + bindings.map {|k, v| Query::Variable.new(k, v).to_sxp_bin} + end + + ## + # Returns an S-Expression (SXP) representation + # + # @return [String] + def to_sxp + to_sxp_bin.to_sxp + end + end + + class Query::Variable + + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + [:var, name, (value.to_sxp_bin if value)].compact + end + + ## + # Returns an S-Expression (SXP) representation + # + # @return [String] + def to_sxp + to_sxp_bin.to_sxp + end + end end diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 8debc56..2491303 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -96,7 +96,7 @@ def insert_statement(statement) end ## - # Updates the datastore by reasoning over the formula, optionally yielding each conclusion + # Updates the datastore by reasoning over the formula, optionally yielding each conclusion; uses triples from the graph associated with this formula as the dataset over which to reason. # # @param [Hash{Symbol => Object}] options # @option options [Boolean] :apply @@ -113,14 +113,16 @@ def execute(**options, &block) log_info("reasoner: think start") { "count: #{count}"} while @mutable.count > count count = @mutable.count - log_depth {formula.execute(@mutable, **options)} + dataset = RDF::Graph.new << @mutable.project_graph(nil) + log_depth {formula.execute(dataset, **options)} @mutable << formula end log_info("reasoner: think end") { "count: #{count}"} else # Run one iteration log_info("reasoner: apply start") { "count: #{count}"} - log_depth {formula.execute(@mutable, **options)} + dataset = RDF::Graph.new << @mutable.project_graph(nil) + log_depth {formula.execute(dataset, **options)} @mutable << formula log_info("reasoner: apply end") { "count: #{count}"} end @@ -271,6 +273,12 @@ def formula end end + # Order operands in each formula by either the graph_name or subject + formulae.each_value do |operator| + next unless operator.is_a?(Algebra::Formula) + operator.reorder_operands! + end + # Formula is that without a graph name formulae[nil] end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 1642113..1278021 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -15,8 +15,6 @@ pending "support for lists" when *%w{t1018b2 t103 t104 t105 concat} pending "support for string" - when *%w{t2005 t555} - pending "understanding output filtering" when *%w{t06proof} pending "support for math" when *%w{t01} @@ -25,9 +23,9 @@ pending "support for log:conclusion" when *%w{conjunction} pending "support for log:conjunction" - when *%w{t553 t554} + when *%w{t553} pending "support for inference over quoted graphs" - when *%w{norm} + when *%w{t2005} pending "something else" end @@ -38,8 +36,7 @@ reader = RDF::N3::Reader.new(t.input, base_uri: t.base, canonicalize: false, - validate: false, - logger: t.logger) + validate: false) reasoner = RDF::N3::Reasoner.new(reader, base_uri: t.base, @@ -49,17 +46,13 @@ if t.positive_test? begin - if t.options["think"] - reasoner.execute(logger: t.logger, think: t.options['think']) - if t.options["conclusions"] - repo << reasoner.conclusions - elsif t.options["data"] - repo << reasoner.data - else - repo << reasoner - end + reasoner.execute(logger: t.logger, think: !!t.options['think']) + if t.options["filter"] + repo << reasoner.conclusions + elsif t.options["data"] + repo << reasoner.data else - repo << reader + repo << reasoner end rescue Exception => e expect(e.message).to produce("Not exception #{e.inspect}: #{e.backtrace.join("\n")}", t) From 6381e1ae411d398f357d85c211a749d3e1a01e90 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 27 Jan 2019 18:59:56 -0800 Subject: [PATCH 32/40] Reorganize algebra classes into modules/directories.Add template files for Math and String operators. --- examples/forAllSomeConfusion2.n3 | 7 + lib/rdf/n3/algebra.rb | 134 ++++++++++++++---- lib/rdf/n3/algebra/list/append.rb | 13 ++ lib/rdf/n3/algebra/list/in.rb | 9 ++ lib/rdf/n3/algebra/list/last.rb | 11 ++ lib/rdf/n3/algebra/list/member.rb | 7 + lib/rdf/n3/algebra/listAppend.rb | 9 -- lib/rdf/n3/algebra/listIn.rb | 9 -- lib/rdf/n3/algebra/listLast.rb | 9 -- lib/rdf/n3/algebra/listMember.rb | 7 - .../{logConclusion.rb => log/conclusion.rb} | 4 +- .../{logConjunction.rb => log/conjunction.rb} | 4 +- .../algebra/{logEqualTo.rb => log/equalTo.rb} | 4 +- .../algebra/{logImplies.rb => log/implies.rb} | 4 +- .../{logIncludes.rb => log/includes.rb} | 4 +- .../{logNotEqualTo.rb => log/notEqualTo.rb} | 4 +- .../{logNotIncludes.rb => log/notIncludes.rb} | 4 +- .../outputString.rb} | 4 +- lib/rdf/n3/algebra/math/absoluteValue.rb | 9 ++ lib/rdf/n3/algebra/math/difference.rb | 9 ++ lib/rdf/n3/algebra/math/equalTo.rb | 9 ++ lib/rdf/n3/algebra/math/exponentiation.rb | 9 ++ lib/rdf/n3/algebra/math/greaterThan.rb | 9 ++ lib/rdf/n3/algebra/math/integerQuotient.rb | 9 ++ lib/rdf/n3/algebra/math/lessThan.rb | 9 ++ lib/rdf/n3/algebra/math/memberCount.rb | 9 ++ lib/rdf/n3/algebra/math/negation.rb | 9 ++ lib/rdf/n3/algebra/math/notEqualTo.rb | 9 ++ lib/rdf/n3/algebra/math/notGreaterThan.rb | 9 ++ lib/rdf/n3/algebra/math/notLessThan.rb | 9 ++ lib/rdf/n3/algebra/math/product.rb | 9 ++ lib/rdf/n3/algebra/math/quotient.rb | 9 ++ lib/rdf/n3/algebra/math/remainder.rb | 9 ++ lib/rdf/n3/algebra/math/rounded.rb | 9 ++ lib/rdf/n3/algebra/math/sum.rb | 9 ++ lib/rdf/n3/algebra/str/concatenation.rb | 9 ++ lib/rdf/n3/algebra/str/contains.rb | 9 ++ .../n3/algebra/str/containsIgnoringCase.rb | 9 ++ lib/rdf/n3/algebra/str/endsWith.rb | 9 ++ lib/rdf/n3/algebra/str/equalIgnoringCase.rb | 9 ++ lib/rdf/n3/algebra/str/format.rb | 9 ++ lib/rdf/n3/algebra/str/greaterThan.rb | 9 ++ lib/rdf/n3/algebra/str/lessThan.rb | 9 ++ lib/rdf/n3/algebra/str/matches.rb | 9 ++ .../n3/algebra/str/notEqualIgnoringCase.rb | 9 ++ lib/rdf/n3/algebra/str/notGreaterThan.rb | 9 ++ lib/rdf/n3/algebra/str/notLessThan.rb | 9 ++ lib/rdf/n3/algebra/str/notMatches.rb | 9 ++ lib/rdf/n3/algebra/str/replace.rb | 12 ++ lib/rdf/n3/algebra/str/scrape.rb | 9 ++ lib/rdf/n3/algebra/str/startsWith.rb | 9 ++ spec/suite_reasoner_spec.rb | 4 +- 52 files changed, 471 insertions(+), 80 deletions(-) create mode 100644 examples/forAllSomeConfusion2.n3 create mode 100644 lib/rdf/n3/algebra/list/append.rb create mode 100644 lib/rdf/n3/algebra/list/in.rb create mode 100644 lib/rdf/n3/algebra/list/last.rb create mode 100644 lib/rdf/n3/algebra/list/member.rb delete mode 100644 lib/rdf/n3/algebra/listAppend.rb delete mode 100644 lib/rdf/n3/algebra/listIn.rb delete mode 100644 lib/rdf/n3/algebra/listLast.rb delete mode 100644 lib/rdf/n3/algebra/listMember.rb rename lib/rdf/n3/algebra/{logConclusion.rb => log/conclusion.rb} (84%) rename lib/rdf/n3/algebra/{logConjunction.rb => log/conjunction.rb} (76%) rename lib/rdf/n3/algebra/{logEqualTo.rb => log/equalTo.rb} (71%) rename lib/rdf/n3/algebra/{logImplies.rb => log/implies.rb} (97%) rename lib/rdf/n3/algebra/{logIncludes.rb => log/includes.rb} (87%) rename lib/rdf/n3/algebra/{logNotEqualTo.rb => log/notEqualTo.rb} (60%) rename lib/rdf/n3/algebra/{logNotIncludes.rb => log/notIncludes.rb} (82%) rename lib/rdf/n3/algebra/{logOutputString.rb => log/outputString.rb} (64%) create mode 100644 lib/rdf/n3/algebra/math/absoluteValue.rb create mode 100644 lib/rdf/n3/algebra/math/difference.rb create mode 100644 lib/rdf/n3/algebra/math/equalTo.rb create mode 100644 lib/rdf/n3/algebra/math/exponentiation.rb create mode 100644 lib/rdf/n3/algebra/math/greaterThan.rb create mode 100644 lib/rdf/n3/algebra/math/integerQuotient.rb create mode 100644 lib/rdf/n3/algebra/math/lessThan.rb create mode 100644 lib/rdf/n3/algebra/math/memberCount.rb create mode 100644 lib/rdf/n3/algebra/math/negation.rb create mode 100644 lib/rdf/n3/algebra/math/notEqualTo.rb create mode 100644 lib/rdf/n3/algebra/math/notGreaterThan.rb create mode 100644 lib/rdf/n3/algebra/math/notLessThan.rb create mode 100644 lib/rdf/n3/algebra/math/product.rb create mode 100644 lib/rdf/n3/algebra/math/quotient.rb create mode 100644 lib/rdf/n3/algebra/math/remainder.rb create mode 100644 lib/rdf/n3/algebra/math/rounded.rb create mode 100644 lib/rdf/n3/algebra/math/sum.rb create mode 100644 lib/rdf/n3/algebra/str/concatenation.rb create mode 100644 lib/rdf/n3/algebra/str/contains.rb create mode 100644 lib/rdf/n3/algebra/str/containsIgnoringCase.rb create mode 100644 lib/rdf/n3/algebra/str/endsWith.rb create mode 100644 lib/rdf/n3/algebra/str/equalIgnoringCase.rb create mode 100644 lib/rdf/n3/algebra/str/format.rb create mode 100644 lib/rdf/n3/algebra/str/greaterThan.rb create mode 100644 lib/rdf/n3/algebra/str/lessThan.rb create mode 100644 lib/rdf/n3/algebra/str/matches.rb create mode 100644 lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb create mode 100644 lib/rdf/n3/algebra/str/notGreaterThan.rb create mode 100644 lib/rdf/n3/algebra/str/notLessThan.rb create mode 100644 lib/rdf/n3/algebra/str/notMatches.rb create mode 100644 lib/rdf/n3/algebra/str/replace.rb create mode 100644 lib/rdf/n3/algebra/str/scrape.rb create mode 100644 lib/rdf/n3/algebra/str/startsWith.rb diff --git a/examples/forAllSomeConfusion2.n3 b/examples/forAllSomeConfusion2.n3 new file mode 100644 index 0000000..2c61b81 --- /dev/null +++ b/examples/forAllSomeConfusion2.n3 @@ -0,0 +1,7 @@ +@forSome :x. +:x a :Person. +@forAll :y.{:y a :Person.}=>{:y :loves :x.}. + +:Julie a :Person . +:Steve a :Person. +:Nancy a :Person. diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index d37a63b..8b4300c 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -7,37 +7,115 @@ module RDF::N3 # # @author [Gregg Kellogg](http://greggkellogg.net/) module Algebra - autoload :Formula, 'rdf/n3/algebra/formula' - - autoload :ListAppend, 'rdf/n3/algebra/listAppend' - autoload :ListIn, 'rdf/n3/algebra/listIn' - autoload :ListLast, 'rdf/n3/algebra/listLast' - autoload :ListMember, 'rdf/n3/algebra/listMember' - - autoload :LogConclusion, 'rdf/n3/algebra/logConclusion' - autoload :LogConjunction, 'rdf/n3/algebra/logConjunction' - autoload :LogEqualTo, 'rdf/n3/algebra/logEqualTo' - autoload :LogImplies, 'rdf/n3/algebra/logImplies' - autoload :LogIncludes, 'rdf/n3/algebra/logIncludes' - autoload :LogNotEqualTo, 'rdf/n3/algebra/logNotEqualTo' - autoload :LogNotIncludes, 'rdf/n3/algebra/logNotIncludes' - autoload :LogOutputString, 'rdf/n3/algebra/logOutputString' + autoload :Formula, 'rdf/n3/algebra/formula' + + module List + autoload :Append, 'rdf/n3/algebra/list/append' + autoload :In, 'rdf/n3/algebra/list/in' + autoload :Last, 'rdf/n3/algebra/list/last' + autoload :Member, 'rdf/n3/algebra/list/member' + end + + module Log + autoload :Conclusion, 'rdf/n3/algebra/log/conclusion' + autoload :Conjunction, 'rdf/n3/algebra/log/conjunction' + autoload :EqualTo, 'rdf/n3/algebra/log/equalTo' + autoload :Implies, 'rdf/n3/algebra/log/implies' + autoload :Includes, 'rdf/n3/algebra/log/includes' + autoload :NotEqualTo, 'rdf/n3/algebra/log/notEqualTo' + autoload :NotIncludes, 'rdf/n3/algebra/log/notIncludes' + autoload :OutputString, 'rdf/n3/algebra/log/outputString' + end + + module Math + autoload :AbsoluteValue, 'rdf/n3/algebra/math/absoluteValue' + autoload :Difference, 'rdf/n3/algebra/math/difference' + autoload :EqualTo, 'rdf/n3/algebra/math/equalTo' + autoload :Exponentiation, 'rdf/n3/algebra/math/exponentiation' + autoload :GreaterThan, 'rdf/n3/algebra/math/greaterThan' + autoload :IntegerQuotient, 'rdf/n3/algebra/math/integerQuotient' + autoload :LessThan, 'rdf/n3/algebra/math/lessThan' + autoload :MemberCount, 'rdf/n3/algebra/math/memberCount' + autoload :Negation, 'rdf/n3/algebra/math/negation' + autoload :NotEqualTo, 'rdf/n3/algebra/math/notEqualTo' + autoload :NotGreaterThan, 'rdf/n3/algebra/math/notGreaterThan' + autoload :NotLessThan, 'rdf/n3/algebra/math/notLessThan' + autoload :Product, 'rdf/n3/algebra/math/product' + autoload :Quotient, 'rdf/n3/algebra/math/quotient' + autoload :Remainder, 'rdf/n3/algebra/math/remainder' + autoload :Rounded, 'rdf/n3/algebra/math/rounded' + autoload :Sum, 'rdf/n3/algebra/math/sum' + end + + module Str + autoload :Concatenation, 'rdf/n3/algebra/str/concatenation' + autoload :Contains, 'rdf/n3/algebra/str/contains' + autoload :ContainsIgnoringCase, 'rdf/n3/algebra/str/containsIgnoringCase' + autoload :EndsWith, 'rdf/n3/algebra/str/endsWith' + autoload :EqualIgnoringCase, 'rdf/n3/algebra/str/equalIgnoringCase' + autoload :Format, 'rdf/n3/algebra/str/format' + autoload :GreaterThan, 'rdf/n3/algebra/str/greaterThan' + autoload :LessThan, 'rdf/n3/algebra/str/lessThan' + autoload :Matches, 'rdf/n3/algebra/str/matches' + autoload :NotEqualIgnoringCase, 'rdf/n3/algebra/str/notEqualIgnoringCase' + autoload :NotGreaterThan, 'rdf/n3/algebra/str/notGreaterThan' + autoload :NotLessThan, 'rdf/n3/algebra/str/notLessThan' + autoload :NotMatches, 'rdf/n3/algebra/str/notMatches' + autoload :Replace, 'rdf/n3/algebra/str/replace' + autoload :Scrape, 'rdf/n3/algebra/str/scrape' + autoload :StartsWith, 'rdf/n3/algebra/str/startsWith' + end def for(uri) { - RDF::N3::List.append => ListAppend, - RDF::N3::List.in => ListIn, - RDF::N3::List.last => ListLast, - RDF::N3::List.member => ListMember, - - RDF::N3::Log.conclusion => LogConclusion, - RDF::N3::Log.conjunction => LogConjunction, - RDF::N3::Log.equalTo => LogEqualTo, - RDF::N3::Log.implies => LogImplies, - RDF::N3::Log.includes => LogIncludes, - RDF::N3::Log.notEqualTo => LogNotEqualTo, - RDF::N3::Log.notIncludes => LogNotIncludes, - RDF::N3::Log.outputString => LogOutputString, + RDF::N3::List.append => List::Append, + RDF::N3::List.in => List::In, + RDF::N3::List.last => List::Last, + RDF::N3::List.member => List::Member, + + RDF::N3::Log.conclusion => Log::Conclusion, + RDF::N3::Log.conjunction => Log::Conjunction, + RDF::N3::Log.equalTo => Log::EqualTo, + RDF::N3::Log.implies => Log::Implies, + RDF::N3::Log.includes => Log::Includes, + RDF::N3::Log.notEqualTo => Log::NotEqualTo, + RDF::N3::Log.notIncludes => Log::NotIncludes, + RDF::N3::Log.outputString => Log::OutputString, + + RDF::N3::Math.absoluteValue => Math::AbsoluteValue, + RDF::N3::Math.difference => Math::Difference, + RDF::N3::Math.equalTo => Math::EqualTo, + RDF::N3::Math.exponentiation => Math::Exponentiation, + RDF::N3::Math.greaterThan => Math::GreaterThan, + RDF::N3::Math.integerQuotient => Math::IntegerQuotient, + RDF::N3::Math.lessThan => Math::LessThan, + RDF::N3::Math.memberCount => Math::MemberCount, + RDF::N3::Math.negation => Math::Negation, + RDF::N3::Math.notEqualTo => Math::NotEqualTo, + RDF::N3::Math.notGreaterThan => Math::NotGreaterThan, + RDF::N3::Math.notLessThan => Math::NotLessThan, + RDF::N3::Math.product => Math::Product, + RDF::N3::Math.quotient => Math::Quotient, + RDF::N3::Math.remainder => Math::Remainder, + RDF::N3::Math.rounded => Math::Rounded, + RDF::N3::Math.sum => Math::Sum, + + RDF::N3::Str.concatenation => Str::Concatenation, + RDF::N3::Str.contains => Str::Contains, + RDF::N3::Str.containsIgnoringCase => Str::ContainsIgnoringCase, + RDF::N3::Str.endsWith => Str::EndsWith, + RDF::N3::Str.equalIgnoringCase => Str::EqualIgnoringCase, + RDF::N3::Str.format => Str::Format, + RDF::N3::Str.greaterThan => Str::GreaterThan, + RDF::N3::Str.lessThan => Str::LessThan, + RDF::N3::Str.matches => Str::Matches, + RDF::N3::Str.notEqualIgnoringCase => Str::NotEqualIgnoringCase, + RDF::N3::Str.notGreaterThan => Str::NotGreaterThan, + RDF::N3::Str.notLessThan => Str::NotLessThan, + RDF::N3::Str.notMatches => Str::NotMatches, + RDF::N3::Str.replace => Str::Replace, + RDF::N3::Str.scrape => Str::Scrape, + RDF::N3::Str.startsWith => Str::StartsWith, }[uri] end module_function :for diff --git a/lib/rdf/n3/algebra/list/append.rb b/lib/rdf/n3/algebra/list/append.rb new file mode 100644 index 0000000..d9be709 --- /dev/null +++ b/lib/rdf/n3/algebra/list/append.rb @@ -0,0 +1,13 @@ +module RDF::N3::Algebra::List + ## + # Iff the subject is a list of lists and the concatenation of all those lists is the object, then this is true. + # @example + # ( (1 2) (3 4) ) list:append (1 2 3 4). + # + # The object can be calculated as a function of the subject. + class Append < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :listAppend + end +end diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb new file mode 100644 index 0000000..10aed00 --- /dev/null +++ b/lib/rdf/n3/algebra/list/in.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::List + ## + # Iff the object is a list and the subject is in that list, then this is true. + class In < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :listIn + end +end diff --git a/lib/rdf/n3/algebra/list/last.rb b/lib/rdf/n3/algebra/list/last.rb new file mode 100644 index 0000000..301f655 --- /dev/null +++ b/lib/rdf/n3/algebra/list/last.rb @@ -0,0 +1,11 @@ +module RDF::N3::Algebra::List + ## + # Iff the suject is a list and the obbject is the last thing that list, then this is true. + # + # The object can be calculated as a function of the list. + class Last < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :listLast + end +end diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb new file mode 100644 index 0000000..9633743 --- /dev/null +++ b/lib/rdf/n3/algebra/list/member.rb @@ -0,0 +1,7 @@ +module RDF::N3::Algebra::List + ## + # Iff the subject is a list and the obbject is in that list, then this is true. + class Member < SPARQL::Algebra::Operator::Binary + NAME = :listMember + end +end diff --git a/lib/rdf/n3/algebra/listAppend.rb b/lib/rdf/n3/algebra/listAppend.rb deleted file mode 100644 index 068aae7..0000000 --- a/lib/rdf/n3/algebra/listAppend.rb +++ /dev/null @@ -1,9 +0,0 @@ -module RDF::N3::Algebra - ## - # Any statement mentioning anything in this class is considered boring and purged by the cwm --purge option. This is a convenience, and does not have any value when published as a general fact on the web. - class ListAppend < SPARQL::Algebra::Operator::Binary - include RDF::Util::Logger - - NAME = :listAppend - end -end diff --git a/lib/rdf/n3/algebra/listIn.rb b/lib/rdf/n3/algebra/listIn.rb deleted file mode 100644 index c5a0fd4..0000000 --- a/lib/rdf/n3/algebra/listIn.rb +++ /dev/null @@ -1,9 +0,0 @@ -module RDF::N3::Algebra - ## - # Any statement mentioning anything in this class is considered boring and purged by the cwm --purge option. This is a convenience, and does not have any value when published as a general fact on the web. - class ListIn < SPARQL::Algebra::Operator::Binary - include RDF::Util::Logger - - NAME = :listIn - end -end diff --git a/lib/rdf/n3/algebra/listLast.rb b/lib/rdf/n3/algebra/listLast.rb deleted file mode 100644 index f6c4541..0000000 --- a/lib/rdf/n3/algebra/listLast.rb +++ /dev/null @@ -1,9 +0,0 @@ -module RDF::N3::Algebra - ## - # Any statement mentioning anything in this class is considered boring and purged by the cwm --purge option. This is a convenience, and does not have any value when published as a general fact on the web. - class ListLast < SPARQL::Algebra::Operator::Binary - include RDF::Util::Logger - - NAME = :listLast - end -end diff --git a/lib/rdf/n3/algebra/listMember.rb b/lib/rdf/n3/algebra/listMember.rb deleted file mode 100644 index 3723eb3..0000000 --- a/lib/rdf/n3/algebra/listMember.rb +++ /dev/null @@ -1,7 +0,0 @@ -module RDF::N3::Algebra - ## - # Any statement mentioning anything in this class is considered boring and purged by the cwm --purge option. This is a convenience, and does not have any value when published as a general fact on the web. - class ListMember < SPARQL::Algebra::Operator::Binary - NAME = :listMember - end -end diff --git a/lib/rdf/n3/algebra/logConclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb similarity index 84% rename from lib/rdf/n3/algebra/logConclusion.rb rename to lib/rdf/n3/algebra/log/conclusion.rb index 29f036b..569fe4e 100644 --- a/lib/rdf/n3/algebra/logConclusion.rb +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -1,9 +1,9 @@ -module RDF::N3::Algebra +module RDF::N3::Algebra::Log ## # All possible conclusions which can be drawn from a formula. # # The object of this function, a formula, is the set of conclusions which can be drawn from the subject formula, by successively applying any rules it contains to the data it contains. This is equivalent to cwm's "--think" command line function. It does use built-ins, so it may for example indirectly invoke other documents, validate signatures, etc. - class LogConclusion < SPARQL::Algebra::Operator::Binary + class Conclusion < SPARQL::Algebra::Operator::Binary NAME = :logConclusion end end diff --git a/lib/rdf/n3/algebra/logConjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb similarity index 76% rename from lib/rdf/n3/algebra/logConjunction.rb rename to lib/rdf/n3/algebra/log/conjunction.rb index f6c1c1e..55bc59c 100644 --- a/lib/rdf/n3/algebra/logConjunction.rb +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -1,9 +1,9 @@ -module RDF::N3::Algebra +module RDF::N3::Algebra::Log ## # A function to merge formulae: logical AND. # # The subject is a list of formulae. The object, which can be generated, is a formula containing a copy of each of the formulae in the list on the left. A cwm built-in function. - class LogConjunction < SPARQL::Algebra::Operator::Binary + class Conjunction < SPARQL::Algebra::Operator::Binary NAME = :logConjunction end end diff --git a/lib/rdf/n3/algebra/logEqualTo.rb b/lib/rdf/n3/algebra/log/equalTo.rb similarity index 71% rename from lib/rdf/n3/algebra/logEqualTo.rb rename to lib/rdf/n3/algebra/log/equalTo.rb index 6e96d1a..e93a7e5 100644 --- a/lib/rdf/n3/algebra/logEqualTo.rb +++ b/lib/rdf/n3/algebra/log/equalTo.rb @@ -1,7 +1,7 @@ -module RDF::N3::Algebra +module RDF::N3::Algebra::Log ## # True if the subject and object are the same RDF node (symbol or literal). Do not confuse with owl:sameAs. A cwm built-in logical operator, RDF graph level. - class LogEqualTo < SPARQL::Algebra::Operator::Binary + class EqualTo < SPARQL::Algebra::Operator::Binary NAME = :logEqualTo end end diff --git a/lib/rdf/n3/algebra/logImplies.rb b/lib/rdf/n3/algebra/log/implies.rb similarity index 97% rename from lib/rdf/n3/algebra/logImplies.rb rename to lib/rdf/n3/algebra/log/implies.rb index c12798c..5d1c362 100644 --- a/lib/rdf/n3/algebra/logImplies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -1,11 +1,11 @@ -module RDF::N3::Algebra +module RDF::N3::Algebra::Log ## # Logical implication. # # This is the relation between the antecedent (subject) and conclusion (object) of a rule. The application of a rule to a knowledge-base is as follows. For every substitution which, applied to the antecedent, gives a formula which is a subset of the knowledge-base, then the result of applying that same substitution to the conclusion may be added to the knowledge-base. # # related: See log:conclusion. - class LogImplies < SPARQL::Algebra::Operator::Binary + class Implies < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Query include SPARQL::Algebra::Update include RDF::Enumerable diff --git a/lib/rdf/n3/algebra/logIncludes.rb b/lib/rdf/n3/algebra/log/includes.rb similarity index 87% rename from lib/rdf/n3/algebra/logIncludes.rb rename to lib/rdf/n3/algebra/log/includes.rb index f3633b4..ee3aa3d 100644 --- a/lib/rdf/n3/algebra/logIncludes.rb +++ b/lib/rdf/n3/algebra/log/includes.rb @@ -1,4 +1,4 @@ -module RDF::N3::Algebra +module RDF::N3::Algebra::Log ## # The subject formula includes the object formula. # @@ -7,7 +7,7 @@ module RDF::N3::Algebra # Variable substitution is applied recursively to nested compound terms such as formulae, lists and sets. # # (Understood natively by cwm when in in the antecedent of a rule. You can use this to peer inside nested formulae.) - class LogIncludes < SPARQL::Algebra::Operator::Binary + class Includes < SPARQL::Algebra::Operator::Binary NAME = :logIncludes end end diff --git a/lib/rdf/n3/algebra/logNotEqualTo.rb b/lib/rdf/n3/algebra/log/notEqualTo.rb similarity index 60% rename from lib/rdf/n3/algebra/logNotEqualTo.rb rename to lib/rdf/n3/algebra/log/notEqualTo.rb index 5aa4b2e..8346b6c 100644 --- a/lib/rdf/n3/algebra/logNotEqualTo.rb +++ b/lib/rdf/n3/algebra/log/notEqualTo.rb @@ -1,7 +1,7 @@ -module RDF::N3::Algebra +module RDF::N3::Algebra::Log ## # Equality in this sense is actually the same URI. A cwm built-in logical operator. - class LogNotEqualTo < SPARQL::Algebra::Operator::Binary + class NotEqualTo < SPARQL::Algebra::Operator::Binary NAME = :logNotEqualTo end end diff --git a/lib/rdf/n3/algebra/logNotIncludes.rb b/lib/rdf/n3/algebra/log/notIncludes.rb similarity index 82% rename from lib/rdf/n3/algebra/logNotIncludes.rb rename to lib/rdf/n3/algebra/log/notIncludes.rb index a3260c8..d73513d 100644 --- a/lib/rdf/n3/algebra/logNotIncludes.rb +++ b/lib/rdf/n3/algebra/log/notIncludes.rb @@ -1,4 +1,4 @@ -module RDF::N3::Algebra +module RDF::N3::Algebra::Log ## # The object formula is NOT a subset of subject. True iff log:includes is false. The converse of log:includes. # (Understood natively by cwm. The subject formula may contain variables.) @@ -6,7 +6,7 @@ module RDF::N3::Algebra # (In cwm, variables must of course end up getting bound before the log:include test can be done, or an infinite result set would result) # # Related: See includes - class LogNotIncludes < SPARQL::Algebra::Operator::Binary + class NotIncludes < SPARQL::Algebra::Operator::Binary NAME = :logNotIncludes end end diff --git a/lib/rdf/n3/algebra/logOutputString.rb b/lib/rdf/n3/algebra/log/outputString.rb similarity index 64% rename from lib/rdf/n3/algebra/logOutputString.rb rename to lib/rdf/n3/algebra/log/outputString.rb index cd51786..0b525e5 100644 --- a/lib/rdf/n3/algebra/logOutputString.rb +++ b/lib/rdf/n3/algebra/log/outputString.rb @@ -1,7 +1,7 @@ -module RDF::N3::Algebra +module RDF::N3::Algebra::Log ## # The subject is a key and the object is a string, where the strings are to be output in the order of the keys. - class LogOutputString < SPARQL::Algebra::Operator::Binary + class OutputString < SPARQL::Algebra::Operator::Binary NAME = :logOutputString end end diff --git a/lib/rdf/n3/algebra/math/absoluteValue.rb b/lib/rdf/n3/algebra/math/absoluteValue.rb new file mode 100644 index 0000000..f7287fe --- /dev/null +++ b/lib/rdf/n3/algebra/math/absoluteValue.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the absolute value of the subject. + class AbsoluteValue < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathAbsoluteValue + end +end diff --git a/lib/rdf/n3/algebra/math/difference.rb b/lib/rdf/n3/algebra/math/difference.rb new file mode 100644 index 0000000..15f89c9 --- /dev/null +++ b/lib/rdf/n3/algebra/math/difference.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a pair of numbers. The object is calculated by subtracting the second number of the pair from the first. + class Difference < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathDifference + end +end diff --git a/lib/rdf/n3/algebra/math/equalTo.rb b/lib/rdf/n3/algebra/math/equalTo.rb new file mode 100644 index 0000000..0505047 --- /dev/null +++ b/lib/rdf/n3/algebra/math/equalTo.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is EQUAL TO a number of which the object is a string representation. + class EqualTo < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathEqualTo + end +end diff --git a/lib/rdf/n3/algebra/math/exponentiation.rb b/lib/rdf/n3/algebra/math/exponentiation.rb new file mode 100644 index 0000000..9eaf9bd --- /dev/null +++ b/lib/rdf/n3/algebra/math/exponentiation.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a pair of numbers. The object is calculated by raising the first number of the power of the second. + class Exponentiation < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathExponentiation + end +end diff --git a/lib/rdf/n3/algebra/math/greaterThan.rb b/lib/rdf/n3/algebra/math/greaterThan.rb new file mode 100644 index 0000000..890675a --- /dev/null +++ b/lib/rdf/n3/algebra/math/greaterThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is greater than the number of which the object is a string representation. + class GreaterThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathGreaterThan + end +end diff --git a/lib/rdf/n3/algebra/math/integerQuotient.rb b/lib/rdf/n3/algebra/math/integerQuotient.rb new file mode 100644 index 0000000..5d1287b --- /dev/null +++ b/lib/rdf/n3/algebra/math/integerQuotient.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a pair of integer numbers. The object is calculated by dividing the first number of the pair by the second, ignoring remainder. + class IntegerQuotient < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathIntegerQuotient + end +end diff --git a/lib/rdf/n3/algebra/math/lessThan.rb b/lib/rdf/n3/algebra/math/lessThan.rb new file mode 100644 index 0000000..c37aac0 --- /dev/null +++ b/lib/rdf/n3/algebra/math/lessThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is LESS than a number of which the object is a string representation. + class LessThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathLessThan + end +end diff --git a/lib/rdf/n3/algebra/math/memberCount.rb b/lib/rdf/n3/algebra/math/memberCount.rb new file mode 100644 index 0000000..8e26fae --- /dev/null +++ b/lib/rdf/n3/algebra/math/memberCount.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The number of items in a list. The subject is a list, the object is calculated as the number of members. + class MemberCount < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathMemberCount + end +end diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb new file mode 100644 index 0000000..9538f42 --- /dev/null +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject or object is calculated to be the negation of the other. + class Negation < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathNegation + end +end diff --git a/lib/rdf/n3/algebra/math/notEqualTo.rb b/lib/rdf/n3/algebra/math/notEqualTo.rb new file mode 100644 index 0000000..e97c3f3 --- /dev/null +++ b/lib/rdf/n3/algebra/math/notEqualTo.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is NOT EQUAL to a number of which the object is a string representation. + class NotEqualTo < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathNotEqualTo + end +end diff --git a/lib/rdf/n3/algebra/math/notGreaterThan.rb b/lib/rdf/n3/algebra/math/notGreaterThan.rb new file mode 100644 index 0000000..fa46ec1 --- /dev/null +++ b/lib/rdf/n3/algebra/math/notGreaterThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is NOT greater than the number of which the object is a string representation. + class NotGreaterThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathNotGreaterThan + end +end diff --git a/lib/rdf/n3/algebra/math/notLessThan.rb b/lib/rdf/n3/algebra/math/notLessThan.rb new file mode 100644 index 0000000..8bd3774 --- /dev/null +++ b/lib/rdf/n3/algebra/math/notLessThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is NOT LESS than a number of which the object is a string representation. + class NotLessThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathNotLessThan + end +end diff --git a/lib/rdf/n3/algebra/math/product.rb b/lib/rdf/n3/algebra/math/product.rb new file mode 100644 index 0000000..6634b29 --- /dev/null +++ b/lib/rdf/n3/algebra/math/product.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a list of numbers. The object is calculated as the arithmentic product of those numbers. + class Product < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathProduct + end +end diff --git a/lib/rdf/n3/algebra/math/quotient.rb b/lib/rdf/n3/algebra/math/quotient.rb new file mode 100644 index 0000000..5050c7e --- /dev/null +++ b/lib/rdf/n3/algebra/math/quotient.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a pair of numbers. The object is calculated by dividing the first number of the pair by the second. + class Quotient < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathQuotient + end +end diff --git a/lib/rdf/n3/algebra/math/remainder.rb b/lib/rdf/n3/algebra/math/remainder.rb new file mode 100644 index 0000000..4d26d01 --- /dev/null +++ b/lib/rdf/n3/algebra/math/remainder.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a pair of integers. The object is calculated by dividing the first number of the pair by the second and taking the remainder. + class Remainder < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathRemainder + end +end diff --git a/lib/rdf/n3/algebra/math/rounded.rb b/lib/rdf/n3/algebra/math/rounded.rb new file mode 100644 index 0000000..1c9ab50 --- /dev/null +++ b/lib/rdf/n3/algebra/math/rounded.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the subject rounded to the nearest integer. + class Rounded < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathRounded + end +end diff --git a/lib/rdf/n3/algebra/math/sum.rb b/lib/rdf/n3/algebra/math/sum.rb new file mode 100644 index 0000000..f8b0fcb --- /dev/null +++ b/lib/rdf/n3/algebra/math/sum.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a list of numbers. The object is calculated as the arithmentic sum of those numbers. + class Sum < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathSum + end +end diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb new file mode 100644 index 0000000..ac8b500 --- /dev/null +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # The subject is a list of strings. The object is calculated as a concatenation of those strings. + class Concatenation < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strConcatenation + end +end diff --git a/lib/rdf/n3/algebra/str/contains.rb b/lib/rdf/n3/algebra/str/contains.rb new file mode 100644 index 0000000..6f38c1e --- /dev/null +++ b/lib/rdf/n3/algebra/str/contains.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the subject string contains the object string. + class Contains < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strContains + end +end diff --git a/lib/rdf/n3/algebra/str/containsIgnoringCase.rb b/lib/rdf/n3/algebra/str/containsIgnoringCase.rb new file mode 100644 index 0000000..01c00ce --- /dev/null +++ b/lib/rdf/n3/algebra/str/containsIgnoringCase.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the subject string contains the object string, with the comparison done ignoring the difference between upper case and lower case characters. + class ContainsIgnoringCase < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strContainsIgnoringCase + end +end diff --git a/lib/rdf/n3/algebra/str/endsWith.rb b/lib/rdf/n3/algebra/str/endsWith.rb new file mode 100644 index 0000000..f7ec877 --- /dev/null +++ b/lib/rdf/n3/algebra/str/endsWith.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the subject string ends with the object string. + class EndsWith < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strEndsWith + end +end diff --git a/lib/rdf/n3/algebra/str/equalIgnoringCase.rb b/lib/rdf/n3/algebra/str/equalIgnoringCase.rb new file mode 100644 index 0000000..fe51875 --- /dev/null +++ b/lib/rdf/n3/algebra/str/equalIgnoringCase.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the subject string is the same as object string ignoring differences between upper and lower case. + class EqualIgnoringCase < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strEqualIgnoringCase + end +end diff --git a/lib/rdf/n3/algebra/str/format.rb b/lib/rdf/n3/algebra/str/format.rb new file mode 100644 index 0000000..61724bf --- /dev/null +++ b/lib/rdf/n3/algebra/str/format.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # The subject is a list, whose first member is a format string, and whose remaining members are arguments to the format string. The formating string is in the style of python's % operator, very similar to C's sprintf(). The object is calculated from the subject. + class Format < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strFormat + end +end diff --git a/lib/rdf/n3/algebra/str/greaterThan.rb b/lib/rdf/n3/algebra/str/greaterThan.rb new file mode 100644 index 0000000..488606a --- /dev/null +++ b/lib/rdf/n3/algebra/str/greaterThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the string is greater than the object when ordered according to Unicode(tm) code order + class GreaterThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strGreaterThan + end +end diff --git a/lib/rdf/n3/algebra/str/lessThan.rb b/lib/rdf/n3/algebra/str/lessThan.rb new file mode 100644 index 0000000..fabbb90 --- /dev/null +++ b/lib/rdf/n3/algebra/str/lessThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the string is less than the object when ordered according to Unicode(tm) code order. + class LessThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strLessThan + end +end diff --git a/lib/rdf/n3/algebra/str/matches.rb b/lib/rdf/n3/algebra/str/matches.rb new file mode 100644 index 0000000..cc5b6e7 --- /dev/null +++ b/lib/rdf/n3/algebra/str/matches.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # The subject is a string; the object is is a regular expression in the perl, python style. It is true iff the string matches the regexp. + class Matches < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strMatches + end +end diff --git a/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb b/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb new file mode 100644 index 0000000..7795f6c --- /dev/null +++ b/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the subject string is the NOT same as object string ignoring differences between upper and lower case. + class NotEqualIgnoringCase < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strNotEqualIgnoringCase + end +end diff --git a/lib/rdf/n3/algebra/str/notGreaterThan.rb b/lib/rdf/n3/algebra/str/notGreaterThan.rb new file mode 100644 index 0000000..fba338c --- /dev/null +++ b/lib/rdf/n3/algebra/str/notGreaterThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the string is NOT greater than the object when ordered according to Unicode(tm) code order. + class NotGreaterThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strNotGreaterThan + end +end diff --git a/lib/rdf/n3/algebra/str/notLessThan.rb b/lib/rdf/n3/algebra/str/notLessThan.rb new file mode 100644 index 0000000..81ca196 --- /dev/null +++ b/lib/rdf/n3/algebra/str/notLessThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the string is NOT less than the object when ordered according to Unicode(tm) code order. + class NotLessThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strNotLessThan + end +end diff --git a/lib/rdf/n3/algebra/str/notMatches.rb b/lib/rdf/n3/algebra/str/notMatches.rb new file mode 100644 index 0000000..64ca466 --- /dev/null +++ b/lib/rdf/n3/algebra/str/notMatches.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # The subject string; the object is is a regular expression in the perl, python style. It is true iff the string does NOT match the regexp. + class NotMatches < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strNotMatches + end +end diff --git a/lib/rdf/n3/algebra/str/replace.rb b/lib/rdf/n3/algebra/str/replace.rb new file mode 100644 index 0000000..bc2ec47 --- /dev/null +++ b/lib/rdf/n3/algebra/str/replace.rb @@ -0,0 +1,12 @@ +module RDF::N3::Algebra::Str + ## + # A built-in for replacing characters or sub. takes a list of 3 strings; the first is the input data, the second the old and the third the new string. The object is calculated as the rplaced string. + # + # @example + # ("fofof bar", "of", "baz") string:replace "fbazbaz bar" + class Replace < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strReplace + end +end diff --git a/lib/rdf/n3/algebra/str/scrape.rb b/lib/rdf/n3/algebra/str/scrape.rb new file mode 100644 index 0000000..a941651 --- /dev/null +++ b/lib/rdf/n3/algebra/str/scrape.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # The subject is a list of two strings. The second string is a regular expression in the perl, python style. It must contain one group (a part in parentheses). If the first string in the list matches the regular expression, then the object is calculated as being thepart of the first string which matches the group. + class Scrape < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strScrape + end +end diff --git a/lib/rdf/n3/algebra/str/startsWith.rb b/lib/rdf/n3/algebra/str/startsWith.rb new file mode 100644 index 0000000..f5b8863 --- /dev/null +++ b/lib/rdf/n3/algebra/str/startsWith.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the subject string starts with the object string. + class StartsWith < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strStartsWith + end +end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 1278021..3ea4729 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -18,8 +18,8 @@ when *%w{t06proof} pending "support for math" when *%w{t01} - pending "support for log:supports" - when *%w{conclusion-simple conclusion} + pending "support for log:supports" # log:supports not defined in vocabulary + when *%w{conclusion-simple} pending "support for log:conclusion" when *%w{conjunction} pending "support for log:conjunction" From f2938bcedacd7081ec43be2035e9d91ed7b37399 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 29 Jan 2019 15:42:41 -0800 Subject: [PATCH 33/40] Updates for RDF support for existential and universal as well as (non-)deterministic variables in serialization and creation. --- Gemfile | 3 +-- lib/rdf/n3/algebra/formula.rb | 37 ++++++++++++++++++++++------------- lib/rdf/n3/reader.rb | 8 +++++--- lib/rdf/n3/writer.rb | 9 +++++---- spec/reader_spec.rb | 10 +++++----- spec/writer_spec.rb | 4 ++-- 6 files changed, 41 insertions(+), 30 deletions(-) diff --git a/Gemfile b/Gemfile index 71bcd52..60de3c8 100644 --- a/Gemfile +++ b/Gemfile @@ -2,8 +2,7 @@ source "https://rubygems.org" gemspec -#gem "rdf", git: "https://github.com/ruby-rdf/rdf", branch: "develop" -gem "rdf", path: '../rdf' +gem "rdf", git: "https://github.com/ruby-rdf/rdf", branch: "develop" group :development do gem "rdf-spec", git: "https://github.com/ruby-rdf/rdf-spec", branch: "develop" diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 2126f0a..4fc6747 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -16,6 +16,8 @@ class Formula < SPARQL::Algebra::Operator ## # Yields solutions from patterns and other operands. Solutions are created by evaluating each pattern and other sub-operand against `queryable`. # + # When executing, blank nodes are turned into non-distinguished existential variables, noted with `$$`. These variables are removed from the returned solutions, as they can't be bound outside of the formula. + # # @param [RDF::Queryable] queryable # the graph or repository to query # @param [Hash{Symbol => Object}] options @@ -44,7 +46,10 @@ def execute(queryable, **options, &block) end end log_debug {"(formula solutions) #{@solutions.to_sxp}"} - @solutions + + # Only return solutions with distinguished variables + variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?('$$')} + variable_names.empty? ? @solutions : @solutions.dup.project(*variable_names) end ## @@ -56,12 +61,8 @@ def execute(queryable, **options, &block) # @yieldreturn [void] ignored def each(&block) @solutions ||= begin - # If there are no solutions, create bindings for all existential variables using the variable name as the bnode identifier - RDF::Query::Solutions.new( - [RDF::Query::Solution.new( - patterns.ndvars.inject({}) {|memo, v| memo.merge(v.name => RDF::Node.intern(v.name))} - )] - ) + # If there are no solutions, create a single solution + RDF::Query::Solutions.new(RDF::Query::Solution.new) end log_debug {"formula #{graph_name} each #{@solutions.to_sxp}"} @@ -72,7 +73,13 @@ def each(&block) end # Yield patterns by binding variables + # FIXME: do we need to do something with non-bound non-distinguished extistential variables? @solutions.each do |solution| + # Bind blank nodes to the solution when it doesn't contain a solution for an existential variable + existential_vars.each do |var| + solution[var.name] ||= RDF::Node.intern(var.name.to_s.sub(/^\$+/, '')) + end + log_debug {"(formula apply) #{solution.to_sxp} to BGP"} # Yield each variable statement which is constant after applying solution patterns.each do |pattern| @@ -117,17 +124,17 @@ def graph_name; @options[:graph_name]; end ## # Statements memoizer def statements - # BNodes in statements are existential variables + # BNodes in statements are non-distinguished existential variables @statements ||= operands. select {|op| op.is_a?(RDF::Statement)}. map do |pattern| - # Map nodes to variables (except when in top-level formula) + # Map nodes to non-distinguished existential variables (except when in top-level formula) if graph_name terms = {} [:subject, :predicate, :object].each do |r| terms[r] = case o = pattern.send(r) - when RDF::Node then RDF::Query::Variable.new(o.id, distinguished: false) + when RDF::Node then RDF::Query::Variable.new(o.id, existential: true, distinguished: false) else o end end @@ -160,6 +167,12 @@ def sub_ops @sub_ops ||= operands.reject {|op| op.is_a?(RDF::Statement)} end + ## + # Existential vars in this formula + def existential_vars + @existentials ||= patterns.vars.select(&:existential?) + end + ## # Reorder operands by graph_name or subject def reorder_operands! @@ -167,11 +180,7 @@ def reorder_operands! end def to_sxp_bin - @existentials = ndvars.uniq - @universals = vars.uniq - @existentials [:formula, graph_name].compact + - (Array(universals).empty? ? [] : [universals.unshift(:universals)]) + - (Array(existentials).empty? ? [] : [existentials.unshift(:existentials)]) + operands.map(&:to_sxp_bin) end end diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 2d49619..6ee65f9 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -9,6 +9,8 @@ module RDF::N3 # # Separate pass to create branch_table from n3-selectors.n3 # + # This implementation uses distinguished variables for both universal and explicit existential variables (defined with `@forSome`). Variables created from blank nodes are non-distinguished. Distinguished existential variables are tracked using `$`, internally, as the RDF `query_pattern` logic looses details of the variable definition in solutions, where the variable is represented using a symbol. + # # @todo # * Formulae as RDF::Query representations # * Formula expansion similar to SPARQL Construct @@ -244,7 +246,7 @@ def existentialFinish pd = @prod_data.pop forSome = Array(pd[:symbol]) forSome.each do |term| - var = univar(term, distinguished: false) + var = univar(term, existential: true) add_var_to_formula(@formulae.last, term, var) end end @@ -655,10 +657,10 @@ def bnode(label = nil) end end - def univar(label, distinguished: true) + def univar(label, existential: false) # Label using any provided label, followed by seed, followed by incrementing index value = "#{label}_#{unique_label}" - RDF::Query::Variable.new(value, distinguished: distinguished) + RDF::Query::Variable.new(value, existential: existential) end # add a pattern or statement diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 7077605..4942f92 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -391,8 +391,9 @@ def preprocess @options[:prefixes] = {} # Will define actual used when matched repo.each {|statement| preprocess_statement(statement)} - @universals = repo.enum_term.to_a.select {|r| r.is_a?(RDF::Query::Variable) && r.distinguished?}.uniq - @existentials = repo.enum_term.to_a.select {|r| r.is_a?(RDF::Query::Variable) && !r.distinguished?}.uniq + vars = repo.enum_term.to_a.uniq.select {|r| r.is_a?(RDF::Query::Variable)} + @universals = vars.reject(&:existential?) + @existentials = vars - @universals end # Perform any statement preprocessing required. This is used to perform reference counts and determine required @@ -453,7 +454,7 @@ def reset # @return [String] def quoted(string) if string.to_s.match(/[\t\n\r]/) - string = string.gsub('\\', '\\\\\\\\').gsub('"""', '\\"""') + string = string.gsub('\\', '\\\\\\\\').gsub('"""', '\\"\\"\\"') %("""#{string}""") else "\"#{escaped(string)}\"" @@ -499,7 +500,7 @@ def collection(node, position) def p_term(resource, position) #log_debug("p_term") {"#{resource.to_sxp}, #{position}"} l = if resource.is_a?(RDF::Query::Variable) - format_term(RDF::URI(resource.name.to_s)) + format_term(RDF::URI(resource.name.to_s.sub(/^\$/, ''))) elsif resource == RDF.nil "()" else diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 4cf9e34..78be377 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -1277,17 +1277,17 @@ ) end - def parse(input, options = {}) + def parse(input, **options) options = { logger: logger, validate: false, canonicalize: false, }.merge(options) - graph = options[:repo] || RDF::Repository.new - RDF::N3::Reader.new(input, options).each_statement do |statement| - graph << statement + repo = options[:repo] || RDF::Repository.new + RDF::N3::Reader.new(input, **options).each_statement do |statement| + repo << statement end - graph + repo end def test_file(filepath) diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 111c120..270e2a2 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -611,7 +611,7 @@ case t.name when *%w(n3_10003 n3_10004 n3_10008) skip "Blank Node predicates" - when *%w(n3_10012 n3_10016 n3_10017) + when *%w(n3_10012 n3_10017) pending "Investigate" when *%w(n3_10013) pending "Number syntax" @@ -629,7 +629,7 @@ case t.name when *%w(n3_10003 n3_10004 n3_10008) skip "Blank Node predicates" - when *%w(n3_10012 n3_10016 n3_10017) + when *%w(n3_10012 n3_10017) pending "Investigate" when *%w(n3_10013) pending "Number syntax" From 9e0c20d54b321368727227448309b0fe59a5cbcd Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 29 Jan 2019 16:58:04 -0800 Subject: [PATCH 34/40] Merge solutions from parent into solutions from child, using parent bindings in formula query. This allows variables from parent to be in the solution set, even if they're not bound through a local query. --- lib/rdf/n3/algebra/formula.rb | 27 +++++++- spec/reasoner_spec.rb | 112 ++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 spec/reasoner_spec.rb diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 4fc6747..8527c91 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -31,18 +31,41 @@ class Formula < SPARQL::Algebra::Operator # @return [RDF::Solutions] distinct solutions def execute(queryable, **options, &block) log_debug {"formula #{graph_name} #{operands.to_sxp}"} + + # If we were passed solutions in options, extract bindings to use for query log_debug {"(formula bindings) #{options.fetch(:bindings, {}).map {|k,v| RDF::Query::Variable.new(k,v)}.to_sxp}"} # Only query as patterns if this is an embedded formula @query ||= RDF::Query.new(patterns).optimize! - @solutions = @query.patterns.empty? ? RDF::Query::Solutions.new : queryable.query(@query, **options) + options[:bindings] = options[:solutions].bindings if options.has_key?(:solutions) + @solutions = @query.patterns.empty? ? RDF::Query::Solutions.new : queryable.query(@query, options.merge(solutions: RDF::Query::Solution.new)) + + # Merge solution sets + if options[:solutions] + if @solutions.empty? + @solutions = options[:solutions] + elsif options[:solutions].empty? + @solutions + else + # Merge solutions + old_solutions, @solutions = @solutions, RDF::Query::Solutions() + options[:solutions].each do |s1| + old_solutions.each do |s2| + @solutions << s1.compatible?(s2) ? s1.merge(s2) : s2 + end + end + end + end + + # Reject solutions which include variables as values + @solutions.filter {|s| s.enum_value.none?(&:variable?)} @solutions.distinct! # Use our solutions for sub-ops, without binding solutions from those sub-ops # Join solutions from other operands log_depth do sub_ops.each do |op| - op.execute(queryable, bindings: @solutions.bindings) + op.execute(queryable, solutions: @solutions) end end log_debug {"(formula solutions) #{@solutions.to_sxp}"} diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb new file mode 100644 index 0000000..59f1536 --- /dev/null +++ b/spec/reasoner_spec.rb @@ -0,0 +1,112 @@ +# coding: utf-8 +require_relative 'spec_helper' +require 'rdf/trig' + +describe "RDF::N3::Reasoner" do + let(:logger) {RDF::Spec.logger} + + context "n3:log" do + context "log:implies" do + { + "r1" => { + input: %( + @forAll :a, :b, :c, :x, :y, :z. + ( "one" "two" ) a :whatever. + { (:a :b) a :whatever } log:implies { :a a :SUCCESS. :b a :SUCCESS }. + ), + expect: %( + ( "one" "two" ) a :whatever. + "one" a :SUCCESS. + "two" a :SUCCESS. + ) + }, + "unify2" => { + input: %( + ( 17 ) a :TestCase. + { ( ?x ) a :TestCase} => { ?x a :RESULT }. + ), + expect: %( + ( 17 ) a :TestCase. + 17 a :RESULT. + ) + }, + "unify3" => { + input: %( + ( ( 17 ) ) a :TestCase. + { ( ( ?x ) ) a :TestCase} => { ?x a :RESULT }. + { 17 a :RESULT } => { :THIS_TEST a :SUCCESS }. + ), + expect: %( + ( ( 17 ) ) a :TestCase. + 17 a :RESULT. + :THIS_TEST a :SUCCESS. + ) + }, + "double" => { + input: %( + @keywords is, of, a. + dan a Man; home []. + { ?WHO home ?WHERE. ?WHERE in ?REGION } => { ?WHO homeRegion ?REGION }. + { dan home ?WHERE} => {?WHERE in Texas} . + ), + expect: %( + :dan a :Man; + :home [:in :Texas ]; + :homeRegion :Texas . + ), + }, + "single_gen" => { + input: %( + :a :b "A noun", 3.14159265359 . + {:a :b ?X} => { [ a :Thing ] } . + ), + expect: %( + :a :b "A noun", 3.14159265359 . + [ a :Thing] + ) + }, + "uses variables bound in parent" => { + input: %( + :a :b :c. + ?x :b :c. # ?x bound to :a + {:a :b :c} => {?x :d :e}. + ), + expect: %( + :a :b :c; :d :e. + ) + } + }.each do |name, options| + it name do + result = parse(options[:expect]) + expect(reason(options[:input])).to be_equivalent_graph(result, logger: logger) + end + end + end + end + + # Parse N3 input into a repository + def parse(input, **options) + repo = options[:repo] || RDF::Repository.new + RDF::N3::Reader.new(input, **options).each_statement do |statement| + repo << statement + end + repo + end + + # Reason over input, returning a repo + def reason(input, base_uri: 'http://example.com/', filter: false, data: true, think: true, **options) + input = parse(input, **options) if input.is_a?(String) + reasoner = RDF::N3::Reasoner.new(input, base_uri: base_uri) + repo = RDF::Repository.new + + reasoner.execute(logger: logger, think: think) + if filter + repo << reasoner.conclusions + elsif data + repo << reasoner.data + else + repo << reasoner + end + repo + end +end From 54a16e01ebc10c4ff4af5031bd39712b5ecacc02 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 3 Feb 2019 16:29:38 -0800 Subject: [PATCH 35/40] Use `RDF::Query::Solutions#merge` and merge solutions from all built-in predicates. log:implies always returns solutions it started with, but retains solutions from subject to use when evaluating object. --- examples/unboundExistentialConsequent.n3 | 5 ++++ lib/rdf/n3/algebra/formula.rb | 31 ++++-------------------- lib/rdf/n3/algebra/log/implies.rb | 9 ++++--- lib/rdf/n3/reasoner.rb | 11 +++------ 4 files changed, 19 insertions(+), 37 deletions(-) create mode 100644 examples/unboundExistentialConsequent.n3 diff --git a/examples/unboundExistentialConsequent.n3 b/examples/unboundExistentialConsequent.n3 new file mode 100644 index 0000000..d37cc48 --- /dev/null +++ b/examples/unboundExistentialConsequent.n3 @@ -0,0 +1,5 @@ +@prefix : . + +:s :p :o. + @forAll :x. {:x :p :o}=>{:x :b :c}. + @forSome :y. {:s :p :y}=>{:a :b :y}. \ No newline at end of file diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 8527c91..17337bd 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -41,31 +41,16 @@ def execute(queryable, **options, &block) @solutions = @query.patterns.empty? ? RDF::Query::Solutions.new : queryable.query(@query, options.merge(solutions: RDF::Query::Solution.new)) # Merge solution sets - if options[:solutions] - if @solutions.empty? - @solutions = options[:solutions] - elsif options[:solutions].empty? - @solutions - else - # Merge solutions - old_solutions, @solutions = @solutions, RDF::Query::Solutions() - options[:solutions].each do |s1| - old_solutions.each do |s2| - @solutions << s1.compatible?(s2) ? s1.merge(s2) : s2 - end - end - end - end - # Reject solutions which include variables as values - @solutions.filter {|s| s.enum_value.none?(&:variable?)} - @solutions.distinct! + @solutions = @solutions + .merge(options[:solutions]) + .filter {|s| s.enum_value.none?(&:variable?)} - # Use our solutions for sub-ops, without binding solutions from those sub-ops + # Use our solutions for sub-ops # Join solutions from other operands log_depth do sub_ops.each do |op| - op.execute(queryable, solutions: @solutions) + @solutions = op.execute(queryable, solutions: @solutions) end end log_debug {"(formula solutions) #{@solutions.to_sxp}"} @@ -196,12 +181,6 @@ def existential_vars @existentials ||= patterns.vars.select(&:existential?) end - ## - # Reorder operands by graph_name or subject - def reorder_operands! - @operands = @operands.sort_by {|op| op.graph_name || op.subject} - end - def to_sxp_bin [:formula, graph_name].compact + operands.map(&:to_sxp_bin) diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 5d1c362..5ac4895 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -29,9 +29,12 @@ class Implies < SPARQL::Algebra::Operator::Binary def execute(queryable, **options, &block) @queryable = queryable log_debug {"logImplies #{graph_name}"} + orig_solutions = options[:solutions] @solutions = log_depth {operands.first.execute(queryable, **options, &block)} log_debug {"(logImplies solutions) #{@solutions.to_sxp}"} - @solutions + + # Return original solutions, without bindings + orig_solutions end ## @@ -66,8 +69,8 @@ def each(&block) end end - # Graph name associated with this operation, using the name of the first operand + # Graph name associated with this operation, using the name of the parent # @return [RDF::Resource] - def graph_name; operands.first {|o| o.respond_to?(:graph_name)}.graph_name; end + def graph_name; parent.graph_name; end end end diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 2491303..c050cb8 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -242,6 +242,7 @@ def formula require 'sparql' unless defined?(:SPARQL) @formula ||= begin + # Create formulae from statement graph_names formulae = (@mutable.graph_names.unshift(nil)).inject({}) do |memo, graph_name| memo.merge(graph_name => Algebra::Formula.new(graph_name: graph_name, **@options)) end @@ -252,14 +253,14 @@ def formula @mutable.each_statement do |statement| pattern = statement.variable? ? RDF::Query::Pattern.from(statement) : statement - # A graph name indicates a formula. If not already allocated, create a new formula and use that for inserting statements or other operators + # A graph name indicates a formula. form = formulae[pattern.graph_name] # Formulae may be the subject or object of a known operator if klass = Algebra.for(pattern.predicate) fs = formulae.fetch(pattern.subject, pattern.subject) fo = formulae.fetch(pattern.object, pattern.object) - form.operands << klass.new(fs, fo, **@options) + form.operands << klass.new(fs, fo, parent: form, **@options) else # Add formulae as direct operators if formulae.has_key?(pattern.subject) @@ -273,12 +274,6 @@ def formula end end - # Order operands in each formula by either the graph_name or subject - formulae.each_value do |operator| - next unless operator.is_a?(Algebra::Formula) - operator.reorder_operands! - end - # Formula is that without a graph name formulae[nil] end From de9cd6c2cda5595503736bf229b14ff98c04c637 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 5 Feb 2019 22:01:51 -0800 Subject: [PATCH 36/40] Progress for string functions. --- lib/rdf/n3/algebra/formula.rb | 2 +- lib/rdf/n3/algebra/log/implies.rb | 6 +++++ lib/rdf/n3/algebra/str/startsWith.rb | 34 ++++++++++++++++++++++++++++ spec/reasoner_spec.rb | 21 +++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 17337bd..bb50d07 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -56,7 +56,7 @@ def execute(queryable, **options, &block) log_debug {"(formula solutions) #{@solutions.to_sxp}"} # Only return solutions with distinguished variables - variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?('$$')} + variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?(/^(\$\$|\?\?)/)} variable_names.empty? ? @solutions : @solutions.dup.project(*variable_names) end diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 5ac4895..09a5405 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -49,6 +49,12 @@ def each(&block) log_debug {"logImplies #{graph_name} each #{@solutions.to_sxp}"} subject, object = operands + if @solutions == RDF::Literal::FALSE + # Some evalaluatable operand evaluated to false + log_debug("(logImplies implication false)") + return + end + # Graph based on solutions from subject subject_graph = log_depth {RDF::Graph.new {|g| g << subject}} diff --git a/lib/rdf/n3/algebra/str/startsWith.rb b/lib/rdf/n3/algebra/str/startsWith.rb index f5b8863..3ce41c7 100644 --- a/lib/rdf/n3/algebra/str/startsWith.rb +++ b/lib/rdf/n3/algebra/str/startsWith.rb @@ -2,8 +2,42 @@ module RDF::N3::Algebra::Str ## # True iff the subject string starts with the object string. class StartsWith < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Evaluatable include RDF::Util::Logger NAME = :strStartsWith + + ## + # The STRSTARTS function corresponds to the XPath fn:starts-with function. The arguments must be argument compatible otherwise an error is raised. + # + # For such input pairs, the function returns true if the lexical form of arg1 starts with the lexical form of arg2, otherwise it returns false. + # + # @example + # strStarts("foobar", "foo") #=> true + # strStarts("foobar"@en, "foo"@en) #=> true + # strStarts("foobar"^^xsd:string, "foo"^^xsd:string) #=> true + # strStarts("foobar"^^xsd:string, "foo") #=> true + # strStarts("foobar", "foo"^^xsd:string) #=> true + # strStarts("foobar"@en, "foo") #=> true + # strStarts("foobar"@en, "foo"^^xsd:string) #=> true + # + # @param [RDF::Literal] left + # a literal + # @param [RDF::Literal] right + # a literal + # @return [RDF::Literal::Boolean] + # @raise [TypeError] if operands are not compatible + def apply(left, right) + case + when !left.compatible?(right) + raise TypeError, "expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}" + when left.to_s.start_with?(right.to_s) then RDF::Literal::TRUE + else RDF::Literal::FALSE + end + end + + # Graph name associated with this operation, just a random BNode + # @return [RDF::Resource] + def graph_name; RDF::Node.new; end end end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 59f1536..f5f655e 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -84,6 +84,27 @@ end end + context "n3:string" do + context "string:startsWith" do + { + "literal starts with literal" => { + input: %( + @prefix string: . + {"abc" string:startsWith "a"} => {:test a :Success}. + ), + expect: %( + :test a :Success. + ) + } + }.each do |name, options| + it name do + result = parse(options[:expect]) + expect(reason(options[:input])).to be_equivalent_graph(result, logger: logger) + end + end + end + end + # Parse N3 input into a repository def parse(input, **options) repo = options[:repo] || RDF::Repository.new From fbdef3346ffdf512a6b5530781e08b1005bcb1e6 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 5 Feb 2019 22:02:19 -0800 Subject: [PATCH 37/40] Progress for string functions. --- lib/rdf/n3/algebra/formula.rb | 16 +++----- lib/rdf/n3/algebra/log/implies.rb | 21 ++++------ lib/rdf/n3/algebra/str/startsWith.rb | 61 +++++++++++++++++----------- spec/reasoner_spec.rb | 2 + 4 files changed, 53 insertions(+), 47 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index bb50d07..03b44f1 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -24,21 +24,17 @@ class Formula < SPARQL::Algebra::Operator # any additional keyword options # @option options [RDF::Query::Solutions] solutions # optional initial solutions for chained queries - # @yield [statement] - # each matching statement - # @yieldparam [RDF::Statement] solution - # @yieldreturn [void] ignored # @return [RDF::Solutions] distinct solutions - def execute(queryable, **options, &block) + def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options) log_debug {"formula #{graph_name} #{operands.to_sxp}"} # If we were passed solutions in options, extract bindings to use for query - log_debug {"(formula bindings) #{options.fetch(:bindings, {}).map {|k,v| RDF::Query::Variable.new(k,v)}.to_sxp}"} + bindings = solutions.bindings + log_debug {"(formula bindings) #{bindings.map {|k,v| RDF::Query::Variable.new(k,v)}.to_sxp}"} # Only query as patterns if this is an embedded formula @query ||= RDF::Query.new(patterns).optimize! - options[:bindings] = options[:solutions].bindings if options.has_key?(:solutions) - @solutions = @query.patterns.empty? ? RDF::Query::Solutions.new : queryable.query(@query, options.merge(solutions: RDF::Query::Solution.new)) + @solutions = @query.patterns.empty? ? solutions : queryable.query(@query, solutions: solutions, bindings: bindings, **options) # Merge solution sets # Reject solutions which include variables as values @@ -56,7 +52,7 @@ def execute(queryable, **options, &block) log_debug {"(formula solutions) #{@solutions.to_sxp}"} # Only return solutions with distinguished variables - variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?(/^(\$\$|\?\?)/)} + variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?('$$')} variable_names.empty? ? @solutions : @solutions.dup.project(*variable_names) end @@ -70,7 +66,7 @@ def execute(queryable, **options, &block) def each(&block) @solutions ||= begin # If there are no solutions, create a single solution - RDF::Query::Solutions.new(RDF::Query::Solution.new) + RDF::Query::Solutions(RDF::Query::Solution.new) end log_debug {"formula #{graph_name} each #{@solutions.to_sxp}"} diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 09a5405..4935f5c 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -21,20 +21,15 @@ class Implies < SPARQL::Algebra::Operator::Binary # any additional keyword options # @option options [RDF::Query::Solutions] solutions # optional initial solutions for chained queries - # @yield [statement] - # each matching statement - # @yieldparam [RDF::Statement] solution - # @yieldreturn [void] ignored # @return [RDF::Solutions] distinct solutions - def execute(queryable, **options, &block) + def execute(queryable, solutions:, **options) @queryable = queryable - log_debug {"logImplies #{graph_name}"} - orig_solutions = options[:solutions] - @solutions = log_depth {operands.first.execute(queryable, **options, &block)} + log_debug {"logImplies"} + @solutions = log_depth {operands.first.execute(queryable, solutions: solutions, **options)} log_debug {"(logImplies solutions) #{@solutions.to_sxp}"} # Return original solutions, without bindings - orig_solutions + solutions end ## @@ -46,12 +41,12 @@ def execute(queryable, **options, &block) # @yieldreturn [void] ignored def each(&block) @solutions ||= RDF::Query::Solutions.new - log_debug {"logImplies #{graph_name} each #{@solutions.to_sxp}"} + log_debug {"logImplies each #{@solutions.to_sxp}"} subject, object = operands - if @solutions == RDF::Literal::FALSE + if @solutions.empty? # Some evalaluatable operand evaluated to false - log_debug("(logImplies implication false)") + log_debug("(logImplies implication false - no solutions)") return end @@ -67,7 +62,7 @@ def each(&block) # Yield statements into the default graph log_depth do object.each do |statement| - block.call(RDF::Statement.from(statement.to_triple, inferred: true)) + block.call(RDF::Statement.from(statement.to_triple, inferred: true, graph_name: graph_name)) end end else diff --git a/lib/rdf/n3/algebra/str/startsWith.rb b/lib/rdf/n3/algebra/str/startsWith.rb index 3ce41c7..cda654c 100644 --- a/lib/rdf/n3/algebra/str/startsWith.rb +++ b/lib/rdf/n3/algebra/str/startsWith.rb @@ -2,42 +2,55 @@ module RDF::N3::Algebra::Str ## # True iff the subject string starts with the object string. class StartsWith < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Evaluatable + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :strStartsWith ## - # The STRSTARTS function corresponds to the XPath fn:starts-with function. The arguments must be argument compatible otherwise an error is raised. + # The string:startsWith operator corresponds to the XPath fn:starts-with function. The arguments must be argument compatible otherwise an error is raised. # - # For such input pairs, the function returns true if the lexical form of arg1 starts with the lexical form of arg2, otherwise it returns false. + # For constant inputs that evaulate to true, the original solutions are returned. # - # @example - # strStarts("foobar", "foo") #=> true - # strStarts("foobar"@en, "foo"@en) #=> true - # strStarts("foobar"^^xsd:string, "foo"^^xsd:string) #=> true - # strStarts("foobar"^^xsd:string, "foo") #=> true - # strStarts("foobar", "foo"^^xsd:string) #=> true - # strStarts("foobar"@en, "foo") #=> true - # strStarts("foobar"@en, "foo"^^xsd:string) #=> true + # For constant inputs that evaluate to false, the empty solution set is returned. XXX # - # @param [RDF::Literal] left - # a literal - # @param [RDF::Literal] right - # a literal - # @return [RDF::Literal::Boolean] + # Otherwise, for variable operands, it binds matching variables to the solution set. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] # @raise [TypeError] if operands are not compatible - def apply(left, right) - case - when !left.compatible?(right) - raise TypeError, "expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}" - when left.to_s.start_with?(right.to_s) then RDF::Literal::TRUE - else RDF::Literal::FALSE + def execute(queryable, solutions:, **options) + log_debug {"strStartsWith #{operands.to_sxp}"} + @solutions = solutions.filter do |solution| + left, right = operands.map {|op| op.evaluate(solution.bindings)} + if !left.compatible?(right) + log_debug {"(strStartsWith incompatible operands #{[left, right].to_sxp})"} + false + elsif !left.to_s.start_with?(right.to_s) + log_debug {"(strStartsWith false #{[left, right].to_sxp})"} + false + else + log_debug {"(strStartsWith true #{[left, right].to_sxp})"} + true + end end end - # Graph name associated with this operation, just a random BNode + ## + # Does not yield statements. + # + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + def each(&block) + end + + # Graph name associated with this operation, using the name of the parent # @return [RDF::Resource] - def graph_name; RDF::Node.new; end + def graph_name; parent.graph_name; end end end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index f5f655e..e9626e6 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -90,9 +90,11 @@ "literal starts with literal" => { input: %( @prefix string: . + :a :b :c . {"abc" string:startsWith "a"} => {:test a :Success}. ), expect: %( + :a :b :c . :test a :Success. ) } From d419b1a1af9711ac491a91a65f3f88ac81ae6a91 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 19 Apr 2019 16:00:49 -0700 Subject: [PATCH 38/40] Skip a test on subgraph inferrence and update documentation. --- README.md | 5 ++++- spec/suite_reasoner_spec.rb | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 38d2711..a684528 100755 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Write a graph to a file: end ### Reasoning -Partial N3 reasoning is supported. Instantiate a reasoner from a dataset: +Experimental N3 reasoning is supported. Instantiate a reasoner from a dataset: RDF::N3::Reasoner.new do |reasoner| RDF::N3::Reader.open("etc/foaf.n3") {|reader| reasoner << reader} @@ -94,6 +94,8 @@ results in: g = RDF::Node.new() RDF::Statement(f, <#loves>, h) +Note that the behavior of both existential and universal variables is not entirely in keeping with the [Team Submission][], and neither work quite like SPARQL variables. When used in the antecedent part of an implication, universal variables should behave much like SPARQL variables. This area is subject to a fair amount of change. + ## Implementation Notes The parser is driven through a rules table contained in lib/rdf/n3/reader/meta.rb. This includes branch rules to indicate productions to be taken based on a current production. Terminals are denoted @@ -191,6 +193,7 @@ see or the accompanying {file:UNLICENSE} file. [RDF.rb]: http://ruby-rdf.github.com/rdf [RDF::Turtle]: http://ruby-rdf.github.com/rdf-turtle/ [N3]: http://www.w3.org/DesignIssues/Notation3.html "Notation-3" +[Team Submission]: https://www.w3.org/TeamSubmission/n3/ [Turtle]: http://www.w3.org/TR/turtle/ [N-Triples]: http://www.w3.org/TR/n-triples/ [YARD]: http://yardoc.org/ diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 3ea4729..c4ac34e 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -23,7 +23,7 @@ pending "support for log:conclusion" when *%w{conjunction} pending "support for log:conjunction" - when *%w{t553} + when *%w{t553 t554} pending "support for inference over quoted graphs" when *%w{t2005} pending "something else" From c1cf56027eec5c9a947437799ed1f584bc688997 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 19 Apr 2019 16:02:23 -0700 Subject: [PATCH 39/40] Version 3.1.0. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index cb2b00e..fd2a018 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.1 +3.1.0 From ba77d8f8cf909fb17e46b9305c9a5506eb16990b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 19 Apr 2019 16:27:14 -0700 Subject: [PATCH 40/40] Update some HTTP URLs to HTTPS. --- CONTRIBUTING.md | 8 +++---- README.md | 46 ++++++++++++++++++++--------------------- etc/doap.n3 | 4 ++-- etc/doap.nt | 4 ++-- lib/rdf/n3.rb | 2 +- lib/rdf/n3/format.rb | 2 +- lib/rdf/n3/reader/n3.n3 | 2 +- rdf-n3.gemspec | 2 +- 8 files changed, 35 insertions(+), 35 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3eb40fa..cabeb41 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Community contributions are essential for keeping Ruby RDF great. We want to kee This repository uses [Git Flow](https://github.com/nvie/gitflow) to manage development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration. -* create or respond to an issue on the [Github Repository](http://github.com/ruby-rdf/rdf-n3/issues) +* create or respond to an issue on the [Github Repository](https://github.com/ruby-rdf/rdf-n3/issues) * Fork and clone the repo: `git clone git@github.com:your-username/rdf-n3.git` * Install bundle: @@ -30,7 +30,7 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to manage devel of thumb, additions larger than about 15 lines of code), we need an explicit [public domain dedication][PDD] on record from you. -[YARD]: http://yardoc.org/ -[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md -[PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html +[YARD]: https://yardoc.org/ +[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md +[PDD]: https://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html [pr]: https://github.com/ruby-rdf/rdf-n3/compare/ diff --git a/README.md b/README.md index a684528..dde8f4d 100755 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # RDF::N3 reader/writer and reasoner Notation-3 reader/writer for [RDF.rb][RDF.rb] . -[![Gem Version](https://badge.fury.io/rb/rdf-n3.png)](http://badge.fury.io/rb/rdf-n3) -[![Build Status](https://travis-ci.org/ruby-rdf/rdf-n3.png?branch=master)](http://travis-ci.org/ruby-rdf/rdf-n3) +[![Gem Version](https://badge.fury.io/rb/rdf-n3.png)](https://badge.fury.io/rb/rdf-n3) +[![Build Status](https://travis-ci.org/ruby-rdf/rdf-n3.png?branch=master)](https://travis-ci.org/ruby-rdf/rdf-n3) ## Description RDF::N3 is an Notation-3 parser for Ruby using the [RDF.rb][RDF.rb] library suite. Also implements N3 Entailment. @@ -110,10 +110,10 @@ http://www.w3.org/2000/10/swap/grammar/n3.n3 (along with bnf-rules.n3) using cwm [n3-selectors.n3][file:lib/rdf/n3/reader/n3-selectors.rb] is itself used to generate meta.rb using script/build_meta. ## Dependencies -* [RDF.rb](http://rubygems.org/gems/rdf) (~> 3.0, >= 3.0.10) +* [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.0, >= 3.0.10) ## Documentation -Full documentation available on [RubyDoc.info](http://rubydoc.info/github/ruby-rdf/rdf-n3/frames) +Full documentation available on [RubyDoc.info](https://rubydoc.info/github/ruby-rdf/rdf-n3) ### Principle Classes * {RDF::N3} @@ -148,21 +148,21 @@ Full documentation available on [RubyDoc.info](http://rubydoc.info/github/ruby-r ## Resources * [RDF.rb][RDF.rb] * [Distiller](http://rdf.greggkellogg.net/distiller) -* [Documentation](http://rubydoc.info/github/ruby-rdf/rdf-n3/master/frames) +* [Documentation](https://rubydoc.info/github/ruby-rdf/rdf-n3/) * [History](file:file.History.html) * [Notation-3][N3] -* [N3 Primer](http://www.w3.org/2000/10/swap/Primer.html) -* [N3 Reification](http://www.w3.org/DesignIssues/Reify.html) +* [N3 Primer](https://www.w3.org/2000/10/swap/Primer.html) +* [N3 Reification](https://www.w3.org/DesignIssues/Reify.html) * [Turtle][Turtle] -* [W3C SWAP Test suite](http://www.w3.org/2000/10/swap/test/README.html) -* [W3C Turtle Test suite](http://www.w3.org/2001/sw/DataAccess/df1/tests/README.txt) +* [W3C SWAP Test suite](https://www.w3.org/2000/10/swap/test/README.html) +* [W3C Turtle Test suite](https://www.w3.org/2001/sw/DataAccess/df1/tests/README.txt) * [N-Triples][N-Triples] ## Author -* [Gregg Kellogg](http://github.com/gkellogg) - +* [Gregg Kellogg](https://github.com/gkellogg) - ## Contributors -* [Nicholas Humfrey](http://github.com/njh) - +* [Nicholas Humfrey](https://github.com/njh) - ## Contributing This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration. @@ -182,21 +182,21 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange develo ## License This is free and unencumbered public domain software. For more information, -see or the accompanying {file:UNLICENSE} file. +see or the accompanying {file:UNLICENSE} file. ## Feedback * -* -* -* +* +* +* -[RDF.rb]: http://ruby-rdf.github.com/rdf -[RDF::Turtle]: http://ruby-rdf.github.com/rdf-turtle/ -[N3]: http://www.w3.org/DesignIssues/Notation3.html "Notation-3" +[RDF.rb]: https://ruby-rdf.github.com/rdf +[RDF::Turtle]: https://ruby-rdf.github.com/rdf-turtle/ +[N3]: https://www.w3.org/DesignIssues/Notation3.html "Notation-3" [Team Submission]: https://www.w3.org/TeamSubmission/n3/ -[Turtle]: http://www.w3.org/TR/turtle/ -[N-Triples]: http://www.w3.org/TR/n-triples/ -[YARD]: http://yardoc.org/ -[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md -[PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html +[Turtle]: https://www.w3.org/TR/turtle/ +[N-Triples]: https://www.w3.org/TR/n-triples/ +[YARD]: https://yardoc.org/ +[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md +[PDD]: https://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html [SPARQL S-Expressions]: https://jena.apache.org/documentation/notes/sse.html diff --git a/etc/doap.n3 b/etc/doap.n3 index 200ef1d..84226c7 100644 --- a/etc/doap.n3 +++ b/etc/doap.n3 @@ -9,7 +9,7 @@ <> a doap:Project ; doap:name "RDF::N3" ; - doap:homepage ; + doap:homepage ; doap:license ; doap:shortdesc "N3 reader/writer for Ruby."@en ; doap:description "RDF::N3 is an Notation-3 reader/writer and reasoner for the RDF.rb library suite."@en ; @@ -20,7 +20,7 @@ ; doap:download-page ; doap:mailing-list ; - doap:bug-database ; + doap:bug-database ; doap:blog ; doap:developer ; doap:helper ; diff --git a/etc/doap.nt b/etc/doap.nt index cbc5a7f..0449e43 100644 --- a/etc/doap.nt +++ b/etc/doap.nt @@ -1,6 +1,6 @@ . "RDF::N3" . - . + . . "N3 reader/writer reader/writer and reasoner for Ruby."@en . "RDF::N3 is an Notation-3 reader/writer for the RDF.rb library suite."@en . @@ -11,7 +11,7 @@ . . . - . + . . . . diff --git a/lib/rdf/n3.rb b/lib/rdf/n3.rb index bc808ac..1f948fc 100644 --- a/lib/rdf/n3.rb +++ b/lib/rdf/n3.rb @@ -16,7 +16,7 @@ module RDF # end # # @see http://www.rubydoc.info/github/ruby-rdf/rdf/ - # @see http://www.w3.org/TR/REC-rdf-syntax/ + # @see https://www.w3.org/TR/REC-rdf-syntax/ # # @author [Gregg Kellogg](http://greggkellogg.net/) module N3 diff --git a/lib/rdf/n3/format.rb b/lib/rdf/n3/format.rb index b62183c..2d1ec6f 100644 --- a/lib/rdf/n3/format.rb +++ b/lib/rdf/n3/format.rb @@ -15,7 +15,7 @@ module RDF::N3 # @example Obtaining serialization format file extension mappings # RDF::Format.file_extensions #=> {n3: "text/n3"} # - # @see http://www.w3.org/TR/rdf-testcases/#ntriples + # @see https://www.w3.org/TR/rdf-testcases/#ntriples class Format < RDF::Format content_type 'text/n3', extension: :n3, aliases: %w(text/rdf+n3;q=0.2 application/rdf+n3;q=0.2) content_encoding 'utf-8' diff --git a/lib/rdf/n3/reader/n3.n3 b/lib/rdf/n3/reader/n3.n3 index 14b5811..ff013b5 100644 --- a/lib/rdf/n3/reader/n3.n3 +++ b/lib/rdf/n3/reader/n3.n3 @@ -235,7 +235,7 @@ quickvariable cfg:matches "\\?[A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff # Whitespace is not allowed # was: "[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9]+)?"; -langcode cfg:matches "[a-z]+(-[a-z0-9]+)*"; # http://www.w3.org/TR/rdf-testcases/#language +langcode cfg:matches "[a-z]+(-[a-z0-9]+)*"; # https://www.w3.org/TR/rdf-testcases/#language cfg:canStartWith "a". diff --git a/rdf-n3.gemspec b/rdf-n3.gemspec index 87866c3..d08bac6 100755 --- a/rdf-n3.gemspec +++ b/rdf-n3.gemspec @@ -6,7 +6,7 @@ Gem::Specification.new do |gem| gem.date = File.mtime('VERSION').strftime('%Y-%m-%d') gem.name = %q{rdf-n3} - gem.homepage = %q{http://ruby-rdf.github.com/rdf-n3} + gem.homepage = %q{https://ruby-rdf.github.com/rdf-n3} gem.license = 'Unlicense' gem.summary = %q{Notation3 reader/writer and reasoner for RDF.rb.} gem.description = %q{RDF::N3 is an Notation-3 reader/writer and reasoner for the RDF.rb library suite.}