From 487dfac0ec2db9647b459a86067c5ce89b7c8c61 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 16 Dec 2019 13:48:35 -0800 Subject: [PATCH 001/193] Update dependencies. --- rdf-n3.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdf-n3.gemspec b/rdf-n3.gemspec index 73e125d..771b7df 100755 --- a/rdf-n3.gemspec +++ b/rdf-n3.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'rspec', '~> 3.9' gem.add_development_dependency 'rspec-its', '~> 1.3' gem.add_development_dependency 'rdf-spec', '~> 3.1' - gem.add_development_dependency 'rdf-isomorphic', '~> 3.0' + gem.add_development_dependency 'rdf-isomorphic', '~> 3.1' gem.add_development_dependency 'rdf-trig', '~> 3.1' gem.add_development_dependency 'rdf-vocab', '~> 3.1' gem.add_development_dependency 'yard' , '~> 0.9.20' From cf123adce8a70047ff7ce5a7f5452fc347ef8314 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 30 Apr 2020 10:03:16 -0700 Subject: [PATCH 002/193] Improve etc/doap --- etc/doap.n3 | 20 ++++++++++---------- etc/doap.nt | 42 +++++++++++++++++++++--------------------- etc/doap.ttl | 1 + spec/suite_helper.rb | 2 +- 4 files changed, 33 insertions(+), 32 deletions(-) create mode 120000 etc/doap.ttl diff --git a/etc/doap.n3 b/etc/doap.n3 index 84226c7..712a6f3 100644 --- a/etc/doap.n3 +++ b/etc/doap.n3 @@ -1,4 +1,4 @@ -@base . +@base . @prefix rdf: . @prefix rdfs: . @prefix dc: . @@ -10,7 +10,7 @@ <> a doap:Project ; doap:name "RDF::N3" ; doap:homepage ; - doap:license ; + 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 ; doap:created "2010-06-03"^^xsd:date; @@ -18,13 +18,13 @@ doap:implements ; doap:category , ; - doap:download-page ; - doap:mailing-list ; + doap:download-page <> ; + doap:mailing-list ; doap:bug-database ; - doap:blog ; - doap:developer ; + doap:blog ; + doap:developer ; doap:helper ; - doap:maintainer ; - doap:documenter ; - foaf:maker ; - dc:creator . + doap:maintainer ; + doap:documenter ; + foaf:maker ; + dc:creator . diff --git a/etc/doap.nt b/etc/doap.nt index 0449e43..ae64e41 100644 --- a/etc/doap.nt +++ b/etc/doap.nt @@ -1,21 +1,21 @@ - . - "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 . - "2010-06-03"^^ . - "Ruby" . - . - . - . - . - . - . - . - . - . - . - . - . - . + . + . + . + "Ruby" . + "N3 reader/writer for Ruby."@en . + . + . + . + . + . + "RDF::N3 is an Notation-3 reader/writer and reasoner for the RDF.rb library suite."@en . + . + . + . + "RDF::N3" . + . + . + "2010-06-03"^^ . + . + . + . diff --git a/etc/doap.ttl b/etc/doap.ttl new file mode 120000 index 0000000..d64db8e --- /dev/null +++ b/etc/doap.ttl @@ -0,0 +1 @@ +doap.n3 \ No newline at end of file diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index a6b7ac2..7176da2 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -59,7 +59,7 @@ def self.open_file(filename_or_url, **options, &block) remote_document end else - original_open_file(filename_or_url, options, &block) + original_open_file(filename_or_url, **options, &block) end end end From 8a4d3445900acf57c9915b5c651b585be5b6b88b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 30 Apr 2020 11:37:22 -0700 Subject: [PATCH 003/193] Documentation improvements. --- README.md | 50 +++++++++++++++++++++--------------------- lib/rdf/n3/reader.rb | 2 +- lib/rdf/n3/reasoner.rb | 4 ++-- lib/rdf/n3/vocab.rb | 41 ++++++++++++++++++++++++++++------ lib/rdf/n3/writer.rb | 2 +- 5 files changed, 63 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index dde8f4d..5c44b43 100755 --- a/README.md +++ b/README.md @@ -52,19 +52,19 @@ Experimental N3 reasoning is supported. Instantiate a reasoner from a dataset: 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}) + * list:append (not implemented yet - See {RDF::N3::Algebra::List::Append}) + * list:in (not implemented yet - See {RDF::N3::Algebra::List::In}) + * list:last (not implemented yet - See {RDF::N3::Algebra::List::Last}) + * list:member (not implemented yet - See {RDF::N3::Algebra::List::Member}) * 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}) + * log:conclusion (not implemented yet - See {RDF::N3::Algebra::Log::Conclusion}) + * log:conjunction (not implemented yet - See {RDF::N3::Algebra::Log::Conjunction}) + * log:equalTo (See {RDF::N3::Algebra::Log::EqualTo}) + * log:implies (See {RDF::N3::Algebra::Log::Implies}) + * log:includes (See {RDF::N3::Algebra::Log::Includes}) + * log:notEqualTo (not implemented yet - See {RDF::N3::Algebra::Log::NotEqualTo}) + * log:notIncludes (not implemented yet - See {RDF::N3::Algebra::Log::NotIncludes}) + * log:outputString (not implemented yet - See {RDF::N3::Algebra::Log::OutputString}) 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: @@ -81,7 +81,7 @@ when turned into an RDF Repository results in the following quads _:ora _:moby _:form . _:ora "Ora" _:form . -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. +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: @@ -123,18 +123,18 @@ Full documentation available on [RubyDoc.info](https://rubydoc.info/github/ruby- * {RDF::N3::Writer} * {RDF::N3::Algebra} * {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} + * {RDF::N3::Algebra::List::Append} + * {RDF::N3::Algebra::List::In} + * {RDF::N3::Algebra::List::Last} + * {RDF::N3::Algebra::List::Member} + * {RDF::N3::Algebra::Log::Conclusion} + * {RDF::N3::Algebra::Log::Conjunction} + * {RDF::N3::Algebra::Log::EqualTo} + * {RDF::N3::Algebra::Log::Implies} + * {RDF::N3::Algebra::Log::Includes} + * {RDF::N3::Algebra::Log::NotEqualTo} + * {RDF::N3::Algebra::Log::NotIncludes} + * {RDF::N3::Algebra::Log::OutputString} ### Additional vocabularies * {RDF::N3::Log} diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index a3834da..2ff949c 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -727,7 +727,7 @@ def unique_label end # Find any variable that may be defined in the formula identified by `bn` - # @param [RDF::Node] bn name of formula + # @param [RDF::Node] sym name of formula # @param [#to_s] name # @return [RDF::Query::Variable] def find_var(sym, name) diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 963da32..b145b05 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -17,7 +17,7 @@ class Reasoner # Opens a Notation-3 file, and parses it to initialize the reasoner # - # @param [String, #to_s] filename + # @param [String, #to_s] file # @yield [reasoner] `self` # @yieldparam [RDF::N3::Reasoner] reasoner # @yieldreturn [void] ignored @@ -137,7 +137,7 @@ def execute(**options, &block) ## # Reason with results in a duplicate datastore # - # @see {execute} + # @see execute def reason(**options, &block) self.dup.reason!(**options, &block) end diff --git a/lib/rdf/n3/vocab.rb b/lib/rdf/n3/vocab.rb index bf0e009..18a2dff 100644 --- a/lib/rdf/n3/vocab.rb +++ b/lib/rdf/n3/vocab.rb @@ -1,9 +1,36 @@ 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 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 + # @!parse + # # Crypto namespace + # class Crypto < RDF::Vocabulary; end + const_set("Crypto", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/crypto#"))) + + # @!parse + # # List namespace + # class List < RDF::Vocabulary; end + const_set("List", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/list#"))) + + # @!parse + # # Log namespace + # class Log < RDF::Vocabulary; end + const_set("Log", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/log#"))) + + # @!parse + # # Math namespace + # class Math < RDF::Vocabulary; end + const_set("Math", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/math#"))) + + # @!parse + # # Rei namespace + # class Rei < RDF::Vocabulary; end + const_set("Rei", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/reify#"))) + + # @!parse + # # Str namespace + # class Str < RDF::Vocabulary; end + const_set("Str", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/string#"))) + + # @!parse + # # Time namespace + # class Time < RDF::Vocabulary; end + const_set("Time", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/time#"))) end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index d8470ad..33a47b5 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -410,7 +410,7 @@ def preprocess_statement(statement) end # Perform graph-specific preprocessing - # @param [Statement] + # @param [Statement] statement def preprocess_graph_statement(statement) bump_reference(statement.object) # Count properties of this subject From 557971d05be922c63503049c677b7e2c14fc5952 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 19 May 2020 15:34:08 -0700 Subject: [PATCH 004/193] Normalize URLs to HTTPS, where possible. --- UNLICENSE | 2 +- etc/doap.n3 | 2 +- etc/doap.nt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/UNLICENSE b/UNLICENSE index 68a49da..efb9808 100644 --- a/UNLICENSE +++ b/UNLICENSE @@ -21,4 +21,4 @@ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -For more information, please refer to +For more information, please refer to diff --git a/etc/doap.n3 b/etc/doap.n3 index 712a6f3..46c5fe8 100644 --- a/etc/doap.n3 +++ b/etc/doap.n3 @@ -10,7 +10,7 @@ <> a doap:Project ; doap:name "RDF::N3" ; doap:homepage ; - doap:license ; + 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 ; doap:created "2010-06-03"^^xsd:date; diff --git a/etc/doap.nt b/etc/doap.nt index ae64e41..20f476e 100644 --- a/etc/doap.nt +++ b/etc/doap.nt @@ -13,7 +13,7 @@ . . "RDF::N3" . - . + . . "2010-06-03"^^ . . From 647f8e17aa400dd63cc7916ca9a389cdb62c166b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 20 May 2020 13:25:26 -0700 Subject: [PATCH 005/193] Use CC0 license. --- etc/doap.n3 | 2 +- etc/doap.nt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/doap.n3 b/etc/doap.n3 index 46c5fe8..71ee83e 100644 --- a/etc/doap.n3 +++ b/etc/doap.n3 @@ -10,7 +10,7 @@ <> a doap:Project ; doap:name "RDF::N3" ; doap:homepage ; - doap:license ; + 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 ; doap:created "2010-06-03"^^xsd:date; diff --git a/etc/doap.nt b/etc/doap.nt index 20f476e..511eb7e 100644 --- a/etc/doap.nt +++ b/etc/doap.nt @@ -13,7 +13,7 @@ . . "RDF::N3" . - . + . . "2010-06-03"^^ . . From e6b81d9628edd3870138b3fe6afcbab00ea67e37 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 28 May 2020 16:32:47 -0700 Subject: [PATCH 006/193] Update doap:license to https://unlicense.org/. --- etc/doap.n3 | 2 +- etc/doap.nt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/doap.n3 b/etc/doap.n3 index 71ee83e..ff2310f 100644 --- a/etc/doap.n3 +++ b/etc/doap.n3 @@ -10,7 +10,7 @@ <> a doap:Project ; doap:name "RDF::N3" ; doap:homepage ; - doap:license ; + 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 ; doap:created "2010-06-03"^^xsd:date; diff --git a/etc/doap.nt b/etc/doap.nt index 511eb7e..81910fc 100644 --- a/etc/doap.nt +++ b/etc/doap.nt @@ -13,7 +13,7 @@ . . "RDF::N3" . - . + . . "2010-06-03"^^ . . From 68512c4bdc37fb9806d6789b55395645e195583f Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 30 May 2020 12:29:21 -0700 Subject: [PATCH 007/193] Update doap:license (again) to https://unlicense.org/1.0/. --- etc/doap.n3 | 2 +- etc/doap.nt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/doap.n3 b/etc/doap.n3 index ff2310f..c646b53 100644 --- a/etc/doap.n3 +++ b/etc/doap.n3 @@ -10,7 +10,7 @@ <> a doap:Project ; doap:name "RDF::N3" ; doap:homepage ; - doap:license ; + 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 ; doap:created "2010-06-03"^^xsd:date; diff --git a/etc/doap.nt b/etc/doap.nt index 81910fc..cb33a3a 100644 --- a/etc/doap.nt +++ b/etc/doap.nt @@ -13,7 +13,7 @@ . . "RDF::N3" . - . + . . "2010-06-03"^^ . . From 637e2ca2457013c682875c1d1789cca743e04cf8 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 19 Jul 2020 14:04:59 -0700 Subject: [PATCH 008/193] Update reader to use `EBNF::PEG::Parser` instead of original N3 reasoning LL(1) parser. --- .gitignore | 1 + .yardopts | 2 - AUTHORS | 1 - Gemfile | 6 + History.markdown | 99 ---- Rakefile | 33 +- etc/n3.ebnf | 147 ++++++ etc/n3.peg.sxp | 239 ++++++++++ etc/n3.sxp | 120 +++++ etc/notation3.ebnf | 106 ----- etc/notation3.ll1.sxp | 587 ------------------------ examples/example-1.n3 | 2 +- examples/example-2.n3 | 2 +- lib/rdf/n3.rb | 12 +- lib/rdf/n3/meta.rb | 220 +++++++++ lib/rdf/n3/reader.rb | 734 ++++++++++++------------------ lib/rdf/n3/reader/bnf-rules.n3 | 134 ------ lib/rdf/n3/reader/meta.rb | 641 -------------------------- lib/rdf/n3/reader/n3-selectors.n3 | Bin 39115 -> 0 bytes lib/rdf/n3/reader/n3.n3 | 261 ----------- lib/rdf/n3/reader/parser.rb | 239 ---------- lib/rdf/n3/terminals.rb | 77 ++++ lib/rdf/n3/writer.rb | 4 +- rdf-n3.gemspec | 3 +- script/build_meta | 249 ---------- script/parse | 26 +- spec/reader_spec.rb | 379 +++++++-------- spec/spec_helper.rb | 21 +- 28 files changed, 1321 insertions(+), 3024 deletions(-) delete mode 100644 AUTHORS delete mode 100644 History.markdown create mode 100644 etc/n3.ebnf create mode 100644 etc/n3.peg.sxp create mode 100644 etc/n3.sxp delete mode 100644 etc/notation3.ebnf delete mode 100644 etc/notation3.ll1.sxp create mode 100644 lib/rdf/n3/meta.rb delete mode 100644 lib/rdf/n3/reader/bnf-rules.n3 delete mode 100644 lib/rdf/n3/reader/meta.rb delete mode 100644 lib/rdf/n3/reader/n3-selectors.n3 delete mode 100644 lib/rdf/n3/reader/n3.n3 delete mode 100644 lib/rdf/n3/reader/parser.rb create mode 100644 lib/rdf/n3/terminals.rb delete mode 100755 script/build_meta diff --git a/.gitignore b/.gitignore index 762e32b..c9e8a74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +/coverage /doc /pkg /.yardoc diff --git a/.yardopts b/.yardopts index c4ea1fd..5d6b9c1 100644 --- a/.yardopts +++ b/.yardopts @@ -6,7 +6,5 @@ --markup markdown --readme README.md - -History.markdown -AUTHORS VERSION UNLICENSE diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 53fa739..0000000 --- a/AUTHORS +++ /dev/null @@ -1 +0,0 @@ -* Gregg Kellogg \ No newline at end of file diff --git a/Gemfile b/Gemfile index 714d784..cf7e09d 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,12 @@ group :development do gem 'sxp', git: "https://github.com/dryruby/sxp.rb", branch: "develop" end +group :development, :test do + gem 'simplecov', platforms: :mri, require: false + gem 'coveralls', platforms: :mri, require: false +end + group :debug do + gem 'awesome_print', github: 'MatthiasWinkelmann/awesome_print' gem "byebug", platform: :mri end diff --git a/History.markdown b/History.markdown deleted file mode 100644 index fdd5ab8..0000000 --- a/History.markdown +++ /dev/null @@ -1,99 +0,0 @@ -## 0.3.6 -* Update for RDF.rb 0.3.4 -* Added format detection. - -## 0.3.5 -* Use RDF::List for reading and writing lists. -* Performance improvements. -* Writer whitespace and property ordering improvements. -* Remove explicit Turtle support in Reader. - -## 0.3.4.1 -* In Reader, if no base\_uri is used, make sure that @prefix : <#> is generated, not @prefix : <>. -* In Writer, fix bug when trying to use `:standard\_prefixes` option. - -## 0.3.4 -* Reader accepts 1.0E1 in addition to 1.0e1 (case-insensitive match on exponent). -* Writer was not outputting xsd prefix if it was only used in a literal datatype. -* Use bare representations of xsd:integer, xsd:boolean, xsd:double, and xsd:decimal. -* Implement literal canonicalization (on option) in writer. - -## 0.3.3.1 -* Fixed bug in writer when given a base URI. - -## 0.3.3 -* Update dependencies to RDF.rb 0.3.3 -* Update specs to use open-uri-cached and Spira; no longer directly include W3C test cases. -* Use Bundler when running specs. -* Only output prefix definitions used in serialization. -* Fixed stack overflow in regular expression when matching long multi-line literals. -* Fixed bug (issue 14) where illegal QNames were generated in writer. - -## 0.3.2 -* Skipped - -## 0.3.1.3 -* Normalize language tags to lower case (only when canonicalizing). SPARQL specs expect the reader - to not screw with the language case for equivalence tests. - -## 0.3.1.2 -* Normalize language tags to lower case. - -## 0.3.1.1 -* Assert formats for :ttl, :turtle, and :notation3 in addition to :n3 - -## 0.3.1 -* Add application/turtle, application/x-turtle, text/rdf+n3 and application/rdf+n3 as mime types - matching this format, even though only text/turtle and text/n3 are valid. - -## 0.3.0 -* New Predictive-Parser based N3 Reader, substantially faster than previous Treetop-based parser -* RDF.rb 0.3.0 compatibility updates - * Remove literal_normalization and qname_hacks, add back uri_hacks (until 0.3.0) - * Use nil for default namespace - * In Writer - * Use only :prefixes for creating QNames. - * Add :standard_prefixes and :default_namespace options. - * Use """ for multi-line quotes, or anything including escaped characters - * In Reader - * URI canonicalization and validation. - * Added :canonicalize, and :intern options. - * Added #prefixes method returning a hash of prefix definitions. - * Change :strict option to :validate. - * Add check to ensure that predicates are not literals, it's not legal in any RDF variant. -* RSpec 2 compatibility - -## 0.2.3 -* In Writer, set @base_uri not @base, as :base_uri is an attribute. -* Relativize URLs without matching as regexp. -* Allow mixed case literal languages. -* Improve N3 Unicode support for Ruby 1.9 -* Improve Turtle/N3 Writer to use unescaped and qname'd values - -## 0.2.2 -* Ruby 1.9.2 compatibility -* Added script/tc to run test cases -* Fixed RDF.to_s != RDF.to_uri.to_s in writer, it worke for every other vocabulary -* Handle XMLLiteral when value is a Nokogiri node set. -* Simplify process_uri by not having a special case for ^# type URIs. -* Unescape values when creating URIs. -* URI normalization isn't required for N3, so removed. -* Added Reader#rewind and #close as stubs because document is parsed on initialize and input is closed. - -## 0.2.1 -* Compatible with RDF.rb 0.2.1 - -## 0.0.3 -* Replace require against rdf/rdfxml/patches/* with rdf/n3/patches/* - -## 0.0.2 -* N3 parsing and Turtle serialization substantially complete. - * A little more work needed on some tests and some lingering issues in RDF.rb to be resolved. -* Added script/console and script/parse -* Updates to reader to bring it in line with other readers. Implement uri() and ns() as helper functions for constructing URIs. -* Literal_normalization to override RDF::Literal.initialize and create Literal#valid? -* rdf_escape Literals when serializing via to_s -* Remove trailing "#" from URIs when normalizing. - -## 0.0.1 -* First port from RdfContext version 0.5.4 diff --git a/Rakefile b/Rakefile index 86cf690..491e5c0 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,4 @@ require 'rubygems' -require 'yard' -require 'rspec/core/rake_task' namespace :gem do desc "Build the rdf-n3-#{File.read('VERSION').chomp}.gem file" @@ -14,22 +12,27 @@ namespace :gem do end end -RSpec::Core::RakeTask.new(:spec) +namespace :etc do + ETC_FILES = %w{etc/n3.sxp etc/n3.peg.sxp} + desc 'Remove generated files in etc' + task :clean do + %x(rm #{ETC_FILES.join(' ')}) + end -desc "Run specs through RCov" -RSpec::Core::RakeTask.new("spec:rcov") do |spec| - spec.rcov = true - spec.rcov_opts = %q[--exclude "spec"] + desc 'Create versions of ebnf files in etc' + task build: ETC_FILES end -namespace :doc do - YARD::Rake::YardocTask.new +file "etc/n3.sxp" => "etc/n3.ebnf" do |t| + %x{ebnf -o #{t.name} #{t.source}} +end - desc "Generate HTML report specs" - RSpec::Core::RakeTask.new("spec") do |spec| - spec.rspec_opts = ["--format", "html", "-o", "doc/spec.html"] - end +file "etc/n3.peg.sxp" => "etc/n3.ebnf" do |t| + %x{ebnf --peg -o #{t.name} #{t.source}} end -task specs: :spec -task default: :spec +task :meta => %w{lib/rdf/n3/meta.rb} + +file "lib/rdf/n3/meta.rb" => "etc/n3.ebnf" do + %x(ebnf --peg -f rb --mod-name RDF::N3::Meta -o lib/rdf/n3/meta.rb etc/n3.ebnf) +end diff --git a/etc/n3.ebnf b/etc/n3.ebnf new file mode 100644 index 0000000..4a7f3ba --- /dev/null +++ b/etc/n3.ebnf @@ -0,0 +1,147 @@ +# EBNF Notation3 Grammar based pm Antlr4. +# From https://github.com/w3c/N3/blob/master/grammar/n3.g4 + +[1] n3Doc ::= (n3Statement '.' | sparqlDirective)* + +[2] n3Statement ::= n3Directive | triples | existential | universal + +[3] n3Directive ::= prefixID | base + +[4] sparqlDirective ::= sparqlBase | sparqlPrefix + +[5] sparqlBase ::= BASE IRIREF + +[6] sparqlPrefix ::= PREFIX PNAME_NS IRIREF + +[7] prefixID ::= '@prefix' PNAME_NS IRIREF + +[8] base ::= '@base' IRIREF + +[9] triples ::= (subject | blankNodePropertyList) predicateObjectList? + +[10] predicateObjectList ::= verb objectList (';' (verb objectList)?)* + +[11] objectList ::= object (',' object)* + +[12] verb ::= predicate + | 'a' + | '@a' + | 'has' expression + | '@has' expression + | 'is' expression 'of' + | '@is' expression '@of' + | '<=' + | '=>' + | '=' + +[13] subject ::= expression + +[14] predicate ::= (expression | '^' expression) + /* allow first predicate in a path to also be inverted */ + +[15] object ::= expression + +[16] expression ::= path + +[17] path ::= pathItem ('!' path | '^' path)? + +[18] pathItem ::= iri + | blankNode + | quickVar + | collection + | blankNodePropertyList + | literal + | formula + +[19] literal ::= rdfLiteral + | numericLiteral + | BOOLEAN_LITERAL + +[20] blankNodePropertyList ::= '[' predicateObjectList? ']' + +[21] collection ::= '(' object* ')' + +[22] formula ::= '{' formulaContent? '}' + +[23] formulaContent ::= n3Statement ('.' formulaContent?)? + | sparqlDirective formulaContent? + +[24] numericLiteral ::= DOUBLE | DECIMAL | INTEGER + +[25] rdfLiteral ::= STRING (LANGTAG | '^^' iri)? + +[26] iri ::= IRIREF | prefixedName + +[27] iriList ::= iri ( ',' iri )* + +[28] prefixedName ::= PNAME_LN | PNAME_NS + # PNAME_NS will be matched for ':' (i.e., "empty") prefixedNames + # hence this cannot be a lexer rule; for s/p/o of only ':', PNAME_NS will be returned + # instead of PrefixedName token + +[29] blankNode ::= BLANK_NODE_LABEL | ANON + +[30] quickVar ::= QUICK_VAR_NAME + # only made this a parser rule for consistency + # (all other path-items are also parser rules) + +[31] existential ::= '@forSome' iriList + +[32] universal ::= '@forAll' iriList + +@terminals + +[33] BOOLEAN_LITERAL ::= 'true' + | 'false' + | '@true' + | '@false' + +[34] STRING ::= STRING_LITERAL_LONG_SINGLE_QUOTE + | STRING_LITERAL_LONG_QUOTE + | STRING_LITERAL_QUOTE + | STRING_LITERAL_SINGLE_QUOTE + +/* borrowed from SPARQL spec, which excludes newlines and other nastiness */ +[139s] IRIREF ::= '<' ([^<>"{}|^`\]-[#x00-#x1F])* '>' +[140s] PNAME_NS ::= PN_PREFIX? ':' +[141s] PNAME_LN ::= PNAME_NS PN_LOCAL +[142s] BLANK_NODE_LABEL ::= '_:' ( PN_CHARS_U | [0-9] ) ((PN_CHARS|'.')* PN_CHARS)? +[145s] LANGTAG ::= "@" ([a-zA-Z]+ ( "-" [a-zA-Z0-9]+ )*) - ("is" | "has") +[146s] INTEGER ::= [0-9]+ +[147s] DECIMAL ::= [0-9]* '.' [0-9]+ +[148s] DOUBLE ::= [0-9]+ '.' [0-9]* EXPONENT + | '.' ([0-9])+ EXPONENT | ([0-9])+ EXPONENT +[155s] EXPONENT ::= [eE] [+-]? [0-9]+ +[156s] STRING_LITERAL_QUOTE ::= '"' ( [^#x22#x5C#xA#xD] | ECHAR | UCHAR )* '"' +[157s] STRING_LITERAL_SINGLE_QUOTE ::= "'" ( [^#x27#x5C#xA#xD] | ECHAR | UCHAR )* "'" +[158s] STRING_LITERAL_LONG_SINGLE_QUOTE ::= "'''" ( ( "'" | "''" )? ( [^'\] | ECHAR | UCHAR ) )* "'''" +[159s] STRING_LITERAL_LONG_QUOTE ::= '"""' ( ( '"' | '""' )? ( [^"\] | ECHAR | UCHAR ) )* '"""' +[35] UCHAR ::= ( "\u" HEX HEX HEX HEX ) | ( "\U" HEX HEX HEX HEX HEX HEX HEX HEX ) +[160s] ECHAR ::= "\" [tbnrf\"'] +[162s] WS ::= #x20 | #x9 | #xD | #xA +[163s] ANON ::= '[' WS* ']' +[36] QUICK_VAR_NAME ::= "?" PN_LOCAL + /* Allows fuller character set */ +[164s] 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] +[165s] PN_CHARS_U ::= PN_CHARS_BASE | '_' +[167s] PN_CHARS ::= PN_CHARS_U | "-" | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040] +/* BASE and PREFIX must be case-insensitive, hence these monstrosities */ +[37] BASE ::= ('B'|'b') ('A'|'a') ('S'|'s') ('E'|'e') +[38] PREFIX ::= ('P'|'p') ('R'|'r') ('E'|'e') ('F'|'f') ('I'|'i') ('X'|'x') +[168s] PN_PREFIX ::= PN_CHARS_BASE ( ( PN_CHARS | "." )* PN_CHARS )? +[169s] PN_LOCAL ::= ( PN_CHARS_U | ':' | [0-9] | PLX ) ( ( PN_CHARS | '.' | ':' | PLX )* ( PN_CHARS | ':' | PLX ) ) ? +[170s] PLX ::= PERCENT | PN_LOCAL_ESC +[171s] PERCENT ::= '%' HEX HEX +[172s] HEX ::= [0-9] | [A-F] | [a-f] +[173s] PN_LOCAL_ESC ::= '\' ( '_' | '~' | '.' | '-' | '!' | '$' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | ';' | '=' + | '/' | '?' | '#' | '@' | '%' ) +[39] COMMENT ::= ('#' - '#x') [^#xA#xC#xD]* + +# Ignore all whitespace and comments between non-terminals +@pass ( WS | COMMENT )* + + diff --git a/etc/n3.peg.sxp b/etc/n3.peg.sxp new file mode 100644 index 0000000..8f14c6e --- /dev/null +++ b/etc/n3.peg.sxp @@ -0,0 +1,239 @@ +( + (rule n3Doc "1" (star _n3Doc_1)) + (rule _n3Doc_1 "1.1" (alt _n3Doc_2 sparqlDirective)) + (rule _n3Doc_2 "1.2" (seq n3Statement ".")) + (rule n3Statement "2" (alt n3Directive triples existential universal)) + (rule n3Directive "3" (alt prefixID base)) + (rule sparqlDirective "4" (alt sparqlBase sparqlPrefix)) + (rule sparqlBase "5" (seq BASE IRIREF)) + (rule sparqlPrefix "6" (seq PREFIX PNAME_NS IRIREF)) + (rule prefixID "7" (seq "@prefix" PNAME_NS IRIREF)) + (rule base "8" (seq "@base" IRIREF)) + (rule triples "9" (seq _triples_1 _triples_2)) + (rule _triples_1 "9.1" (alt subject blankNodePropertyList)) + (rule _triples_2 "9.2" (opt predicateObjectList)) + (rule predicateObjectList "10" (seq verb objectList _predicateObjectList_1)) + (rule _predicateObjectList_1 "10.1" (star _predicateObjectList_2)) + (rule _predicateObjectList_2 "10.2" (seq ";" _predicateObjectList_3)) + (rule _predicateObjectList_3 "10.3" (opt _predicateObjectList_4)) + (rule _predicateObjectList_4 "10.4" (seq verb objectList)) + (rule objectList "11" (seq object _objectList_1)) + (rule _objectList_1 "11.1" (star _objectList_2)) + (rule _objectList_2 "11.2" (seq "," object)) + (rule verb "12" + (alt predicate "a" "@a" _verb_1 _verb_2 _verb_3 _verb_4 "<=" "=>" "=")) + (rule _verb_1 "12.1" (seq "has" expression)) + (rule _verb_2 "12.2" (seq "@has" expression)) + (rule _verb_3 "12.3" (seq "is" expression "of")) + (rule _verb_4 "12.4" (seq "@is" expression "@of")) + (rule subject "13" (seq expression)) + (rule predicate "14" (alt expression _predicate_1)) + (rule _predicate_1 "14.1" (seq "^" expression)) + (rule object "15" (seq expression)) + (rule expression "16" (seq path)) + (rule path "17" (seq pathItem _path_1)) + (rule _path_1 "17.1" (opt _path_2)) + (rule _path_2 "17.2" (alt _path_3 _path_4)) + (rule _path_3 "17.3" (seq "!" path)) + (rule _path_4 "17.4" (seq "^" path)) + (rule pathItem "18" + (alt iri blankNode quickVar collection blankNodePropertyList literal formula)) + (rule literal "19" (alt rdfLiteral numericLiteral BOOLEAN_LITERAL)) + (rule blankNodePropertyList "20" (seq "[" _blankNodePropertyList_1 "]")) + (rule _blankNodePropertyList_1 "20.1" (opt predicateObjectList)) + (rule collection "21" (seq "(" _collection_1 ")")) + (rule _collection_1 "21.1" (star object)) + (rule formula "22" (seq "{" _formula_1 "}")) + (rule _formula_1 "22.1" (opt formulaContent)) + (rule formulaContent "23" (alt _formulaContent_1 _formulaContent_2)) + (rule _formulaContent_1 "23.1" (seq n3Statement _formulaContent_3)) + (rule _formulaContent_3 "23.3" (opt _formulaContent_4)) + (rule _formulaContent_4 "23.4" (seq "." _formulaContent_5)) + (rule _formulaContent_5 "23.5" (opt formulaContent)) + (rule _formulaContent_2 "23.2" (seq sparqlDirective _formulaContent_6)) + (rule _formulaContent_6 "23.6" (opt formulaContent)) + (rule numericLiteral "24" (alt DOUBLE DECIMAL INTEGER)) + (rule rdfLiteral "25" (seq STRING _rdfLiteral_1)) + (rule _rdfLiteral_1 "25.1" (opt _rdfLiteral_2)) + (rule _rdfLiteral_2 "25.2" (alt LANGTAG _rdfLiteral_3)) + (rule _rdfLiteral_3 "25.3" (seq "^^" iri)) + (rule iri "26" (alt IRIREF prefixedName)) + (rule iriList "27" (seq iri _iriList_1)) + (rule _iriList_1 "27.1" (star _iriList_2)) + (rule _iriList_2 "27.2" (seq "," iri)) + (rule prefixedName "28" (alt PNAME_LN PNAME_NS)) + (rule blankNode "29" (alt BLANK_NODE_LABEL ANON)) + (rule quickVar "30" (seq QUICK_VAR_NAME)) + (rule existential "31" (seq "@forSome" iriList)) + (rule universal "32" (seq "@forAll" iriList)) + (terminals _terminals (seq)) + (terminal BOOLEAN_LITERAL "33" (alt "true" "false" "@true" "@false")) + (terminal STRING "34" + (alt STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE )) + (terminal IRIREF "139s" (seq "<" _IRIREF_1 ">")) + (terminal _IRIREF_1 "139s.1" (star _IRIREF_2)) + (terminal _IRIREF_2 "139s.2" (diff _IRIREF_3 _IRIREF_4)) + (terminal _IRIREF_3 "139s.3" (range "^<>\"{}|^`\\")) + (terminal _IRIREF_4 "139s.4" (range "#x00-#x1F")) + (terminal PNAME_NS "140s" (seq _PNAME_NS_1 ":")) + (terminal _PNAME_NS_1 "140s.1" (opt PN_PREFIX)) + (terminal PNAME_LN "141s" (seq PNAME_NS PN_LOCAL)) + (terminal BLANK_NODE_LABEL "142s" + (seq "_:" _BLANK_NODE_LABEL_1 _BLANK_NODE_LABEL_2)) + (terminal _BLANK_NODE_LABEL_1 "142s.1" (alt PN_CHARS_U _BLANK_NODE_LABEL_3)) + (terminal _BLANK_NODE_LABEL_3 "142s.3" (range "0-9")) + (terminal _BLANK_NODE_LABEL_2 "142s.2" (opt _BLANK_NODE_LABEL_4)) + (terminal _BLANK_NODE_LABEL_4 "142s.4" (seq _BLANK_NODE_LABEL_5 PN_CHARS)) + (terminal _BLANK_NODE_LABEL_5 "142s.5" (star _BLANK_NODE_LABEL_6)) + (terminal _BLANK_NODE_LABEL_6 "142s.6" (alt PN_CHARS ".")) + (terminal LANGTAG "145s" (seq "@" _LANGTAG_1)) + (terminal _LANGTAG_1 "145s.1" (diff _LANGTAG_2 _LANGTAG_3)) + (terminal _LANGTAG_2 "145s.2" (seq _LANGTAG_4 _LANGTAG_5)) + (terminal _LANGTAG_4 "145s.4" (plus _LANGTAG_6)) + (terminal _LANGTAG_6 "145s.6" (range "a-zA-Z")) + (terminal _LANGTAG_5 "145s.5" (star _LANGTAG_7)) + (terminal _LANGTAG_7 "145s.7" (seq "-" _LANGTAG_8)) + (terminal _LANGTAG_8 "145s.8" (plus _LANGTAG_9)) + (terminal _LANGTAG_9 "145s.9" (range "a-zA-Z0-9")) + (terminal _LANGTAG_3 "145s.3" (alt "is" "has")) + (terminal INTEGER "146s" (plus _INTEGER_1)) + (terminal _INTEGER_1 "146s.1" (range "0-9")) + (terminal DECIMAL "147s" (seq _DECIMAL_1 "." _DECIMAL_2)) + (terminal _DECIMAL_1 "147s.1" (star _DECIMAL_3)) + (terminal _DECIMAL_3 "147s.3" (range "0-9")) + (terminal _DECIMAL_2 "147s.2" (plus _DECIMAL_4)) + (terminal _DECIMAL_4 "147s.4" (range "0-9")) + (terminal DOUBLE "148s" (alt _DOUBLE_1 _DOUBLE_2 _DOUBLE_3)) + (terminal _DOUBLE_1 "148s.1" (seq _DOUBLE_4 "." _DOUBLE_5 EXPONENT)) + (terminal _DOUBLE_4 "148s.4" (plus _DOUBLE_6)) + (terminal _DOUBLE_6 "148s.6" (range "0-9")) + (terminal _DOUBLE_5 "148s.5" (star _DOUBLE_7)) + (terminal _DOUBLE_7 "148s.7" (range "0-9")) + (terminal _DOUBLE_2 "148s.2" (seq "." _DOUBLE_8 EXPONENT)) + (terminal _DOUBLE_8 "148s.8" (plus _DOUBLE_9)) + (terminal _DOUBLE_9 "148s.9" (range "0-9")) + (terminal _DOUBLE_3 "148s.3" (seq _DOUBLE_10 EXPONENT)) + (terminal _DOUBLE_10 "148s.10" (plus _DOUBLE_11)) + (terminal _DOUBLE_11 "148s.11" (range "0-9")) + (terminal EXPONENT "155s" (seq _EXPONENT_1 _EXPONENT_2 _EXPONENT_3)) + (terminal _EXPONENT_1 "155s.1" (range "eE")) + (terminal _EXPONENT_2 "155s.2" (opt _EXPONENT_4)) + (terminal _EXPONENT_4 "155s.4" (range "+-")) + (terminal _EXPONENT_3 "155s.3" (plus _EXPONENT_5)) + (terminal _EXPONENT_5 "155s.5" (range "0-9")) + (terminal STRING_LITERAL_QUOTE "156s" (seq "\"" _STRING_LITERAL_QUOTE_1 "\"")) + (terminal _STRING_LITERAL_QUOTE_1 "156s.1" (star _STRING_LITERAL_QUOTE_2)) + (terminal _STRING_LITERAL_QUOTE_2 "156s.2" + (alt _STRING_LITERAL_QUOTE_3 ECHAR UCHAR)) + (terminal _STRING_LITERAL_QUOTE_3 "156s.3" (range "^#x22#x5C#xA#xD")) + (terminal STRING_LITERAL_SINGLE_QUOTE "157s" + (seq "'" _STRING_LITERAL_SINGLE_QUOTE_1 "'")) + (terminal _STRING_LITERAL_SINGLE_QUOTE_1 "157s.1" + (star _STRING_LITERAL_SINGLE_QUOTE_2)) + (terminal _STRING_LITERAL_SINGLE_QUOTE_2 "157s.2" + (alt _STRING_LITERAL_SINGLE_QUOTE_3 ECHAR UCHAR)) + (terminal _STRING_LITERAL_SINGLE_QUOTE_3 "157s.3" (range "^#x27#x5C#xA#xD")) + (terminal STRING_LITERAL_LONG_SINGLE_QUOTE "158s" + (seq "'''" _STRING_LITERAL_LONG_SINGLE_QUOTE_1 "'''")) + (terminal _STRING_LITERAL_LONG_SINGLE_QUOTE_1 "158s.1" + (star _STRING_LITERAL_LONG_SINGLE_QUOTE_2)) + (terminal _STRING_LITERAL_LONG_SINGLE_QUOTE_2 "158s.2" + (seq _STRING_LITERAL_LONG_SINGLE_QUOTE_3 _STRING_LITERAL_LONG_SINGLE_QUOTE_4)) + (terminal _STRING_LITERAL_LONG_SINGLE_QUOTE_3 "158s.3" + (opt _STRING_LITERAL_LONG_SINGLE_QUOTE_5)) + (terminal _STRING_LITERAL_LONG_SINGLE_QUOTE_5 "158s.5" (alt "'" "''")) + (terminal _STRING_LITERAL_LONG_SINGLE_QUOTE_4 "158s.4" + (alt _STRING_LITERAL_LONG_SINGLE_QUOTE_6 ECHAR UCHAR)) + (terminal _STRING_LITERAL_LONG_SINGLE_QUOTE_6 "158s.6" (range "^'\\")) + (terminal STRING_LITERAL_LONG_QUOTE "159s" + (seq "\"\"\"" _STRING_LITERAL_LONG_QUOTE_1 "\"\"\"")) + (terminal _STRING_LITERAL_LONG_QUOTE_1 "159s.1" (star _STRING_LITERAL_LONG_QUOTE_2)) + (terminal _STRING_LITERAL_LONG_QUOTE_2 "159s.2" + (seq _STRING_LITERAL_LONG_QUOTE_3 _STRING_LITERAL_LONG_QUOTE_4)) + (terminal _STRING_LITERAL_LONG_QUOTE_3 "159s.3" (opt _STRING_LITERAL_LONG_QUOTE_5)) + (terminal _STRING_LITERAL_LONG_QUOTE_5 "159s.5" (alt "\"" "\"\"")) + (terminal _STRING_LITERAL_LONG_QUOTE_4 "159s.4" + (alt _STRING_LITERAL_LONG_QUOTE_6 ECHAR UCHAR)) + (terminal _STRING_LITERAL_LONG_QUOTE_6 "159s.6" (range "^\"\\")) + (terminal UCHAR "35" (alt _UCHAR_1 _UCHAR_2)) + (terminal _UCHAR_1 "35.1" (seq "\\u" HEX HEX HEX HEX)) + (terminal _UCHAR_2 "35.2" (seq "\\U" HEX HEX HEX HEX HEX HEX HEX HEX)) + (terminal ECHAR "160s" (seq "\\" _ECHAR_1)) + (terminal _ECHAR_1 "160s.1" (range "tbnrf\\\"'")) + (terminal WS "162s" (alt _WS_1 _WS_2 _WS_3 _WS_4)) + (terminal _WS_1 "162s.1" (hex "#x20")) + (terminal _WS_2 "162s.2" (hex "#x9")) + (terminal _WS_3 "162s.3" (hex "#xD")) + (terminal _WS_4 "162s.4" (hex "#xA")) + (terminal ANON "163s" (seq "[" _ANON_1 "]")) + (terminal _ANON_1 "163s.1" (star WS)) + (terminal QUICK_VAR_NAME "36" (seq "?" PN_LOCAL)) + (terminal PN_CHARS_BASE "164s" + (alt _PN_CHARS_BASE_1 _PN_CHARS_BASE_2 _PN_CHARS_BASE_3 _PN_CHARS_BASE_4 + _PN_CHARS_BASE_5 _PN_CHARS_BASE_6 _PN_CHARS_BASE_7 _PN_CHARS_BASE_8 + _PN_CHARS_BASE_9 _PN_CHARS_BASE_10 _PN_CHARS_BASE_11 _PN_CHARS_BASE_12 + _PN_CHARS_BASE_13 _PN_CHARS_BASE_14 )) + (terminal _PN_CHARS_BASE_1 "164s.1" (range "A-Z")) + (terminal _PN_CHARS_BASE_2 "164s.2" (range "a-z")) + (terminal _PN_CHARS_BASE_3 "164s.3" (range "#x00C0-#x00D6")) + (terminal _PN_CHARS_BASE_4 "164s.4" (range "#x00D8-#x00F6")) + (terminal _PN_CHARS_BASE_5 "164s.5" (range "#x00F8-#x02FF")) + (terminal _PN_CHARS_BASE_6 "164s.6" (range "#x0370-#x037D")) + (terminal _PN_CHARS_BASE_7 "164s.7" (range "#x037F-#x1FFF")) + (terminal _PN_CHARS_BASE_8 "164s.8" (range "#x200C-#x200D")) + (terminal _PN_CHARS_BASE_9 "164s.9" (range "#x2070-#x218F")) + (terminal _PN_CHARS_BASE_10 "164s.10" (range "#x2C00-#x2FEF")) + (terminal _PN_CHARS_BASE_11 "164s.11" (range "#x3001-#xD7FF")) + (terminal _PN_CHARS_BASE_12 "164s.12" (range "#xF900-#xFDCF")) + (terminal _PN_CHARS_BASE_13 "164s.13" (range "#xFDF0-#xFFFD")) + (terminal _PN_CHARS_BASE_14 "164s.14" (range "#x10000-#xEFFFF")) + (terminal PN_CHARS_U "165s" (alt PN_CHARS_BASE "_")) + (terminal PN_CHARS "167s" + (alt PN_CHARS_U "-" _PN_CHARS_1 _PN_CHARS_2 _PN_CHARS_3 _PN_CHARS_4)) + (terminal _PN_CHARS_1 "167s.1" (range "0-9")) + (terminal _PN_CHARS_2 "167s.2" (hex "#x00B7")) + (terminal _PN_CHARS_3 "167s.3" (range "#x0300-#x036F")) + (terminal _PN_CHARS_4 "167s.4" (range "#x203F-#x2040")) + (terminal BASE "37" (seq _BASE_1 _BASE_2 _BASE_3 _BASE_4)) + (terminal _BASE_1 "37.1" (alt "B" "b")) + (terminal _BASE_2 "37.2" (alt "A" "a")) + (terminal _BASE_3 "37.3" (alt "S" "s")) + (terminal _BASE_4 "37.4" (alt "E" "e")) + (terminal PREFIX "38" + (seq _PREFIX_1 _PREFIX_2 _PREFIX_3 _PREFIX_4 _PREFIX_5 _PREFIX_6)) + (terminal _PREFIX_1 "38.1" (alt "P" "p")) + (terminal _PREFIX_2 "38.2" (alt "R" "r")) + (terminal _PREFIX_3 "38.3" (alt "E" "e")) + (terminal _PREFIX_4 "38.4" (alt "F" "f")) + (terminal _PREFIX_5 "38.5" (alt "I" "i")) + (terminal _PREFIX_6 "38.6" (alt "X" "x")) + (terminal PN_PREFIX "168s" (seq PN_CHARS_BASE _PN_PREFIX_1)) + (terminal _PN_PREFIX_1 "168s.1" (opt _PN_PREFIX_2)) + (terminal _PN_PREFIX_2 "168s.2" (seq _PN_PREFIX_3 PN_CHARS)) + (terminal _PN_PREFIX_3 "168s.3" (star _PN_PREFIX_4)) + (terminal _PN_PREFIX_4 "168s.4" (alt PN_CHARS ".")) + (terminal PN_LOCAL "169s" (seq _PN_LOCAL_1 _PN_LOCAL_2)) + (terminal _PN_LOCAL_1 "169s.1" (alt PN_CHARS_U ":" _PN_LOCAL_3 PLX)) + (terminal _PN_LOCAL_3 "169s.3" (range "0-9")) + (terminal _PN_LOCAL_2 "169s.2" (opt _PN_LOCAL_4)) + (terminal _PN_LOCAL_4 "169s.4" (seq _PN_LOCAL_5 _PN_LOCAL_6)) + (terminal _PN_LOCAL_5 "169s.5" (star _PN_LOCAL_7)) + (terminal _PN_LOCAL_7 "169s.7" (alt PN_CHARS "." ":" PLX)) + (terminal _PN_LOCAL_6 "169s.6" (alt PN_CHARS ":" PLX)) + (terminal PLX "170s" (alt PERCENT PN_LOCAL_ESC)) + (terminal PERCENT "171s" (seq "%" HEX HEX)) + (terminal HEX "172s" (alt _HEX_1 _HEX_2 _HEX_3)) + (terminal _HEX_1 "172s.1" (range "0-9")) + (terminal _HEX_2 "172s.2" (range "A-F")) + (terminal _HEX_3 "172s.3" (range "a-f")) + (terminal PN_LOCAL_ESC "173s" (seq "\\" _PN_LOCAL_ESC_1)) + (terminal _PN_LOCAL_ESC_1 "173s.1" + (alt "_" "~" "." "-" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" "/" "?" "#" + "@" "%" )) + (terminal COMMENT "39" (seq _COMMENT_1 _COMMENT_2)) + (terminal _COMMENT_1 "39.1" (diff "#" "#x")) + (terminal _COMMENT_2 "39.2" (star _COMMENT_3)) + (terminal _COMMENT_3 "39.3" (range "^#xA#xC#xD")) + (pass _pass (star __pass_1)) + (rule __pass_1 (alt WS COMMENT))) diff --git a/etc/n3.sxp b/etc/n3.sxp new file mode 100644 index 0000000..24cb864 --- /dev/null +++ b/etc/n3.sxp @@ -0,0 +1,120 @@ +( + (rule n3Doc "1" (star (alt (seq n3Statement ".") sparqlDirective))) + (rule n3Statement "2" (alt n3Directive triples existential universal)) + (rule n3Directive "3" (alt prefixID base)) + (rule sparqlDirective "4" (alt sparqlBase sparqlPrefix)) + (rule sparqlBase "5" (seq BASE IRIREF)) + (rule sparqlPrefix "6" (seq PREFIX PNAME_NS IRIREF)) + (rule prefixID "7" (seq "@prefix" PNAME_NS IRIREF)) + (rule base "8" (seq "@base" IRIREF)) + (rule triples "9" (seq (alt subject blankNodePropertyList) (opt predicateObjectList))) + (rule predicateObjectList "10" + (seq verb objectList (star (seq ";" (opt (seq verb objectList)))))) + (rule objectList "11" (seq object (star (seq "," object)))) + (rule verb "12" + (alt predicate "a" "@a" + (seq "has" expression) + (seq "@has" expression) + (seq "is" expression "of") + (seq "@is" expression "@of") "<=" "=>" "=" )) + (rule subject "13" (seq expression)) + (rule predicate "14" (alt expression (seq "^" expression))) + (rule object "15" (seq expression)) + (rule expression "16" (seq path)) + (rule path "17" (seq pathItem (opt (alt (seq "!" path) (seq "^" path))))) + (rule pathItem "18" + (alt iri blankNode quickVar collection blankNodePropertyList literal formula)) + (rule literal "19" (alt rdfLiteral numericLiteral BOOLEAN_LITERAL)) + (rule blankNodePropertyList "20" (seq "[" (opt predicateObjectList) "]")) + (rule collection "21" (seq "(" (star object) ")")) + (rule formula "22" (seq "{" (opt formulaContent) "}")) + (rule formulaContent "23" + (alt + (seq n3Statement (opt (seq "." (opt formulaContent)))) + (seq sparqlDirective (opt formulaContent))) ) + (rule numericLiteral "24" (alt DOUBLE DECIMAL INTEGER)) + (rule rdfLiteral "25" (seq STRING (opt (alt LANGTAG (seq "^^" iri))))) + (rule iri "26" (alt IRIREF prefixedName)) + (rule iriList "27" (seq iri (star (seq "," iri)))) + (rule prefixedName "28" (alt PNAME_LN PNAME_NS)) + (rule blankNode "29" (alt BLANK_NODE_LABEL ANON)) + (rule quickVar "30" (seq QUICK_VAR_NAME)) + (rule existential "31" (seq "@forSome" iriList)) + (rule universal "32" (seq "@forAll" iriList)) + (terminals _terminals (seq)) + (terminal BOOLEAN_LITERAL "33" (alt "true" "false" "@true" "@false")) + (terminal STRING "34" + (alt STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE )) + (terminal IRIREF "139s" + (seq "<" (star (diff (range "^<>\"{}|^`\\") (range "#x00-#x1F"))) ">")) + (terminal PNAME_NS "140s" (seq (opt PN_PREFIX) ":")) + (terminal PNAME_LN "141s" (seq PNAME_NS PN_LOCAL)) + (terminal BLANK_NODE_LABEL "142s" + (seq "_:" (alt PN_CHARS_U (range "0-9")) (opt (seq (star (alt PN_CHARS ".")) PN_CHARS)))) + (terminal LANGTAG "145s" + (seq "@" + (diff (seq (plus (range "a-zA-Z")) (star (seq "-" (plus (range "a-zA-Z0-9"))))) (alt "is" "has"))) ) + (terminal INTEGER "146s" (plus (range "0-9"))) + (terminal DECIMAL "147s" (seq (star (range "0-9")) "." (plus (range "0-9")))) + (terminal DOUBLE "148s" + (alt + (seq (plus (range "0-9")) "." (star (range "0-9")) EXPONENT) + (seq "." (plus (range "0-9")) EXPONENT) + (seq (plus (range "0-9")) EXPONENT)) ) + (terminal EXPONENT "155s" (seq (range "eE") (opt (range "+-")) (plus (range "0-9")))) + (terminal STRING_LITERAL_QUOTE "156s" + (seq "\"" (star (alt (range "^#x22#x5C#xA#xD") ECHAR UCHAR)) "\"")) + (terminal STRING_LITERAL_SINGLE_QUOTE "157s" + (seq "'" (star (alt (range "^#x27#x5C#xA#xD") ECHAR UCHAR)) "'")) + (terminal STRING_LITERAL_LONG_SINGLE_QUOTE "158s" + (seq "'''" (star (seq (opt (alt "'" "''")) (alt (range "^'\\") ECHAR UCHAR))) "'''")) + (terminal STRING_LITERAL_LONG_QUOTE "159s" + (seq "\"\"\"" (star (seq (opt (alt "\"" "\"\"")) (alt (range "^\"\\") ECHAR UCHAR))) "\"\"\"")) + (terminal UCHAR "35" + (alt (seq "\\u" HEX HEX HEX HEX) (seq "\\U" HEX HEX HEX HEX HEX HEX HEX HEX))) + (terminal ECHAR "160s" (seq "\\" (range "tbnrf\\\"'"))) + (terminal WS "162s" (alt (hex "#x20") (hex "#x9") (hex "#xD") (hex "#xA"))) + (terminal ANON "163s" (seq "[" (star WS) "]")) + (terminal QUICK_VAR_NAME "36" (seq "?" PN_LOCAL)) + (terminal PN_CHARS_BASE "164s" + (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 "165s" (alt PN_CHARS_BASE "_")) + (terminal PN_CHARS "167s" + (alt PN_CHARS_U "-" + (range "0-9") + (hex "#x00B7") + (range "#x0300-#x036F") + (range "#x203F-#x2040")) ) + (terminal BASE "37" (seq (alt "B" "b") (alt "A" "a") (alt "S" "s") (alt "E" "e"))) + (terminal PREFIX "38" + (seq (alt "P" "p") (alt "R" "r") (alt "E" "e") (alt "F" "f") (alt "I" "i") (alt "X" "x"))) + (terminal PN_PREFIX "168s" + (seq PN_CHARS_BASE (opt (seq (star (alt PN_CHARS ".")) PN_CHARS)))) + (terminal PN_LOCAL "169s" + (seq + (alt PN_CHARS_U ":" (range "0-9") PLX) + (opt (seq (star (alt PN_CHARS "." ":" PLX)) (alt PN_CHARS ":" PLX)))) ) + (terminal PLX "170s" (alt PERCENT PN_LOCAL_ESC)) + (terminal PERCENT "171s" (seq "%" HEX HEX)) + (terminal HEX "172s" (alt (range "0-9") (range "A-F") (range "a-f"))) + (terminal PN_LOCAL_ESC "173s" + (seq "\\" + (alt "_" "~" "." "-" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" "/" "?" "#" + "@" "%" )) ) + (terminal COMMENT "39" (seq (diff "#" "#x") (star (range "^#xA#xC#xD")))) + (pass _pass (star (alt WS COMMENT)))) diff --git a/etc/notation3.ebnf b/etc/notation3.ebnf deleted file mode 100644 index 13a5a6a..0000000 --- a/etc/notation3.ebnf +++ /dev/null @@ -1,106 +0,0 @@ -# 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 deleted file mode 100644 index 8ef9a3e..0000000 --- a/etc/notation3.ll1.sxp +++ /dev/null @@ -1,587 +0,0 @@ -( - (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 "_" "~" "." "-" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" "/" "?" "#" - "@" "%" )) )) diff --git a/examples/example-1.n3 b/examples/example-1.n3 index e039c35..869cbd9 100644 --- a/examples/example-1.n3 +++ b/examples/example-1.n3 @@ -1,5 +1,5 @@ @prefix log: . -@keywords. +# @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, diff --git a/examples/example-2.n3 b/examples/example-2.n3 index fdcc04d..933d0d3 100644 --- a/examples/example-2.n3 +++ b/examples/example-2.n3 @@ -1,4 +1,4 @@ -@keywords. +# @keywords. @forAll x, y, z. { x wrote y. y log:includes {z weather w}. diff --git a/lib/rdf/n3.rb b/lib/rdf/n3.rb index 1f948fc..7f7eba1 100644 --- a/lib/rdf/n3.rb +++ b/lib/rdf/n3.rb @@ -25,11 +25,11 @@ module N3 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 :Reasoner, 'rdf/n3/reasoner' - autoload :VERSION, 'rdf/n3/version' - autoload :Writer, 'rdf/n3/writer' + autoload :Meta, 'rdf/n3/meta' + autoload :Reader, 'rdf/n3/reader' + autoload :Reasoner, 'rdf/n3/reasoner' + autoload :Terminals, 'rdf/n3/terminals' + autoload :VERSION, 'rdf/n3/version' + autoload :Writer, 'rdf/n3/writer' end end \ No newline at end of file diff --git a/lib/rdf/n3/meta.rb b/lib/rdf/n3/meta.rb new file mode 100644 index 0000000..4439334 --- /dev/null +++ b/lib/rdf/n3/meta.rb @@ -0,0 +1,220 @@ +# This file is automatically generated by ebnf version 2.1.0 +# Derived from etc/n3.ebnf +module RDF::N3::Meta + RULES = [ + EBNF::Rule.new(:n3Doc, "1", [:star, :_n3Doc_1]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_n3Doc_1, "1.1", [:alt, :_n3Doc_2, :sparqlDirective]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_n3Doc_2, "1.2", [:seq, :n3Statement, "."]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:n3Statement, "2", [:alt, :n3Directive, :triples, :existential, :universal]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:n3Directive, "3", [:alt, :prefixID, :base]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:sparqlDirective, "4", [:alt, :sparqlBase, :sparqlPrefix]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:sparqlBase, "5", [:seq, :BASE, :IRIREF]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:sparqlPrefix, "6", [:seq, :PREFIX, :PNAME_NS, :IRIREF]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:prefixID, "7", [:seq, "@prefix", :PNAME_NS, :IRIREF]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:base, "8", [:seq, "@base", :IRIREF]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:triples, "9", [:seq, :_triples_1, :_triples_2]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_triples_1, "9.1", [:alt, :subject, :blankNodePropertyList]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_triples_2, "9.2", [:opt, :predicateObjectList]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:predicateObjectList, "10", [:seq, :verb, :objectList, :_predicateObjectList_1]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_predicateObjectList_1, "10.1", [:star, :_predicateObjectList_2]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_predicateObjectList_2, "10.2", [:seq, ";", :_predicateObjectList_3]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_predicateObjectList_3, "10.3", [:opt, :_predicateObjectList_4]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_predicateObjectList_4, "10.4", [:seq, :verb, :objectList]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:objectList, "11", [:seq, :object, :_objectList_1]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_objectList_1, "11.1", [:star, :_objectList_2]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_objectList_2, "11.2", [:seq, ",", :object]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:verb, "12", [:alt, :predicate, "a", "@a", :_verb_1, :_verb_2, :_verb_3, :_verb_4, "<=", "=>", "="]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_verb_1, "12.1", [:seq, "has", :expression]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_verb_2, "12.2", [:seq, "@has", :expression]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_verb_3, "12.3", [:seq, "is", :expression, "of"]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_verb_4, "12.4", [:seq, "@is", :expression, "@of"]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:subject, "13", [:seq, :expression]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:predicate, "14", [:alt, :expression, :_predicate_1]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_predicate_1, "14.1", [:seq, "^", :expression]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:object, "15", [:seq, :expression]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:expression, "16", [:seq, :path]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:path, "17", [:seq, :pathItem, :_path_1]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_path_1, "17.1", [:opt, :_path_2]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_path_2, "17.2", [:alt, :_path_3, :_path_4]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_path_3, "17.3", [:seq, "!", :path]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_path_4, "17.4", [:seq, "^", :path]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:pathItem, "18", [:alt, :iri, :blankNode, :quickVar, :collection, :blankNodePropertyList, :literal, :formula]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:literal, "19", [:alt, :rdfLiteral, :numericLiteral, :BOOLEAN_LITERAL]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:blankNodePropertyList, "20", [:seq, "[", :_blankNodePropertyList_1, "]"]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_blankNodePropertyList_1, "20.1", [:opt, :predicateObjectList]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:collection, "21", [:seq, "(", :_collection_1, ")"]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_collection_1, "21.1", [:star, :object]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:formula, "22", [:seq, "{", :_formula_1, "}"]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_formula_1, "22.1", [:opt, :formulaContent]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:formulaContent, "23", [:alt, :_formulaContent_1, :_formulaContent_2]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_formulaContent_1, "23.1", [:seq, :n3Statement, :_formulaContent_3]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_formulaContent_3, "23.3", [:opt, :_formulaContent_4]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_formulaContent_4, "23.4", [:seq, ".", :_formulaContent_5]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_formulaContent_5, "23.5", [:opt, :formulaContent]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_formulaContent_2, "23.2", [:seq, :sparqlDirective, :_formulaContent_6]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_formulaContent_6, "23.6", [:opt, :formulaContent]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:numericLiteral, "24", [:alt, :DOUBLE, :DECIMAL, :INTEGER]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:rdfLiteral, "25", [:seq, :STRING, :_rdfLiteral_1]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_rdfLiteral_1, "25.1", [:opt, :_rdfLiteral_2]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_rdfLiteral_2, "25.2", [:alt, :LANGTAG, :_rdfLiteral_3]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_rdfLiteral_3, "25.3", [:seq, "^^", :iri]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:iri, "26", [:alt, :IRIREF, :prefixedName]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:iriList, "27", [:seq, :iri, :_iriList_1]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_iriList_1, "27.1", [:star, :_iriList_2]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_iriList_2, "27.2", [:seq, ",", :iri]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:prefixedName, "28", [:alt, :PNAME_LN, :PNAME_NS]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:blankNode, "29", [:alt, :BLANK_NODE_LABEL, :ANON]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:quickVar, "30", [:seq, :QUICK_VAR_NAME]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:existential, "31", [:seq, "@forSome", :iriList]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:universal, "32", [:seq, "@forAll", :iriList]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_terminals, nil, [:seq], kind: :terminals).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:BOOLEAN_LITERAL, "33", [:alt, "true", "false", "@true", "@false"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:STRING, "34", [:alt, :STRING_LITERAL_LONG_SINGLE_QUOTE, :STRING_LITERAL_LONG_QUOTE, :STRING_LITERAL_QUOTE, :STRING_LITERAL_SINGLE_QUOTE], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:IRIREF, "139s", [:seq, "<", :_IRIREF_1, ">"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_IRIREF_1, "139s.1", [:star, :_IRIREF_2], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_IRIREF_2, "139s.2", [:diff, :_IRIREF_3, :_IRIREF_4], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_IRIREF_3, "139s.3", [:range, "^<>\"{}|^`\\"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_IRIREF_4, "139s.4", [:range, "#x00-#x1F"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:PNAME_NS, "140s", [:seq, :_PNAME_NS_1, ":"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PNAME_NS_1, "140s.1", [:opt, :PN_PREFIX], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:PNAME_LN, "141s", [:seq, :PNAME_NS, :PN_LOCAL], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:BLANK_NODE_LABEL, "142s", [:seq, "_:", :_BLANK_NODE_LABEL_1, :_BLANK_NODE_LABEL_2], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_BLANK_NODE_LABEL_1, "142s.1", [:alt, :PN_CHARS_U, :_BLANK_NODE_LABEL_3], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_BLANK_NODE_LABEL_3, "142s.3", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_BLANK_NODE_LABEL_2, "142s.2", [:opt, :_BLANK_NODE_LABEL_4], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_BLANK_NODE_LABEL_4, "142s.4", [:seq, :_BLANK_NODE_LABEL_5, :PN_CHARS], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_BLANK_NODE_LABEL_5, "142s.5", [:star, :_BLANK_NODE_LABEL_6], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_BLANK_NODE_LABEL_6, "142s.6", [:alt, :PN_CHARS, "."], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:LANGTAG, "145s", [:seq, "@", :_LANGTAG_1], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_LANGTAG_1, "145s.1", [:diff, :_LANGTAG_2, :_LANGTAG_3], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_LANGTAG_2, "145s.2", [:seq, :_LANGTAG_4, :_LANGTAG_5], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_LANGTAG_4, "145s.4", [:plus, :_LANGTAG_6], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_LANGTAG_6, "145s.6", [:range, "a-zA-Z"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_LANGTAG_5, "145s.5", [:star, :_LANGTAG_7], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_LANGTAG_7, "145s.7", [:seq, "-", :_LANGTAG_8], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_LANGTAG_8, "145s.8", [:plus, :_LANGTAG_9], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_LANGTAG_9, "145s.9", [:range, "a-zA-Z0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_LANGTAG_3, "145s.3", [:alt, "is", "has"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:INTEGER, "146s", [:plus, :_INTEGER_1], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_INTEGER_1, "146s.1", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:DECIMAL, "147s", [:seq, :_DECIMAL_1, ".", :_DECIMAL_2], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DECIMAL_1, "147s.1", [:star, :_DECIMAL_3], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DECIMAL_3, "147s.3", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DECIMAL_2, "147s.2", [:plus, :_DECIMAL_4], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DECIMAL_4, "147s.4", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:DOUBLE, "148s", [:alt, :_DOUBLE_1, :_DOUBLE_2, :_DOUBLE_3], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DOUBLE_1, "148s.1", [:seq, :_DOUBLE_4, ".", :_DOUBLE_5, :EXPONENT], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DOUBLE_4, "148s.4", [:plus, :_DOUBLE_6], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DOUBLE_6, "148s.6", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DOUBLE_5, "148s.5", [:star, :_DOUBLE_7], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DOUBLE_7, "148s.7", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DOUBLE_2, "148s.2", [:seq, ".", :_DOUBLE_8, :EXPONENT], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DOUBLE_8, "148s.8", [:plus, :_DOUBLE_9], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DOUBLE_9, "148s.9", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DOUBLE_3, "148s.3", [:seq, :_DOUBLE_10, :EXPONENT], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DOUBLE_10, "148s.10", [:plus, :_DOUBLE_11], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_DOUBLE_11, "148s.11", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:EXPONENT, "155s", [:seq, :_EXPONENT_1, :_EXPONENT_2, :_EXPONENT_3], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_EXPONENT_1, "155s.1", [:range, "eE"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_EXPONENT_2, "155s.2", [:opt, :_EXPONENT_4], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_EXPONENT_4, "155s.4", [:range, "+-"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_EXPONENT_3, "155s.3", [:plus, :_EXPONENT_5], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_EXPONENT_5, "155s.5", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:STRING_LITERAL_QUOTE, "156s", [:seq, "\"", :_STRING_LITERAL_QUOTE_1, "\""], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_QUOTE_1, "156s.1", [:star, :_STRING_LITERAL_QUOTE_2], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_QUOTE_2, "156s.2", [:alt, :_STRING_LITERAL_QUOTE_3, :ECHAR, :UCHAR], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_QUOTE_3, "156s.3", [:range, "^#x22#x5C#xA#xD"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:STRING_LITERAL_SINGLE_QUOTE, "157s", [:seq, "'", :_STRING_LITERAL_SINGLE_QUOTE_1, "'"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_SINGLE_QUOTE_1, "157s.1", [:star, :_STRING_LITERAL_SINGLE_QUOTE_2], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_SINGLE_QUOTE_2, "157s.2", [:alt, :_STRING_LITERAL_SINGLE_QUOTE_3, :ECHAR, :UCHAR], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_SINGLE_QUOTE_3, "157s.3", [:range, "^#x27#x5C#xA#xD"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:STRING_LITERAL_LONG_SINGLE_QUOTE, "158s", [:seq, "'''", :_STRING_LITERAL_LONG_SINGLE_QUOTE_1, "'''"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_1, "158s.1", [:star, :_STRING_LITERAL_LONG_SINGLE_QUOTE_2], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_2, "158s.2", [:seq, :_STRING_LITERAL_LONG_SINGLE_QUOTE_3, :_STRING_LITERAL_LONG_SINGLE_QUOTE_4], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_3, "158s.3", [:opt, :_STRING_LITERAL_LONG_SINGLE_QUOTE_5], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_5, "158s.5", [:alt, "'", "''"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_4, "158s.4", [:alt, :_STRING_LITERAL_LONG_SINGLE_QUOTE_6, :ECHAR, :UCHAR], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_6, "158s.6", [:range, "^'\\"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:STRING_LITERAL_LONG_QUOTE, "159s", [:seq, "\"\"\"", :_STRING_LITERAL_LONG_QUOTE_1, "\"\"\""], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_1, "159s.1", [:star, :_STRING_LITERAL_LONG_QUOTE_2], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_2, "159s.2", [:seq, :_STRING_LITERAL_LONG_QUOTE_3, :_STRING_LITERAL_LONG_QUOTE_4], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_3, "159s.3", [:opt, :_STRING_LITERAL_LONG_QUOTE_5], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_5, "159s.5", [:alt, "\"", "\"\""], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_4, "159s.4", [:alt, :_STRING_LITERAL_LONG_QUOTE_6, :ECHAR, :UCHAR], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_6, "159s.6", [:range, "^\"\\"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:UCHAR, "35", [:alt, :_UCHAR_1, :_UCHAR_2], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_UCHAR_1, "35.1", [:seq, "\\u", :HEX, :HEX, :HEX, :HEX], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_UCHAR_2, "35.2", [:seq, "\\U", :HEX, :HEX, :HEX, :HEX, :HEX, :HEX, :HEX, :HEX], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:ECHAR, "160s", [:seq, "\\", :_ECHAR_1], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_ECHAR_1, "160s.1", [:range, "tbnrf\\\"'"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:WS, "162s", [:alt, :_WS_1, :_WS_2, :_WS_3, :_WS_4], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_WS_1, "162s.1", [:hex, "#x20"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_WS_2, "162s.2", [:hex, "#x9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_WS_3, "162s.3", [:hex, "#xD"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_WS_4, "162s.4", [:hex, "#xA"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:ANON, "163s", [:seq, "[", :_ANON_1, "]"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_ANON_1, "163s.1", [:star, :WS], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:QUICK_VAR_NAME, "36", [:seq, "?", :PN_LOCAL], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:PN_CHARS_BASE, "164s", [:alt, :_PN_CHARS_BASE_1, :_PN_CHARS_BASE_2, :_PN_CHARS_BASE_3, :_PN_CHARS_BASE_4, :_PN_CHARS_BASE_5, :_PN_CHARS_BASE_6, :_PN_CHARS_BASE_7, :_PN_CHARS_BASE_8, :_PN_CHARS_BASE_9, :_PN_CHARS_BASE_10, :_PN_CHARS_BASE_11, :_PN_CHARS_BASE_12, :_PN_CHARS_BASE_13, :_PN_CHARS_BASE_14], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_1, "164s.1", [:range, "A-Z"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_2, "164s.2", [:range, "a-z"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_3, "164s.3", [:range, "#x00C0-#x00D6"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_4, "164s.4", [:range, "#x00D8-#x00F6"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_5, "164s.5", [:range, "#x00F8-#x02FF"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_6, "164s.6", [:range, "#x0370-#x037D"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_7, "164s.7", [:range, "#x037F-#x1FFF"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_8, "164s.8", [:range, "#x200C-#x200D"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_9, "164s.9", [:range, "#x2070-#x218F"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_10, "164s.10", [:range, "#x2C00-#x2FEF"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_11, "164s.11", [:range, "#x3001-#xD7FF"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_12, "164s.12", [:range, "#xF900-#xFDCF"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_13, "164s.13", [:range, "#xFDF0-#xFFFD"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_BASE_14, "164s.14", [:range, "#x10000-#xEFFFF"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:PN_CHARS_U, "165s", [:alt, :PN_CHARS_BASE, "_"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:PN_CHARS, "167s", [:alt, :PN_CHARS_U, "-", :_PN_CHARS_1, :_PN_CHARS_2, :_PN_CHARS_3, :_PN_CHARS_4], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_1, "167s.1", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_2, "167s.2", [:hex, "#x00B7"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_3, "167s.3", [:range, "#x0300-#x036F"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_CHARS_4, "167s.4", [:range, "#x203F-#x2040"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:BASE, "37", [:seq, :_BASE_1, :_BASE_2, :_BASE_3, :_BASE_4], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_BASE_1, "37.1", [:alt, "B", "b"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_BASE_2, "37.2", [:alt, "A", "a"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_BASE_3, "37.3", [:alt, "S", "s"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_BASE_4, "37.4", [:alt, "E", "e"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:PREFIX, "38", [:seq, :_PREFIX_1, :_PREFIX_2, :_PREFIX_3, :_PREFIX_4, :_PREFIX_5, :_PREFIX_6], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PREFIX_1, "38.1", [:alt, "P", "p"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PREFIX_2, "38.2", [:alt, "R", "r"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PREFIX_3, "38.3", [:alt, "E", "e"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PREFIX_4, "38.4", [:alt, "F", "f"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PREFIX_5, "38.5", [:alt, "I", "i"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PREFIX_6, "38.6", [:alt, "X", "x"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:PN_PREFIX, "168s", [:seq, :PN_CHARS_BASE, :_PN_PREFIX_1], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_PREFIX_1, "168s.1", [:opt, :_PN_PREFIX_2], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_PREFIX_2, "168s.2", [:seq, :_PN_PREFIX_3, :PN_CHARS], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_PREFIX_3, "168s.3", [:star, :_PN_PREFIX_4], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_PREFIX_4, "168s.4", [:alt, :PN_CHARS, "."], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:PN_LOCAL, "169s", [:seq, :_PN_LOCAL_1, :_PN_LOCAL_2], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_LOCAL_1, "169s.1", [:alt, :PN_CHARS_U, ":", :_PN_LOCAL_3, :PLX], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_LOCAL_3, "169s.3", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_LOCAL_2, "169s.2", [:opt, :_PN_LOCAL_4], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_LOCAL_4, "169s.4", [:seq, :_PN_LOCAL_5, :_PN_LOCAL_6], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_LOCAL_5, "169s.5", [:star, :_PN_LOCAL_7], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_LOCAL_7, "169s.7", [:alt, :PN_CHARS, ".", ":", :PLX], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_LOCAL_6, "169s.6", [:alt, :PN_CHARS, ":", :PLX], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:PLX, "170s", [:alt, :PERCENT, :PN_LOCAL_ESC], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:PERCENT, "171s", [:seq, "%", :HEX, :HEX], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:HEX, "172s", [:alt, :_HEX_1, :_HEX_2, :_HEX_3], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_HEX_1, "172s.1", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_HEX_2, "172s.2", [:range, "A-F"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_HEX_3, "172s.3", [:range, "a-f"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:PN_LOCAL_ESC, "173s", [:seq, "\\", :_PN_LOCAL_ESC_1], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_PN_LOCAL_ESC_1, "173s.1", [:alt, "_", "~", ".", "-", "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=", "/", "?", "#", "@", "%"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:COMMENT, "39", [:seq, :_COMMENT_1, :_COMMENT_2], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_COMMENT_1, "39.1", [:diff, "#", "#x"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_COMMENT_2, "39.2", [:star, :_COMMENT_3], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_COMMENT_3, "39.3", [:range, "^#xA#xC#xD"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_pass, nil, [:star, :__pass_1], kind: :pass).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:__pass_1, nil, [:alt, :WS, :COMMENT]).extend(EBNF::PEG::Rule), + ] +end + diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 2ff949c..2ce009c 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -1,4 +1,7 @@ # coding: utf-8 +require 'rdf/reader' +require 'ebnf' + module RDF::N3 ## # A Notation-3/Turtle parser in Ruby @@ -20,15 +23,20 @@ class Reader < RDF::Reader format Format include RDF::Util::Logger + include EBNF::PEG::Parser include Meta - include Parser - - N3_KEYWORDS = %w(a is of has keywords prefix base true false forSome forAny) + include Terminals - # The Blank nodes allocated for formula + # Nodes used as Formulae graph names + # # @return [Array] attr_reader :formulae + # Allocated variables by formula + # + # @return [Hash{Symbol => RDF::Node}] + attr_reader :variables + ## # Initializes the N3 reader instance. # @@ -40,7 +48,7 @@ class Reader < RDF::Reader # @option options [Boolean] :validate (false) # whether to validate the parsed statements and values # @option options [Boolean] :canonicalize (false) - # whether to canonicalize parsed literals + # whether to canonicalize parsed literals and URIs. # @option options [Boolean] :intern (true) # whether to intern all parsed URIs # @option options [Hash] :prefixes (Hash.new) @@ -55,7 +63,6 @@ def initialize(input = $stdin, **options, &block) 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 @@ -63,17 +70,13 @@ def initialize(input = $stdin, **options, &block) @productions = [] @prod_data = [] - @branches = BRANCHES # Get from meta class - @regexps = REGEXPS # Get from meta class - - @formulae = [] # Nodes used as Formulae graph names - @formulae_nodes = {} + @formulae = [] @label_uniquifier ||= "#{Random.new_seed}_000000" @bnodes = {} # allocated bnodes by formula - @variables = {} # allocated variables by formula + @variables = {} if options[:base_uri] - log_info("@uri") { base_uri.inspect} + progress("base_uri") { base_uri.inspect} namespace(nil, uri("#{base_uri}#")) end @@ -87,9 +90,9 @@ def initialize(input = $stdin, **options, &block) #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} + progress("validate") {validate?.inspect} + progress("canonicalize") {canonicalize?.inspect} + progress("intern") {intern?.inspect} if block_given? case block.arity @@ -112,14 +115,40 @@ def inspect def each_statement(&block) if block_given? @callback = block - - parse(START.to_sym) + parse(@input, + :n3Doc, + RDF::N3::Meta::RULES, + whitespace: WS, + **@options + ) do |context, *data| + case context + when :base_uri + uri = data.first + 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.to_s.match(/[\/\#]$/) ? base_uri : process_uri("#{uri}#")) + debug("@base") {"@base=#{base_uri}"} + end + end if validate? && log_statistics[:error] raise RDF::ReaderError, "Errors found during processing" end end enum_for(:each_statement) + rescue EBNF::PEG::Parser::Error => e + case e.message + when /found "@kewords/ + raise RDF::ReaderError, "@keywords has been removed" + else + raise RDF::ReaderError, e.message + end end ## @@ -140,343 +169,265 @@ def each_triple end protected - # Start of production - def onStart(prod) - handler = "#{prod}Start".to_sym - 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_info("#{handler}(#{respond_to?(handler, true)})", depth: depth) {"#{prod}: #{@prod_data.last.inspect}"} - send(handler) if respond_to?(handler, true) - end - - # Process of a token - def onToken(prod, tok) - unless @productions.empty? - parentProd = @productions.last - handler = "#{parentProd}Token".to_sym - 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 << {} + ## + # Parser terminals and productions + ## + terminal(:BOOLEAN_LITERAL, %r{@?(?:true|false)}) do |value| + RDF::Literal.new(value.start_with?('@') ? value[1..-1] : value, + datatype: RDF::XSD.boolean, + canonicalize: canonicalize?) end - - def declarationToken(prod, tok) - case prod - when "@prefix", "@base", "@keywords" - add_prod_data(:prod, prod) - when "prefix" - add_prod_data(:prefix, tok[0..-2]) - when "explicituri" - add_prod_data(:explicituri, tok[1..-2]) - else - add_prod_data(prod.to_sym, tok) - end + terminal(:IRIREF, IRIREF) {|value| process_uri(value[1..-2])} + terminal(:PNAME_NS, PNAME_NS) + terminal(:PNAME_LN, PNAME_LN) {|value| RDF::NTriples.unescape(value)} + terminal(:BLANK_NODE_LABEL, BLANK_NODE_LABEL) {|value| bnode(value[2..-1])} + terminal(:LANGTAG, LANGTAG) {|value| value[1..-1]} + terminal(:INTEGER, INTEGER) {|value| RDF::Literal::Integer.new(value, canonicalize: canonicalize?)} + terminal(:DECIMAL, DECIMAL) {|value| RDF::Literal::Decimal.new(value, canonicalize: canonicalize?)} + terminal(:DOUBLE, DOUBLE) {|value| RDF::Literal::Double.new(value, canonicalize: canonicalize?)} + terminal(:STRING_LITERAL_QUOTE, STRING_LITERAL_QUOTE) do |value| + RDF::NTriples.unescape(value[1..-2]) end - - def declarationFinish - decl = @prod_data.pop - case decl[:prod] - when "@prefix" - uri = process_uri(decl[:explicituri]) - namespace(decl[:prefix], uri) - when "@base" - # 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" - 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? - end - @userkeys = true - else - error("declarationFinish: FIXME #{decl.inspect}") - end + terminal(:STRING_LITERAL_SINGLE_QUOTE, STRING_LITERAL_SINGLE_QUOTE) do |value| + RDF::NTriples.unescape(value[1..-2]) end - - # Document start, instantiate - def documentStart(prod) - @formulae.push(nil) - @prod_data << {} + terminal(:STRING_LITERAL_LONG_SINGLE_QUOTE, STRING_LITERAL_LONG_SINGLE_QUOTE) do |value| + RDF::NTriples.unescape(value[3..-4]) end - - def dtlangToken(prod, tok) - add_prod_data(:langcode, tok) if prod == "langcode" + terminal(:STRING_LITERAL_LONG_QUOTE, STRING_LITERAL_LONG_QUOTE) do |value| + RDF::NTriples.unescape(value[3..-4]) end + terminal(:ANON, ANON) {bnode} + terminal(:QUICK_VAR_NAME, QUICK_VAR_NAME) {|value| univar(value[1..-1])} + terminal(:BASE, BASE) + terminal(:PREFIX, PREFIX) - def existentialStart(prod) - @prod_data << {} + start_production(:n3Doc) do |data, block| + formulae.push(nil) end - # Apart from the set of statements, a formula also has a set of URIs of symbols which are universally quantified, - # and a set of URIs of symbols which are existentially quantified. - # Variables are then in general symbols which have been quantified. + # Cleanup packrat storage after successfully parsing # - # Here we allocate a variable (making up a name) and record with the defining formula. Quantification is done - # when the formula is completed against all in-scope variables - def existentialFinish - pd = @prod_data.pop - forSome = Array(pd[:symbol]) - forSome.each do |term| - var = univar(term, existential: true) - add_var_to_formula(@formulae.last, term, var) - end - end + # (rule _n3Doc_1 "1.1" (alt _n3Doc_2 sparqlDirective)) + production(:_n3Doc_1, clear_packrat: true) {|value| value} - def expressionStart(prod) - @prod_data << {} + # (rule sparqlBase "5" (seq BASE IRIREF)) + production(:sparqlBase) do |value, data, block| + block.call(:base_uri, value.last[:IRIREF]) 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] - 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] - dir_list += expression[:directiontail] if expression[:directiontail] - @prod_data.last[:directiontail] = dir_list if dir_list - elsif expression[:pathitem] && expression[:pathtail] - add_prod_data(:expression, process_path(expression)) - elsif expression[:pathitem] - add_prod_data(:expression, expression[:pathitem]) - else - error("expressionFinish: FIXME #{expression.inspect}") - end + # (rule sparqlPrefix "6" (seq PREFIX PNAME_NS IRIREF)) + production(:sparqlPrefix) do |value| + namespace(value[1][:PNAME_NS][0..-2], value.last[:IRIREF]) end - def literalStart(prod) - @prod_data << {} + # (rule prefixID "7" (seq "@prefix" PNAME_NS IRIREF)) + production(:prefixID) do |value| + namespace(value[1][:PNAME_NS][0..-2], value.last[:IRIREF]) end - def literalToken(prod, tok) - tok = tok[0, 3] == '"""' ? tok[3..-4] : tok[1..-2] - add_prod_data(:string, tok) + # (rule base "8" (seq "@base" IRIREF)) + production(:base) do |value, data, block| + block.call(:base_uri, value.last[:IRIREF]) 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) + # (rule triples "9" (seq _triples_1 _triples_2)) + production(:triples) {|value| value} + # (rule _triples_1 "9.1" (alt subject blankNodePropertyList)) + production(:_triples_1) do |value, data| + debug("triples") {"subject: #{data[:subject]}"} + prod_data[:subject] = data[:subject] end - - def objectStart(prod) - @prod_data << {} + # (rule _triples_2 "9.2" (opt predicateObjectList)) + start_production(:_triples_2) do |data| + debug("triples") {"subject: #{data[:subject]}"} + data[:subject] = prod_data[:subject] + nil end - def objectFinish - object = @prod_data.pop - if object[:expression] - add_prod_data(:object, object[:expression]) - else - error("objectFinish: FIXME #{object.inspect}") - end + # (rule predicateObjectList "10" (seq verb objectList _predicateObjectList_1)) + start_production(:predicateObjectList, as_hash: true) do |data| + # placed here by `subject` or `blankNodePropertyList` + data[:subject] = prod_data[:subject] end - def pathitemStart(prod) - @prod_data << {} + # (rule objectList "11" (seq object _objectList_1)) + start_production(:objectList) do |data| + subject, predicate = prod_data[:subject], prod_data[:verb] + debug("objectList(start)") {"subject: #{subject.inspect}, predicate: #{predicate.inspect}"} end + production(:objectList) do |value| + subject, predicate = prod_data[:subject], prod_data[:verb] + debug("objectList") {"subject: #{subject.inspect}, predicate: #{predicate.inspect}"} + objects = Array(value.last[:_objectList_1]).unshift(value.first[:object]) - def pathitemToken(prod, tok) - case prod - when "numericliteral" - nl = RDF::NTriples.unescape(tok) - datatype = case nl - when /e/i then RDF::XSD.double - 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('?', ':')) - var = uri.variable? ? uri : univar(uri) - add_var_to_formula(@formulae[-2], uri, var) - # Also add var to this formula - add_var_to_formula(@formulae.last, uri, 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) - when "[", "(" - # Push on state for content of blank node - @prod_data << {} - when "]", ")" - # Construct - symbol = process_anonnode(@prod_data.pop) - add_prod_data(:symbol, symbol) - when "{" - # A new formula, push on a node as a named graph - 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, var| - @variables[node][name] = var + # Emit triples with given subject and verb for each object + objects.each do |object| + if prod_data[:invert] + add_statement(:objectList, object, predicate, subject) + else + add_statement(:objectList, subject, predicate, object) end - when "}" - # Pop off the formula - formula = @formulae.pop - add_prod_data(:symbol, formula) - else - error("pathitemToken(#{prod}, #{tok}): FIXME") end end - - def pathitemFinish - pathitem = @prod_data.pop - if pathitem[:pathlist] - error("pathitemFinish(pathlist): FIXME #{pathitem.inspect}") - elsif pathitem[:propertylist] - error("pathitemFinish(propertylist): FIXME #{pathitem.inspect}") - elsif pathitem[:symbol] || pathitem[:literal] - add_prod_data(:pathitem, pathitem[:symbol] || pathitem[:literal]) - else - error("pathitemFinish: FIXME #{pathitem.inspect}") + # (rule _objectList_1 "11.1" (star _objectList_2)) + # (rule _objectList_2 "11.2" (seq "," object)) + production(:_objectList_2) {|value| value.last[:object]} + + # (rule verb "12" (alt predicate "a" "@a" _verb_1 _verb_2 _verb_3 _verb_4 "=" "<=" "=>")) + # Adds verb to prod_data for objectList + production(:verb) do |value, data| + prod_data[:verb] = case value + when RDF::Term + prod_data[:invert] = data[:invert] + value + when 'a', '@a' then RDF.type + when '=' then RDF::OWL.sameAs + when '=>' then RDF::N3::Log.implies + when '<=' + prod_data[:invert] = true + RDF::N3::Log.implies + when Array # forms of has and is xxx of + if value.first[:has] || value.first[:@has] + value[1][:expression] + elsif value.first[:is] || value.first[:@is] + prod_data[:invert] = true + value[1][:expression] + end end end - def pathlistStart(prod) - @prod_data << {pathlist: []} - end - - def pathlistFinish - pathlist = @prod_data.pop - # Flatten propertylist into an array - expr = @prod_data.last.delete(:expression) - add_prod_data(:pathlist, expr) if expr - add_prod_data(:pathlist, pathlist[:pathlist]) if pathlist[:pathlist] + # (rule subject "13" (seq expression)) + production(:subject) do |value| + # Put in prod_data, so it's available to predicateObjectList + prod_data[:subject] = value.last[:expression] end - def pathtailStart(prod) - @prod_data << {pathtail: []} + # (rule predicate "14" (alt expression _predicate_1)) + production(:predicate) do |value, data| + prod_data[:invert] = data[:invert] + value end - - def pathtailToken(prod, tok) - case tok - when "!", "." - add_prod_data(:direction, :forward) - when "^" - add_prod_data(:direction, :reverse) - end + # (rule _predicate_1 "14.1" (seq "^" expression)) + production(:_predicate_1) do |value| + prod_data[:invert] = true + value.last[:expression] 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] + # (rule object "15" (seq expression)) + production(:object) do |value| + value.last[:expression] end - def propertylistStart(prod) - @prod_data << {} + # (rule expression "16" (seq path)) + production(:expression) do |value| + path = value.last[:path] + case path + when Hash # path + # Result is a bnode + process_path(path) + else + path + end end - def propertylistFinish - propertylist = @prod_data.pop - # Flatten propertylist into an array - ary = [propertylist, propertylist.delete(:propertylist)].flatten.compact - @prod_data.last[:propertylist] = ary + # (rule path "17" (seq pathItem _path_1)) + start_production(:path, as_hash: true) + production(:path) do |value, data| + if value[:_path_1] + { + pathitem: value[:pathItem], + direction: value[:_path_1][:"!"] ? :forward : :reverse, + pathtail: value[:_path_1][:path] + } + else + value[:pathItem] + end end - - def simpleStatementStart(prod) - @prod_data << {} + # (rule _path_3 "17.3" (seq "!" path)) + start_production(:_path_3, as_hash: true) + # (rule _path_4 "17.4" (seq "^" path)) + start_production(:_path_4, as_hash: true) + + # (rule blankNodePropertyList "20" (seq "[" _blankNodePropertyList_1 "]")) + production(:blankNodePropertyList) {|value| value[1][:_blankNodePropertyList_1]} + # (rule _blankNodePropertyList_1 "20.1" (opt predicateObjectList)) + start_production(:_blankNodePropertyList_1) {|data| data[:subject] = bnode} + # Returns the blank node subject + production(:_blankNodePropertyList_1) do |value, data| + data[:subject] + end + + # (rule collection "21" (seq "(" _collection_1 ")")) + production(:collection) {|value| value[1][:_collection_1]} + # (rule _collection_1 "21.1" (star object)) + production(:_collection_1) do |value| + list = RDF::List[*value] + list.each_statement do |statement| + next if statement.predicate == RDF.type && statement.object == RDF.List + add_statement(":collection", statement.subject, statement.predicate, statement.object) + end + list.subject 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| - predicate = p[:verb] - next unless predicate - 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| - if p[:invert] - add_statement("simpleStatementFinish", object, predicate, subject) - else - add_statement("simpleStatementFinish", subject, predicate, object) - end - end + # A new formula, push on a node as a named graph + # + # (rule formula "22" (seq "{" _formula_1 "}")) + production(:formula) {|value| value[1][:_formula_1]} + + start_production(:_formula_1) do |data| + node = RDF::Node.new(".form_#{unique_label}") + formulae.push(node) + debug(:formula) {"id: #{node}, depth: #{formulae.length}"} + + # Promote variables defined on the earlier formula to this formula + variables[node] = {} + variables.fetch(formulae[-2], {}).each do |name, var| + variables[node][name] = var end end - def subjectStart(prod) - @prod_data << {} + # Pop off the formula + production(:_formula_1) do |value| + # Result is the BNode associated with the formula + debug(:formula) {"pop: #{formulae.last}, depth: #{formulae.length}"} + formulae.pop end + - def subjectFinish - subject = @prod_data.pop - - if subject[:expression] - add_prod_data(:subject, subject[:expression]) - else - error("unknown expression type") - end + # (rule rdfLiteral "25" (seq STRING _rdfLiteral_1)) + production(:rdfLiteral) do |value| + str = value.first[:STRING] + lang = value.last[:_rdfLiteral_1].to_sym if value.last[:_rdfLiteral_1].is_a?(String) + datatype = value.last[:_rdfLiteral_1] if value.last[:_rdfLiteral_1].is_a?(RDF::URI) + RDF::Literal(str, language: lang, datatype: datatype, canonicalize: canonicalize?) end + # (rule _rdfLiteral_3 "25.3" (seq "^^" iri)) + production(:_rdfLiteral_3) {|value| value.last[:iri]} - def symbolToken(prod, tok) - term = case prod - when 'explicituri' - process_uri(tok[1..-2]) - when 'qname' - process_qname(tok) - else - error("symbolToken(#{prod}, #{tok}): FIXME #{term.inspect}") - end + # (rule iriList "27" (seq iri _iriList_1)) + production(:iriList) do |value| + Array(value.last[:_iriList_1]).unshift(value.first[:iri]) + end + # (rule _iriList_2 "27.2" (seq "," iri)) + production(:_iriList_2) {|value| value.last[:iri]} - add_prod_data(:symbol, term) + # (rule prefixedName "28" (alt PNAME_LN PNAME_NS)) + production(:prefixedName) do |value| + process_pname(value) end - def universalStart(prod) - @prod_data << {} + # 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 + # + # (rule quickVar "30" (seq QUICK_VAR_NAME)) + production(:quickVar) do |value| + var = value.first[:QUICK_VAR_NAME] + add_var_to_formula(formulae[-2], var, var) + # Also add var to this formula + add_var_to_formula(formulae.last, var, var) + var end # Apart from the set of statements, a formula also has a set of URIs of symbols which are universally quantified, @@ -485,168 +436,80 @@ def universalStart(prod) # # Here we allocate a variable (making up a name) and record with the defining formula. Quantification is done # when the formula is completed against all in-scope variables - def universalFinish - pd = @prod_data.pop - forAll = Array(pd[:symbol]) - forAll.each do |term| - add_var_to_formula(@formulae.last, term, univar(term)) - end - end - - def verbStart(prod) - @prod_data << {} - end - - def verbToken(prod, tok) - term = case prod - when '<=' - add_prod_data(:expression, RDF::N3::Log.implies) - add_prod_data(:invert, true) - when '=>' - add_prod_data(:expression, RDF::N3::Log.implies) - when '=' - add_prod_data(:expression, RDF::OWL.sameAs) - when '@a' - add_prod_data(:expression, RDF.type) - when '@has', "@of" - # Syntactic sugar - when '@is' - add_prod_data(:invert, true) - else - error("verbToken(#{prod}, #{tok}): FIXME #{term.inspect}") + # + # (rule existential "31" (seq "@forSome" iriList)) + production(:existential) do |value| + value.last[:iriList].each do |iri| + var = univar(iri, true) + add_var_to_formula(formulae.last, iri, var) end - - add_prod_data(:symbol, term) end - def verbFinish - verb = @prod_data.pop - if verb[:expression] - error("Literal may not be used as a predicate") if verb[:expression].is_a?(RDF::Literal) - error("Formula may not be used as a peredicate") if @formulae_nodes.has_key?(verb[:expression]) - add_prod_data(:verb, verb[:expression]) - add_prod_data(:invert, true) if verb[:invert] - else - error("verbFinish: FIXME #{verb.inspect}") + # Apart from the set of statements, a formula also has a set of URIs of symbols which are universally quantified, + # and a set of URIs of symbols which are existentially quantified. + # Variables are then in general symbols which have been quantified. + # + # Here we allocate a variable (making up a name) and record with the defining formula. Quantification is done + # when the formula is completed against all in-scope variables + # + # (rule universal "32" (seq "@forAll" iriList)) + production(:universal) do |value| + value.last[:iriList].each do |iri| + add_var_to_formula(formulae.last, iri, univar(iri)) end end - private + private ################### # Utility Functions ################### - def process_anonnode(anonnode) - log_debug("process_anonnode", depth: depth) {anonnode.inspect} - - if anonnode[:propertylist] - properties = anonnode[:propertylist] - bnode = bnode() - properties.each do |p| - predicate = p[:verb] - log_debug("process_anonnode(verb)", depth: depth) {predicate.inspect} - objects = Array(p[: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] - 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) - end - list.subject - end - end - # Process a path, such as: - # :a.:b means [is :b of :a] Deprecated # :a!:b means [is :b of :a] => :a :b [] # :a^:b means [:b :a] => [] :b :a # # Create triple and return property used for next iteration - def process_path(expression) - log_debug("process_path", depth: depth) {expression.inspect} - - pathitem = expression[:pathitem] - pathtail = expression[:pathtail] - - direction_list = [expression[:direction], expression[:directiontail]].flatten.compact + # + # Result is last created bnode + def process_path(path) + pathitem, direction, pathtail = path[:pathitem], path[:direction], path[:pathtail] + debug("process_path") {path.inspect} - pathtail.each do |pred| - direction = direction_list.shift + while pathtail bnode = RDF::Node.new + pred = pathtail.is_a?(RDF::Term) ? pathtail : pathtail[:pathitem] if direction == :reverse add_statement("process_path(reverse)", bnode, pred, pathitem) else add_statement("process_path(forward)", pathitem, pred, bnode) end pathitem = bnode + direction = pathtail[:direction] if pathtail.is_a?(Hash) + pathtail = pathtail.is_a?(Hash) && pathtail[:pathtail] end pathitem end def process_uri(uri) - uri(base_uri, RDF::NTriples.unescape(uri)) - end - - def process_qname(tok) - if tok.include?(":") - prefix, name = tok.split(":") - elsif @userkeys - # If the @keywords directive is given, the keywords given will thereafter be recognized - # without a "@" prefix, and anything else is a local name in the default namespace. - prefix, name = "", tok - elsif %w(true false).include?(tok) - # The words true and false are boolean literals. - # - # They were added to Notation3 in 2006-02 in discussion with the SPARQL language developers, the Data - # Access Working Group. Note that no existing documents will have used a naked true or false word, without a - # @keyword statement which would make it clear that they were not to be treated as keywords. Furthermore any - # 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 (#{tok}).") - end + uri(base_uri, RDF::NTriples.unescape(uri.to_s)) + end + + def process_pname(value) + prefix, name = value.split(":") uri = if prefix(prefix) - log_debug('process_qname(ns)', depth: depth) {"#{prefix(prefix)}, #{name}"} + debug('process_pname(ns)') {"#{prefix(prefix)}, #{name}"} ns(prefix, name) - 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) + debug('process_pname(default_ns)', name) namespace(nil, uri("#{base_uri}#")) unless prefix(nil) ns(nil, name) end - log_debug('process_qname', depth: depth) {uri.inspect} + debug('process_pname') {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] - when nil - @prod_data.last[sym] = value - when Array - @prod_data.last[sym] += Array(value) - else - @prod_data.last[sym] = Array(@prod_data.last[sym]) + Array(value) - end - end - # Keep track of allocated BNodes. Blank nodes are allocated to the formula. def bnode(label = nil) if label @@ -657,7 +520,7 @@ def bnode(label = nil) end end - def univar(label, existential: false) + 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, existential: existential) @@ -678,7 +541,8 @@ def add_statement(node, subject, predicate, object) else RDF::Statement(subject, predicate, object) end - log_debug("statement(#{node})", depth: depth) {statement.to_s} + debug("statement(#{node})") {statement.to_s} + error("statement(#{node})", "Statement is invalid: #{statement.inspect}") if validate? && statement.invalid? @callback.call(statement) end @@ -687,17 +551,10 @@ def namespace(prefix, uri) if uri == '#' uri = prefix(nil).to_s + '#' end - log_debug("namespace", depth: depth) {"'#{prefix}' <#{uri}>"} + debug("namespace") {"'#{prefix}' <#{uri}>"} prefix(prefix, uri(uri)) end - # Is this an allowable keyword? - def keyword_check(kw) - unless (@keywords || %w(a is of has)).include?(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) @@ -713,10 +570,11 @@ def uri(value, append = nil) value end - def ns(prefix, suffix) + # Decode a PName + def ns(prefix = nil, suffix = nil) base = prefix(prefix).to_s suffix = suffix.to_s.sub(/^\#/, "") if base.index("#") - log_debug("ns", depth: depth) {"base: '#{base}', suffix: '#{suffix}'"} + debug("ns") {"base: '#{base}', suffix: '#{suffix}'"} uri(base + suffix.to_s) end @@ -731,7 +589,7 @@ def unique_label # @param [#to_s] name # @return [RDF::Query::Variable] def find_var(sym, name) - (@variables[sym] ||= {})[name.to_s] + (variables[sym] ||= {})[name.to_s] end # Add a variable to the formula identified by `bn`, returning the variable. Useful as an LRU for variable name lookups @@ -740,7 +598,7 @@ def find_var(sym, name) # @param [RDF::Query::Variable] var # @return [RDF::Query::Variable] def add_var_to_formula(bn, name, var) - (@variables[bn] ||= {})[name.to_s] = var + (variables[bn] ||= {})[name.to_s] = var end end end diff --git a/lib/rdf/n3/reader/bnf-rules.n3 b/lib/rdf/n3/reader/bnf-rules.n3 deleted file mode 100644 index 1640f99..0000000 --- a/lib/rdf/n3/reader/bnf-rules.n3 +++ /dev/null @@ -1,134 +0,0 @@ -# -# Baccus - Naur Form (BNF) vocabulary -# - -@prefix rdf: . -@prefix rdfs: . -@prefix bnf: . -@prefix : . -@prefix rul: . -@prefix n3: . -@prefix list: . -@prefix doc: . -@prefix log: . -@prefix string: . -@keywords a, is, of. - - -<> rdfs:comment - -"""This set of rules process a BNF graph in its basic -cfg:mustBeOneOf BNF form and create the branching tables to drive a -predictive parser. - -See also cfg2bnf.n3 which expands the shothand ontology into the basic -BNF terms. -""". - -#_____________________________________ - - -# Enumerate options: - -{ ?x bnf:mustBeOneSequence ?y} => { ?x optionTail ?y }. - -{?x optionTail [rdf:first ?y; rdf:rest ?z]} => { - ?x bnf:branch [ bnf:sequence ?y]; - optionTail ?z. - }. - -{ ?x bnf:branch [bnf:sequence ?y] } => { ?y sequenceTail ?y }. - -sequenceTail a log:Chaff. -optionTail a log:Chaff. - -{ ?x sequenceTail [ rdf:rest ?z ] } => { ?x sequenceTail ?z }. - -# What productions can follow each other? -# This is used for working out when to - -{ ?x sequenceTail [ rdf:first ?y; rdf:rest [ rdf:first ?z ]] } => - { ?y bnf:canPrecede ?z }. - -{ ?x bnf:branch [ - bnf:sequence [ - list:last ?y]]. - ?x bnf:canPrecede ?z} => - { ?y bnf:canPrecede ?z }. - -{ ?x bnf:canPrecede ?y. - ?y bnf:branch [ bnf:sequence () ]. - ?y bnf:canPrecede ?z. -} => { - - ?x bnf:canPrecede ?z. -}. - - -bnf:eof bnf:canStartWith "@EOFDUMMY". # @@ kludge - -# Have to separate the next three rules or cwm seems to -# get screwed up and assume there is no solution @@@ - -{ ?x bnf:branch [bnf:sequence [ rdf:first ?y ]]. - } => { ?x bnf:TEST ?y }. - -{ ?x bnf:TEST ?y . - ?y log:rawType log:Literal. } => { ?x bnf:canStartWithLiteral ?y }. - -{ ?x bnf:canStartWithLiteral ?y . -# (?y "(.).*") string:scrape ?c # Use whole string - } => { ?y bnf:canStartWith ?y }. - -#______________________________________________________________ - - - -# Rules for determining branching - -# A branch has a sequence, which is the given BNF production, and -# one or more conditions, which are the strings on which to consider -# that branch. N3 is a langauge in whch the look-ahead often is only -# one character, and may allways be a constsnt string rather than a -# regexp (check). - -# A branchTail is a sequnece which a branch could start with -{ ?x bnf:branch ?b. - ?b bnf:sequence ?s. -} => { - ?b bnf:branchTail ?s. -}. - -{ ?b bnf:branchTail ?s. - ?s rdf:first [ bnf:branch [ bnf:sequence () ]]; - rdf:rest ?t -} => { - ?b bnf:branchTail ?t. -}. - - -{ ?x bnf:branch ?b. - ?b bnf:branchTail ?s. - ?s rdf:first [bnf:canStartWith ?y]. -} => { - ?x bnf:canStartWith ?y. - ?b bnf:condition ?y. -}. - - - -{ ?x bnf:branch ?b; - bnf:canPrecede ?z. - ?z log:rawType log:Literal. - ?b bnf:sequence (). -} => { ?b bnf:condition ?z}. - -{ ?x bnf:branch ?b; - bnf:canPrecede [bnf:canStartWith ?z]. - ?b bnf:sequence (). -} => { ?b bnf:condition ?z}. - - - - -#ends diff --git a/lib/rdf/n3/reader/meta.rb b/lib/rdf/n3/reader/meta.rb deleted file mode 100644 index 54e2d9e..0000000 --- a/lib/rdf/n3/reader/meta.rb +++ /dev/null @@ -1,641 +0,0 @@ -# coding: utf-8 -# This file is automatically generated by script/build_meta -# Branch and Regexp tables derived from http://www.w3.org/2000/10/swap/grammar/n3-selectors.n3 -module RDF::N3::Meta - BRANCHES = { - :"_:_g0" => { - "." => [], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol", - :"_:_g5"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol", - :"_:_g5"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol", - :"_:_g5"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol", - :"_:_g5"], - "}" => [], - }, - :"_:_g1" => { - "." => [], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol", - :"_:_g4"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol", - :"_:_g4"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol", - :"_:_g4"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol", - :"_:_g4"], - "}" => [], - }, - :"_:_g2" => { - "." => [], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#barename", - :"_:_g3"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#barename", - :"_:_g3"], - "}" => [], - }, - :"_:_g3" => { - "," => [",", - :"http://www.w3.org/2000/10/swap/grammar/n3#barename", - :"_:_g3"], - "." => [], - "}" => [], - }, - :"_:_g4" => { - "," => [",", - :"http://www.w3.org/2000/10/swap/grammar/n3#symbol", - :"_:_g4"], - "." => [], - "}" => [], - }, - :"_:_g5" => { - "," => [",", - :"http://www.w3.org/2000/10/swap/grammar/n3#symbol", - :"_:_g5"], - "." => [], - "}" => [], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#boolean" => { - "@false" => ["@false"], - "@true" => ["@true"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#declaration" => { - "@base" => ["@base", - :"http://www.w3.org/2000/10/swap/grammar/n3#explicituri"], - "@keywords" => ["@keywords", - :"_:_g2"], - "@prefix" => ["@prefix", - :"http://www.w3.org/2000/10/swap/grammar/n3#prefix", - :"http://www.w3.org/2000/10/swap/grammar/n3#explicituri"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#document" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "@EOFDUMMY" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "@base" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "@forAll" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "@forSome" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "@keywords" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "@prefix" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional", - :"http://www.w3.org/2000/10/swap/grammar/bnf#eof"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#dtlang" => { - "!" => [], - "\"" => [], - "(" => [], - ")" => [], - "+" => [], - "," => [], - "-" => [], - "." => [], - "0" => [], - ":" => [], - ";" => [], - "<" => [], - "<=" => [], - "=" => [], - "=>" => [], - "?" => [], - "@" => ["@", - :"http://www.w3.org/2000/10/swap/grammar/n3#langcode"], - "@a" => [], - "@false" => [], - "@has" => [], - "@is" => [], - "@of" => [], - "@true" => [], - "[" => [], - "]" => [], - "^" => [], - "^^" => ["^^", - :"http://www.w3.org/2000/10/swap/grammar/n3#symbol"], - "_" => [], - "a" => [], - "{" => [], - "}" => [], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#existential" => { - "@forSome" => ["@forSome", - :"_:_g1"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#expression" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#pathitem", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#formulacontent" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "@base" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "@forAll" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "@forSome" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "@keywords" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "@prefix" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "}" => [], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#literal" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#string", - :"http://www.w3.org/2000/10/swap/grammar/n3#dtlang"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#object" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail" => { - "," => [",", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail"], - "." => [], - ";" => [], - "]" => [], - "}" => [], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#pathitem" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#literal"], - "(" => ["(", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist", - ")"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#numericliteral"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#numericliteral"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#numericliteral"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#quickvariable"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#boolean"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#boolean"], - "[" => ["[", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist", - "]"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#symbol"], - "{" => ["{", - :"http://www.w3.org/2000/10/swap/grammar/n3#formulacontent", - "}"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - ")" => [], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression", - :"http://www.w3.org/2000/10/swap/grammar/n3#pathlist"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#pathtail" => { - "!" => ["!", - :"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "\"" => [], - "(" => [], - ")" => [], - "+" => [], - "," => [], - "-" => [], - "." => [], - "0" => [], - ":" => [], - ";" => [], - "<" => [], - "<=" => [], - "=" => [], - "=>" => [], - "?" => [], - "@a" => [], - "@false" => [], - "@has" => [], - "@is" => [], - "@of" => [], - "@true" => [], - "[" => [], - "]" => [], - "^" => ["^", - :"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "_" => [], - "a" => [], - "{" => [], - "}" => [], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "." => [], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "<=" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "=" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "=>" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "@a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "@has" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "@is" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "]" => [], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#verb", - :"http://www.w3.org/2000/10/swap/grammar/n3#object", - :"http://www.w3.org/2000/10/swap/grammar/n3#objecttail", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail"], - "}" => [], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylisttail" => { - "." => [], - ";" => [";", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "]" => [], - "}" => [], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#subject", - :"http://www.w3.org/2000/10/swap/grammar/n3#propertylist"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#statement" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "@base" => [:"http://www.w3.org/2000/10/swap/grammar/n3#declaration"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "@forAll" => [:"http://www.w3.org/2000/10/swap/grammar/n3#universal"], - "@forSome" => [:"http://www.w3.org/2000/10/swap/grammar/n3#existential"], - "@keywords" => [:"http://www.w3.org/2000/10/swap/grammar/n3#declaration"], - "@prefix" => [:"http://www.w3.org/2000/10/swap/grammar/n3#declaration"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#simpleStatement"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#statementlist" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "@base" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "@forAll" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "@forSome" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "@keywords" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "@prefix" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail"], - "}" => [], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "@EOFDUMMY" => [], - "@base" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "@forAll" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "@forSome" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "@keywords" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "@prefix" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#statement", - ".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statements_optional"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#statementtail" => { - "." => [".", - :"http://www.w3.org/2000/10/swap/grammar/n3#statementlist"], - "}" => [], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#subject" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#symbol" => { - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#qname"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#explicituri"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#qname"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#qname"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#universal" => { - "@forAll" => ["@forAll", - :"_:_g0"], - }, - :"http://www.w3.org/2000/10/swap/grammar/n3#verb" => { - "\"" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "(" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "+" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "-" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "0" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - ":" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "<" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "<=" => ["<="], - "=" => ["="], - "=>" => ["=>"], - "?" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "@a" => ["@a"], - "@false" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "@has" => ["@has", - :"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "@is" => ["@is", - :"http://www.w3.org/2000/10/swap/grammar/n3#expression", - "@of"], - "@true" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "[" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "_" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "a" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - "{" => [:"http://www.w3.org/2000/10/swap/grammar/n3#expression"], - }, - } - - if RUBY_VERSION >= "1.9.0" - BARENAME_START = "A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\u{10000}-\u{effff}" - BARENAME_TAIL = "0-9#{BARENAME_START}\u00b7\u0300-\u036f\u203f-\u2040\\-" - else - BARENAME_START = "A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\xff" - BARENAME_TAIL = "0-9#{BARENAME_START}\xb7\\-" - end - REGEXPS = { - :"http://www.w3.org/2000/10/swap/grammar/n3#barename" => Regexp.compile(%(^[#{BARENAME_START}][#{BARENAME_TAIL}]*)), - :"http://www.w3.org/2000/10/swap/grammar/n3#explicituri" => Regexp.compile("^<[^>]*>"), - :"http://www.w3.org/2000/10/swap/grammar/n3#langcode" => Regexp.compile("^[a-zA-Z]+(-[a-zA-Z0-9]+)*"), - :"http://www.w3.org/2000/10/swap/grammar/n3#prefix" => Regexp.compile(%(^([#{BARENAME_START}][#{BARENAME_TAIL}]*)?:)), - :"http://www.w3.org/2000/10/swap/grammar/n3#qname" => Regexp.compile(%(^(([#{BARENAME_START}][#{BARENAME_TAIL}]*)?:)?([#{BARENAME_START}][#{BARENAME_TAIL}]*)?)), - :"http://www.w3.org/2000/10/swap/grammar/n3#quickvariable" => Regexp.compile(%(^\\?[#{BARENAME_START}][#{BARENAME_TAIL}]*)), - :"http://www.w3.org/2000/10/swap/grammar/n3#string" => Regexp.compile("(\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\")"), - - # Hack to replace integer|double|decimal with numericliteral - :"http://www.w3.org/2000/10/swap/grammar/n3#numericliteral" => Regexp.compile(%(^[-+]?[0-9]+(\\.[0-9]+)?([eE][-+]?[0-9]+)?)) - } -end diff --git a/lib/rdf/n3/reader/n3-selectors.n3 b/lib/rdf/n3/reader/n3-selectors.n3 deleted file mode 100644 index 441225917077896f22100cce4910317b1c6d25bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39115 zcmeHQOK%*<5zbk^Vl}?(%36{V?L$@)xlUrAWY_>@1j&*ThRY$f@jmF?r76W0Kwol7 z;9HPeF1bgiI2VIJbPW6>!kh#Bg;aG<*Yu-mULq;3xI~aB-P7IGRbSVuYi7HL(`0|} ztkG%Qe>$HZc2-x9kB?W5*Hdc7@!{&hG#-uO>FVxy|GkaI%8hP1 za=c!C?0DT7Iz1Ye9}DZYVL&odQ`~5D_b1b>$tY>ufA7h`+Q#i04eJlv?l5h4nYMMN z?E|LmL)`U^=w_tW>Gb3A=hLL0>?I9gLGldPF`Er0<9nJA?BANl)A_Fl^QR3Qg@c>n z;HVY3w8LzN@%-sC1oAH<>_dy!GR??m zo-IHK(sTboNDx7ZABjV7X!Vh1;6~*&AHWb?v&)Vg#^ZzjWG~_Ag)wSq%#PKF5=O!h zHFP(gCG2QWDZ@d3Fh80Ogemk;HOqcHCB~(4t)o33oPR67;HyhNtl?Tpu=AWZ=4LpqgS(a|RrzCipUWemt4( zG1HufEbdB-$d*-9fT83py*|{MQ0)S-kLfOf_Q_=0X!Nq!tTO;MPshpp(`0WDKYD%$ z@f#a>uCI>pi8(wmdoMmjS13jwcoi4eL2;l*O$vG|3$?l*fA-0*zWDUh-zcF0vW&-1 z4}YGCx2|8o&4$SyO-^=KYV}o@}zxs`uG2yeAoWpUr%5E?WFzs zhwoqi@Us2-^v9pUf1U31wzu1B?VqRf{_6*4W`EL?)AreSC#C1UMf{vhhDkiu?0od` z)}uz$)sB=KEJR(Uq|LT>r}4P|)J21CdxQ~Nqe&=92R2&iP+X}42Xt?M2!Pe>c~$2u z`RXVc_Y-!O#*RyugGY&i)H4)G(ET=nJPl@^#*X2Gl{i>xk5qeDys=Mm2~^o>mWKn- zE%C4%97jj9`7e^s#>tkC9R`RRnsx`twS~F}w9H=8ABwf`d_V0qo6<(!+5I9g%(Guq*T1@>Vl|CUccXwI_UFr7X z2-C(&Q^XCtllWqm>>1W8)d44%5tYa*5mf(JJ+ z#UIzD{7F|*b=TDYGhogKfcPfBK0F3|^kq?Q;hePB;A@X4m z`4EUC@e7)b_X0t#n67oD0D*|U6%5)1QAg=DX7D^CV#qXktNEgZAkTY(MA|8%B+H36 z(_BZpLkM{gEX$yxf!DC4rRB>hU-NMgH_AVY)Om-q*-Jj&>1BOzC7T8VbG$dwqDh@k z9~qF9GRqVgkfs!b-t^|g^`hXEt5((s_*qHyq|YDKc$-_v*A41P-^WpnzS2;QzT!}g z_qc_8jiR1}t3vmzsaC#mgL~9hOKN=AKf$_{Q$e~1uRRz#x^S7nH3zrvdKO08s19G_ zsKy;`=Eq#4ubC+;56Fg z>2$J4Ty}O{&e+rmGdjlkbCZblPhyyvyw;aJ9ysw5!01jsoXHs=+Tms^j2|3OWS9*e zl!R?eb@B{qS|G?6m;{kYo2K51*-e2Zu<=En4&hc8J&B;D0qJ5gbm_@pY9775&JT)T z%2{Sw)^LNDt?}^8Q=&l2ajTKZi0UnnHA|lgncz##wP}brzEsW##};7&LIcxIp4I){ zMY0fPFt0eyuAB=if&9Z{!0aB90C;aLOy_`=UcR|{ z;8G&v0zerx4(7qS&v&&5g@<-=ZGkEgqc71r_OBbI`^IMZOd!(x4rnSS%ZvizuaDCn zF3m$-s&LK4qV5yYJ-YL%XZEzTc@;^h^9szbj@Dp=%nMj0>lPx1=OPT;Tcf$l_@1`b zPbF(~6&AxBBv2s(f6EjeFu~^4#ij6!vq=HnlK;p7_$$YMu9OMNnBX$L-Gl#C_wp?& z7eX!{#Roc39XC1;)BkVI(4M(~gT>9iP2Vv({ac4IRQ5z&{3Fq&r5;E$1qrkHbTB?J zV+-uM6&#F!<3Nz2tp zma22AuAVN5Sj}#bnD1Hl=wvlI`nW&xN(qtHv@q+kh&9VCPdI8u^QUyI^@g4~ByQ~M zqdfO=u0#PJtV&4NSaB*X46wK^5~Fd}(icB)Lu7HXbd}jpC99JSe+Pm)E?a=iVlxJ+ zgT384=QNbfV*W%1*=zrPjZ0^1FtCQ~oGhJdU6wf@<(xXwbPp!V0#TJ7&gi!B&r-`E z7M_a<9v>JAv+M=6j~MkNMTv?iauDm?p5AtDqBQ!*_<@$o@F;2$Vu(wR-aQO%AsjC{ayPj;&{ngQ+|K->5bP#jCtNQ{~B`a2NSQ8Yd!WLL!NT}fHezqXg86dBRXYxocd#R(ON=J(GtA%T&92YYr>Xi zaV6`Qxlo4-g^WT@S=c;S6(8MzHeu#3<_-5yO zsp((Rox4_sZxc(JR`f$@uSiYy_NndV<;{AzhmwfpuOGAS3h>Hh8o}i&{FpVeXjwsw zOn@`#=ulTfRefQto=3QVE@5kP!Q5Em!@4)0%VU5XfIt5j7-!y&;~HR{O*_4hxxKxp zNSF;qhr1NrFWK=rj7FD80 zDu0=*%l|^W zKg5;E{FA*2i!!LoY^A_9h&37KoT?_eZ0C|17cL3SdzZ|KP#Q9)U?VkAu8mIzu^)KU zePzrrH|N_-LzA>~Ai%3m!lILNx=J-7jO3+sFDr5Ng#hLo55-^YcBTB*)5N8O9O$v^ zQ^N@F>~@Z3dsA(eeTbFyz3Nt)5q$({xK5{kg9V@mtQ{_X{TYLHt(Le_skG-Be%*wC zPzDsN=EIL*8|Yuvpq&B3QuQI|A&4rW48kOWe-*Z30@&JDVPrp%s4OI}rS+se=?}ix z+SU4AJ8i)w!1AW^r)3X^^>rHeY|v_syFkS}S-0rshb5N<^ko(eKf{2#yrfGi{qYXh z2>`pJD?x;21X$2z%A?s1euQ;>8;-I%+0kSfu~*b_rh~HfQPhNdMbvv7ZNuNrt>$J2 z{)7Ks`8H~9-i)GFi#l7k@Y&Y4P3L*_u=Nc5ZcSt^S<+0b7GUi{cMI1b3%hDiI3LRt z3~)SnK?J1$Z)Vs$<4Pbs7W2BIR*i~XClw&P9Dv~y7uDTOzi}Of|NeI%KD*Sm+=pJkpk(;fL57odG-5VRNp`uRz>$5C z+qxVn@+~dkt6T*RrT7M!w3hQrQzh?VB^9=GZ9ux`=GwT*N&4QdgF!4W zrIYH2T$GuDPm$ZDK_<;y!x}1t^7B!vvOzN4@cL6R+3*m}=kSAC(^YEiA(r3yhjUs? z9geTF@B?|>2gS?c=vu47_w1#U4>i(-+l6Q9EewiMTp0Qa7gpn0vjULN(Q`V7lSFuX zcMi2IgMUpGF{Xmc*_JdQ7jWCa8sA!bXSOO-+8aTavFVP&XfQvdlBaI9g2{Zf3K}mQ zii#p=#ZwVB;voxK^$AcuV(PzTehBQc(r1#yTV&rw!zHr3om!AW`daBoqEzn>X2i{W zC_AHUNaZ`}icJ5VIMrCJ!exqBbrXhJ^}JYrU*WG4`unj0kN#vdino$O{4L>R?;(7H z|FeBnS*v35!t)WPOLLe&h`Y0a9JMa{oW%+v`m2Vx53U^IkfLjkIK>1)9Q+!xgSg#z zn&3+%2H;m2bB0z&&#lX-^G`c&JUj7<4WxOkcmWQsxYC89$)`7?6<&zvwUZ&#h)=ZN ztbQ`11BSw}f#0@7GXm^YjQZu)!$)vSaTish8D=P5>O&p;}bf=v_FV$uWJz?y{f}reAc( dNZTU7fWvxy0F&dLp9f%Y`gag8YFi30{|BeT4+#JO diff --git a/lib/rdf/n3/reader/n3.n3 b/lib/rdf/n3/reader/n3.n3 deleted file mode 100644 index ff013b5..0000000 --- a/lib/rdf/n3/reader/n3.n3 +++ /dev/null @@ -1,261 +0,0 @@ -# Notation3 in Notation3 -# Context Free Grammar without tokenization -# -@prefix rdf: . -@prefix rdfs: . -@prefix cfg: . -@prefix rul: . -@prefix : . -@prefix n3: . -@prefix list: . -@prefix string: . -@keywords a, is, of. - - -# Issues: -# - string token regexp not right FIXED -# - tokenizing rules in general: whitespace are not defined in n3.n3 -# and it would be nice for the *entire* syntax description to be in RDF. -# - encoding really needs specifying -# - @keywords affects tokenizing -# - comments (tokenizer deals with) -# - We assume ASCII, in fact should use not notNameChars for i18n - -# tokenizing: -# Absorb anything until end of regexp, then stil white space -# period followed IMMEDIATELY by an opener or name char is taken as "!". -# Except after a "." used instead of in those circumstances, -# ws may be inserted between tokens. -# WS MUST be inserted between tokens where ambiguity would arise. -# (possible ending characters of one and beginning characters overlap) -# - -#<> cfg:syntaxFor [ cfg:internetMediaType -# ]. - - -# __________________________________________________________________ -# -# The N3 Full Grammar - - -language a cfg:Language; - cfg:document document; - cfg:whiteSpace "@@@@@". - - -document a rul:Used; - cfg:mustBeOneSequence( - - ( -# [ cfg:zeroOrMore declaration ] -# [ cfg:zeroOrMore universal ] -# [ cfg:zeroOrMore existential ] - statements_optional - cfg:eof - ) - ). - -statements_optional cfg:mustBeOneSequence (() ( statement "." statements_optional ) ). - -# Formula does NOT need period on last statement - -formulacontent cfg:mustBeOneSequence ( - ( statementlist ) - ). - - -statementlist cfg:mustBeOneSequence ( - ( ) - ( statement statementtail ) - ). - -statementtail cfg:mustBeOneSequence ( - ( ) - ( "." statementlist ) - ). - - -statement cfg:mustBeOneSequence ( - (declaration) - (universal) - (existential) - (simpleStatement) - ). - -universal cfg:mustBeOneSequence ( - ( - "@forAll" - [ cfg:commaSeparatedListOf symbol ] - )). - -existential cfg:mustBeOneSequence( - ( "@forSome" - [ cfg:commaSeparatedListOf symbol ] - )). - - -declaration cfg:mustBeOneSequence( - ( "@base" explicituri ) - ( "@prefix" prefix explicituri ) - ( "@keywords" [ cfg:commaSeparatedListOf barename ] ) - ). - - -simpleStatement cfg:mustBeOneSequence(( subject propertylist )). - -propertylist cfg:mustBeOneSequence ( - ( ) - ( predicate object objecttail propertylisttail ) - ). - -propertylisttail cfg:mustBeOneSequence ( - ( ) - ( ";" propertylist ) - ). - - -objecttail cfg:mustBeOneSequence ( - ( ) - ( "," object objecttail ) - ). - - -predicate cfg:mustBeOneSequence ( - ( expression ) - ( "@has" expression ) - ( "@is" expression "@of" ) - ( "@a" ) - ( "=" ) - ( "=>" ) - ( "<=" ) - ). - -subject cfg:mustBeOneSequence ((expression)). - -object cfg:mustBeOneSequence ((expression)). - -expression cfg:mustBeOneSequence( - ( pathitem pathtail ) - ). - -pathtail cfg:mustBeOneSequence( - ( ) - ( "!" expression ) - ( "^" expression ) - ). - - -pathitem cfg:mustBeOneSequence ( - ( symbol ) - ( "{" formulacontent "}" ) - ( quickvariable ) - ( numericliteral ) - ( literal ) - ( "[" propertylist "]" ) - ( "(" pathlist ")" ) - ( boolean ) -# ( "@this" ) # Deprocated. Was allowed for this log:forAll x -). - - -boolean cfg:mustBeOneSequence ( - ( "@true" ) - ( "@false" ) -) . - -pathlist cfg:mustBeOneSequence (() (expression pathlist)). - -symbol cfg:mustBeOneSequence ( - (explicituri) - (qname) - ). - - -numericliteral cfg:mustBeOneSequence ( - ( integer ) - ( rational ) - ( double ) - ( decimal ) -) . - -rational cfg:mustBeOneSequence (( integer "/" unsignedint)). - - -literal cfg:mustBeOneSequence(( string dtlang)). - -dtlang cfg:mustBeOneSequence( () ("@" langcode) ("^^" symbol)). - - -#______________________________________________________________________ -# -# TERMINALS -# -# "canStartWith" actually gives "a" for the whole class of alpha characters -# and "0" for any of the digits 0-9. This is used to build the branching -# tables. -# -integer cfg:matches """[-+]?[0-9]+"""; - cfg:canStartWith "0", "-", "+". -unsignedint cfg:matches """[0-9]+"""; - cfg:canStartWith "0". -double cfg:matches """[-+]?[0-9]+(\\.[0-9]+)?([eE][-+]?[0-9]+)"""; - cfg:canStartWith "0", "-", "+". -decimal cfg:matches """[-+]?[0-9]+\\.[0-9]*"""; - cfg:canStartWith "0", "-", "+". - -#numericliteral cfg:matches """[-+]?[0-9]+(\\.[0-9]+)?(e[-+]?[0-9]+)?"""; -# cfg:canStartWith "0", "-", "+". - -explicituri cfg:matches "<[^>]*>"; - cfg:canStartWith "<". - -prefix cfg:matches "([A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff][\\-0-9A-Z_a-z\u00b7\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u037d\u037f-\u1fff\u200c-\u200d\u203f-\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff]*)?:"; - cfg:canStartWith "a", "_", ":". # @@ etc unicode - -qname cfg:matches "(([A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff][\\-0-9A-Z_a-z\u00b7\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u037d\u037f-\u1fff\u200c-\u200d\u203f-\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff]*)?:)?[A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff][\\-0-9A-Z_a-z\u00b7\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u037d\u037f-\u1fff\u200c-\u200d\u203f-\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff]*"; - cfg:canStartWith "a", "_", ":". # @@ etc unicode - -# ASCII version: -#barename cfg:matches "[a-zA-Z_][a-zA-Z0-9_]*"; # subset of qname -# cfg:canStartWith "a", "_". # @@ etc - -# This is the XML1.1 -barename cfg:matches "[A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff][\\-0-9A-Z_a-z\u00b7\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u037d\u037f-\u1fff\u200c-\u200d\u203f-\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff]*"; - cfg:canStartWith "a", "_". # @@ etc . - -# as far as I can tell, the regexp should be -# barename cfg:matches "[A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff][\\-0-9A-Z_a-z\u00b7\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u037d\u037f-\u1fff\u200c-\u200d\u203f-\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff]*" . -# - -quickvariable cfg:matches "\\?[A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff][\\-0-9A-Z_a-z\u00b7\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u037d\u037f-\u1fff\u200c-\u200d\u203f-\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff]*"; # ? barename - cfg:canStartWith "?". # - -# Maybe dtlang should just be part of string regexp? -# Whitespace is not allowed - -# was: "[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9]+)?"; -langcode cfg:matches "[a-z]+(-[a-z0-9]+)*"; # https://www.w3.org/TR/rdf-testcases/#language - cfg:canStartWith "a". - - -# raw regexp single quoted would be "([^"]|(\\"))*" -# See: -# $ PYTHONPATH=$SWAP python -# >>> import tokenize -# >>> import notation3 -# >>> print notation3.stringToN3(tokenize.Double3) -# "[^\"\\\\]*(?:(?:\\\\.|\"(?!\"\"))[^\"\\\\]*)*\"\"\"" -# >>> print notation3.stringToN3(tokenize.Double) -# "[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"" -# After that we have to prefix with one or three opening \" which -# the python regexp doesn't have. -# -# string3 cfg:matches "\"\"\"[^\"\\\\]*(?:(?:\\\\.|\"(?!\"\"))[^\"\\\\]*)*\"\"\"". -# string1 cfg:matches "\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"". - -string cfg:matches "(\"\"\"[^\"\\\\]*(?:(?:\\\\.|\"(?!\"\"))[^\"\\\\]*)*\"\"\")|(\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\")"; - cfg:canStartWith "\"". - - -#ends diff --git a/lib/rdf/n3/reader/parser.rb b/lib/rdf/n3/reader/parser.rb deleted file mode 100644 index 903ea32..0000000 --- a/lib/rdf/n3/reader/parser.rb +++ /dev/null @@ -1,239 +0,0 @@ -# coding: utf-8 -# Simple parser to go through productions without attempting evaluation -module RDF::N3 - module Parser - START = 'http://www.w3.org/2000/10/swap/grammar/n3#document' - R_WHITESPACE = Regexp.compile('\A\s*(?:#.*$)?') - R_MLSTRING = Regexp.compile("^.*([^\"\\\\]*)\"\"\"") - 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? - pushed = false - 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]}"} - - # 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? - sequence = prod_branch[tok] - if sequence.nil? - dump_stack(todo_stack) if $verbose - 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", depth: depth) {sequence.inspect} - todo_stack.last[:terms] += sequence - end - - #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)", depth: depth) {term.to_s} - word = buffer[0, term.length] - if word == term - onToken(term, word) - consume(term.length) - elsif '@' + word.chop == term && @keywords.include?(word.chop) - onToken(term, word.chop) - consume(term.length - 1) - else - error("Found '#{buffer[0, 10]}...'; #{term} expected") - end - elsif regexp = @regexps[term] - if abbr(term) == 'string' && buffer[0, 3] == '"""' - # Read until end of multi-line comment if this is the start of a multi-line comment - string = '"""' - consume(3) - next_line = buffer - #log_debug("ml-str(start)", depth: depth) {next_line.dump} - until md = R_MLSTRING.match(next_line) - begin - string += next_line - next_line = readline - rescue EOFError - error("EOF reached searching for end of multi-line comment") - end - end - string += md[0].to_s - consume(md[0].to_s.length) - onToken('string', string) - #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", 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)", 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 - end - end - while !todo_stack.empty? - todo_stack.pop - self.onFinish - end - end - - # Memoizer for get_token - def token - unless @memo.has_key?(@pos) - tok = self.get_token - @memo[@pos] = tok - 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] == '"' - - j = 0 - j += 1 while buffer[j+1, 1] && !NOT_NAME_CHARS.include?(buffer[j+1, 1]) - name = buffer[1, j] - if name == 'keywords' - @keywords = [] - @keyword_mode = true - end - return '@' + name - end - - j = 0 - j += 1 while buffer[j, 1] && !NOT_QNAME_CHARS.include?(buffer[j, 1]) - word = buffer[0, j] - error("Tokenizer expected qname, found #{buffer[0, 10]}") unless word - if @keyword_mode - @keywords << word - elsif @keywords.include?(word) - if word == 'keywords' - @keywords = [] - @keyword_mode = true - end - return '@' + word.to_s # implicit keyword - end - - 'a' - end - - def whitespace - while buffer && md = R_WHITESPACE.match(buffer) - return unless md[0].length > 0 - consume(md[0].length) - #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}]", 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}]", 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 - end - - def onFinish - prod = @productions.pop() - $stdout.puts ' ' * @productions.length + '/' + prod - end - - def onToken(prod, tok) - $stdout.puts ' ' * @productions.length + "#{prod}(#{tok})" - end - - def dump_stack(stack) - STDERR.puts "\nstack trace:" - stack.reverse.each do |se| - STDERR.puts "#{se[:prod]}" - STDERR.puts " " + case se[:terms] - when nil then "nil" - when [] then "empty" - else se[:terms].join(",\n ") - 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) - @productions = [] - - @branches = branches - @regexps = regexps - parse(START.to_sym) - end - end -end \ No newline at end of file diff --git a/lib/rdf/n3/terminals.rb b/lib/rdf/n3/terminals.rb new file mode 100644 index 0000000..d44504d --- /dev/null +++ b/lib/rdf/n3/terminals.rb @@ -0,0 +1,77 @@ +# encoding: utf-8 +module RDF::N3 + module Terminals + # Definitions of token regular expressions used for lexical analysis + ## + # Unicode regular expressions for Ruby 1.9+ with the Oniguruma engine. + U_CHARS1 = Regexp.compile(<<-EOS.gsub(/\s+/, '')) + [\\u00C0-\\u00D6]|[\\u00D8-\\u00F6]|[\\u00F8-\\u02FF]| + [\\u0370-\\u037D]|[\\u037F-\\u1FFF]|[\\u200C-\\u200D]| + [\\u2070-\\u218F]|[\\u2C00-\\u2FEF]|[\\u3001-\\uD7FF]| + [\\uF900-\\uFDCF]|[\\uFDF0-\\uFFFD]|[\\u{10000}-\\u{EFFFF}] + EOS + U_CHARS2 = Regexp.compile("\\u00B7|[\\u0300-\\u036F]|[\\u203F-\\u2040]", Regexp::FIXEDENCODING).freeze + IRI_RANGE = Regexp.compile("[[^<>\"{}|^`\\\\]&&[^\\x00-\\x20]]", Regexp::FIXEDENCODING).freeze + + ESCAPE_CHAR4 = /\\u(?:[0-9A-Fa-f]{4,4})/u.freeze # \uXXXX + ESCAPE_CHAR8 = /\\U(?:[0-9A-Fa-f]{8,8})/u.freeze # \UXXXXXXXX + UCHAR = /#{ESCAPE_CHAR4}|#{ESCAPE_CHAR8}/n.freeze + # 170s + PERCENT = /%[0-9A-Fa-f]{2}/u.freeze + # 172s + PN_LOCAL_ESC = /\\[_~\.\-\!$\&'\(\)\*\+,;=\/\?\#@%]/u.freeze + # 169s + PLX = /#{PERCENT}|#{PN_LOCAL_ESC}/u.freeze + # 163s + PN_CHARS_BASE = /[A-Z]|[a-z]|#{U_CHARS1}/u.freeze + # 164s + PN_CHARS_U = /_|#{PN_CHARS_BASE}/u.freeze + # 166s + PN_CHARS = /-|[0-9]|#{PN_CHARS_U}|#{U_CHARS2}/u.freeze + PN_LOCAL_BODY = /(?:(?:\.|:|#{PN_CHARS}|#{PLX})*(?:#{PN_CHARS}|:|#{PLX}))?/u.freeze + PN_CHARS_BODY = /(?:(?:\.|#{PN_CHARS})*#{PN_CHARS})?/u.freeze + # 167s + PN_PREFIX = /#{PN_CHARS_BASE}#{PN_CHARS_BODY}/u.freeze + # 168s + PN_LOCAL = /(?:[0-9]|:|#{PN_CHARS_U}|#{PLX})#{PN_LOCAL_BODY}/u.freeze + # 154s + EXPONENT = /[eE][+-]?[0-9]+/u.freeze + # 159s + ECHAR = /\\[tbnrf\\"']/u.freeze + # 18 + IRIREF = /<(?:#{IRI_RANGE}|#{UCHAR})*>/u.freeze + # 139s + PNAME_NS = /#{PN_PREFIX}?:/u.freeze + # 140s + PNAME_LN = /#{PNAME_NS}#{PN_LOCAL}/u.freeze + # 141s + BLANK_NODE_LABEL = /_:(?:[0-9]|#{PN_CHARS_U})(?:(?:#{PN_CHARS}|\.)*#{PN_CHARS})?/u.freeze + # 144s + # XXX: negative-lookahed for @is and @has + LANGTAG = /@(?!(?:is|has))(?:[a-zA-Z]+(?:-[a-zA-Z0-9]+)*)/u.freeze + # 19 + INTEGER = /[+-]?[0-9]+/u.freeze + # 20 + DECIMAL = /[+-]?(?:[0-9]*\.[0-9]+)/u.freeze + # 21 + DOUBLE = /[+-]?(?:[0-9]+\.[0-9]*#{EXPONENT}|\.?[0-9]+#{EXPONENT})/u.freeze + # 22 + STRING_LITERAL_SINGLE_QUOTE = /'(?:[^\'\\\n\r]|#{ECHAR}|#{UCHAR})*'/u.freeze + # 23 + STRING_LITERAL_QUOTE = /"(?:[^\"\\\n\r]|#{ECHAR}|#{UCHAR})*"/u.freeze + # 24 + STRING_LITERAL_LONG_SINGLE_QUOTE = /'''(?:(?:'|'')?(?:[^'\\]|#{ECHAR}|#{UCHAR}))*'''/um.freeze + # 25 + STRING_LITERAL_LONG_QUOTE = /"""(?:(?:"|"")?(?:[^"\\]|#{ECHAR}|#{UCHAR}))*"""/um.freeze + + BASE = /base/i.freeze + PREFIX = /prefix/i.freeze + QUICK_VAR_NAME = /\?#{PN_LOCAL}/.freeze + + # 161s + WS = /(?:\s|(?:#[^\n\r]*))+/um.freeze + # 162s + ANON = /\[#{WS}*\]/um.freeze + + end +end \ No newline at end of file diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 33a47b5..47768df 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -49,7 +49,7 @@ module RDF::N3 class Writer < RDF::Writer format RDF::N3::Format include RDF::Util::Logger - QNAME = Meta::REGEXPS[:"http://www.w3.org/2000/10/swap/grammar/n3#qname"] + include Terminals # @return [RDF::Repository] Repository of statements serialized attr_accessor :repo @@ -222,7 +222,7 @@ def get_pname(resource) # Make sure pname is a valid pname if pname - md = QNAME.match(pname) + md = PNAME_LN.match(pname) pname = nil unless md.to_s.length == pname.length end diff --git a/rdf-n3.gemspec b/rdf-n3.gemspec index 771b7df..f7dac3b 100755 --- a/rdf-n3.gemspec +++ b/rdf-n3.gemspec @@ -15,12 +15,13 @@ Gem::Specification.new do |gem| gem.email = 'public-rdf-ruby@w3.org' gem.platform = Gem::Platform::RUBY - gem.files = %w(README.md History.markdown AUTHORS VERSION UNLICENSE) + Dir.glob('lib/**/*.rb') + gem.files = %w(README.md VERSION UNLICENSE) + Dir.glob('lib/**/*.rb') gem.require_paths = %w(lib) gem.required_ruby_version = '>= 2.4' gem.requirements = [] + gem.add_dependency 'ebnf', '~> 2.1' gem.add_dependency 'rdf', '~> 3.1' gem.add_dependency 'sparql', '~> 3.1' gem.add_runtime_dependency 'sxp', '~> 1.1' diff --git a/script/build_meta b/script/build_meta deleted file mode 100755 index 954ac87..0000000 --- a/script/build_meta +++ /dev/null @@ -1,249 +0,0 @@ -#!/usr/bin/env ruby -# build_meta --- generator of parser tables for SPARQL::Grammar::Parser -# Derived from: -# http://www.w3.org/2000/10/swap/grammar/predictiveParser.py -# - predictiveParser.py, Tim Berners-Lee, 2004 -# -require 'rubygems' -$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", 'lib'))) -require 'rdf/n3' -require 'getoptlong' - -# Build rdf/n3/parser/meta.rb from http://www.w3.org/2000/10/swap/grammar/n3-selectors.n3 - -class BNF < RDF::Vocabulary("http://www.w3.org/2000/10/swap/grammar/bnf#"); end -class REGEX < RDF::Vocabulary("http://www.w3.org/2000/10/swap/grammar/regex#"); end -class N3 < RDF::Vocabulary("http://www.w3.org/2000/10/swap/grammar/n3#"); end - -class PredictiveParser - attr_accessor :already, :agenda, :errors, :literalTerminals, :branchTable, :tokenRegexps - attr_accessor :graph - - def initialize - @already = [] - @agenda = [] - @errors = [] - @literalTerminals = {} - @branchTable = {} - @tokenRegexps = {} - end - - def parse(file) - progress("Loading " + file) - @graph = RDF::Graph.load(file) - progress("Loaded #{@graph.count} statements.") - end - - def recordError(str) - errors << str - "##### ERROR: #{str}" - end - - def progress(str); puts(str); end - def chatty(str); progress(str) if $verbose; end - - def runProduction(lhs) - doProduction(lhs) - while !@agenda.empty? - x = @agenda.shift - @already << x - doProduction(x) - end - - if !@errors.empty? - progress("###### FAILED with #{errors.length} errors.") - @errors.each {|s| progress(" #{s}")} - exit(-2) - else - progress("Ok for predictive parsing") - end - end - - # Generate branch tables for one production - def doProduction(lhs) - if lhs == BNF.void - progress("\nvoid") - return - end - if lhs == BNF.eof - progress( "\nEOF") - return - end - if lhs.is_a?(RDF::Literal) - literalTerminals[lhs.value()] = 1 - return - end - - branchDict = {} - - rhs = graph.first_object(subject: lhs, predicate: BNF.matches) - if rhs - chatty("\nToken #{lhs} matches regexp #{rhs}") - tokenRegexps[lhs] = rhs.value - - cc = graph.query({subject: lhs, predicate: BNF.canStartWith}) - progress(recordError("No record of what token #{lhs} can start with")) if cc.empty? - cc.each {|statement| chatty(" Can start with: #{statement.object}")} - return - end - - rhs = graph.first_object(subject: lhs, predicate: BNF.mustBeOneSequence) - unless rhs - progress(recordError("No definition of #{lhs}")) - # raise RuntimeError("No definition of %s in\n %s" %(`lhs`, `g`)) - return - end - - options = rhs - progress("\nProduction #{lhs} :: #{options}") - graph.query({subject: lhs, predicate: BNF.canPrecede}) do |statement| - chatty(" Can precede '#{statement.object}'") - end - - graph.query({subject: lhs, predicate: BNF.branch}) do |statement| - branch = statement.object - sequence = graph.first_object(subject: statement.object, predicate: BNF.sequence) - option = RDF::List.new(subject: sequence, graph: graph).to_a - progress(" option: #{option}") - - option.each do |part| - agenda << part unless already.include?(part) || agenda.include?(part) - end - - conditions = graph.query({subject: branch, predicate: BNF.condition}).map(&:object) - if conditions.empty? - progress(recordError("NO SELECTOR for #{lhs} option #{option}")) - if option.empty? - # Void case - the tricky one - graph.pattern(subject: lhs, predicate: BNF.canPrecede) do |st| - progress(" Can precede #{st.object}") - end - end - end - - progress(" Conditions: #{conditions.to_a.map(&:to_s)}") - conditions.each do |str1| - if branchDict.has_key?(str1) - progress( - "Conflict: #{str1} is also the condition for #{branchDict[str1]}") - end - branchDict[str1] = option - end - end - - branchDict.keys.each do |str1| - branchDict.keys.each do |str2| - s1, s2 = str1.to_s, str2.to_s - if (s1.index(s2) == 0 || s2.index(s1) == 0) && branchDict[str1] != branchDict[str2] - progress("WARNING: for #{lhs}, #{str1} indicates #{branchDict[str1]}, but #{str2} indicates #{branchDict[str2]}") - end - end - end - - branchTable[lhs] = branchDict - end - - def litOrNot(value) - (value.is_a?(RDF::Literal) ? "" : ":") + value.to_s.dump - end - - def outputBranchTable(io, indent = 0) - ind0 = ' ' * indent - ind1 = ind0 + ' ' - ind2 = ind1 + ' ' - ind3 = ind2 + ' ' - io.puts "#{ind0}BRANCHES = {" - branchTable.keys.sort_by(&:to_s).each do |prod| - # Special case double, integer, and decimal to output just a numericliteral, due to a parser conflict - next if prod.to_s =~ /numericliteral/ - io.puts "#{ind1}#{litOrNot(prod)} => {" - branchTable[prod].keys.sort_by(&:to_s).each do |term| - list = branchTable[prod][term].map {|t2| litOrNot(t2)}.join(",\n#{ind3}") - io.puts "#{ind2}#{litOrNot(term)} => [#{list}]," - end - io.puts "#{ind1}}," - end - io.puts "#{ind0}}\n" - end - - def outputRegexpTable(io, indent = 0) - ind0 = ' ' * indent - ind1 = ind0 + ' ' - io.puts "#{ind0}REGEXPS = {" - tokenRegexps.keys.sort_by(&:to_s).each do |prod| - # Special case double, integer, and decimal to output just a numericliteral, due to a parser conflict - next if prod.to_s =~ /(integer|double|decimal)/ - io.puts "#{ind1}#{litOrNot(prod)} => Regexp.compile(" + - case prod.to_s - when /barename/ then %q(%(^[#{BARENAME_START}][#{BARENAME_TAIL}]*)) - when /explicituri/ then %q("^<[^>]*>") - when /langcode/ then %q("^[a-zA-Z]+(-[a-zA-Z0-9]+)*") - when /prefix/ then %q(%(^([#{BARENAME_START}][#{BARENAME_TAIL}]*)?:)) - when /qname/ then %q(%(^(([#{BARENAME_START}][#{BARENAME_TAIL}]*)?:)?([#{BARENAME_START}][#{BARENAME_TAIL}]*)?)) - when /variable/ then %q(%(^\\\\?[#{BARENAME_START}][#{BARENAME_TAIL}]*)) - else tokenRegexps[prod].dump - end + ")," - end - - io.puts "\n#{ind1}# Hack to replace integer|double|decimal with numericliteral" - io.puts "#{ind1}#{litOrNot(N3.numericliteral)} => Regexp.compile(" + %q(%(^[-+]?[0-9]+(\\\\.[0-9]+)?(e[-+]?[0-9]+)?)) + ")" - io.puts "#{ind0}}\n" - end -end - -$verbose = false -$debug = false -grammarFile = File.expand_path(File.join(File.dirname(__FILE__), "../lib/rdf/n3/reader/n3-selectors.n3")) -start = N3.document -output = STDOUT - -opts = GetoptLong.new( - ["--debug", GetoptLong::NO_ARGUMENT], - ["--verbose", GetoptLong::NO_ARGUMENT], - ["--grammar", GetoptLong::REQUIRED_ARGUMENT], - ["--start", GetoptLong::REQUIRED_ARGUMENT], - ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT], - ["--help", "-?", GetoptLong::NO_ARGUMENT] -) -opts.each do |opt, arg| - case opt - when '--verbose' then $verbose = true - when '--debug' then $debug = true - when '--grammar' then grammarFile = arg - when '--as' then parseAs = arg - when '--output' then output = File.open(arg, "w") - when '--help' - puts %(Usage: build_meta --grammar=file --as=uri [--output=file] - --grammar=file This is the RDF augmented grammar - --start=uri This is the URI of the production as which the document - is to be parsed - --output=file Where to save output -) - exit(0) - end -end - -pp = PredictiveParser.new - -pp.parse(grammarFile) -pp.runProduction(start) - -unless output == STDOUT - output.puts "# This file is automatically generated by #{__FILE__}" - output.puts "# Branch and Regexp tables derived from #{grammarFile}" - output.puts "module RDF::N3::Meta" -end -pp.outputBranchTable(output, 1) -output.puts %q( - if RUBY_VERSION >= "1.9.0" - BARENAME_START = "A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd\u{10000}-\u{effff}" - BARENAME_TAIL = "0-9#{BARENAME_START}\u00b7\u0300-\u036f\u203f-\u2040\\\\-" - else - BARENAME_START = "A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\xff" - BARENAME_TAIL = "0-9#{BARENAME_START}\xb7\\\\-" - end -) -pp.outputRegexpTable(output, 1) -unless output == STDOUT - output.puts "end" -end diff --git a/script/parse b/script/parse index cedfa88..398c315 100755 --- a/script/parse +++ b/script/parse @@ -20,7 +20,7 @@ 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].merge(logger: nil)) do |reader| + 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 @@ -35,7 +35,7 @@ def run(input, **options) 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| + reader_class.new(input, **options[:parser_options]).each do |statement| num += 1 if options[:errors] && statement.invalid? $stderr.puts "Invalid statement at #{r.lineno}: #{statement.inspect}" @@ -46,17 +46,17 @@ def run(input, **options) end end elsif options[:output_format] == :sxp - reader_class.new(input, options[:parser_options]) do |reader| + reader_class.new(input, **options[:parser_options]) do |reader| 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| + reader_class.new(input, **options[:parser_options]).each do |statement| num += 1 options[:output].puts statement.inspect end else - reader = reader_class.new(input, options[:parser_options]) + reader = reader_class.new(input, **options[:parser_options]) repo = RDF::Repository.new << reader num = repo.count options[:output].puts repo.dump(options[:output_format], prefixes: reader.prefixes, standard_prefixes: true, logger: options[:logger]) @@ -68,16 +68,18 @@ def run(input, **options) puts secs = Time.new - start puts "Parsed #{num} statements in #{secs} seconds @ #{num/secs} statements/second." +rescue RDF::ReaderError => e + STDERR.puts "Backtrace: " + e.backtrace.join("\n ") if $verbose + exit(1) rescue Exception => e - fname = input.respond_to?(:path) ? input.path : "-stdin-" - STDERR.puts("Error in #{fname}: #{e.message}") - STDERR.puts "Backtrace: " + e.backtrace.join("\n ") - raise e + STDERR.puts "Error: #{e.message}" + STDERR.puts "Backtrace: " + e.backtrace.join("\n ") if $verbose + exit(1) end logger = Logger.new(STDERR) logger.level = Logger::WARN -logger.formatter = lambda {|severity, datetime, progname, msg| "#{severity}: #{msg}\n"} +logger.formatter = lambda {|severity, datetime, progname, msg| "%5s %s\n" % [severity, msg]} parser_options = { base_uri: "http://example.com", @@ -113,7 +115,7 @@ OPT_ARGS = [ ["--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"], + ["--verbose", GetoptLong::NO_ARGUMENT, "Verbose output"], ] def usage @@ -159,7 +161,7 @@ opts.each do |opt, arg| 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 '--validate' then parser_options[:validate] = true when '--verbose' then $verbose = true end end diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index e7c2a38..d2ac1ef 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -9,8 +9,14 @@ let!(:doap_count) {File.open(doap_nt).each_line.to_a.length} let(:logger) {RDF::Spec.logger} + after(:each) do |example| + puts logger.to_s if + example.exception && + !example.exception.is_a?(RSpec::Expectations::ExpectationNotMetError) + end + it_behaves_like 'an RDF::Reader' do - let(:reader) {RDF::N3::Reader.new(reader_input)} + let(:reader) {RDF::N3::Reader.new(reader_input, logger: logger)} let(:reader_input) {File.read(doap)} let(:reader_count) {doap_count} end @@ -82,7 +88,7 @@ it "should yield statements" do inner = double("inner") expect(inner).to receive(:called).with(RDF::Statement).exactly(15) - RDF::N3::Reader.new(@sampledoc).each_statement do |statement| + RDF::N3::Reader.new(@sampledoc, logger: logger).each_statement do |statement| inner.called(statement.class) end end @@ -169,7 +175,13 @@ end it "should parse long literal with escape" do - n3 = %(@prefix : . :a :b "\\U00015678another" .) + n3 = %(@prefix : . :a :b """\\U00015678another""" .) + statement = parse(n3).statements.first + expect(statement.object.value).to eq "\u{15678}another" + end + + it "should parse long literal single quote with escape" do + n3 = %(@prefix : . :a :b '''\\U00015678another''' .) statement = parse(n3).statements.first expect(statement.object.value).to eq "\u{15678}another" end @@ -191,11 +203,11 @@ baz more ), - "trailing escaped double-quote" => %q( "), + "trailing escaped double-quote" => %q( " ), "regression.n3" => %q(sameDan.n3 sameThing.n3 --think --apply=forgetDups.n3 --purge --n3="/" ) }.each do |test, string| it "parses #{test}" do - graph = parse(%(:a :b """#{string}""")) + graph = parse(%(:a :b """#{string}""" .)) expect(graph.size).to eq 1 expect(graph.statements.first.object.value).to eq string end @@ -312,7 +324,7 @@ { %(<#Dürst> a "URI straight in UTF8".) => %( "URI straight in UTF8" .), - #%(:a :related :ひらがな .) => %( .), + %(: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, format: :n3) @@ -336,7 +348,7 @@ describe "with n3 grammar" do describe "syntactic expressions" do - it "should create typed literals with qname" do + it "should create typed literals with pname" do n3doc = %( @prefix rdf: . @prefix foaf: . @@ -387,6 +399,12 @@ 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 '^xxx'", pending: "grammar confusion" do + n3 = %("value" ^:prop :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, format: :n3) + end + it "should generate inverse predicate for 'is xxx of' with object list" do n3 = %("value" is :prop of :b, :c . ) nt = %( @@ -397,7 +415,7 @@ end it "should generate inverse predicate for 'is xxx of' with blankNodePropertyList" do - n3 = %([ is :prop of :George]) + n3 = %([ is :prop of :George] .) nt = %( _:bn . ) @@ -456,7 +474,7 @@ }.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, format: :n3) + expect(parse("#{n3} .", base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end end @@ -495,6 +513,17 @@ expect(statement.predicate).not_to equal statement.object end + it "creates variable for quickVar" do + n3 = %(?x :y :z .) + g = parse(n3, base_uri: "http://a/b") + statement = g.statements.first + expect(statement.subject).to be_variable + expect(statement.predicate).not_to be_variable + expect(statement.object).not_to be_variable + expect(statement.subject).not_to equal statement.predicate + expect(statement.subject).not_to equal statement.object + end + it "substitutes node for URI with @forSome" do n3 = %(@forSome :x . :x :y :z .) g = parse(n3, base_uri: "http://a/b") @@ -526,6 +555,14 @@ 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 (sparqlPrefix)" do + n3 = %(PrEfIx : :a : :b .) + nt = %( + . + ) + 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 n3 = %(@prefix : . :a : :b .) nt = %( @@ -551,6 +588,15 @@ 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 (sparqlBase)" do + n3 = %(BaSe <> :a . <#c> :d .) + nt = %( + . + . + ) + 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 n3 = %(@base . <> :a . <#c> :d .) nt = %( @@ -608,80 +654,7 @@ end end - describe "keywords" do - [ - %(base <>.), - %(keywords a.), - %(:a is :b of :c.), - %(:a @is :b of :c.), - %(:a is :b @of :c.), - %(:a has :b :c.), - ].each do |n3| - it "should require @ if keywords set to empty for '#{n3}'" do - expect do - parse("@keywords . #{n3}", base_uri: "http://a/b") - end.to raise_error(RDF::ReaderError) - end - end - - [ - %(prefix :<>.), - ].each do |n3| - it "parses as local name if keywords set to empty for '#{n3}'" do - expect do - parse("@keywords . #{n3}", base_uri: "http://a/b") - end.not_to raise_error - end - end - { - %(:a a :b) => %( .), - %(:a :b true) => %( .), - %(:a :b false) => %( .), - %(c :a :t) => %( .), - %(:c a :t) => %( .), - %(: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, format: :n3) - end - end - - { - %(@keywords true. :a :b true.) => %( "true"^^ .), - %(@keywords false. :a :b false.) => %( "false"^^ .), - %(@keywords a. :a a :b.) => %( .), - %(@keywords is. :a is :b @of :c.) => %( .), - %(@keywords of. :a @is :b of :c.) => %( .), - %(@keywords has. :a has :b :c.) => %( .), - } .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, format: :n3) - end - end - - it "should raise error if unknown keyword set" do - n3 = %(@keywords foo.) - expect do - parse(n3, base_uri: "http://a/b", validate: true) - 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 = %( - _:a a :p. - @prefix _: . - _:a a :p. - ) - nt = %( - . - _:a . - ) - 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 n3 = %( @prefix a: . @@ -752,134 +725,112 @@ 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 - n3 = %(@prefix a: . a:b a:oneRef [ a:pp "1" ; a:qq "2" ] .) - nt = %( - _:bnode0 "1" . - _:bnode0 "2" . - _:bnode0 . - ) - 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 - n3 = %( - @prefix a: . - - a:b1 a:twoRef _:a . - a:b2 a:twoRef _:a . - - _:a :pred [ a:pp "1" ; a:qq "2" ]. - ) - nt = %( - _:a . - _:a . - _:bnode0 "1" . - _:bnode0 "2" . - _: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, format: :n3) - end - - it "should create nested BNodes" do - n3 = %( - @prefix a: . - - a:a a:p [ a:p2 [ a:p3 "v1" , "v2" ; a:p4 "v3" ] ; a:p5 "v4" ] . - ) - nt = %( - _:bnode0 "v1" . - _:bnode0 "v2" . - _:bnode0 "v3" . - _:bnode1 _:bnode0 . - _:bnode1 "v4" . - _:bnode1 . - ) - 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 - it "should create bnode for path x!p" do - 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, 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, format: :n3) - end - - it "should decode :joe!fam:mother!loc:office!loc:zip as Joe's mother's office's zipcode" do - n3 = %( - @prefix fam: . - @prefix loc: . - - :joe!fam:mother!loc:office!loc:zip . - ) + context "blankNodePropertyList" do + it "should create BNode as a single object" do + n3 = %(@prefix a: . a:b a:oneRef [ a:pp "1" ; a:qq "2" ] .) nt = %( - :joe _:bnode0 . - _:bnode0 _:bnode1 . - _:bnode1 _:bnode2 . + _:bnode0 "1" . + _:bnode0 "2" . + _: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, format: :n3) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, 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 + it "should create a shared BNode" do n3 = %( - @prefix fam: . - @prefix loc: . + @prefix a: . + + a:b1 a:twoRef _:a . + a:b2 a:twoRef _:a . - :joe!fam:mother^fam:mother . + _:a :pred [ a:pp "1" ; a:qq "2" ]. ) nt = %( - :joe _:bnode0 . - _:bnode1 _:bnode0 . + _:a . + _:a . + _:bnode0 "1" . + _:bnode0 "2" . + _: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, format: :n3) end - it "should decode path with property list." do + it "should create nested BNodes" do n3 = %( - @prefix a: . - :a2!a:b2!a:c2 :q1 "3" ; :q2 "4" , "5" . - ) - nt = %( - :a2 _:bnode0 . - _:bnode0 _:bnode1 . - _:bnode1 :q1 "3" . - _:bnode1 :q2 "4" . - _: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, format: :n3) - end + @prefix a: . - it "should decode path as object(1)" do - n3 = %(:a :b "lit"^:c.) + a:a a:p [ a:p2 [ a:p3 "v1" , "v2" ; a:p4 "v3" ] ; a:p5 "v4" ] . + ) nt = %( - :a :b _:bnode . - _:bnode :c "lit" . + _:bnode0 "v1" . + _:bnode0 "v2" . + _:bnode0 "v3" . + _:bnode1 _:bnode0 . + _:bnode1 "v4" . + _: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, format: :n3) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end + end - it "should decode path as object(2)" do - n3 = %(@prefix a: . :r :p :o!a:p1!a:p2 .) - nt = %( - :o _:bnode0 . - _:bnode0 _:bnode1 . - :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, format: :n3) + context "property paths" do + { + "subject x!p": [ + %(:x2!:y2 :p2 "3" .), + %(:x2 :y2 _:bnode0 . _:bnode0 :p2 "3" .) + ], + "subject x^p": [ + %(:x2^:y2 :p2 "3" .), + %(_:bnode0 :y2 :x2 . _:bnode0 :p2 "3" .) + ], + "alberts mother inverse of metor to auntieAnne": [ + %(:albert!fam:mother :mentor!:inverse :auntieAnne .), + %( + :albert :mother _:bnode0 . + _:bnode0 _:pred0 :auntieAnne . + :mentor :inverse _:pred0 . + ) + ], + "albert doesnt admire grumpy": [ + %(:albert :admires!:converse :grumpy .), + %(:albert _:pred0 :grumpy . :admires :converse _:pred0 .) + ], + "1+2=3": [ + %{("1" "2")!:sum a :THREE.}, + %{("1" "2") :sum _:bnode0 . _:bnode0 a :THREE .} + ], + "relatedTo": [ + %{(:a!:b :c^:d) :relatedTo (:e!:f!:g ) .}, + %{ + :a :b _:bnode0 . + _:bnode1 :d :c . + :e :f _:bnode2 . + _:bnode2 :g _:bnode3 . + (_:bnode0 _:bnode1) :relatedTo (_:bnode3) . + } + ], + "joes mothers offices zip": [ + %{:joe!:mother!:office!:zip .}, + %{:joe :mother [:office [:zip []]] .} + ], + "Anyone whose mother is Joe's mother": [ + %{:joe!:mother^:mother .}, + %{:joe :mother _:bnode0 . [:mother _:bnode0] .} + ], + "path as object(1)": [ + %{:a :b "lit"^:c.}, + %{:a :b [:c "lit"] .} + ], + "path as object(2)": [ + %(:r :p :o!:p1!:p2 .), + %{:o :p1 [:p2 _:bnode1] . :r :p _:bnode1 .} + ] + }.each do |title, (n3, res)| + it title do + expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(res, base_uri: "http://a/b")} + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected,slogger: logger, format: :n3) + end end end end @@ -888,7 +839,7 @@ before(:each) { @repo = RDF::Repository.new } it "creates an RDF::Node instance for formula" do - n3 = %(:a :b {}) + n3 = %(:a :b {} .) nq = %(:a :b _:c .) result = parse(n3, repo: @repo, base_uri: "http://a/b") expected = parse(nq, repo: @repo, base_uri: "http://a/b") @@ -896,7 +847,7 @@ end it "adds statements with graph_name" do - n3 = %(:a :b {[:c :d]}) + n3 = %(:a :b {[:c :d]} .) 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")} @@ -927,7 +878,7 @@ it "creates unique bnodes within different formula" do n3 = %( _:a a :Thing . - {_:a a :Thing} => {_: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 @@ -982,7 +933,7 @@ describe "object lists" do it "should create 2 statements for simple list" do - n3 = %(:a :b :c, :d) + 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, format: :n3) @@ -1075,21 +1026,6 @@ ) . a:p "value" . ) - nt = %( - _:bnode3 . - "value" . - _:bnode3 _:bnode5 . - _:bnode3 _:bnode2 . - _:bnode5 "v1" . - _:bnode2 . - _:bnode2 _:bnode1 . - _:bnode1 . - _:bnode1 _:bnode0 . - _:bnode0 _:bnode4 . - _:bnode0 . - _:bnode4 "inner list" . - _:bnode4 . - ) g = parse(n3, base_uri: "http://a/b") expect(g.subjects.to_a.length).to eq 8 n = g.first_object(subject: RDF::URI.new("http://foo/a#a"), predicate: RDF::URI.new("http://foo/a#p")) @@ -1239,20 +1175,19 @@ end describe "validation" do - { - %(:y :p1 "xyz"^^xsd:integer .) => %r("xyz" is not a valid .*), - %(:y :p1 "12xyz"^^xsd:integer .) => %r("12xyz" is not a valid .*), - %(:y :p1 "xy.z"^^xsd:double .) => %r("xy\.z" is not a valid .*), - %(:y :p1 "+1.0z"^^xsd:double .) => %r("\+1.0z" is not a valid .*), - %(:a :b .) =>RDF::ReaderError, - %(:a :b 'single quote' .) => RDF::ReaderError, - %(:a "literal value" :b .) => RDF::ReaderError, - %(@keywords prefix. :e prefix :f .) => RDF::ReaderError - }.each_pair do |n3, error| - it "should raise '#{error}' for '#{n3}'" do + [ + %(:y :p1 "xyz"^^xsd:integer .), + %(:y :p1 "12xyz"^^xsd:integer .), + %(:y :p1 "xy.z"^^xsd:double .), + %(:y :p1 "+1.0z"^^xsd:double .), + %(:a :b .), + %(:a "literal value" :b .), + %(@keywords prefix. :e prefix :f .), + ].each do |n3| + it "should raise ReaderError for '#{n3}'" do expect { parse("@prefix xsd: . #{n3}", base_uri: "http://a/b", validate: true) - }.to raise_error(error) + }.to raise_error(RDF::ReaderError) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1df5aa0..460c8cc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,7 +9,20 @@ require 'rdf/ntriples' require 'rdf/spec' require 'rdf/spec/matchers' -require 'yaml' # XXX should be in open-uri/cached + +begin + require 'simplecov' + require 'coveralls' + SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter + ]) + SimpleCov.start do + add_filter "/spec/" + end +rescue LoadError => e + STDERR.puts "Coverage Skipped: #{e.message}" +end ::RSpec.configure do |c| c.filter_run focus: true @@ -19,12 +32,6 @@ } 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 From 38a27615b16af54c041a1cb1158ae4e75e203f3c Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 19 Jul 2020 14:59:02 -0700 Subject: [PATCH 009/193] Remove unused array patches. --- lib/rdf/n3.rb | 1 - lib/rdf/n3/patches/array_hacks.rb | 53 ------------------------------- 2 files changed, 54 deletions(-) delete mode 100644 lib/rdf/n3/patches/array_hacks.rb diff --git a/lib/rdf/n3.rb b/lib/rdf/n3.rb index 7f7eba1..fa74b9d 100644 --- a/lib/rdf/n3.rb +++ b/lib/rdf/n3.rb @@ -24,7 +24,6 @@ module N3 require 'rdf/n3/format' require 'rdf/n3/vocab' require 'rdf/n3/extensions' - require 'rdf/n3/patches/array_hacks' autoload :Meta, 'rdf/n3/meta' autoload :Reader, 'rdf/n3/reader' autoload :Reasoner, 'rdf/n3/reasoner' diff --git a/lib/rdf/n3/patches/array_hacks.rb b/lib/rdf/n3/patches/array_hacks.rb deleted file mode 100644 index 17c20cd..0000000 --- a/lib/rdf/n3/patches/array_hacks.rb +++ /dev/null @@ -1,53 +0,0 @@ -class Array - # http://wiki.rubygarden.org/Ruby/page/show/ArrayPermute - # Permute an array, and call a block for each permutation - # Author: Paul Battley - def permute(prefixed=[]) - if (length < 2) - # there are no elements left to permute - yield(prefixed + self) - else - # recursively permute the remaining elements - each_with_index do |e, i| - (self[0,i]+self[(i+1)..-1]).permute(prefixed+[e]) { |a| yield a } - end - end - end unless Array.method_defined?(:permute) - - # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options: - # * :words_connector - The sign or word used to join the elements in arrays with two or more elements (default: ", ") - # * :two_words_connector - The sign or word used to join the elements in arrays with two elements (default: " and ") - # * :last_word_connector - The sign or word used to join the last element in arrays with three or more elements (default: ", and ") - def to_sentence(**options) - default_words_connector = ", " - default_two_words_connector = " and " - default_last_word_connector = ", and " - - # Try to emulate to_senteces previous to 2.3 - if options.has_key?(:connector) || options.has_key?(:skip_last_comma) - ::ActiveSupport::Deprecation.warn(":connector has been deprecated. Use :words_connector instead", caller) if options.has_key? :connector - ::ActiveSupport::Deprecation.warn(":skip_last_comma has been deprecated. Use :last_word_connector instead", caller) if options.has_key? :skip_last_comma - - skip_last_comma = options.delete :skip_last_comma - if connector = options.delete(:connector) - options[:last_word_connector] ||= skip_last_comma ? connector : ", #{connector}" - else - options[:last_word_connector] ||= skip_last_comma ? default_two_words_connector : default_last_word_connector - end - end - -# options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) - options = {words_connector: default_words_connector, two_words_connector: default_two_words_connector, last_word_connector: default_last_word_connector}.merge(options) - - case length - when 0 - "" - when 1 - self[0].to_s - when 2 - "#{self[0]}#{options[:two_words_connector]}#{self[1]}" - else - "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}" - end - end unless Array.method_defined?(:to_sentence) -end From b0e1b0ec3e7ee8cfee6b7fdeba2bdd4be2f56efa Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 19 Jul 2020 16:48:46 -0700 Subject: [PATCH 010/193] Use refinements for `Statement.valid?` and related to allow more generalized RDF statements. --- lib/rdf/n3.rb | 14 +++---- lib/rdf/n3/extensions.rb | 10 +---- lib/rdf/n3/reader.rb | 1 + lib/rdf/n3/refinements.rb | 77 +++++++++++++++++++++++++++++++++++++++ lib/rdf/n3/writer.rb | 1 + spec/reader_spec.rb | 11 ++++++ spec/writer_spec.rb | 8 ++-- 7 files changed, 103 insertions(+), 19 deletions(-) create mode 100644 lib/rdf/n3/refinements.rb diff --git a/lib/rdf/n3.rb b/lib/rdf/n3.rb index fa74b9d..b2037e8 100644 --- a/lib/rdf/n3.rb +++ b/lib/rdf/n3.rb @@ -1,4 +1,3 @@ -$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..'))) require 'rdf' module RDF @@ -24,11 +23,12 @@ module N3 require 'rdf/n3/format' require 'rdf/n3/vocab' require 'rdf/n3/extensions' - autoload :Meta, 'rdf/n3/meta' - autoload :Reader, 'rdf/n3/reader' - autoload :Reasoner, 'rdf/n3/reasoner' - autoload :Terminals, 'rdf/n3/terminals' - autoload :VERSION, 'rdf/n3/version' - autoload :Writer, 'rdf/n3/writer' + require 'rdf/n3/refinements' + autoload :Meta, 'rdf/n3/meta' + autoload :Reader, 'rdf/n3/reader' + autoload :Reasoner, 'rdf/n3/reasoner' + autoload :Terminals, 'rdf/n3/terminals' + autoload :VERSION, 'rdf/n3/version' + autoload :Writer, 'rdf/n3/writer' end end \ No newline at end of file diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index da17e85..ac26e07 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -22,12 +22,7 @@ def contain?(other) end end - class Statement - # Override variable? - def variable? - to_a.any? {|term| !term.is_a?(RDF::Term) || term.variable?} || graph_name && graph_name.variable? - end - + class RDF::Statement # Transform Statement into an SXP # @return [Array] def to_sxp_bin @@ -43,8 +38,7 @@ def to_sxp end end - class Query::Solution - + class RDF::Query::Solution # Transform Statement into an SXP # @return [Array] def to_sxp_bin diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 2ce009c..43764f1 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -21,6 +21,7 @@ module RDF::N3 # @author [Gregg Kellogg](http://greggkellogg.net/) class Reader < RDF::Reader format Format + using Refinements include RDF::Util::Logger include EBNF::PEG::Parser diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb new file mode 100644 index 0000000..e80dc9e --- /dev/null +++ b/lib/rdf/n3/refinements.rb @@ -0,0 +1,77 @@ +# Refinements on core RDF class behavior +module RDF::N3::Refinements + refine ::RDF::Statement do + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + [(variable? ? :pattern : :triple), subject, predicate, object, graph_name].compact + end + + ## + # Override `valid?` terms as subjects and resources as predicates. + # + # @return [Boolean] + def valid? + has_subject? && subject.term? && subject.valid? && + has_predicate? && predicate.resource? && predicate.valid? && + has_object? && object.term? && object.valid? && + (has_graph? ? (graph_name.resource? && graph_name.valid?) : true) + end + + ## + # @return [Boolean] + def invalid? + !valid? + end + + ## + # Default validate! implementation, overridden in concrete classes + # @return [RDF::Value] `self` + # @raise [ArgumentError] if the value is invalid + # @since 0.3.9 + def validate! + raise ArgumentError, "#{self.inspect} is not valid" if invalid? + self + end + alias_method :validate, :validate! + + ## + # Returns an S-Expression (SXP) representation + # + # @return [String] + def to_sxp + to_sxp_bin.to_sxp + end + end + + refine ::RDF::Query::Solution do + # 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 + + refine ::RDF::Query::Pattern do + ## + # Is this pattern composed only of valid components? + # + # @return [Boolean] `true` or `false` + def valid? + (has_subject? ? (subject.term? || subject.variable?) && subject.valid? : true) && + (has_predicate? ? (predicate.resource? || predicate.variable?) && predicate.valid? : true) && + (has_object? ? (object.term? || object.variable?) && object.valid? : true) && + (has_graph? ? (graph_name.resource? || graph_name.variable?) && graph_name.valid? : true) + rescue NoMethodError + false + end + end +end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 47768df..85a45c4 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -50,6 +50,7 @@ class Writer < RDF::Writer format RDF::N3::Format include RDF::Util::Logger include Terminals + using Refinements # @return [RDF::Repository] Repository of statements serialized attr_accessor :repo diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index d2ac1ef..f4f1bf8 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -1190,6 +1190,17 @@ }.to raise_error(RDF::ReaderError) end end + + [ + %(:y _:p1 "z" .), + %("y" :p1 "z" .), + ].each do |n3| + it "'#{n3}' is valid" do + expect { + parse("@prefix xsd: . #{n3}", base_uri: "http://a/b", validate: true) + }.not_to raise_error + end + end end it "should parse rdf_core testcase" do diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index e4555ef..d8754dd 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -38,7 +38,7 @@ regexp: [ %r(^@base \.$), %r(^ \.$)], base_uri: "http://a/" }, - "qname URIs with prefix" => { + "pname URIs with prefix" => { input: %( .), regexp: [ %r(^@prefix ex: \.$), @@ -46,7 +46,7 @@ ], prefixes: {ex: "http://example.com/"} }, - "qname URIs with empty prefix" => { + "pname URIs with empty prefix" => { input: %( .), regexp: [ %r(^@prefix : \.$), @@ -55,7 +55,7 @@ prefixes: {"" => "http://example.com/"} }, # see example-files/arnau-registered-vocab.rb - "qname URIs with empty suffix" => { + "pname URIs with empty suffix" => { input: %( .), regexp: [ %r(^@prefix foaf: \.$), @@ -127,7 +127,7 @@ ], standard_prefixes: true, prefixes: {} }, - "should not use qname with illegal local part" => { + "should not use pname with illegal local part" => { input: %( @prefix db: . @prefix dbo: . From 41d15981a342ba844e935fc8ab719a369d8f1e96 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 19 Jul 2020 16:49:15 -0700 Subject: [PATCH 011/193] Disallow formulae from being used as predicates. --- lib/rdf/n3/reader.rb | 12 ++++++++++++ spec/suite_parser_spec.rb | 16 ++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 43764f1..0ddc76f 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -33,6 +33,11 @@ class Reader < RDF::Reader # @return [Array] attr_reader :formulae + # All nodes allocated to formulae + # + # @return [Hash{RDF::Node => RDF::Graph}] + attr_reader :formula_nodes + # Allocated variables by formula # # @return [Hash{Symbol => RDF::Node}] @@ -72,6 +77,7 @@ def initialize(input = $stdin, **options, &block) @prod_data = [] @formulae = [] + @formula_nodes = {} @label_uniquifier ||= "#{Random.new_seed}_000000" @bnodes = {} # allocated bnodes by formula @variables = {} @@ -297,6 +303,11 @@ def each_triple value[1][:expression] end end + + error("Literal may not be used as a predicate") if prod_data[:verb].is_a?(RDF::Literal) + error("Formula may not be used as a peredicate") if formula_nodes.has_key?(prod_data[:verb]) + + prod_data[:verb] end # (rule subject "13" (seq expression)) @@ -381,6 +392,7 @@ def each_triple start_production(:_formula_1) do |data| node = RDF::Node.new(".form_#{unique_label}") formulae.push(node) + formula_nodes[node] = true debug(:formula) {"id: #{node}, depth: #{formulae.length}"} # Promote variables defined on the earlier formula to this formula diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index d3402f2..451b478 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -4,6 +4,14 @@ 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 + let(:logger) {RDF::Spec.logger} + + after(:each) do |example| + puts logger.to_s if + example.exception && + !example.exception.is_a?(RSpec::Expectations::ExpectationNotMetError) + end + require_relative 'suite_helper' Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-parser.n3") do |m| @@ -17,9 +25,13 @@ pending("numeric representation") when *%w(n3_10003 n3_10006) pending("Verified test results are incorrect") + when *%w(n3_10009 n3_10018 n3_20002) + pending("Not allowed with new grammar") + when *%w(n3_10021) + pending("stack overflow") end - t.logger = RDF::Spec.logger + t.logger = logger t.logger.info t.inspect t.logger.info "source:\n#{t.input}" @@ -55,7 +67,7 @@ elsif t.syntax? expect { repo << reader - repo.dump(:nquads).should produce("not this", t.logger) + repo.dump(:nquads).to produce("not this", t.logger) }.to raise_error(RDF::ReaderError) else expect(repo).not_to be_equivalent_graph(output_repo, t) From ea97c42774612c5c7bceebf4b27ced4a04ef37e5 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 20 Jul 2020 10:21:04 -0700 Subject: [PATCH 012/193] Remove `@a`, `@is expr @of`, `@has expr`, `@true`, and `@false`. --- etc/n3.ebnf | 14 ++++---------- etc/n3.peg.sxp | 15 ++++++--------- etc/n3.sxp | 11 +++++------ lib/rdf/n3/meta.rb | 14 ++++++-------- lib/rdf/n3/reader.rb | 18 ++++++++++-------- spec/reader_spec.rb | 12 +++++------- 6 files changed, 36 insertions(+), 48 deletions(-) diff --git a/etc/n3.ebnf b/etc/n3.ebnf index 4a7f3ba..24b4548 100644 --- a/etc/n3.ebnf +++ b/etc/n3.ebnf @@ -25,19 +25,16 @@ [12] verb ::= predicate | 'a' - | '@a' | 'has' expression - | '@has' expression | 'is' expression 'of' - | '@is' expression '@of' + | '<-' expression # Synonym for is expression of | '<=' | '=>' | '=' [13] subject ::= expression -[14] predicate ::= (expression | '^' expression) - /* allow first predicate in a path to also be inverted */ +[14] predicate ::= expression [15] object ::= expression @@ -91,10 +88,7 @@ @terminals -[33] BOOLEAN_LITERAL ::= 'true' - | 'false' - | '@true' - | '@false' +[33] BOOLEAN_LITERAL ::= 'true' | 'false' [34] STRING ::= STRING_LITERAL_LONG_SINGLE_QUOTE | STRING_LITERAL_LONG_QUOTE @@ -102,7 +96,7 @@ | STRING_LITERAL_SINGLE_QUOTE /* borrowed from SPARQL spec, which excludes newlines and other nastiness */ -[139s] IRIREF ::= '<' ([^<>"{}|^`\]-[#x00-#x1F])* '>' +[139s] IRIREF ::= '<' ([^<>"{}|^`\]-[#x00-#x20])* '>' [140s] PNAME_NS ::= PN_PREFIX? ':' [141s] PNAME_LN ::= PNAME_NS PN_LOCAL [142s] BLANK_NODE_LABEL ::= '_:' ( PN_CHARS_U | [0-9] ) ((PN_CHARS|'.')* PN_CHARS)? diff --git a/etc/n3.peg.sxp b/etc/n3.peg.sxp index 8f14c6e..4b0608b 100644 --- a/etc/n3.peg.sxp +++ b/etc/n3.peg.sxp @@ -20,15 +20,12 @@ (rule objectList "11" (seq object _objectList_1)) (rule _objectList_1 "11.1" (star _objectList_2)) (rule _objectList_2 "11.2" (seq "," object)) - (rule verb "12" - (alt predicate "a" "@a" _verb_1 _verb_2 _verb_3 _verb_4 "<=" "=>" "=")) + (rule verb "12" (alt predicate "a" _verb_1 _verb_2 _verb_3 "<=" "=>" "=")) (rule _verb_1 "12.1" (seq "has" expression)) - (rule _verb_2 "12.2" (seq "@has" expression)) - (rule _verb_3 "12.3" (seq "is" expression "of")) - (rule _verb_4 "12.4" (seq "@is" expression "@of")) + (rule _verb_2 "12.2" (seq "is" expression "of")) + (rule _verb_3 "12.3" (seq "<-" expression)) (rule subject "13" (seq expression)) - (rule predicate "14" (alt expression _predicate_1)) - (rule _predicate_1 "14.1" (seq "^" expression)) + (rule predicate "14" (seq expression)) (rule object "15" (seq expression)) (rule expression "16" (seq path)) (rule path "17" (seq pathItem _path_1)) @@ -67,7 +64,7 @@ (rule existential "31" (seq "@forSome" iriList)) (rule universal "32" (seq "@forAll" iriList)) (terminals _terminals (seq)) - (terminal BOOLEAN_LITERAL "33" (alt "true" "false" "@true" "@false")) + (terminal BOOLEAN_LITERAL "33" (alt "true" "false")) (terminal STRING "34" (alt STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_LONG_QUOTE STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE )) @@ -75,7 +72,7 @@ (terminal _IRIREF_1 "139s.1" (star _IRIREF_2)) (terminal _IRIREF_2 "139s.2" (diff _IRIREF_3 _IRIREF_4)) (terminal _IRIREF_3 "139s.3" (range "^<>\"{}|^`\\")) - (terminal _IRIREF_4 "139s.4" (range "#x00-#x1F")) + (terminal _IRIREF_4 "139s.4" (range "#x00-#x20")) (terminal PNAME_NS "140s" (seq _PNAME_NS_1 ":")) (terminal _PNAME_NS_1 "140s.1" (opt PN_PREFIX)) (terminal PNAME_LN "141s" (seq PNAME_NS PN_LOCAL)) diff --git a/etc/n3.sxp b/etc/n3.sxp index 24cb864..5cd382e 100644 --- a/etc/n3.sxp +++ b/etc/n3.sxp @@ -12,13 +12,12 @@ (seq verb objectList (star (seq ";" (opt (seq verb objectList)))))) (rule objectList "11" (seq object (star (seq "," object)))) (rule verb "12" - (alt predicate "a" "@a" + (alt predicate "a" (seq "has" expression) - (seq "@has" expression) (seq "is" expression "of") - (seq "@is" expression "@of") "<=" "=>" "=" )) + (seq "<-" expression) "<=" "=>" "=" )) (rule subject "13" (seq expression)) - (rule predicate "14" (alt expression (seq "^" expression))) + (rule predicate "14" (seq expression)) (rule object "15" (seq expression)) (rule expression "16" (seq path)) (rule path "17" (seq pathItem (opt (alt (seq "!" path) (seq "^" path))))) @@ -42,12 +41,12 @@ (rule existential "31" (seq "@forSome" iriList)) (rule universal "32" (seq "@forAll" iriList)) (terminals _terminals (seq)) - (terminal BOOLEAN_LITERAL "33" (alt "true" "false" "@true" "@false")) + (terminal BOOLEAN_LITERAL "33" (alt "true" "false")) (terminal STRING "34" (alt STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_LONG_QUOTE STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE )) (terminal IRIREF "139s" - (seq "<" (star (diff (range "^<>\"{}|^`\\") (range "#x00-#x1F"))) ">")) + (seq "<" (star (diff (range "^<>\"{}|^`\\") (range "#x00-#x20"))) ">")) (terminal PNAME_NS "140s" (seq (opt PN_PREFIX) ":")) (terminal PNAME_LN "141s" (seq PNAME_NS PN_LOCAL)) (terminal BLANK_NODE_LABEL "142s" diff --git a/lib/rdf/n3/meta.rb b/lib/rdf/n3/meta.rb index 4439334..f2aa8de 100644 --- a/lib/rdf/n3/meta.rb +++ b/lib/rdf/n3/meta.rb @@ -23,14 +23,12 @@ module RDF::N3::Meta EBNF::Rule.new(:objectList, "11", [:seq, :object, :_objectList_1]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_objectList_1, "11.1", [:star, :_objectList_2]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_objectList_2, "11.2", [:seq, ",", :object]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:verb, "12", [:alt, :predicate, "a", "@a", :_verb_1, :_verb_2, :_verb_3, :_verb_4, "<=", "=>", "="]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:verb, "12", [:alt, :predicate, "a", :_verb_1, :_verb_2, :_verb_3, "<=", "=>", "="]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_verb_1, "12.1", [:seq, "has", :expression]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_verb_2, "12.2", [:seq, "@has", :expression]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_verb_3, "12.3", [:seq, "is", :expression, "of"]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_verb_4, "12.4", [:seq, "@is", :expression, "@of"]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_verb_2, "12.2", [:seq, "is", :expression, "of"]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_verb_3, "12.3", [:seq, "<-", :expression]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:subject, "13", [:seq, :expression]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:predicate, "14", [:alt, :expression, :_predicate_1]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_predicate_1, "14.1", [:seq, "^", :expression]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:predicate, "14", [:seq, :expression]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:object, "15", [:seq, :expression]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:expression, "16", [:seq, :path]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:path, "17", [:seq, :pathItem, :_path_1]).extend(EBNF::PEG::Rule), @@ -68,13 +66,13 @@ module RDF::N3::Meta EBNF::Rule.new(:existential, "31", [:seq, "@forSome", :iriList]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:universal, "32", [:seq, "@forAll", :iriList]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_terminals, nil, [:seq], kind: :terminals).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:BOOLEAN_LITERAL, "33", [:alt, "true", "false", "@true", "@false"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:BOOLEAN_LITERAL, "33", [:alt, "true", "false"], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:STRING, "34", [:alt, :STRING_LITERAL_LONG_SINGLE_QUOTE, :STRING_LITERAL_LONG_QUOTE, :STRING_LITERAL_QUOTE, :STRING_LITERAL_SINGLE_QUOTE], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:IRIREF, "139s", [:seq, "<", :_IRIREF_1, ">"], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_IRIREF_1, "139s.1", [:star, :_IRIREF_2], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_IRIREF_2, "139s.2", [:diff, :_IRIREF_3, :_IRIREF_4], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_IRIREF_3, "139s.3", [:range, "^<>\"{}|^`\\"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_IRIREF_4, "139s.4", [:range, "#x00-#x1F"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_IRIREF_4, "139s.4", [:range, "#x00-#x20"], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:PNAME_NS, "140s", [:seq, :_PNAME_NS_1, ":"], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_PNAME_NS_1, "140s.1", [:opt, :PN_PREFIX], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:PNAME_LN, "141s", [:seq, :PNAME_NS, :PN_LOCAL], kind: :terminal).extend(EBNF::PEG::Rule), diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 0ddc76f..88447b7 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -180,7 +180,7 @@ def each_triple ## # Parser terminals and productions ## - terminal(:BOOLEAN_LITERAL, %r{@?(?:true|false)}) do |value| + terminal(:BOOLEAN_LITERAL, %r{true|false}) do |value| RDF::Literal.new(value.start_with?('@') ? value[1..-1] : value, datatype: RDF::XSD.boolean, canonicalize: canonicalize?) @@ -282,23 +282,26 @@ def each_triple # (rule _objectList_2 "11.2" (seq "," object)) production(:_objectList_2) {|value| value.last[:object]} - # (rule verb "12" (alt predicate "a" "@a" _verb_1 _verb_2 _verb_3 _verb_4 "=" "<=" "=>")) + # (rule verb "12" (alt predicate "a" _verb_1 _verb_2 _verb_3 "<=" "=>" "=")) # Adds verb to prod_data for objectList production(:verb) do |value, data| prod_data[:verb] = case value when RDF::Term prod_data[:invert] = data[:invert] value - when 'a', '@a' then RDF.type + when 'a' then RDF.type when '=' then RDF::OWL.sameAs when '=>' then RDF::N3::Log.implies when '<=' prod_data[:invert] = true RDF::N3::Log.implies when Array # forms of has and is xxx of - if value.first[:has] || value.first[:@has] + if value.first[:has] value[1][:expression] - elsif value.first[:is] || value.first[:@is] + elsif value.first[:is] + prod_data[:invert] = true + value[1][:expression] + elsif value.first[:'<-'] prod_data[:invert] = true value[1][:expression] end @@ -316,10 +319,9 @@ def each_triple prod_data[:subject] = value.last[:expression] end - # (rule predicate "14" (alt expression _predicate_1)) + # (rule predicate "14" (seq expression)) production(:predicate) do |value, data| - prod_data[:invert] = data[:invert] - value + value.first[:expression] end # (rule _predicate_1 "14.1" (seq "^" expression)) production(:_predicate_1) do |value| diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index f4f1bf8..6b22d6a 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -381,7 +381,7 @@ 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 + it "should generate rdf:type for '@a'", pending: 'deprecated' 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, format: :n3) @@ -393,14 +393,14 @@ 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 + it "should generate inverse predicate for '@is xxx @of'", pending: 'deprecated' 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, format: :n3) end - it "should generate inverse predicate for '^xxx'", pending: "grammar confusion" do - n3 = %("value" ^:prop :b . :b :prop "value" .) + it "should generate inverse predicate for '<- xxx'" do + n3 = %("value" <- :prop :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, format: :n3) end @@ -436,7 +436,7 @@ 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 + it "should generate predicate for '@has xxx'", pending: 'deprecated' 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, format: :n3) @@ -461,8 +461,6 @@ end { - %(:a :b @true) => %( "true"^^ .), - %(:a :b @false) => %( "false"^^ .), %(:a :b 1) => %( "1"^^ .), %(:a :b -1) => %( "-1"^^ .), %(:a :b +1) => %( "+1"^^ .), From dd0b9769a3de56db0afef9d31cb310284dc62f94 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 20 Jul 2020 13:26:50 -0700 Subject: [PATCH 013/193] Update writer. --- lib/rdf/n3/reader.rb | 11 ++++++----- lib/rdf/n3/writer.rb | 2 +- spec/writer_spec.rb | 12 ++++++++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 88447b7..de3a9c6 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -78,7 +78,7 @@ def initialize(input = $stdin, **options, &block) @formulae = [] @formula_nodes = {} - @label_uniquifier ||= "#{Random.new_seed}_000000" + @label_uniquifier = "#{Random.new_seed}_000000" @bnodes = {} # allocated bnodes by formula @variables = {} @@ -206,7 +206,7 @@ def each_triple RDF::NTriples.unescape(value[3..-4]) end terminal(:ANON, ANON) {bnode} - terminal(:QUICK_VAR_NAME, QUICK_VAR_NAME) {|value| univar(value[1..-1])} + terminal(:QUICK_VAR_NAME, QUICK_VAR_NAME) terminal(:BASE, BASE) terminal(:PREFIX, PREFIX) @@ -438,10 +438,11 @@ def each_triple # # (rule quickVar "30" (seq QUICK_VAR_NAME)) production(:quickVar) do |value| - var = value.first[:QUICK_VAR_NAME] - add_var_to_formula(formulae[-2], var, var) + uri = process_pname(value.first[:QUICK_VAR_NAME].sub('?', ':')) + var = univar(uri) + add_var_to_formula(formulae[-2], uri, var) # Also add var to this formula - add_var_to_formula(formulae.last, var, var) + add_var_to_formula(formulae.last, uri, var) var end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 85a45c4..32fe624 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -223,7 +223,7 @@ def get_pname(resource) # Make sure pname is a valid pname if pname - md = PNAME_LN.match(pname) + md = PNAME_LN.match(pname) || PNAME_NS.match(pname) pname = nil unless md.to_s.length == pname.length end diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index d8754dd..ccd59ca 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -131,11 +131,11 @@ input: %( @prefix db: . @prefix dbo: . - db:Michael_Jackson dbo:artistOf . + db:Michael_Jackson dbo:artistOf . ), regexp: [ %r(^@prefix db: \.$), - %r(^db:Michael_Jackson dbo:artistOf \.$) + %r(^db:Michael_Jackson dbo:artistOf \.$) ], prefixes: { "db" => RDF::URI("http://dbpedia.org/resource/"), @@ -615,6 +615,10 @@ pending "Investigate" when *%w(n3_10013) pending "Number syntax" + when *%w(n3_10009 n3_10018 n3_20002) + pending("Not allowed with new grammar") + when *%w(n3_10021) + pending("stack overflow") end logger.info t.inspect logger.info "source: #{t.input}" @@ -633,6 +637,10 @@ pending "Investigate" when *%w(n3_10013) pending "Number syntax" + when *%w(n3_10009 n3_20002) + pending("Not allowed with new grammar") + when *%w(n3_10021) + pending("stack overflow") end logger.info t.inspect logger.info "source: #{t.expected}" From d5eff5743513e1ff5664d845ac5dc0892d6f440b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 20 Jul 2020 14:54:51 -0700 Subject: [PATCH 014/193] Fix creating a quickVar if a URI has already been matched as a quickVar. --- lib/rdf/n3/reader.rb | 6 +----- lib/rdf/n3/writer.rb | 2 +- spec/reader_spec.rb | 21 +++++++++++++++++++-- spec/suite_parser_spec.rb | 4 ++-- spec/writer_spec.rb | 28 +++++++++++++++++++++++----- 5 files changed, 46 insertions(+), 15 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index de3a9c6..d91a071 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -55,8 +55,6 @@ class Reader < RDF::Reader # whether to validate the parsed statements and values # @option options [Boolean] :canonicalize (false) # whether to canonicalize parsed literals and URIs. - # @option options [Boolean] :intern (true) - # whether to intern all parsed URIs # @option options [Hash] :prefixes (Hash.new) # the prefix mappings to use (not supported by all readers) # @return [reader] @@ -99,7 +97,6 @@ def initialize(input = $stdin, **options, &block) end progress("validate") {validate?.inspect} progress("canonicalize") {canonicalize?.inspect} - progress("intern") {intern?.inspect} if block_given? case block.arity @@ -439,7 +436,7 @@ def each_triple # (rule quickVar "30" (seq QUICK_VAR_NAME)) production(:quickVar) do |value| uri = process_pname(value.first[:QUICK_VAR_NAME].sub('?', ':')) - var = univar(uri) + var = uri.variable? ? uri : univar(uri) add_var_to_formula(formulae[-2], uri, var) # Also add var to this formula add_var_to_formula(formulae.last, uri, var) @@ -577,7 +574,6 @@ def uri(value, append = nil) value = value.join(append) if append 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 = find_var(@formulae.last, value) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 32fe624..751286f 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -172,7 +172,7 @@ def write_epilogue repo.graph_names.each do |graph_name| next if graph_done?(graph_name) - log_debug {"named graph(#{graph_name})"} + log_debug {"formula(#{graph_name})"} @output.write("\n#{indent}") p_term(graph_name, :subject) @output.write(" ") diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 6b22d6a..cfc0d02 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -873,13 +873,30 @@ expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end - it "creates unique bnodes within different formula" do + it "creates unique bnodes within different formula (bnodes)" 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 + statements = result.statements + expect(statements.length).to produce(4, logger) + # All three bnodes should be distinct + nodes_of_a = statements.map(&:to_a).flatten.select {|r| r.node? && r.to_s.start_with?('_:a')} + expect(nodes_of_a.uniq.count).to produce(3, logger) + end + + it "creates unique bnodes within different formula (quickvar)" do + n3 = %( + :a a :Thing . + {?a a :Thing} => {?a a :Thing} . + ) + result = parse(n3, repo: @repo, base_uri: "http://a/b") + statements = result.statements + expect(statements.length).to produce(4, logger) + # only one variable + variables = statements.map(&:to_a).flatten.select {|r| r.variable?} + expect(variables.uniq.count).to produce(1, logger) end context "contexts" do diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index 451b478..3c296d1 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -26,9 +26,9 @@ when *%w(n3_10003 n3_10006) pending("Verified test results are incorrect") when *%w(n3_10009 n3_10018 n3_20002) - pending("Not allowed with new grammar") + skip("Not allowed with new grammar") when *%w(n3_10021) - pending("stack overflow") + skip("stack overflow") end t.logger = logger diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index ccd59ca..bae1deb 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -7,6 +7,12 @@ describe RDF::N3::Writer do let(:logger) {RDF::Spec.logger} + after(:each) do |example| + puts logger.to_s if + example.exception && + !example.exception.is_a?(RSpec::Expectations::ExpectationNotMetError) + end + it_behaves_like 'an RDF::Writer' do let(:writer) {RDF::N3::Writer.new(StringIO.new)} end @@ -561,7 +567,19 @@ %r(} \.), ], input_format: :trig - } + }, + "implication" => { + input: %q( + @prefix xsd: . + ("17"^^xsd:integer) a <#TestCase> . + { ( ?x ) a :TestCase} => { ?x a :RESULT } . + ), + regexp: [ + %r{\(17\) a :TestCase \.}, + %r{\(:x_\d+_\d+\) a :TestCase \.}, + %r{:x_\d+_\d+ a :RESULT \.}, + ] + }, }.each do |name, params| it name do serialize(params[:input], params[:regexp], **params) @@ -623,9 +641,9 @@ 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) + n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true, logger: logger) logger.info "serialized: #{n3}" - g2 = parse(n3, base_uri: t.base) + g2 = parse(n3, base_uri: t.base, logger: logger) expect(g2).to be_equivalent_graph(repo, logger: logger) end @@ -646,9 +664,9 @@ 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) + n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true, logger: logger) logger.info "serialized: #{n3}" - g2 = parse(n3, base_uri: t.base) + g2 = parse(n3, base_uri: t.base, logger: logger) expect(g2).to be_equivalent_graph(repo, logger: logger) end end From c7342d6285f075e129c667081a2df7d1853dd52e Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 20 Jul 2020 14:55:11 -0700 Subject: [PATCH 015/193] Filter out some reasoner tests which use old syntax. --- spec/reasoner_spec.rb | 19 ++++++++++--------- spec/suite_reasoner_spec.rb | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index e9626e6..7a73e21 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -44,10 +44,9 @@ }, "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} . + :dan a :Man; :home []. + { ?WHO :home ?WHERE. ?WHERE :in ?REGION } => { ?WHO :homeRegion ?REGION }. + { :dan :home ?WHERE} => {?WHERE :in :Texas} . ), expect: %( :dan a :Man; @@ -62,7 +61,7 @@ ), expect: %( :a :b "A noun", 3.14159265359 . - [ a :Thing] + [ a :Thing] . ) }, "uses variables bound in parent" => { @@ -77,8 +76,9 @@ } }.each do |name, options| it name do - result = parse(options[:expect]) - expect(reason(options[:input])).to be_equivalent_graph(result, logger: logger) + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input])).to be_equivalent_graph(expected, logger: logger) end end end @@ -100,8 +100,9 @@ } }.each do |name, options| it name do - result = parse(options[:expect]) - expect(reason(options[:input])).to be_equivalent_graph(result, logger: logger) + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input])).to be_equivalent_graph(expected, logger: logger) end end end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index c4ac34e..6559485 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -5,6 +5,13 @@ # W3C N3 Test suite from http://www.w3.org/2000/10/swap/test/n3parser.tests describe "w3c n3 tests" do require_relative 'suite_helper' + let(:logger) {RDF::Spec.logger} + + after(:each) do |example| + puts logger.to_s if + example.exception && + !example.exception.is_a?(RSpec::Expectations::ExpectationNotMetError) + end Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-reasoner.n3") do |m| describe m.label do @@ -27,16 +34,19 @@ pending "support for inference over quoted graphs" when *%w{t2005} pending "something else" + when *%w{t2004u5 t2007 t12} + skip("Not allowed with new grammar") end - t.logger = RDF::Spec.logger + t.logger = 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) + validate: false, + logger: t.logger) reasoner = RDF::N3::Reasoner.new(reader, base_uri: t.base, From b379cf702c3bfd582d756e370dcf097ab40820db Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 20 Jul 2020 15:45:17 -0700 Subject: [PATCH 016/193] Update documentation. --- .yardopts | 1 + README.md | 61 ++++++----- etc/n3.ebnf | 214 +++++++++++++++++++------------------- lib/rdf/n3/reader.rb | 2 + lib/rdf/n3/refinements.rb | 58 +++++------ 5 files changed, 170 insertions(+), 166 deletions(-) diff --git a/.yardopts b/.yardopts index 5d6b9c1..4c61f9a 100644 --- a/.yardopts +++ b/.yardopts @@ -8,3 +8,4 @@ - VERSION UNLICENSE +etc/n3.ebnf diff --git a/README.md b/README.md index 5c44b43..799fa8e 100755 --- a/README.md +++ b/README.md @@ -3,18 +3,25 @@ Notation-3 reader/writer for [RDF.rb][RDF.rb] . [![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) +[![Coverage Status](https://coveralls.io/repos/ruby-rdf/rdf-n3/badge.svg)](https://coveralls.io/r/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. +RDF::N3 is an Notation-3 parser and reasoner for Ruby using the [RDF.rb][RDF.rb] library suite. Reader inspired from TimBL predictiveParser and Python librdf implementation. -## Turtle deprecated -Support for Turtle mime-types and specific format support has been deprecated from this gem, -as Turtle is now implemented using [RDF::Turtle][RDF::Turtle]. +## Uses CG Specification +This version tracks the [W3C N3 Community Group][] [Specification][N3] which has incompatibilities with the [Design Issues][] version. Notably: + +* The `@keywords` declaration is removed, and most form of `@` keywords (e.g., `@is`, `@has`, `@true`) are no longer supported. +* Terminals adhere closely to their counterparts in [Turtle][]. +* The modifier `<-` is introduced as a synonym for `is ... of`. +* The SPARQL `BASE` and `PREFIX` declarations are supported. + +This brings N3 closer to compatibility with Turtle. ## Features -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. +RDF::N3 parses [Notation-3][N3], [Turtle][] and [N-Triples][] into statements or quads. It also performs reasoning and serializes to N3. Install with `gem install rdf-n3` @@ -51,12 +58,15 @@ Experimental N3 reasoning is supported. Instantiate a reasoner from a dataset: 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 +#### RDF List vocabulary + * list:append (not implemented yet - See {RDF::N3::Algebra::List::Append}) * list:in (not implemented yet - See {RDF::N3::Algebra::List::In}) * list:last (not implemented yet - See {RDF::N3::Algebra::List::Last}) * list:member (not implemented yet - See {RDF::N3::Algebra::List::Member}) -* RDF Log vocabulary + +#### RDF Log vocabulary + * log:conclusion (not implemented yet - See {RDF::N3::Algebra::Log::Conclusion}) * log:conjunction (not implemented yet - See {RDF::N3::Algebra::Log::Conjunction}) * log:equalTo (See {RDF::N3::Algebra::Log::EqualTo}) @@ -66,7 +76,7 @@ Reasoning is performed by turning a repository containing formula and predicate * log:notIncludes (not implemented yet - See {RDF::N3::Algebra::Log::NotIncludes}) * log:outputString (not implemented yet - See {RDF::N3::Algebra::Log::OutputString}) -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: +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: . @prefix log: . @@ -84,7 +94,7 @@ when turned into an RDF Repository results in the following quads 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: +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> . @@ -97,20 +107,13 @@ results in: 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 -through a set of regular expressions used to match each type of terminal. - -The [meta.rb][file:lib/rdf/n3/reader/meta.rb] file is generated from lib/rdf/n3/reader/n3-selectors.n3 -(taken from http://www.w3.org/2000/10/swap/grammar/n3-selectors.n3) which is the result of parsing -http://www.w3.org/2000/10/swap/grammar/n3.n3 (along with bnf-rules.n3) using cwm using the following command sequence: +The parser is driven through a rules table contained in lib/rdf/n3/reader/meta.rb. These rules are processed through a [Parsing Expression Grammar][PEG] parser implemented in the [EBNF gem][]. - cwm n3.n3 bnf-rules.n3 --think --purge --data > n3-selectors.n3 - -[n3-selectors.n3][file:lib/rdf/n3/reader/n3-selectors.rb] is itself used to generate meta.rb using script/build_meta. +The [meta.rb][file:lib/rdf/n3/reader/meta.rb] file is generated from {file:etc/n3.ebnf N3 EBNF grammar} using a rake task. ## Dependencies -* [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.0, >= 3.0.10) +* [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.1, >= 3.1.4) +* [EBNF][EBNF gem] (~> 2.1) ## Documentation Full documentation available on [RubyDoc.info](https://rubydoc.info/github/ruby-rdf/rdf-n3) @@ -151,19 +154,17 @@ Full documentation available on [RubyDoc.info](https://rubydoc.info/github/ruby- * [Documentation](https://rubydoc.info/github/ruby-rdf/rdf-n3/) * [History](file:file.History.html) * [Notation-3][N3] +* [Team Submission][] * [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](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] +* [Turtle][] +* [W3C SWAP Test suite](https://w3c.github.io/N3/tests/) +* [W3C Turtle Test suite](https://w3c.github.io/rdf-tests/turtle/) +* [N-Triples][] ## Author * [Gregg Kellogg](https://github.com/gkellogg) - -## Contributors -* [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. @@ -191,8 +192,9 @@ see or the accompanying {file:UNLICENSE} file. * [RDF.rb]: https://ruby-rdf.github.com/rdf +[EBNF gem]: https://ruby-rdf.github.com/ebnf [RDF::Turtle]: https://ruby-rdf.github.com/rdf-turtle/ -[N3]: https://www.w3.org/DesignIssues/Notation3.html "Notation-3" +[Design Issues]: https://www.w3.org/DesignIssues/Notation3.html "Notation-3 Design Issues" [Team Submission]: https://www.w3.org/TeamSubmission/n3/ [Turtle]: https://www.w3.org/TR/turtle/ [N-Triples]: https://www.w3.org/TR/n-triples/ @@ -200,3 +202,6 @@ see or the accompanying {file:UNLICENSE} file. [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 +[W3C N3 Community Group]: https://www.w3.org/community/n3-dev/ +[N3]: https://w3c.github.io/N3/spec/ +[PEG]: https://en.wikipedia.org/wiki/Parsing_expression_grammar \ No newline at end of file diff --git a/etc/n3.ebnf b/etc/n3.ebnf index 24b4548..1a08a43 100644 --- a/etc/n3.ebnf +++ b/etc/n3.ebnf @@ -1,141 +1,141 @@ -# EBNF Notation3 Grammar based pm Antlr4. -# From https://github.com/w3c/N3/blob/master/grammar/n3.g4 + # EBNF Notation3 Grammar based pm Antlr4. + # From https://github.com/w3c/N3/blob/master/grammar/n3.g4 -[1] n3Doc ::= (n3Statement '.' | sparqlDirective)* + [1] n3Doc ::= (n3Statement '.' | sparqlDirective)* -[2] n3Statement ::= n3Directive | triples | existential | universal + [2] n3Statement ::= n3Directive | triples | existential | universal -[3] n3Directive ::= prefixID | base + [3] n3Directive ::= prefixID | base -[4] sparqlDirective ::= sparqlBase | sparqlPrefix + [4] sparqlDirective ::= sparqlBase | sparqlPrefix -[5] sparqlBase ::= BASE IRIREF + [5] sparqlBase ::= BASE IRIREF -[6] sparqlPrefix ::= PREFIX PNAME_NS IRIREF + [6] sparqlPrefix ::= PREFIX PNAME_NS IRIREF -[7] prefixID ::= '@prefix' PNAME_NS IRIREF + [7] prefixID ::= '@prefix' PNAME_NS IRIREF -[8] base ::= '@base' IRIREF + [8] base ::= '@base' IRIREF -[9] triples ::= (subject | blankNodePropertyList) predicateObjectList? + [9] triples ::= (subject | blankNodePropertyList) predicateObjectList? -[10] predicateObjectList ::= verb objectList (';' (verb objectList)?)* + [10] predicateObjectList ::= verb objectList (';' (verb objectList)?)* -[11] objectList ::= object (',' object)* + [11] objectList ::= object (',' object)* -[12] verb ::= predicate - | 'a' - | 'has' expression - | 'is' expression 'of' - | '<-' expression # Synonym for is expression of - | '<=' - | '=>' - | '=' + [12] verb ::= predicate + | 'a' + | 'has' expression + | 'is' expression 'of' + | '<-' expression # Synonym for is expression of + | '<=' + | '=>' + | '=' -[13] subject ::= expression + [13] subject ::= expression -[14] predicate ::= expression + [14] predicate ::= expression -[15] object ::= expression + [15] object ::= expression -[16] expression ::= path + [16] expression ::= path -[17] path ::= pathItem ('!' path | '^' path)? + [17] path ::= pathItem ('!' path | '^' path)? -[18] pathItem ::= iri - | blankNode - | quickVar - | collection - | blankNodePropertyList - | literal - | formula + [18] pathItem ::= iri + | blankNode + | quickVar + | collection + | blankNodePropertyList + | literal + | formula -[19] literal ::= rdfLiteral - | numericLiteral - | BOOLEAN_LITERAL + [19] literal ::= rdfLiteral + | numericLiteral + | BOOLEAN_LITERAL -[20] blankNodePropertyList ::= '[' predicateObjectList? ']' + [20] blankNodePropertyList ::= '[' predicateObjectList? ']' -[21] collection ::= '(' object* ')' + [21] collection ::= '(' object* ')' -[22] formula ::= '{' formulaContent? '}' + [22] formula ::= '{' formulaContent? '}' -[23] formulaContent ::= n3Statement ('.' formulaContent?)? - | sparqlDirective formulaContent? + [23] formulaContent ::= n3Statement ('.' formulaContent?)? + | sparqlDirective formulaContent? -[24] numericLiteral ::= DOUBLE | DECIMAL | INTEGER + [24] numericLiteral ::= DOUBLE | DECIMAL | INTEGER -[25] rdfLiteral ::= STRING (LANGTAG | '^^' iri)? + [25] rdfLiteral ::= STRING (LANGTAG | '^^' iri)? -[26] iri ::= IRIREF | prefixedName + [26] iri ::= IRIREF | prefixedName -[27] iriList ::= iri ( ',' iri )* + [27] iriList ::= iri ( ',' iri )* -[28] prefixedName ::= PNAME_LN | PNAME_NS - # PNAME_NS will be matched for ':' (i.e., "empty") prefixedNames - # hence this cannot be a lexer rule; for s/p/o of only ':', PNAME_NS will be returned - # instead of PrefixedName token + [28] prefixedName ::= PNAME_LN | PNAME_NS + # PNAME_NS will be matched for ':' (i.e., "empty") prefixedNames + # hence this cannot be a lexer rule; for s/p/o of only ':', PNAME_NS will be returned + # instead of PrefixedName token -[29] blankNode ::= BLANK_NODE_LABEL | ANON + [29] blankNode ::= BLANK_NODE_LABEL | ANON -[30] quickVar ::= QUICK_VAR_NAME - # only made this a parser rule for consistency - # (all other path-items are also parser rules) + [30] quickVar ::= QUICK_VAR_NAME + # only made this a parser rule for consistency + # (all other path-items are also parser rules) -[31] existential ::= '@forSome' iriList + [31] existential ::= '@forSome' iriList -[32] universal ::= '@forAll' iriList - -@terminals - -[33] BOOLEAN_LITERAL ::= 'true' | 'false' - -[34] STRING ::= STRING_LITERAL_LONG_SINGLE_QUOTE - | STRING_LITERAL_LONG_QUOTE - | STRING_LITERAL_QUOTE - | STRING_LITERAL_SINGLE_QUOTE - -/* borrowed from SPARQL spec, which excludes newlines and other nastiness */ -[139s] IRIREF ::= '<' ([^<>"{}|^`\]-[#x00-#x20])* '>' -[140s] PNAME_NS ::= PN_PREFIX? ':' -[141s] PNAME_LN ::= PNAME_NS PN_LOCAL -[142s] BLANK_NODE_LABEL ::= '_:' ( PN_CHARS_U | [0-9] ) ((PN_CHARS|'.')* PN_CHARS)? -[145s] LANGTAG ::= "@" ([a-zA-Z]+ ( "-" [a-zA-Z0-9]+ )*) - ("is" | "has") -[146s] INTEGER ::= [0-9]+ -[147s] DECIMAL ::= [0-9]* '.' [0-9]+ -[148s] DOUBLE ::= [0-9]+ '.' [0-9]* EXPONENT - | '.' ([0-9])+ EXPONENT | ([0-9])+ EXPONENT -[155s] EXPONENT ::= [eE] [+-]? [0-9]+ -[156s] STRING_LITERAL_QUOTE ::= '"' ( [^#x22#x5C#xA#xD] | ECHAR | UCHAR )* '"' -[157s] STRING_LITERAL_SINGLE_QUOTE ::= "'" ( [^#x27#x5C#xA#xD] | ECHAR | UCHAR )* "'" -[158s] STRING_LITERAL_LONG_SINGLE_QUOTE ::= "'''" ( ( "'" | "''" )? ( [^'\] | ECHAR | UCHAR ) )* "'''" -[159s] STRING_LITERAL_LONG_QUOTE ::= '"""' ( ( '"' | '""' )? ( [^"\] | ECHAR | UCHAR ) )* '"""' -[35] UCHAR ::= ( "\u" HEX HEX HEX HEX ) | ( "\U" HEX HEX HEX HEX HEX HEX HEX HEX ) -[160s] ECHAR ::= "\" [tbnrf\"'] -[162s] WS ::= #x20 | #x9 | #xD | #xA -[163s] ANON ::= '[' WS* ']' -[36] QUICK_VAR_NAME ::= "?" PN_LOCAL - /* Allows fuller character set */ -[164s] 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] -[165s] PN_CHARS_U ::= PN_CHARS_BASE | '_' -[167s] PN_CHARS ::= PN_CHARS_U | "-" | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040] -/* BASE and PREFIX must be case-insensitive, hence these monstrosities */ -[37] BASE ::= ('B'|'b') ('A'|'a') ('S'|'s') ('E'|'e') -[38] PREFIX ::= ('P'|'p') ('R'|'r') ('E'|'e') ('F'|'f') ('I'|'i') ('X'|'x') -[168s] PN_PREFIX ::= PN_CHARS_BASE ( ( PN_CHARS | "." )* PN_CHARS )? -[169s] PN_LOCAL ::= ( PN_CHARS_U | ':' | [0-9] | PLX ) ( ( PN_CHARS | '.' | ':' | PLX )* ( PN_CHARS | ':' | PLX ) ) ? -[170s] PLX ::= PERCENT | PN_LOCAL_ESC -[171s] PERCENT ::= '%' HEX HEX -[172s] HEX ::= [0-9] | [A-F] | [a-f] -[173s] PN_LOCAL_ESC ::= '\' ( '_' | '~' | '.' | '-' | '!' | '$' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | ';' | '=' - | '/' | '?' | '#' | '@' | '%' ) -[39] COMMENT ::= ('#' - '#x') [^#xA#xC#xD]* - -# Ignore all whitespace and comments between non-terminals -@pass ( WS | COMMENT )* + [32] universal ::= '@forAll' iriList + + @terminals + + [33] BOOLEAN_LITERAL ::= 'true' | 'false' + + [34] STRING ::= STRING_LITERAL_LONG_SINGLE_QUOTE + | STRING_LITERAL_LONG_QUOTE + | STRING_LITERAL_QUOTE + | STRING_LITERAL_SINGLE_QUOTE + + /* borrowed from SPARQL spec, which excludes newlines and other nastiness */ + [139s] IRIREF ::= '<' ([^<>"{}|^`\]-[#x00-#x20])* '>' + [140s] PNAME_NS ::= PN_PREFIX? ':' + [141s] PNAME_LN ::= PNAME_NS PN_LOCAL + [142s] BLANK_NODE_LABEL ::= '_:' ( PN_CHARS_U | [0-9] ) ((PN_CHARS|'.')* PN_CHARS)? + [145s] LANGTAG ::= "@" ([a-zA-Z]+ ( "-" [a-zA-Z0-9]+ )*) - ("is" | "has") + [146s] INTEGER ::= [0-9]+ + [147s] DECIMAL ::= [0-9]* '.' [0-9]+ + [148s] DOUBLE ::= [0-9]+ '.' [0-9]* EXPONENT + | '.' ([0-9])+ EXPONENT | ([0-9])+ EXPONENT + [155s] EXPONENT ::= [eE] [+-]? [0-9]+ + [156s] STRING_LITERAL_QUOTE ::= '"' ( [^#x22#x5C#xA#xD] | ECHAR | UCHAR )* '"' + [157s] STRING_LITERAL_SINGLE_QUOTE ::= "'" ( [^#x27#x5C#xA#xD] | ECHAR | UCHAR )* "'" + [158s] STRING_LITERAL_LONG_SINGLE_QUOTE ::= "'''" ( ( "'" | "''" )? ( [^'\] | ECHAR | UCHAR ) )* "'''" + [159s] STRING_LITERAL_LONG_QUOTE ::= '"""' ( ( '"' | '""' )? ( [^"\] | ECHAR | UCHAR ) )* '"""' + [35] UCHAR ::= ( "\u" HEX HEX HEX HEX ) | ( "\U" HEX HEX HEX HEX HEX HEX HEX HEX ) + [160s] ECHAR ::= "\" [tbnrf\"'] + [162s] WS ::= #x20 | #x9 | #xD | #xA + [163s] ANON ::= '[' WS* ']' + [36] QUICK_VAR_NAME ::= "?" PN_LOCAL + /* Allows fuller character set */ + [164s] 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] + [165s] PN_CHARS_U ::= PN_CHARS_BASE | '_' + [167s] PN_CHARS ::= PN_CHARS_U | "-" | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040] + /* BASE and PREFIX must be case-insensitive, hence these monstrosities */ + [37] BASE ::= ('B'|'b') ('A'|'a') ('S'|'s') ('E'|'e') + [38] PREFIX ::= ('P'|'p') ('R'|'r') ('E'|'e') ('F'|'f') ('I'|'i') ('X'|'x') + [168s] PN_PREFIX ::= PN_CHARS_BASE ( ( PN_CHARS | "." )* PN_CHARS )? + [169s] PN_LOCAL ::= ( PN_CHARS_U | ':' | [0-9] | PLX ) ( ( PN_CHARS | '.' | ':' | PLX )* ( PN_CHARS | ':' | PLX ) ) ? + [170s] PLX ::= PERCENT | PN_LOCAL_ESC + [171s] PERCENT ::= '%' HEX HEX + [172s] HEX ::= [0-9] | [A-F] | [a-f] + [173s] PN_LOCAL_ESC ::= '\' ( '_' | '~' | '.' | '-' | '!' | '$' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | ';' | '=' + | '/' | '?' | '#' | '@' | '%' ) + [39] COMMENT ::= ('#' - '#x') [^#xA#xC#xD]* + + # Ignore all whitespace and comments between non-terminals + @pass ( WS | COMMENT )* diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index d91a071..8c595f5 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -177,6 +177,8 @@ def each_triple ## # Parser terminals and productions ## + + # @!parse none terminal(:BOOLEAN_LITERAL, %r{true|false}) do |value| RDF::Literal.new(value.start_with?('@') ? value[1..-1] : value, datatype: RDF::XSD.boolean, diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb index e80dc9e..d39133b 100644 --- a/lib/rdf/n3/refinements.rb +++ b/lib/rdf/n3/refinements.rb @@ -1,12 +1,26 @@ # Refinements on core RDF class behavior +# @see ::RDF::Statement#valid? +# @see ::RDF::Statement#invalid? +# @see ::RDF::Statement#validate! +# @see ::RDF::Query::Pattern#valid? module RDF::N3::Refinements + # @!parse + # # Refinements on RDF::Statement + # class ::RDF::Statement + # # Refines `valid?` to allow literal subjects and BNode predicates. + # # @return [Boolean] + # def valid?; end + # + # # Refines `invalid?` to allow literal subjects and BNode predicates. + # # @return [Boolean] + # def invalid?; end + # + # # Refines `validate!` to allow literal subjects and BNode predicates. + # # @return [RDF::Value] `self` + # # @raise [ArgumentError] if the value is invalid + # def validate!; end + # end refine ::RDF::Statement do - # Transform Statement into an SXP - # @return [Array] - def to_sxp_bin - [(variable? ? :pattern : :triple), subject, predicate, object, graph_name].compact - end - ## # Override `valid?` terms as subjects and resources as predicates. # @@ -28,38 +42,20 @@ def invalid? # Default validate! implementation, overridden in concrete classes # @return [RDF::Value] `self` # @raise [ArgumentError] if the value is invalid - # @since 0.3.9 def validate! raise ArgumentError, "#{self.inspect} is not valid" if invalid? self end alias_method :validate, :validate! - - ## - # Returns an S-Expression (SXP) representation - # - # @return [String] - def to_sxp - to_sxp_bin.to_sxp - end - end - - refine ::RDF::Query::Solution do - # 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 + # @!parse + # # Refinements on RDF::Query::Pattern + # class ::RDF::Query::Pattern + # # Refines `valid?` to allow literal subjects and BNode predicates. + # # @return [Boolean] + # def valid?; end + # end refine ::RDF::Query::Pattern do ## # Is this pattern composed only of valid components? From d599a956b72e8fa07d259a5af16397f95ae9ae29 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 20 Jul 2020 15:53:17 -0700 Subject: [PATCH 017/193] Update simplecov and coveralls dependencies. --- Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index cf7e09d..e83eefe 100644 --- a/Gemfile +++ b/Gemfile @@ -19,8 +19,8 @@ group :development do end group :development, :test do - gem 'simplecov', platforms: :mri, require: false - gem 'coveralls', platforms: :mri, require: false + gem 'simplecov', platforms: :mri + gem 'coveralls', platforms: :mri end group :debug do From ece5fe5e5d9124fe030e4855a11aadc8f1fad699 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 20 Jul 2020 15:59:42 -0700 Subject: [PATCH 018/193] Disable bundler cache (temporarily). --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 83a126b..61e2c25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ rvm: - 2.6 - 2.7 - jruby -cache: bundler +#cache: bundler sudo: false matrix: allow_failures: From 4704de7f01bf15ad18fb98f92069a0e3d1bf650b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 20 Jul 2020 16:08:06 -0700 Subject: [PATCH 019/193] Update minimum versions for coveralls and simplecov. --- .travis.yml | 2 +- Gemfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 61e2c25..83a126b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ rvm: - 2.6 - 2.7 - jruby -#cache: bundler +cache: bundler sudo: false matrix: allow_failures: diff --git a/Gemfile b/Gemfile index e83eefe..2e90f0b 100644 --- a/Gemfile +++ b/Gemfile @@ -19,8 +19,8 @@ group :development do end group :development, :test do - gem 'simplecov', platforms: :mri - gem 'coveralls', platforms: :mri + gem 'simplecov', '~> 0.16', platforms: :mri + gem 'coveralls', '~> 0.8', '>= 0.8.23', platforms: :mri end group :debug do From 7f5f3dacbb022b39a193f2b3063806b19ec7aa12 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 21 Jul 2020 09:44:57 -0700 Subject: [PATCH 020/193] Allow whitespace inside IRIREFs per Team Submission. --- etc/n3.ebnf | 2 +- etc/n3.peg.sxp | 7 ++++--- etc/n3.sxp | 2 +- lib/rdf/n3/meta.rb | 7 ++++--- lib/rdf/n3/reader.rb | 2 +- lib/rdf/n3/terminals.rb | 2 +- spec/reader_spec.rb | 7 +++++++ 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/etc/n3.ebnf b/etc/n3.ebnf index 1a08a43..77f1f05 100644 --- a/etc/n3.ebnf +++ b/etc/n3.ebnf @@ -96,7 +96,7 @@ | STRING_LITERAL_SINGLE_QUOTE /* borrowed from SPARQL spec, which excludes newlines and other nastiness */ - [139s] IRIREF ::= '<' ([^<>"{}|^`\]-[#x00-#x20])* '>' + [139s] IRIREF ::= '<' ([^<>"{}|^`\]-[#x00-#x20] | WS)* '>' [140s] PNAME_NS ::= PN_PREFIX? ':' [141s] PNAME_LN ::= PNAME_NS PN_LOCAL [142s] BLANK_NODE_LABEL ::= '_:' ( PN_CHARS_U | [0-9] ) ((PN_CHARS|'.')* PN_CHARS)? diff --git a/etc/n3.peg.sxp b/etc/n3.peg.sxp index 4b0608b..f7f604f 100644 --- a/etc/n3.peg.sxp +++ b/etc/n3.peg.sxp @@ -70,9 +70,10 @@ STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE )) (terminal IRIREF "139s" (seq "<" _IRIREF_1 ">")) (terminal _IRIREF_1 "139s.1" (star _IRIREF_2)) - (terminal _IRIREF_2 "139s.2" (diff _IRIREF_3 _IRIREF_4)) - (terminal _IRIREF_3 "139s.3" (range "^<>\"{}|^`\\")) - (terminal _IRIREF_4 "139s.4" (range "#x00-#x20")) + (terminal _IRIREF_2 "139s.2" (alt _IRIREF_3 WS)) + (terminal _IRIREF_3 "139s.3" (diff _IRIREF_4 _IRIREF_5)) + (terminal _IRIREF_4 "139s.4" (range "^<>\"{}|^`\\")) + (terminal _IRIREF_5 "139s.5" (range "#x00-#x20")) (terminal PNAME_NS "140s" (seq _PNAME_NS_1 ":")) (terminal _PNAME_NS_1 "140s.1" (opt PN_PREFIX)) (terminal PNAME_LN "141s" (seq PNAME_NS PN_LOCAL)) diff --git a/etc/n3.sxp b/etc/n3.sxp index 5cd382e..63a72fe 100644 --- a/etc/n3.sxp +++ b/etc/n3.sxp @@ -46,7 +46,7 @@ (alt STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_LONG_QUOTE STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE )) (terminal IRIREF "139s" - (seq "<" (star (diff (range "^<>\"{}|^`\\") (range "#x00-#x20"))) ">")) + (seq "<" (star (alt (diff (range "^<>\"{}|^`\\") (range "#x00-#x20")) WS)) ">")) (terminal PNAME_NS "140s" (seq (opt PN_PREFIX) ":")) (terminal PNAME_LN "141s" (seq PNAME_NS PN_LOCAL)) (terminal BLANK_NODE_LABEL "142s" diff --git a/lib/rdf/n3/meta.rb b/lib/rdf/n3/meta.rb index f2aa8de..952f424 100644 --- a/lib/rdf/n3/meta.rb +++ b/lib/rdf/n3/meta.rb @@ -70,9 +70,10 @@ module RDF::N3::Meta EBNF::Rule.new(:STRING, "34", [:alt, :STRING_LITERAL_LONG_SINGLE_QUOTE, :STRING_LITERAL_LONG_QUOTE, :STRING_LITERAL_QUOTE, :STRING_LITERAL_SINGLE_QUOTE], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:IRIREF, "139s", [:seq, "<", :_IRIREF_1, ">"], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_IRIREF_1, "139s.1", [:star, :_IRIREF_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_IRIREF_2, "139s.2", [:diff, :_IRIREF_3, :_IRIREF_4], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_IRIREF_3, "139s.3", [:range, "^<>\"{}|^`\\"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_IRIREF_4, "139s.4", [:range, "#x00-#x20"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_IRIREF_2, "139s.2", [:alt, :_IRIREF_3, :WS], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_IRIREF_3, "139s.3", [:diff, :_IRIREF_4, :_IRIREF_5], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_IRIREF_4, "139s.4", [:range, "^<>\"{}|^`\\"], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_IRIREF_5, "139s.5", [:range, "#x00-#x20"], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:PNAME_NS, "140s", [:seq, :_PNAME_NS_1, ":"], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_PNAME_NS_1, "140s.1", [:opt, :PN_PREFIX], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:PNAME_LN, "141s", [:seq, :PNAME_NS, :PN_LOCAL], kind: :terminal).extend(EBNF::PEG::Rule), diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 8c595f5..fb5c842 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -184,7 +184,7 @@ def each_triple datatype: RDF::XSD.boolean, canonicalize: canonicalize?) end - terminal(:IRIREF, IRIREF) {|value| process_uri(value[1..-2])} + terminal(:IRIREF, IRIREF) {|value| process_uri(value[1..-2].gsub(/\s/, ''))} terminal(:PNAME_NS, PNAME_NS) terminal(:PNAME_LN, PNAME_LN) {|value| RDF::NTriples.unescape(value)} terminal(:BLANK_NODE_LABEL, BLANK_NODE_LABEL) {|value| bnode(value[2..-1])} diff --git a/lib/rdf/n3/terminals.rb b/lib/rdf/n3/terminals.rb index d44504d..4195009 100644 --- a/lib/rdf/n3/terminals.rb +++ b/lib/rdf/n3/terminals.rb @@ -39,7 +39,7 @@ module Terminals # 159s ECHAR = /\\[tbnrf\\"']/u.freeze # 18 - IRIREF = /<(?:#{IRI_RANGE}|#{UCHAR})*>/u.freeze + IRIREF = /<(?:#{IRI_RANGE}|#{UCHAR}|\s)*>/mu.freeze # 139s PNAME_NS = /#{PN_PREFIX}?:/u.freeze # 140s diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index cfc0d02..806b825 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -280,6 +280,13 @@ end end + it "whitespace in uris" do + n3 = %{ <\nhttp://example.org/resource2\n> .} + nt = %{ .} + graph = parse(n3, logger: logger) + expect(graph).to be_equivalent_graph(nt, logger: logger) + end + it "should allow mixed-case language" do n3doc = %(:x2 :p "xyz"@en .) statement = parse(n3doc).statements.first From 050e63e2ecec7b497679912893f51012032d0370 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 21 Jul 2020 09:46:36 -0700 Subject: [PATCH 021/193] Tweak README. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 799fa8e..3686dba 100755 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ RDF::N3 is an Notation-3 parser and reasoner for Ruby using the [RDF.rb][RDF.rb] Reader inspired from TimBL predictiveParser and Python librdf implementation. ## Uses CG Specification -This version tracks the [W3C N3 Community Group][] [Specification][N3] which has incompatibilities with the [Design Issues][] version. Notably: +This version tracks the [W3C N3 Community Group][] [Specification][N3] which has incompatibilities with the [Team Submission][] version. Notably: * The `@keywords` declaration is removed, and most form of `@` keywords (e.g., `@is`, `@has`, `@true`) are no longer supported. * Terminals adhere closely to their counterparts in [Turtle][]. @@ -58,6 +58,8 @@ Experimental N3 reasoning is supported. Instantiate a reasoner from a dataset: 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: +Reasoning is discussed in the [Design Issues][] document. + #### RDF List vocabulary * list:append (not implemented yet - See {RDF::N3::Algebra::List::Append}) From 34b023dd5a97c67b59084a3d403a2ce580f0cd9c Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 21 Jul 2020 15:45:47 -0700 Subject: [PATCH 022/193] Use local escaping (based on LL1::Lexer escape methods), and escape PNames as well as URIs. --- lib/rdf/n3/reader.rb | 56 ++++++++++++++++++++++++++++++++++++-------- spec/w3c-n3 | 1 - 2 files changed, 46 insertions(+), 11 deletions(-) delete mode 120000 spec/w3c-n3 diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index fb5c842..58288fe 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -186,23 +186,26 @@ def each_triple end terminal(:IRIREF, IRIREF) {|value| process_uri(value[1..-2].gsub(/\s/, ''))} terminal(:PNAME_NS, PNAME_NS) - terminal(:PNAME_LN, PNAME_LN) {|value| RDF::NTriples.unescape(value)} + terminal(:PNAME_LN, PNAME_LN) {|value| unescape(value)} terminal(:BLANK_NODE_LABEL, BLANK_NODE_LABEL) {|value| bnode(value[2..-1])} terminal(:LANGTAG, LANGTAG) {|value| value[1..-1]} terminal(:INTEGER, INTEGER) {|value| RDF::Literal::Integer.new(value, canonicalize: canonicalize?)} - terminal(:DECIMAL, DECIMAL) {|value| RDF::Literal::Decimal.new(value, canonicalize: canonicalize?)} + terminal(:DECIMAL, DECIMAL) do |value| + value = "0#{value}" if value.start_with?(".") # Otherwise, it's invalid + RDF::Literal::Decimal.new(value, canonicalize: canonicalize?) + end terminal(:DOUBLE, DOUBLE) {|value| RDF::Literal::Double.new(value, canonicalize: canonicalize?)} terminal(:STRING_LITERAL_QUOTE, STRING_LITERAL_QUOTE) do |value| - RDF::NTriples.unescape(value[1..-2]) + unescape(value[1..-2]) end terminal(:STRING_LITERAL_SINGLE_QUOTE, STRING_LITERAL_SINGLE_QUOTE) do |value| - RDF::NTriples.unescape(value[1..-2]) + unescape(value[1..-2]) end terminal(:STRING_LITERAL_LONG_SINGLE_QUOTE, STRING_LITERAL_LONG_SINGLE_QUOTE) do |value| - RDF::NTriples.unescape(value[3..-4]) + unescape(value[3..-4]) end terminal(:STRING_LITERAL_LONG_QUOTE, STRING_LITERAL_LONG_QUOTE) do |value| - RDF::NTriples.unescape(value[3..-4]) + unescape(value[3..-4]) end terminal(:ANON, ANON) {bnode} terminal(:QUICK_VAR_NAME, QUICK_VAR_NAME) @@ -430,14 +433,14 @@ def each_triple # (rule prefixedName "28" (alt PNAME_LN PNAME_NS)) production(:prefixedName) do |value| - process_pname(value) + process_pname(unescape value) end # 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 # # (rule quickVar "30" (seq QUICK_VAR_NAME)) production(:quickVar) do |value| - uri = process_pname(value.first[:QUICK_VAR_NAME].sub('?', ':')) + uri = process_pname(unescape value.first[:QUICK_VAR_NAME].sub('?', ':')) var = uri.variable? ? uri : univar(uri) add_var_to_formula(formulae[-2], uri, var) # Also add var to this formula @@ -507,11 +510,11 @@ def process_path(path) end def process_uri(uri) - uri(base_uri, RDF::NTriples.unescape(uri.to_s)) + uri(base_uri, unescape(uri.to_s)) end def process_pname(value) - prefix, name = value.split(":") + prefix, name = value.split(":", 2) uri = if prefix(prefix) debug('process_pname(ns)') {"#{prefix(prefix)}, #{name}"} @@ -582,6 +585,39 @@ def uri(value, append = nil) value = var if var value + rescue ArgumentError => e + error("uri", e.message) + end + + + ESCAPE_CHARS = { + '\\t' => "\t", # \u0009 (tab) + '\\n' => "\n", # \u000A (line feed) + '\\r' => "\r", # \u000D (carriage return) + '\\b' => "\b", # \u0008 (backspace) + '\\f' => "\f", # \u000C (form feed) + '\\"' => '"', # \u0022 (quotation mark, double quote mark) + "\\'" => '\'', # \u0027 (apostrophe-quote, single quote mark) + '\\\\' => '\\' # \u005C (backslash) + }.freeze + + # Perform string and codepoint unescaping if defined for this terminal + # @param [String] string + # @return [String] + def unescape(string) + string = string.dup.force_encoding(Encoding::ASCII_8BIT) + + # Decode \uXXXX and \UXXXXXXXX code points: + string = string.gsub(UCHAR) do |c| + s = [(c[2..-1]).hex].pack('U*').force_encoding(Encoding::ASCII_8BIT) + end + + string.force_encoding(Encoding::UTF_8) + + # Decode \t escapes + string.gsub( /\\./u) do |escaped| + ESCAPE_CHARS[escaped] || escaped[1..-1] + end end # Decode a PName diff --git a/spec/w3c-n3 b/spec/w3c-n3 deleted file mode 120000 index 149ed6a..0000000 --- a/spec/w3c-n3 +++ /dev/null @@ -1 +0,0 @@ -../../w3c-n3 \ No newline at end of file From 3472aee549d7ca69fa1a381a3423e240a32a6144 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 21 Jul 2020 15:46:37 -0700 Subject: [PATCH 023/193] Add turtle test suite. --- etc/n3.ebnf | 2 +- spec/.gitignore | 3 +- spec/suite_helper.rb | 143 ++++++++++++++++++++++++++++++++++++-- spec/suite_parser_spec.rb | 4 +- spec/swap | 1 - spec/turtle_spec.rb | 65 +++++++++++++++++ 6 files changed, 209 insertions(+), 9 deletions(-) delete mode 120000 spec/swap create mode 100644 spec/turtle_spec.rb diff --git a/etc/n3.ebnf b/etc/n3.ebnf index 77f1f05..68a6a99 100644 --- a/etc/n3.ebnf +++ b/etc/n3.ebnf @@ -96,7 +96,7 @@ | STRING_LITERAL_SINGLE_QUOTE /* borrowed from SPARQL spec, which excludes newlines and other nastiness */ - [139s] IRIREF ::= '<' ([^<>"{}|^`\]-[#x00-#x20] | WS)* '>' + [139s] IRIREF ::= '<' ([^<>"{}|^`\]-[#x00-#x20] | UCHAR | WS)* '>' [140s] PNAME_NS ::= PN_PREFIX? ':' [141s] PNAME_LN ::= PNAME_NS PN_LOCAL [142s] BLANK_NODE_LABEL ::= '_:' ( PN_CHARS_U | [0-9] ) ((PN_CHARS|'.')* PN_CHARS)? diff --git a/spec/.gitignore b/spec/.gitignore index a4cc2ca..55f2aae 100644 --- a/spec/.gitignore +++ b/spec/.gitignore @@ -1 +1,2 @@ -/uri-cache/ +/w3c-n3 +/w3c-rdf diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 7176da2..a3f99f9 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -1,3 +1,4 @@ +require 'rdf/turtle' require 'rdf/n3' require 'json/ld' @@ -6,6 +7,8 @@ module RDF::Util module File REMOTE_PATH = "https://w3c.github.io/n3/" LOCAL_PATH = ::File.expand_path("../w3c-n3", __FILE__) + '/' + TURTLE_PATH = "http://w3c.github.io/rdf-tests/turtle/" + LOCAL_TPATH = ::File.expand_path("../w3c-rdf/turtle", __FILE__) + '/' class << self alias_method :original_open_file, :open_file @@ -52,6 +55,39 @@ def self.open_file(filename_or_url, **options, &block) # 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 + when (filename_or_url.to_s =~ %r{^#{TURTLE_PATH}} && Dir.exist?(LOCAL_TPATH)) + #puts "attempt to open #{filename_or_url} locally" + localpath = filename_or_url.to_s.sub(TURTLE_PATH, LOCAL_TPATH) + 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 @@ -146,19 +182,19 @@ def negative_test? end def evaluate? - !!attributes['@type'].to_s.match(/Eval/) + !!attributes['@type'].to_s.match?(/Eval/) end def reason? - !!attributes['@type'].to_s.match(/Reason/) + !!attributes['@type'].to_s.match?(/Reason/) end def syntax? - !!attributes['@type'].to_s.match(/Syntax/) + !!attributes['@type'].to_s.match?(/Syntax/) end def reason? - !!attributes['@type'].to_s.match(/Reason/) + !!attributes['@type'].to_s.match?(/Reason/) end def inspect @@ -172,4 +208,103 @@ def inspect end end end + + module TurtleTest + BASE = "http://w3c.github.io/rdf-tests/turtle/" + 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#", + + "approval": {"@id": "rdft:approval", "@type": "@vocab"}, + "comment": "rdfs:comment", + "entries": {"@id": "mf:entries", "@container": "@list"}, + "name": "mf:name", + "action": {"@id": "mf:action", "@type": "@id"}, + "result": {"@id": "mf:result", "@type": "@id"} + }, + "@type": "mf:Manifest", + "entries": { + "@type": [ + "rdft:TestTurtlePositiveSyntax", + "rdft:TestTurtleNegativeSyntax", + "rdft:TestNTriplesPositiveSyntax", + "rdft:TestNTriplesNegativeSyntax", + "rdft:TestTurtleEval", + "rdft:TestTurtleNegativeEval" + ] + } + })) + + class Manifest < JSON::LD::Resource + def self.open(file) + #puts "open: #{file}" + g = RDF::Repository.load(file, format: :ttl) + JSON::LD::API.fromRDF(g) do |expanded| + JSON::LD::API.frame(expanded, FRAME) do |framed| + yield Manifest.new(framed) + end + end + end + + # @param [Hash] json framed JSON-LD + # @return [Array] + def self.from_jsonld(json) + json['@graph'].map {|e| Manifest.new(e)} + 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; :ttl; end + + def base + BASE + action.split('/').last + 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 evaluate? + Array(attributes['@type']).join(" ").match?(/Eval/) + end + + def syntax? + Array(attributes['@type']).join(" ").match?(/Syntax/) + end + + def positive_test? + !Array(attributes['@type']).join(" ").match?(/Negative/) + end + + def negative_test? + !positive_test? + end + + def inspect + super.sub('>', "\n" + + " syntax?: #{syntax?.inspect}\n" + + " positive?: #{positive_test?.inspect}\n" + + " evaluate?: #{evaluate?.inspect}\n" + + ">" + ) + end + end + end end diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index 3c296d1..c56aaf7 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -48,7 +48,7 @@ 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) + expect(e.message).to produce("Exception loading output #{e.inspect}", t) end end @@ -56,7 +56,7 @@ begin repo << reader rescue Exception => e - expect(e.message).to produce("Not exception #{e.inspect}", t.logger) + expect(e.message).to produce("Not exception #{e.inspect}", t) end if t.evaluate? diff --git a/spec/swap b/spec/swap deleted file mode 120000 index 3978bda..0000000 --- a/spec/swap +++ /dev/null @@ -1 +0,0 @@ -../../swap/ \ No newline at end of file diff --git a/spec/turtle_spec.rb b/spec/turtle_spec.rb new file mode 100644 index 0000000..b5132e1 --- /dev/null +++ b/spec/turtle_spec.rb @@ -0,0 +1,65 @@ +$:.unshift "." +require 'spec_helper' + +describe RDF::N3::Reader do + # W3C Turtle Test suite from http://w3c.github.io/rdf-tests/turtle/manifest.ttl + describe "w3c turtle tests" do + require 'suite_helper' + + Fixtures::TurtleTest::Manifest.open("http://w3c.github.io/rdf-tests/turtle/manifest.ttl") do |m| + describe m.comment do + m.entries.each do |t| + specify "#{t.name}: #{t.comment}" do + t.logger = RDF::Spec.logger + t.logger.info t.inspect + t.logger.info "source:\n#{t.input}" + + case t.name + when *%w(turtle-syntax-bad-uri-01) + skip("Spaces allowed in IRIs") + when *%w(turtle-syntax-bad-prefix-01 turtle-syntax-bad-prefix-02) + skip("No prefixes okay") + when *%w(turtle-syntax-bad-struct-02 turtle-syntax-bad-struct-04 turtle-syntax-bad-struct-06 + turtle-syntax-bad-struct-07 turtle-syntax-bad-kw-04 turtle-syntax-bad-n3-extras-01 + turtle-syntax-bad-n3-extras-02 turtle-syntax-bad-n3-extras-03 + turtle-syntax-bad-n3-extras-04 turtle-syntax-bad-n3-extras-05 + turtle-syntax-bad-n3-extras-06 turtle-syntax-bad-n3-extras-09 + turtle-syntax-bad-n3-extras-10 turtle-syntax-bad-n3-extras-11 + turtle-syntax-bad-n3-extras-12 turtle-syntax-bad-struct-14 + turtle-syntax-bad-struct-16 turtle-syntax-bad-struct-17) + skip("This is, in fact, N3") + end + + reader = RDF::N3::Reader.new(t.input, + base_uri: t.base, + canonicalize: false, + validate: true, + logger: t.logger) + + graph = RDF::Repository.new + + if t.positive_test? + begin + graph << reader + rescue Exception => e + expect(e.message).to produce("Not exception #{e.inspect}", t) + end + + if t.evaluate? + output_graph = RDF::Repository.load(t.result, format: :ntriples, base_uri: t.base) + expect(graph).to be_equivalent_graph(output_graph, t) + else + expect(graph).to be_a(RDF::Enumerable) + 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 +end unless ENV['CI'] \ No newline at end of file From 2f1320c6883869326730809545d8ac0d93177ae9 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 22 Jul 2020 15:40:31 -0700 Subject: [PATCH 024/193] Update test suite locations and create script/tc. --- script/tc | 198 ++++++++++++++++++ spec/suite_helper.rb | 166 ++------------- spec/suite_parser_spec.rb | 26 +-- spec/suite_reasoner_spec.rb | 2 +- spec/{turtle_spec.rb => suite_turtle_spec.rb} | 20 +- 5 files changed, 235 insertions(+), 177 deletions(-) create mode 100755 script/tc rename spec/{turtle_spec.rb => suite_turtle_spec.rb} (55%) diff --git a/script/tc b/script/tc new file mode 100755 index 0000000..c858a16 --- /dev/null +++ b/script/tc @@ -0,0 +1,198 @@ +#!/usr/bin/env ruby +require 'rubygems' +$:.unshift(File.expand_path("../../lib", __FILE__)) +require "bundler/setup" +require 'logger' +require 'rdf/turtle' +require 'rdf/isomorphic' +require File.expand_path("../../spec/spec_helper", __FILE__) +require File.expand_path("../../spec/suite_helper", __FILE__) +require 'getoptlong' + +ASSERTOR = "https://greggkellogg.net/foaf#me" +RUN_TIME = Time.now + +def earl_preamble(**options) + options[:output].write File.read(File.expand_path("../../etc/doap#{'-ntriples' if options[:ntriples]}.ttl", __FILE__)) + options[:output].puts %( +<> foaf:primaryTopic ; + dc:issued "#{RUN_TIME.xmlschema}"^^xsd:dateTime ; + foaf:maker <#{ASSERTOR}> . + +<#{ASSERTOR}> a foaf:Person, earl:Assertor; + foaf:name "Gregg Kellogg"; + foaf:title "Implementor"; + foaf:homepage . +) + + options[:output].puts %( + + doap:release . + + a doap:Version; + doap:name "rdf-n3-#{RDF::N3::VERSION}"; + doap:created "#{File.mtime(File.expand_path('../../VERSION', __FILE__)).strftime('%Y-%m-%d')}"^^xsd:date; + doap:revision "#{RDF::N3::VERSION}" . +) +end + +def run_tc(man, tc, **options) + STDERR.write "run #{tc.name}" + + if options[:verbose] + STDERR.puts "\nTestCase: #{tc.inspect}" + STDERR.puts "\nInput:\n" + tc.input + STDERR.puts "\nExpected:\n" + tc.result if tc.result && tc.positiveTest? + end + + logger = options[:live] ? Logger.new(STDERR) : RDF::Spec.logger + logger.level = options[:level] + logger.formatter = lambda {|severity, datetime, progname, msg| "#{severity}: #{msg}\n"} + + start = Time.now + + begin + STDERR.puts "open #{tc.action}" if options[:verbose] + options = { + base_uri: tc.base, + validate: true, + logger: logger + }.merge(options) + + reader = RDF::Reader.for(tc.action).new(tc.input, **options) + + graph = RDF::Repository.new + result = nil + + if tc.positive_test? + begin + graph << reader + rescue Exception => e + STDERR.puts "Unexpected exception: #{e.inspect}" if options[:verbose] + result = "failed" + raise + end + else + begin + graph << reader + STDERR.puts "Expected exception" if options[:verbose] + result = "failed" + rescue RDF::ReaderError + result = "passed" + end + end + + secs = Time.new - start + + if tc.evaluate? && result.nil? + output_graph = RDF::Repository.load(tc.result, format: :ntriples, base_uri: tc.base) + result = graph.isomorphic_with?(output_graph) ? "passed" : "failed" + else + result ||= "passed" + end + + rescue Interrupt + STDERR.puts "(interrupt)" + exit 1 + rescue Exception => e + STDERR.puts "#{"exception:" unless options[:quiet]}: #{e}" + if options[:quiet] + return + else + raise + end + end + + STDERR.puts options[:logger] if options[:verbose] && !options[:live] + + if options[:earl] + options[:output].puts %{ +[ a earl:Assertion; + earl:assertedBy <#{ASSERTOR}>; + earl:subject ; + earl:test <#{tc.id}>; + earl:result [ + a earl:TestResult; + earl:outcome earl:#{result}; + dc:date "#{RUN_TIME.xmlschema}"^^xsd:dateTime]; + earl:mode earl:automatic ] . +} + end + + options[:results][result] ||= 0 + options[:results][result] += 1 + + STDERR.puts "#{"test result:" unless options[:quiet]} #{result} #{"(#{secs} seconds)" unless options[:quiet] || secs < 3}." +end + +options = { + output: STDOUT, + results: {}, + level: Logger::WARN +} + +opts = GetoptLong.new( + ["--level", GetoptLong::REQUIRED_ARGUMENT], + ["--earl", GetoptLong::NO_ARGUMENT], + ["--help", "-?", GetoptLong::NO_ARGUMENT], + ["--live", GetoptLong::NO_ARGUMENT], + ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT], + ["--quiet", "-q", GetoptLong::NO_ARGUMENT], + ["--skip-long", "-s", GetoptLong::NO_ARGUMENT], + ["--validate", GetoptLong::NO_ARGUMENT], + ["--verbose", "-v", GetoptLong::NO_ARGUMENT], +) + +def help(**options) + puts "Usage: #{$0} [options] [test-number ...]" + puts "Options:" + puts " --earl: Generate EARL report" + puts " --level: Log level 0-5" + puts " --live: Show live parsing results, not buffered" + puts " --ntriples: Run N-Triples tests" + puts " --output: Output to specified file" + puts " --quiet: Minimal output" + puts " --skip-long: Avoid files taking too much time" + puts " --validate: Validate input" + puts " --verbose: Verbose processing" + puts " --help,-?: This message" + exit(0) +end + + +opts.each do |opt, arg| + case opt + when '--help' then help(**options) + when '--level' then options[:level] = arg.to_i + when '--earl' + options[:quiet] = options[:earl] = true + options[:level] = Logger::FATAL + when '--live' then options[:live] = true + when '--output' then options[:output] = File.open(arg, "w") + when '--quiet' + options[:quiet] = true + options[:level] = Logger::FATAL + when '--skip-long' then options[:skip] = true + when '--validate' then options[:validate] = true + when '--verbose' then options[:verbose] = true + end +end + +manifests = %w( + grammar/tests/N3Tests/manifest.ttl + grammar/tests/TurtleTests/manifest.ttl + tests/manifest-reasoner.n3 +).map {|r| "https://w3c.github.io/N3/#{r}"} + +earl_preamble(**options) if options[:earl] + +manifests.each do |man| + Fixtures::SuiteTest::Manifest.open(man) do |m| + m.entries.each do |tc| + next unless ARGV.empty? || ARGV.any? {|n| tc.property('@id').match?(/#{n}/) || tc.property('action').match?(/#{n}/)} + run_tc(man, tc, **options) + end + end +end + +options[:results].each {|k, v| puts "#{k}: #{v}"} diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index a3f99f9..88d1d8e 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -5,10 +5,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 = "https://w3c.github.io/n3/" - LOCAL_PATH = ::File.expand_path("../w3c-n3", __FILE__) + '/' - TURTLE_PATH = "http://w3c.github.io/rdf-tests/turtle/" - LOCAL_TPATH = ::File.expand_path("../w3c-rdf/turtle", __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 @@ -55,39 +53,6 @@ def self.open_file(filename_or_url, **options, &block) # 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 - when (filename_or_url.to_s =~ %r{^#{TURTLE_PATH}} && Dir.exist?(LOCAL_TPATH)) - #puts "attempt to open #{filename_or_url} locally" - localpath = filename_or_url.to_s.sub(TURTLE_PATH, LOCAL_TPATH) - 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 @@ -110,17 +75,18 @@ module SuiteTest "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#", + "test": "https://w3c.github.io/N3/tests/test.n3#", + "action": {"@id": "mf:action", "@type": "@id"}, + "approval": {"@id": "rdft:approval", "@type": "@vocab"}, "comment": "rdfs:comment", + "data": {"@id": "test:data", "@type": "xsd:boolean"}, "entries": {"@id": "mf:entries", "@container": "@list"}, + "filter": {"@id": "test:filter", "@type": "xsd:boolean"}, "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"} + "result": {"@id": "mf:result", "@type": "@id"}, + "rules": {"@id": "test:rules", "@type": "xsd:boolean"}, + "think": {"@id": "test:think", "@type": "xsd:boolean"} }, "@type": "mf:Manifest", "entries": { @@ -128,7 +94,13 @@ module SuiteTest "test:TestN3Reason", "test:TestN3Eval", "test:TestN3PositiveSyntax", - "test:TestN3NegativeSyntax" + "test:TestN3NegativeSyntax", + "rdft:TestTurtlePositiveSyntax", + "rdft:TestTurtleNegativeSyntax", + "rdft:TestNTriplesPositiveSyntax", + "rdft:TestNTriplesNegativeSyntax", + "rdft:TestTurtleEval", + "rdft:TestTurtleNegativeEval" ] } })) @@ -136,7 +108,7 @@ module SuiteTest class Manifest < JSON::LD::Resource def self.open(file) #puts "open: #{file}" - g = RDF::Repository.load(file, format: :n3) + g = RDF::Repository.load(file) JSON::LD::API.fromRDF(g) do |expanded| JSON::LD::API.frame(expanded, FRAME) do |framed| yield Manifest.new(framed) @@ -156,6 +128,9 @@ class Entry < JSON::LD::Resource # For debug output formatting def format; :n3; end + # For debug output formatting + def format; :ttl; end + def base action end @@ -208,103 +183,4 @@ def inspect end end end - - module TurtleTest - BASE = "http://w3c.github.io/rdf-tests/turtle/" - 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#", - - "approval": {"@id": "rdft:approval", "@type": "@vocab"}, - "comment": "rdfs:comment", - "entries": {"@id": "mf:entries", "@container": "@list"}, - "name": "mf:name", - "action": {"@id": "mf:action", "@type": "@id"}, - "result": {"@id": "mf:result", "@type": "@id"} - }, - "@type": "mf:Manifest", - "entries": { - "@type": [ - "rdft:TestTurtlePositiveSyntax", - "rdft:TestTurtleNegativeSyntax", - "rdft:TestNTriplesPositiveSyntax", - "rdft:TestNTriplesNegativeSyntax", - "rdft:TestTurtleEval", - "rdft:TestTurtleNegativeEval" - ] - } - })) - - class Manifest < JSON::LD::Resource - def self.open(file) - #puts "open: #{file}" - g = RDF::Repository.load(file, format: :ttl) - JSON::LD::API.fromRDF(g) do |expanded| - JSON::LD::API.frame(expanded, FRAME) do |framed| - yield Manifest.new(framed) - end - end - end - - # @param [Hash] json framed JSON-LD - # @return [Array] - def self.from_jsonld(json) - json['@graph'].map {|e| Manifest.new(e)} - 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; :ttl; end - - def base - BASE + action.split('/').last - 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 evaluate? - Array(attributes['@type']).join(" ").match?(/Eval/) - end - - def syntax? - Array(attributes['@type']).join(" ").match?(/Syntax/) - end - - def positive_test? - !Array(attributes['@type']).join(" ").match?(/Negative/) - end - - def negative_test? - !positive_test? - end - - def inspect - super.sub('>', "\n" + - " syntax?: #{syntax?.inspect}\n" + - " positive?: #{positive_test?.inspect}\n" + - " evaluate?: #{evaluate?.inspect}\n" + - ">" - ) - end - end - end end diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index c56aaf7..1df1813 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -14,22 +14,22 @@ require_relative 'suite_helper' - Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-parser.n3") do |m| + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/grammar/tests/N3Tests/manifest.ttl") do |m| describe m.label do m.entries.each do |t| 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") - when *%w(n3_10013) - pending("numeric representation") - when *%w(n3_10003 n3_10006) - pending("Verified test results are incorrect") - when *%w(n3_10009 n3_10018 n3_20002) - skip("Not allowed with new grammar") - when *%w(n3_10021) - skip("stack overflow") - end + #case t.name + #when *%w(n3_10004 n3_10007 n3_10014 n3_10015 n3_10017) + # pending("Reification not supported") + #when *%w(n3_10013) + # pending("numeric representation") + #when *%w(n3_10003 n3_10006) + # pending("Verified test results are incorrect") + #when *%w(n3_10009 n3_10018 n3_20002) + # skip("Not allowed with new grammar") + #when *%w(n3_10021) + # skip("stack overflow") + #end t.logger = logger t.logger.info t.inspect diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 6559485..38bb922 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -13,7 +13,7 @@ !example.exception.is_a?(RSpec::Expectations::ExpectationNotMetError) end - Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-reasoner.n3") do |m| + 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.comment}" do diff --git a/spec/turtle_spec.rb b/spec/suite_turtle_spec.rb similarity index 55% rename from spec/turtle_spec.rb rename to spec/suite_turtle_spec.rb index b5132e1..fae3fec 100644 --- a/spec/turtle_spec.rb +++ b/spec/suite_turtle_spec.rb @@ -6,7 +6,7 @@ describe "w3c turtle tests" do require 'suite_helper' - Fixtures::TurtleTest::Manifest.open("http://w3c.github.io/rdf-tests/turtle/manifest.ttl") do |m| + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/grammar/tests/TurtleTests/manifest.ttl") do |m| describe m.comment do m.entries.each do |t| specify "#{t.name}: #{t.comment}" do @@ -14,22 +14,6 @@ t.logger.info t.inspect t.logger.info "source:\n#{t.input}" - case t.name - when *%w(turtle-syntax-bad-uri-01) - skip("Spaces allowed in IRIs") - when *%w(turtle-syntax-bad-prefix-01 turtle-syntax-bad-prefix-02) - skip("No prefixes okay") - when *%w(turtle-syntax-bad-struct-02 turtle-syntax-bad-struct-04 turtle-syntax-bad-struct-06 - turtle-syntax-bad-struct-07 turtle-syntax-bad-kw-04 turtle-syntax-bad-n3-extras-01 - turtle-syntax-bad-n3-extras-02 turtle-syntax-bad-n3-extras-03 - turtle-syntax-bad-n3-extras-04 turtle-syntax-bad-n3-extras-05 - turtle-syntax-bad-n3-extras-06 turtle-syntax-bad-n3-extras-09 - turtle-syntax-bad-n3-extras-10 turtle-syntax-bad-n3-extras-11 - turtle-syntax-bad-n3-extras-12 turtle-syntax-bad-struct-14 - turtle-syntax-bad-struct-16 turtle-syntax-bad-struct-17) - skip("This is, in fact, N3") - end - reader = RDF::N3::Reader.new(t.input, base_uri: t.base, canonicalize: false, @@ -46,7 +30,7 @@ end if t.evaluate? - output_graph = RDF::Repository.load(t.result, format: :ntriples, base_uri: t.base) + output_graph = RDF::Repository.load(t.result, format: :ntriples, base_uri: t.base) expect(graph).to be_equivalent_graph(output_graph, t) else expect(graph).to be_a(RDF::Enumerable) From 76a7eabe7a0962b7f8dfae783163e743f60fa665 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 24 Jul 2020 14:50:35 -0700 Subject: [PATCH 025/193] Update test runners. --- script/tc | 12 ++++++++---- spec/suite_helper.rb | 20 +++++--------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/script/tc b/script/tc index c858a16..454a094 100755 --- a/script/tc +++ b/script/tc @@ -37,7 +37,7 @@ def earl_preamble(**options) end def run_tc(man, tc, **options) - STDERR.write "run #{tc.name}" + STDERR.write "run #{tc.name} " if options[:verbose] STDERR.puts "\nTestCase: #{tc.inspect}" @@ -70,7 +70,6 @@ def run_tc(man, tc, **options) rescue Exception => e STDERR.puts "Unexpected exception: #{e.inspect}" if options[:verbose] result = "failed" - raise end else begin @@ -85,8 +84,13 @@ def run_tc(man, tc, **options) secs = Time.new - start if tc.evaluate? && result.nil? - output_graph = RDF::Repository.load(tc.result, format: :ntriples, base_uri: tc.base) - result = graph.isomorphic_with?(output_graph) ? "passed" : "failed" + begin + output_graph = RDF::Repository.load(tc.result, format: :ntriples, base_uri: tc.base) + result = graph.isomorphic_with?(output_graph) ? "passed" : "failed" + rescue Exception => e + STDERR.puts "Unexpected exception reading result: #{e.inspect}" + result = "failed" + end else result ||= "passed" end diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 88d1d8e..53b51fe 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -94,13 +94,7 @@ module SuiteTest "test:TestN3Reason", "test:TestN3Eval", "test:TestN3PositiveSyntax", - "test:TestN3NegativeSyntax", - "rdft:TestTurtlePositiveSyntax", - "rdft:TestTurtleNegativeSyntax", - "rdft:TestNTriplesPositiveSyntax", - "rdft:TestNTriplesNegativeSyntax", - "rdft:TestTurtleEval", - "rdft:TestTurtleNegativeEval" + "test:TestN3NegativeSyntax" ] } })) @@ -149,29 +143,25 @@ def expected end def positive_test? - !attributes['@type'].to_s.match(/Negative/) + attributes['@type'].to_s.match(/N3Positive/) || attributes['@type'].to_s.match(/N3Eval/) end def negative_test? - !positive_test? + attributes['@type'].to_s.match(/N3Negative/) end def evaluate? - !!attributes['@type'].to_s.match?(/Eval/) + !!attributes['@type'].to_s.match?(/N3Eval/) end def reason? - !!attributes['@type'].to_s.match?(/Reason/) + !!attributes['@type'].to_s.match?(/N3Reason/) end def syntax? !!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" + From ab3aadda9646cf711fa634caeed2e168a35865fd Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 25 Jul 2020 14:40:37 -0700 Subject: [PATCH 026/193] Native parser, based on Turtle/TriG logic. Faster and more flexible than PEG parser. --- etc/n3.ebnf | 4 +- etc/n3.peg.sxp | 10 +- etc/n3.sxp | 6 +- lib/rdf/n3/meta.rb | 10 +- lib/rdf/n3/reader.rb | 959 +++++++++++++++++++++++++------------- lib/rdf/n3/terminals.rb | 9 +- script/tc | 2 +- spec/reader_spec.rb | 220 ++++----- spec/suite_parser_spec.rb | 21 +- 9 files changed, 778 insertions(+), 463 deletions(-) diff --git a/etc/n3.ebnf b/etc/n3.ebnf index 68a6a99..7e48f2e 100644 --- a/etc/n3.ebnf +++ b/etc/n3.ebnf @@ -17,7 +17,7 @@ [8] base ::= '@base' IRIREF - [9] triples ::= (subject | blankNodePropertyList) predicateObjectList? + [9] triples ::= subject predicateObjectList? [10] predicateObjectList ::= verb objectList (';' (verb objectList)?)* @@ -54,7 +54,7 @@ | numericLiteral | BOOLEAN_LITERAL - [20] blankNodePropertyList ::= '[' predicateObjectList? ']' + [20] blankNodePropertyList ::= '[' predicateObjectList ']' [21] collection ::= '(' object* ')' diff --git a/etc/n3.peg.sxp b/etc/n3.peg.sxp index f7f604f..1cbfbc7 100644 --- a/etc/n3.peg.sxp +++ b/etc/n3.peg.sxp @@ -9,9 +9,8 @@ (rule sparqlPrefix "6" (seq PREFIX PNAME_NS IRIREF)) (rule prefixID "7" (seq "@prefix" PNAME_NS IRIREF)) (rule base "8" (seq "@base" IRIREF)) - (rule triples "9" (seq _triples_1 _triples_2)) - (rule _triples_1 "9.1" (alt subject blankNodePropertyList)) - (rule _triples_2 "9.2" (opt predicateObjectList)) + (rule triples "9" (seq subject _triples_1)) + (rule _triples_1 "9.1" (opt predicateObjectList)) (rule predicateObjectList "10" (seq verb objectList _predicateObjectList_1)) (rule _predicateObjectList_1 "10.1" (star _predicateObjectList_2)) (rule _predicateObjectList_2 "10.2" (seq ";" _predicateObjectList_3)) @@ -36,8 +35,7 @@ (rule pathItem "18" (alt iri blankNode quickVar collection blankNodePropertyList literal formula)) (rule literal "19" (alt rdfLiteral numericLiteral BOOLEAN_LITERAL)) - (rule blankNodePropertyList "20" (seq "[" _blankNodePropertyList_1 "]")) - (rule _blankNodePropertyList_1 "20.1" (opt predicateObjectList)) + (rule blankNodePropertyList "20" (seq "[" predicateObjectList "]")) (rule collection "21" (seq "(" _collection_1 ")")) (rule _collection_1 "21.1" (star object)) (rule formula "22" (seq "{" _formula_1 "}")) @@ -70,7 +68,7 @@ STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE )) (terminal IRIREF "139s" (seq "<" _IRIREF_1 ">")) (terminal _IRIREF_1 "139s.1" (star _IRIREF_2)) - (terminal _IRIREF_2 "139s.2" (alt _IRIREF_3 WS)) + (terminal _IRIREF_2 "139s.2" (alt _IRIREF_3 UCHAR WS)) (terminal _IRIREF_3 "139s.3" (diff _IRIREF_4 _IRIREF_5)) (terminal _IRIREF_4 "139s.4" (range "^<>\"{}|^`\\")) (terminal _IRIREF_5 "139s.5" (range "#x00-#x20")) diff --git a/etc/n3.sxp b/etc/n3.sxp index 63a72fe..102eed7 100644 --- a/etc/n3.sxp +++ b/etc/n3.sxp @@ -7,7 +7,7 @@ (rule sparqlPrefix "6" (seq PREFIX PNAME_NS IRIREF)) (rule prefixID "7" (seq "@prefix" PNAME_NS IRIREF)) (rule base "8" (seq "@base" IRIREF)) - (rule triples "9" (seq (alt subject blankNodePropertyList) (opt predicateObjectList))) + (rule triples "9" (seq subject (opt predicateObjectList))) (rule predicateObjectList "10" (seq verb objectList (star (seq ";" (opt (seq verb objectList)))))) (rule objectList "11" (seq object (star (seq "," object)))) @@ -24,7 +24,7 @@ (rule pathItem "18" (alt iri blankNode quickVar collection blankNodePropertyList literal formula)) (rule literal "19" (alt rdfLiteral numericLiteral BOOLEAN_LITERAL)) - (rule blankNodePropertyList "20" (seq "[" (opt predicateObjectList) "]")) + (rule blankNodePropertyList "20" (seq "[" predicateObjectList "]")) (rule collection "21" (seq "(" (star object) ")")) (rule formula "22" (seq "{" (opt formulaContent) "}")) (rule formulaContent "23" @@ -46,7 +46,7 @@ (alt STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_LONG_QUOTE STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE )) (terminal IRIREF "139s" - (seq "<" (star (alt (diff (range "^<>\"{}|^`\\") (range "#x00-#x20")) WS)) ">")) + (seq "<" (star (alt (diff (range "^<>\"{}|^`\\") (range "#x00-#x20")) UCHAR WS)) ">")) (terminal PNAME_NS "140s" (seq (opt PN_PREFIX) ":")) (terminal PNAME_LN "141s" (seq PNAME_NS PN_LOCAL)) (terminal BLANK_NODE_LABEL "142s" diff --git a/lib/rdf/n3/meta.rb b/lib/rdf/n3/meta.rb index 952f424..706282a 100644 --- a/lib/rdf/n3/meta.rb +++ b/lib/rdf/n3/meta.rb @@ -12,9 +12,8 @@ module RDF::N3::Meta EBNF::Rule.new(:sparqlPrefix, "6", [:seq, :PREFIX, :PNAME_NS, :IRIREF]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:prefixID, "7", [:seq, "@prefix", :PNAME_NS, :IRIREF]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:base, "8", [:seq, "@base", :IRIREF]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:triples, "9", [:seq, :_triples_1, :_triples_2]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_triples_1, "9.1", [:alt, :subject, :blankNodePropertyList]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_triples_2, "9.2", [:opt, :predicateObjectList]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:triples, "9", [:seq, :subject, :_triples_1]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_triples_1, "9.1", [:opt, :predicateObjectList]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:predicateObjectList, "10", [:seq, :verb, :objectList, :_predicateObjectList_1]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_predicateObjectList_1, "10.1", [:star, :_predicateObjectList_2]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_predicateObjectList_2, "10.2", [:seq, ";", :_predicateObjectList_3]).extend(EBNF::PEG::Rule), @@ -38,8 +37,7 @@ module RDF::N3::Meta EBNF::Rule.new(:_path_4, "17.4", [:seq, "^", :path]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:pathItem, "18", [:alt, :iri, :blankNode, :quickVar, :collection, :blankNodePropertyList, :literal, :formula]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:literal, "19", [:alt, :rdfLiteral, :numericLiteral, :BOOLEAN_LITERAL]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:blankNodePropertyList, "20", [:seq, "[", :_blankNodePropertyList_1, "]"]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_blankNodePropertyList_1, "20.1", [:opt, :predicateObjectList]).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:blankNodePropertyList, "20", [:seq, "[", :predicateObjectList, "]"]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:collection, "21", [:seq, "(", :_collection_1, ")"]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_collection_1, "21.1", [:star, :object]).extend(EBNF::PEG::Rule), EBNF::Rule.new(:formula, "22", [:seq, "{", :_formula_1, "}"]).extend(EBNF::PEG::Rule), @@ -70,7 +68,7 @@ module RDF::N3::Meta EBNF::Rule.new(:STRING, "34", [:alt, :STRING_LITERAL_LONG_SINGLE_QUOTE, :STRING_LITERAL_LONG_QUOTE, :STRING_LITERAL_QUOTE, :STRING_LITERAL_SINGLE_QUOTE], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:IRIREF, "139s", [:seq, "<", :_IRIREF_1, ">"], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_IRIREF_1, "139s.1", [:star, :_IRIREF_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_IRIREF_2, "139s.2", [:alt, :_IRIREF_3, :WS], kind: :terminal).extend(EBNF::PEG::Rule), + EBNF::Rule.new(:_IRIREF_2, "139s.2", [:alt, :_IRIREF_3, :UCHAR, :WS], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_IRIREF_3, "139s.3", [:diff, :_IRIREF_4, :_IRIREF_5], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_IRIREF_4, "139s.4", [:range, "^<>\"{}|^`\\"], kind: :terminal).extend(EBNF::PEG::Rule), EBNF::Rule.new(:_IRIREF_5, "139s.5", [:range, "#x00-#x20"], kind: :terminal).extend(EBNF::PEG::Rule), diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 58288fe..d7feb2f 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -24,8 +24,7 @@ class Reader < RDF::Reader using Refinements include RDF::Util::Logger - include EBNF::PEG::Parser - include Meta + include EBNF::LL1::Parser include Terminals # Nodes used as Formulae graph names @@ -64,15 +63,12 @@ class Reader < RDF::Reader # @raise [Error]:: Raises RDF::ReaderError if validating and an error is found def initialize(input = $stdin, **options, &block) super do - input.rewind if input.respond_to?(:rewind) - @input = input.respond_to?(:read) ? input : StringIO.new(input.to_s) - @lineno = 0 - - @memo = {} - @keyword_mode = false - @keywords = %w(a is of this has).map(&:freeze).freeze - @productions = [] - @prod_data = [] + @options = { + anon_base: "b0", + whitespace: WS, + depth: 0, + }.merge(@options) + @prod_stack = [] @formulae = [] @formula_nodes = {} @@ -82,7 +78,7 @@ def initialize(input = $stdin, **options, &block) if options[:base_uri] progress("base_uri") { base_uri.inspect} - namespace(nil, uri("#{base_uri}#")) + namespace(nil, iri(base_uri.to_s.match?(%r{[#/]$}) ? base_uri : "#{base_uri}#")) end # Prepopulate operator namespaces unless validating @@ -98,6 +94,8 @@ def initialize(input = $stdin, **options, &block) progress("validate") {validate?.inspect} progress("canonicalize") {canonicalize?.inspect} + @lexer = EBNF::LL1::Lexer.new(input, self.class.patterns, **@options) + if block_given? case block.arity when 0 then instance_eval(&block) @@ -118,27 +116,15 @@ def inspect # @return [void] def each_statement(&block) if block_given? + log_recover @callback = block - parse(@input, - :n3Doc, - RDF::N3::Meta::RULES, - whitespace: WS, - **@options - ) do |context, *data| - case context - when :base_uri - uri = data.first - 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.to_s.match(/[\/\#]$/) ? base_uri : process_uri("#{uri}#")) - debug("@base") {"@base=#{base_uri}"} + + begin + while (@lexer.first rescue true) + read_n3Doc end + rescue EBNF::LL1::Lexer::Error, SyntaxError, EOFError, Recovery + # Terminate loop if EOF found while recovering end if validate? && log_statistics[:error] @@ -146,13 +132,6 @@ def each_statement(&block) end end enum_for(:each_statement) - rescue EBNF::PEG::Parser::Error => e - case e.message - when /found "@kewords/ - raise RDF::ReaderError, "@keywords has been removed" - else - raise RDF::ReaderError, e.message - end end ## @@ -174,295 +153,516 @@ def each_triple protected - ## - # Parser terminals and productions - ## + # Terminals passed to lexer. Order matters! # @!parse none - terminal(:BOOLEAN_LITERAL, %r{true|false}) do |value| - RDF::Literal.new(value.start_with?('@') ? value[1..-1] : value, - datatype: RDF::XSD.boolean, - canonicalize: canonicalize?) - end - terminal(:IRIREF, IRIREF) {|value| process_uri(value[1..-2].gsub(/\s/, ''))} + terminal(:ANON, ANON) + terminal(:BLANK_NODE_LABEL, BLANK_NODE_LABEL) + terminal(:IRIREF, IRIREF, unescape: true) + terminal(:DOUBLE, DOUBLE) + terminal(:DECIMAL, DECIMAL) + terminal(:INTEGER, INTEGER) + terminal(:PNAME_LN, PNAME_LN, unescape: true) terminal(:PNAME_NS, PNAME_NS) - terminal(:PNAME_LN, PNAME_LN) {|value| unescape(value)} - terminal(:BLANK_NODE_LABEL, BLANK_NODE_LABEL) {|value| bnode(value[2..-1])} - terminal(:LANGTAG, LANGTAG) {|value| value[1..-1]} - terminal(:INTEGER, INTEGER) {|value| RDF::Literal::Integer.new(value, canonicalize: canonicalize?)} - terminal(:DECIMAL, DECIMAL) do |value| - value = "0#{value}" if value.start_with?(".") # Otherwise, it's invalid - RDF::Literal::Decimal.new(value, canonicalize: canonicalize?) - end - terminal(:DOUBLE, DOUBLE) {|value| RDF::Literal::Double.new(value, canonicalize: canonicalize?)} - terminal(:STRING_LITERAL_QUOTE, STRING_LITERAL_QUOTE) do |value| - unescape(value[1..-2]) - end - terminal(:STRING_LITERAL_SINGLE_QUOTE, STRING_LITERAL_SINGLE_QUOTE) do |value| - unescape(value[1..-2]) - end - terminal(:STRING_LITERAL_LONG_SINGLE_QUOTE, STRING_LITERAL_LONG_SINGLE_QUOTE) do |value| - unescape(value[3..-4]) - end - terminal(:STRING_LITERAL_LONG_QUOTE, STRING_LITERAL_LONG_QUOTE) do |value| - unescape(value[3..-4]) - end - terminal(:ANON, ANON) {bnode} - terminal(:QUICK_VAR_NAME, QUICK_VAR_NAME) - terminal(:BASE, BASE) - terminal(:PREFIX, PREFIX) + terminal(:STRING_LITERAL_LONG_SINGLE_QUOTE, STRING_LITERAL_LONG_SINGLE_QUOTE, unescape: true, partial_regexp: /^'''/) + terminal(:STRING_LITERAL_LONG_QUOTE, STRING_LITERAL_LONG_QUOTE, unescape: true, partial_regexp: /^"""/) + terminal(:STRING_LITERAL_QUOTE, STRING_LITERAL_QUOTE, unescape: true) + terminal(:STRING_LITERAL_SINGLE_QUOTE, STRING_LITERAL_SINGLE_QUOTE, unescape: true) + + # String terminals + terminal(nil, %r( + [\(\){},.;\[\]a!] + | \^\^|\^ + |<-|<=|=>|= + | true|false + | has|is|of + |@forAll|@forSome + )x) - start_production(:n3Doc) do |data, block| - formulae.push(nil) - end + terminal(:PREFIX, PREFIX) + terminal(:BASE, BASE) + terminal(:LANGTAG, LANGTAG) + terminal(:QUICK_VAR_NAME, QUICK_VAR_NAME, unescape: true) - # Cleanup packrat storage after successfully parsing + private + ## + # Read statements and directives # - # (rule _n3Doc_1 "1.1" (alt _n3Doc_2 sparqlDirective)) - production(:_n3Doc_1, clear_packrat: true) {|value| value} - - # (rule sparqlBase "5" (seq BASE IRIREF)) - production(:sparqlBase) do |value, data, block| - block.call(:base_uri, value.last[:IRIREF]) + # [1] n3Doc ::= (n3Statement '.' | sparqlDirective)* + # + # @return [void] + def read_n3Doc + prod(:n3Doc, %w{.}) do + error("read_n3Doc", "Unexpected end of file") unless token = @lexer.first + case token.type + when :BASE, :PREFIX + read_directive || error("Failed to parse directive", production: :directive, token: token) + else + read_n3Statement + if !log_recovering? || @lexer.first === '.' + # If recovering, we will have eaten the closing '.' + token = @lexer.shift + unless token && token.value == '.' + error("Expected '.' following n3Statement", production: :n3Statement, token: token) + end + end + end + end end - # (rule sparqlPrefix "6" (seq PREFIX PNAME_NS IRIREF)) - production(:sparqlPrefix) do |value| - namespace(value[1][:PNAME_NS][0..-2], value.last[:IRIREF]) - end - # (rule prefixID "7" (seq "@prefix" PNAME_NS IRIREF)) - production(:prefixID) do |value| - namespace(value[1][:PNAME_NS][0..-2], value.last[:IRIREF]) + ## + # Read statements and directives + # + # [2] n3Statement ::= n3Directive | triples | existential | universal + # + # @return [void] + def read_n3Statement + prod(:n3Statement, %w{.}) do + error("read_n3Doc", "Unexpected end of file") unless token = @lexer.first + read_uniext || + read_triples || + error("Expected token", production: :statement, token: token) + end end - # (rule base "8" (seq "@base" IRIREF)) - production(:base) do |value, data, block| - block.call(:base_uri, value.last[:IRIREF]) + ## + # Read base and prefix directives + # + # [3] n3Directive ::= prefixID | base + # + # @return [void] + def read_directive + prod(:directive, %w{.}) do + token = @lexer.first + case token.type + when :BASE + prod(:base) do + @lexer.shift + terminated = token.value == '@base' + iri = @lexer.shift + error("Expected IRIREF", production: :base, token: iri) unless iri === :IRIREF + @options[:base_uri] = process_iri(iri.value[1..-2].gsub(/\s/, '')) + namespace(nil, base_uri.to_s.match?(%r{[#/]$}) ? base_uri : iri("#{base_uri}#")) + error("base", "#{token} should be downcased") if token.value.start_with?('@') && token.value != '@base' + + if terminated + error("base", "Expected #{token} to be terminated") unless @lexer.first === '.' + @lexer.shift + elsif @lexer.first === '.' + error("base", "Expected #{token} not to be terminated") + else + true + end + end + when :PREFIX + prod(:prefixID, %w{.}) do + @lexer.shift + pfx, iri = @lexer.shift, @lexer.shift + terminated = token.value == '@prefix' + error("Expected PNAME_NS", production: :prefix, token: pfx) unless pfx === :PNAME_NS + error("Expected IRIREF", production: :prefix, token: iri) unless iri === :IRIREF + debug("prefixID", depth: options[:depth]) {"Defined prefix #{pfx.inspect} mapping to #{iri.inspect}"} + namespace(pfx.value[0..-2], process_iri(iri.value[1..-2].gsub(/\s/, ''))) + error("prefixId", "#{token} should be downcased") if token.value.start_with?('@') && token.value != '@prefix' + + if terminated + error("prefixID", "Expected #{token} to be terminated") unless @lexer.first === '.' + @lexer.shift + elsif @lexer.first === '.' + error("prefixID", "Expected #{token} not to be terminated") + else + true + end + end + end + end end - # (rule triples "9" (seq _triples_1 _triples_2)) - production(:triples) {|value| value} - # (rule _triples_1 "9.1" (alt subject blankNodePropertyList)) - production(:_triples_1) do |value, data| - debug("triples") {"subject: #{data[:subject]}"} - prod_data[:subject] = data[:subject] - end - # (rule _triples_2 "9.2" (opt predicateObjectList)) - start_production(:_triples_2) do |data| - debug("triples") {"subject: #{data[:subject]}"} - data[:subject] = prod_data[:subject] - nil + ## + # Read triples + # + # [9] triples ::= subject predicateObjectList? + # + # @return [Object] returns the last IRI matched, or subject BNode on predicateObjectList? + def read_triples + prod(:triples, %w{.}) do + error("read_triples", "Unexpected end of file") unless token = @lexer.first + case token.type || token.value + when '[' + # blankNodePropertyList predicateObjectList? + subject = read_blankNodePropertyList || error("Failed to parse blankNodePropertyList", production: :triples, token: @lexer.first) + read_predicateObjectList(subject) || subject + else + # subject predicateObjectList + subject = read_path || error("Failed to parse subject", production: :triples, token: @lexer.first) + read_predicateObjectList(subject) || error("Expected predicateObjectList", production: :triples, token: @lexer.first) + end + end end - # (rule predicateObjectList "10" (seq verb objectList _predicateObjectList_1)) - start_production(:predicateObjectList, as_hash: true) do |data| - # placed here by `subject` or `blankNodePropertyList` - data[:subject] = prod_data[:subject] + ## + # Read predicateObjectList + # + # [10] predicateObjectList ::= verb objectList (';' (verb objectList)?)* + # + # @param [RDF::Resource] subject + # @return [RDF::URI] the last matched verb + def read_predicateObjectList(subject) + return if @lexer.first.nil? || %w(. }).include?(@lexer.first.value) + prod(:predicateObjectList, %{;}) do + last_verb = nil + loop do + verb, invert = read_verb + break unless verb + last_verb = verb + prod(:_predicateObjectList_2) do + read_objectList(subject, verb, invert) || error("Expected objectList", production: :predicateObjectList, token: @lexer.first) + end + break unless @lexer.first === ';' + @lexer.shift while @lexer.first === ';' + end + last_verb + end end - # (rule objectList "11" (seq object _objectList_1)) - start_production(:objectList) do |data| - subject, predicate = prod_data[:subject], prod_data[:verb] - debug("objectList(start)") {"subject: #{subject.inspect}, predicate: #{predicate.inspect}"} - end - production(:objectList) do |value| - subject, predicate = prod_data[:subject], prod_data[:verb] - debug("objectList") {"subject: #{subject.inspect}, predicate: #{predicate.inspect}"} - objects = Array(value.last[:_objectList_1]).unshift(value.first[:object]) + ## + # Read objectList + # + # [11] objectList ::= object (',' object)* + # + # @return [RDF::Term] the last matched subject + def read_objectList(subject, predicate, invert) + prod(:objectList, %{,}) do + last_object = nil + while object = prod(:_objectList_2) {read_path} + last_object = object + + if invert + add_statement(:objectList, object, predicate, subject) + else + add_statement(:objectList, subject, predicate, object) + end - # Emit triples with given subject and verb for each object - objects.each do |object| - if prod_data[:invert] - add_statement(:objectList, object, predicate, subject) - else - add_statement(:objectList, subject, predicate, object) + break unless @lexer.first === ',' + @lexer.shift while @lexer.first === ',' end + last_object end end - # (rule _objectList_1 "11.1" (star _objectList_2)) - # (rule _objectList_2 "11.2" (seq "," object)) - production(:_objectList_2) {|value| value.last[:object]} - - # (rule verb "12" (alt predicate "a" _verb_1 _verb_2 _verb_3 "<=" "=>" "=")) - # Adds verb to prod_data for objectList - production(:verb) do |value, data| - prod_data[:verb] = case value - when RDF::Term - prod_data[:invert] = data[:invert] - value - when 'a' then RDF.type - when '=' then RDF::OWL.sameAs - when '=>' then RDF::N3::Log.implies - when '<=' - prod_data[:invert] = true + + ## + # Read a verb + # + # [12] verb = predicate + # | 'a' + # | 'has' expression + # | 'is' expression 'of' + # | '<-' expression + # | '<=' + # | '=>' + # | '=' + # + # @return [RDF::Resource, Boolean] verb and invert? + def read_verb + invert = false + error("read_verb", "Unexpected end of file") unless token = @lexer.first + verb = case token.type || token.value + when 'a' then prod(:verb) {@lexer.shift && RDF.type} + when 'has' then prod(:verb) {@lexer.shift && read_path} + when 'is' then prod(:verb) { + @lexer.shift + invert, v = true, read_path + error( "Expected 'of'", production: :verb, token: @lexer.first) unless @lexer.first.value == 'of' + @lexer.shift + v + } + when '<-' then prod(:verb) { + @lexer.shift + invert = true + read_path + } + when '<=' then prod(:verb) { + @lexer.shift + invert = true RDF::N3::Log.implies - when Array # forms of has and is xxx of - if value.first[:has] - value[1][:expression] - elsif value.first[:is] - prod_data[:invert] = true - value[1][:expression] - elsif value.first[:'<-'] - prod_data[:invert] = true - value[1][:expression] - end + } + when '=>' then prod(:verb) {@lexer.shift && RDF::N3::Log.implies} + when '=' then prod(:verb) {@lexer.shift && RDF::OWL.sameAs} + else read_path end - - error("Literal may not be used as a predicate") if prod_data[:verb].is_a?(RDF::Literal) - error("Formula may not be used as a peredicate") if formula_nodes.has_key?(prod_data[:verb]) - - prod_data[:verb] + [verb, invert] end - # (rule subject "13" (seq expression)) - production(:subject) do |value| - # Put in prod_data, so it's available to predicateObjectList - prod_data[:subject] = value.last[:expression] - end + ## + # subjects, predicates and objects are all expressions, which are all paths + # + # [13] subject ::= expression + # [14] predicate ::= expression + # [16] expression ::= path + # [17] path ::= pathItem ('!' path | '^' path)? + # + # @return [RDF::Resource] + def read_path + return if @lexer.first.nil? || %w/. } ) ]/.include?(@lexer.first.value) + prod(:path) do + pathtail = path = {} + loop do + pathtail[:pathitem] = prod(:pathItem) do + read_iri || + read_blankNode || + read_quickVar || + read_collection || + read_blankNodePropertyList || + read_literal || + read_formula + end - # (rule predicate "14" (seq expression)) - production(:predicate) do |value, data| - value.first[:expression] - end - # (rule _predicate_1 "14.1" (seq "^" expression)) - production(:_predicate_1) do |value| - prod_data[:invert] = true - value.last[:expression] - end - + break if @lexer.first.nil? || !%w(! ^).include?(@lexer.first.value) + prod(:_path_2) do + pathtail[:direction] = @lexer.shift.value == '!' ? :forward : :reverse + pathtail = pathtail[:pathtail] = {} + end + end - # (rule object "15" (seq expression)) - production(:object) do |value| - value.last[:expression] + # Returns the first object in the path + # FIXME: what if it's a verb? + process_path(path) + end end - # (rule expression "16" (seq path)) - production(:expression) do |value| - path = value.last[:path] - case path - when Hash # path - # Result is a bnode - process_path(path) - else - path + ## + # Read a literal + # + # [19] literal ::= rdfLiteral | numericLiteral | BOOLEAN_LITERAL + # + # @return [RDF::Literal] + def read_literal + error("Unexpected end of file", production: :literal) unless token = @lexer.first + case token.type || token.value + when :INTEGER then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.integer)} + when :DECIMAL + prod(:literal) do + value = @lexer.shift.value + value = "0#{value}" if value.start_with?(".") + literal(value, datatype: RDF::XSD.decimal) + end + when :DOUBLE then prod(:literal) {literal(@lexer.shift.value.sub(/\.([eE])/, '.0\1'), datatype: RDF::XSD.double)} + when "true", "false" then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.boolean)} + when :STRING_LITERAL_QUOTE, :STRING_LITERAL_SINGLE_QUOTE + prod(:literal) do + value = @lexer.shift.value[1..-2] + error("read_literal", "Unexpected end of file") unless token = @lexer.first + case token.type || token.value + when :LANGTAG + literal(value, language: @lexer.shift.value[1..-1].to_sym) + when '^^' + @lexer.shift + literal(value, datatype: read_iri) + else + literal(value) + end + end + when :STRING_LITERAL_LONG_QUOTE, :STRING_LITERAL_LONG_SINGLE_QUOTE + prod(:literal) do + value = @lexer.shift.value[3..-4] + error("read_literal", "Unexpected end of file") unless token = @lexer.first + case token.type || token.value + when :LANGTAG + literal(value, language: @lexer.shift.value[1..-1].to_sym) + when '^^' + @lexer.shift + literal(value, datatype: read_iri) + else + literal(value) + end + end end end - # (rule path "17" (seq pathItem _path_1)) - start_production(:path, as_hash: true) - production(:path) do |value, data| - if value[:_path_1] - { - pathitem: value[:pathItem], - direction: value[:_path_1][:"!"] ? :forward : :reverse, - pathtail: value[:_path_1][:path] - } - else - value[:pathItem] + ## + # Read a blankNodePropertyList + # + # [20] blankNodePropertyList ::= '[' predicateObjectList ']' + # + # @return [RDF::Node] + def read_blankNodePropertyList + token = @lexer.first + if token === '[' + prod(:blankNodePropertyList, %{]}) do + @lexer.shift + log_info("blankNodePropertyList", depth: options[:depth]) {"token: #{token.inspect}"} + node = bnode + read_predicateObjectList(node) + error("blankNodePropertyList", "Expected closing ']'") unless @lexer.first === ']' + @lexer.shift + node + end end end - # (rule _path_3 "17.3" (seq "!" path)) - start_production(:_path_3, as_hash: true) - # (rule _path_4 "17.4" (seq "^" path)) - start_production(:_path_4, as_hash: true) - - # (rule blankNodePropertyList "20" (seq "[" _blankNodePropertyList_1 "]")) - production(:blankNodePropertyList) {|value| value[1][:_blankNodePropertyList_1]} - # (rule _blankNodePropertyList_1 "20.1" (opt predicateObjectList)) - start_production(:_blankNodePropertyList_1) {|data| data[:subject] = bnode} - # Returns the blank node subject - production(:_blankNodePropertyList_1) do |value, data| - data[:subject] - end - - # (rule collection "21" (seq "(" _collection_1 ")")) - production(:collection) {|value| value[1][:_collection_1]} - # (rule _collection_1 "21.1" (star object)) - production(:_collection_1) do |value| - list = RDF::List[*value] - list.each_statement do |statement| - next if statement.predicate == RDF.type && statement.object == RDF.List - add_statement(":collection", statement.subject, statement.predicate, statement.object) + + ## + # Read a collection (`RDF::List`) + # + # [21] collection ::= '(' object* ')' + # + # @return [RDF::Node] + def read_collection + if @lexer.first === '(' + prod(:collection, %{)}) do + @lexer.shift + token = @lexer.first + log_info("collection", depth: options[:depth]) {"token: #{token.inspect}"} + objects = [] + while @lexer.first.value != ')' && (object = read_path) + objects << object + end + list = RDF::List.new(values: objects) + list.each_statement do |statement| + add_statement("collection", *statement.to_a) + end + error("collection", "Expected closing ')'") unless @lexer.first === ')' + @lexer.shift + list.subject + end end - list.subject end - # A new formula, push on a node as a named graph + ## + # Read a formula + # + # [22] formula ::= '{' formulaContent? '}' + # [23] formulaContent ::= n3Statement ('.' formulaContent?)? # - # (rule formula "22" (seq "{" _formula_1 "}")) - production(:formula) {|value| value[1][:_formula_1]} + # @return [RDF::Node] + def read_formula + if @lexer.first === '{' + prod(:formula, %(})) do + @lexer.shift + node = RDF::Node.new(".form_#{unique_label}") + formulae.push(node) + formula_nodes[node] = true + debug(:formula, depth: @options[:depth]) {"id: #{node}, depth: #{formulae.length}"} + + # Promote variables defined on the earlier formula to this formula + variables[node] = {} + variables.fetch(formulae[-2], {}).each do |name, var| + variables[node][name] = var + end - start_production(:_formula_1) do |data| - node = RDF::Node.new(".form_#{unique_label}") - formulae.push(node) - formula_nodes[node] = true - debug(:formula) {"id: #{node}, depth: #{formulae.length}"} + read_formulaContent - # Promote variables defined on the earlier formula to this formula - variables[node] = {} - variables.fetch(formulae[-2], {}).each do |name, var| - variables[node][name] = var - end - end + # Pop off the formula + # Result is the BNode associated with the formula + debug(:formula, depth: @options[:depth]) {"pop: #{formulae.last}, depth: #{formulae.length}"} + error("collection", "Expected closing '}'") unless @lexer.shift === '}' - # Pop off the formula - production(:_formula_1) do |value| - # Result is the BNode associated with the formula - debug(:formula) {"pop: #{formulae.last}, depth: #{formulae.length}"} - formulae.pop + formulae.pop + end + end end - - # (rule rdfLiteral "25" (seq STRING _rdfLiteral_1)) - production(:rdfLiteral) do |value| - str = value.first[:STRING] - lang = value.last[:_rdfLiteral_1].to_sym if value.last[:_rdfLiteral_1].is_a?(String) - datatype = value.last[:_rdfLiteral_1] if value.last[:_rdfLiteral_1].is_a?(RDF::URI) - RDF::Literal(str, language: lang, datatype: datatype, canonicalize: canonicalize?) + ## + # Read formula content, similaer to n3Statement + # + # [23] formulaContent ::= n3Statement ('.' formulaContent?)? + # + # @return [void] + def read_formulaContent + prod(:formulaContent, %w(. })) do + loop do + error("read_formulaContent", "Unexpected end of file") unless token = @lexer.first + case token.type + when :BASE, :PREFIX + read_directive || error("Failed to parse directive", production: :directive, token: token) + break if @lexer.first === '}' + else + read_n3Statement + token = @lexer.first + case token.value + when '.' + @lexer.shift + # '.' optional at end of formulaContent + break if @lexer.first === '}' + when '}' + break + else + error("Expected '.' or '}' following n3Statement", production: :formulaContent, token: token) + end + end + end + end end - # (rule _rdfLiteral_3 "25.3" (seq "^^" iri)) - production(:_rdfLiteral_3) {|value| value.last[:iri]} - # (rule iriList "27" (seq iri _iriList_1)) - production(:iriList) do |value| - Array(value.last[:_iriList_1]).unshift(value.first[:iri]) + ## + # Read an IRI + # + # (rule iri "26" (alt IRIREF prefixedName)) + # + # @return [RDF::URI] + def read_iri + token = @lexer.first + case token && token.type + when :IRIREF then prod(:iri) {process_iri(@lexer.shift.value[1..-2].gsub(/\s/, ''))} + when :PNAME_LN, :PNAME_NS then prod(:prefixedName) {process_pname(*@lexer.shift.value)} + end end - # (rule _iriList_2 "27.2" (seq "," iri)) - production(:_iriList_2) {|value| value.last[:iri]} - # (rule prefixedName "28" (alt PNAME_LN PNAME_NS)) - production(:prefixedName) do |value| - process_pname(unescape value) + ## + # Read a blank node + # + # [29] blankNode ::= BLANK_NODE_LABEL | ANON + # + # @return [RDF::Node] + def read_blankNode + token = @lexer.first + case token && token.type + when :BLANK_NODE_LABEL then prod(:blankNode) {bnode(@lexer.shift.value[2..-1])} + when :ANON then @lexer.shift && prod(:blankNode) {bnode} + end end - # 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 + ## + # Read a quickVar # - # (rule quickVar "30" (seq QUICK_VAR_NAME)) - production(:quickVar) do |value| - uri = process_pname(unescape value.first[:QUICK_VAR_NAME].sub('?', ':')) - var = uri.variable? ? uri : univar(uri) - add_var_to_formula(formulae[-2], uri, var) - # Also add var to this formula - add_var_to_formula(formulae.last, uri, var) - var + # [30] quickVar ::= QUICK_VAR_NAME + # + # @return [RDF::Query::Variable] + def read_quickVar + if @lexer.first.type == :QUICK_VAR_NAME + prod(:quickVar) do + token = @lexer.shift + iri = process_pname(token.value.sub('?', ':')) + var = iri.variable? ? iri : univar(iri) + add_var_to_formula(formulae[-2], iri, var) + # Also add var to this formula + add_var_to_formula(formulae.last, iri, var) + var + end + end end - # Apart from the set of statements, a formula also has a set of URIs of symbols which are universally quantified, - # and a set of URIs of symbols which are existentially quantified. - # Variables are then in general symbols which have been quantified. + ## + # Read a list of IRIs # - # Here we allocate a variable (making up a name) and record with the defining formula. Quantification is done - # when the formula is completed against all in-scope variables + # [27] iriList ::= iri ( ',' iri )* # - # (rule existential "31" (seq "@forSome" iriList)) - production(:existential) do |value| - value.last[:iriList].each do |iri| - var = univar(iri, true) - add_var_to_formula(formulae.last, iri, var) + # @return [Array] the list of IRIs + def read_irilist + iris = [] + prod(:iriList, %{,}) do + while iri = read_iri + iris << iri + break unless @lexer.first === ',' + @lexer.shift while @lexer.first === ',' + end end + iris end + ## + # Read a univeral or existential + # # Apart from the set of statements, a formula also has a set of URIs of symbols which are universally quantified, # and a set of URIs of symbols which are existentially quantified. # Variables are then in general symbols which have been quantified. @@ -470,15 +670,23 @@ def each_triple # Here we allocate a variable (making up a name) and record with the defining formula. Quantification is done # when the formula is completed against all in-scope variables # - # (rule universal "32" (seq "@forAll" iriList)) - production(:universal) do |value| - value.last[:iriList].each do |iri| - add_var_to_formula(formulae.last, iri, univar(iri)) + # [31] existential ::= '@forSome' iriList + # [32] universal ::= '@forAll' iriList + # + # @return [void] + def read_uniext + if %w(@forSome @forAll).include?(@lexer.first.value) + token = @lexer.shift + prod(token === '@forAll' ? :universal : :existential) do + iri_list = read_irilist + iri_list.each do |iri| + var = univar(iri, token === '@forSome') + add_var_to_formula(formulae.last, iri, var) + end + end end end - private - ################### # Utility Functions ################### @@ -492,7 +700,7 @@ def each_triple # Result is last created bnode def process_path(path) pathitem, direction, pathtail = path[:pathitem], path[:direction], path[:pathtail] - debug("process_path") {path.inspect} + debug("process_path", depth: @options[:depth]) {path.inspect} while pathtail bnode = RDF::Node.new @@ -509,23 +717,23 @@ def process_path(path) pathitem end - def process_uri(uri) - uri(base_uri, unescape(uri.to_s)) + def process_iri(iri) + iri(base_uri, iri.to_s) end def process_pname(value) prefix, name = value.split(":", 2) - uri = if prefix(prefix) - debug('process_pname(ns)') {"#{prefix(prefix)}, #{name}"} + iri = if prefix(prefix) + #debug('process_pname(ns)', depth: @options[:depth]) {"#{prefix(prefix)}, #{name}"} ns(prefix, name) else - debug('process_pname(default_ns)', name) - namespace(nil, uri("#{base_uri}#")) unless prefix(nil) + #debug('process_pname(default_ns)', name, depth: @options[:depth]) + namespace(nil, iri("#{base_uri}#")) unless prefix(nil) ns(nil, name) end - debug('process_pname') {uri.inspect} - uri + debug('process_pname', depth: @options[:depth]) {iri.inspect} + iri end # Keep track of allocated BNodes. Blank nodes are allocated to the formula. @@ -559,22 +767,22 @@ def add_statement(node, subject, predicate, object) else RDF::Statement(subject, predicate, object) end - debug("statement(#{node})") {statement.to_s} + debug("statement(#{node})", depth: @options[:depth]) {statement.to_s} error("statement(#{node})", "Statement is invalid: #{statement.inspect}") if validate? && statement.invalid? @callback.call(statement) end - def namespace(prefix, uri) - uri = uri.to_s - if uri == '#' - uri = prefix(nil).to_s + '#' + def namespace(prefix, iri) + iri = iri.to_s + if iri == '#' + iri = prefix(nil).to_s + '#' end - debug("namespace") {"'#{prefix}' <#{uri}>"} - prefix(prefix, uri(uri)) + debug("namespace", depth: @options[:depth]) {"'#{prefix}' <#{iri}>"} + prefix(prefix, iri(iri)) end - # Create URIs - def uri(value, append = nil) + # Create IRIs + def iri(value, append = nil) value = RDF::URI(value) value = value.join(append) if append value.validate! if validate? && value.respond_to?(:validate) @@ -586,46 +794,28 @@ def uri(value, append = nil) value rescue ArgumentError => e - error("uri", e.message) - end - - - ESCAPE_CHARS = { - '\\t' => "\t", # \u0009 (tab) - '\\n' => "\n", # \u000A (line feed) - '\\r' => "\r", # \u000D (carriage return) - '\\b' => "\b", # \u0008 (backspace) - '\\f' => "\f", # \u000C (form feed) - '\\"' => '"', # \u0022 (quotation mark, double quote mark) - "\\'" => '\'', # \u0027 (apostrophe-quote, single quote mark) - '\\\\' => '\\' # \u005C (backslash) - }.freeze - - # Perform string and codepoint unescaping if defined for this terminal - # @param [String] string - # @return [String] - def unescape(string) - string = string.dup.force_encoding(Encoding::ASCII_8BIT) - - # Decode \uXXXX and \UXXXXXXXX code points: - string = string.gsub(UCHAR) do |c| - s = [(c[2..-1]).hex].pack('U*').force_encoding(Encoding::ASCII_8BIT) - end - - string.force_encoding(Encoding::UTF_8) - - # Decode \t escapes - string.gsub( /\\./u) do |escaped| - ESCAPE_CHARS[escaped] || escaped[1..-1] + error("iri", e.message) + end + + # Create a literal + def literal(value, **options) + debug("literal", depth: @options[:depth]) do + "value: #{value.inspect}, " + + "options: #{options.inspect}, " + + "validate: #{validate?.inspect}, " + + "c14n?: #{canonicalize?.inspect}" end + RDF::Literal.new(value, validate: validate?, canonicalize: canonicalize?, **options) + rescue ArgumentError => e + error("Argument Error #{e.message}", production: :literal, token: @lexer.first) end # Decode a PName def ns(prefix = nil, suffix = nil) base = prefix(prefix).to_s suffix = suffix.to_s.sub(/^\#/, "") if base.index("#") - debug("ns") {"base: '#{base}', suffix: '#{suffix}'"} - uri(base + suffix.to_s) + #debug("ns", depth: @options[:depth]) {"base: '#{base}', suffix: '#{suffix}'"} + iri(base + suffix.to_s) end # Returns a unique label @@ -650,5 +840,120 @@ def find_var(sym, name) def add_var_to_formula(bn, name, var) (variables[bn] ||= {})[name.to_s] = var end + + def prod(production, recover_to = []) + @prod_stack << {prod: production, recover_to: recover_to} + @options[:depth] += 1 + log_recover("#{production}(start)", depth: options[:depth]) {"token: #{@lexer.first.inspect}"} + yield + rescue EBNF::LL1::Lexer::Error, SyntaxError, Recovery => e + # Lexer encountered an illegal token or the parser encountered + # a terminal which is inappropriate for the current production. + # Perform error recovery to find a reasonable terminal based + # on the follow sets of the relevant productions. This includes + # remaining terms from the current production and the stacked + # productions + case e + when EBNF::LL1::Lexer::Error + @lexer.recover + begin + error("Lexer error", "With input '#{e.input}': #{e.message}", + production: production, + token: e.token) + rescue SyntaxError + end + end + raise EOFError, "End of input found when recovering" if @lexer.first.nil? + debug("recovery", "current token: #{@lexer.first.inspect}", depth: @options[:depth]) + + unless e.is_a?(Recovery) + # Get the list of follows for this sequence, this production and the stacked productions. + debug("recovery", "stack follows:", depth: @options[:depth]) + @prod_stack.reverse.each do |prod| + debug("recovery", level: 1, depth: @options[:depth]) {" #{prod[:prod]}: #{prod[:recover_to].inspect}"} + end + end + + # Find all follows to the top of the stack + follows = @prod_stack.map {|prod| Array(prod[:recover_to])}.flatten.compact.uniq + + # Skip tokens until one is found in follows + while (token = (@lexer.first rescue @lexer.recover)) && follows.none? {|t| token === t} + skipped = @lexer.shift + debug("recovery", depth: @options[:depth]) {"skip #{skipped.inspect}"} + end + debug("recovery", depth: @options[:depth]) {"found #{token.inspect} in follows"} + + # Re-raise the error unless token is a follows of this production + raise Recovery unless Array(recover_to).any? {|t| token === t} + + # Skip that token to get something reasonable to start the next production with + @lexer.shift + ensure + log_info("#{production}(finish)", depth: options[:depth]) + @options[:depth] -= 1 + @prod_stack.pop + end + + ## + # Error information, used as level `0` debug messages. + # + # @overload error(node, message, options) + # @param [String] node Relevant location associated with message + # @param [String] message Error string + # @param [Hash] options + # @option options [URI, #to_s] :production + # @option options [Token] :token + # @see {#debug} + def error(*args) + ctx = "" + ctx += "(found #{options[:token].inspect})" if options[:token] + ctx += ", production = #{options[:production].inspect}" if options[:production] + lineno = (options[:token].lineno if options[:token].respond_to?(:lineno)) || @lexer.lineno + log_error(*args, ctx, + lineno: lineno, + token: options[:token], + production: options[:production], + depth: options[:depth], + exception: SyntaxError,) + end + + # Used for internal error recovery + class Recovery < StandardError; end + + class SyntaxError < RDF::ReaderError + ## + # The current production. + # + # @return [Symbol] + attr_reader :production + + ## + # The invalid token which triggered the error. + # + # @return [String] + attr_reader :token + + ## + # The line number where the error occurred. + # + # @return [Integer] + attr_reader :lineno + + ## + # Initializes a new syntax error instance. + # + # @param [String, #to_s] message + # @param [Hash{Symbol => Object}] options + # @option options [Symbol] :production (nil) + # @option options [String] :token (nil) + # @option options [Integer] :lineno (nil) + def initialize(message, **options) + @production = options[:production] + @token = options[:token] + @lineno = options[:lineno] || (@token.lineno if @token.respond_to?(:lineno)) + super(message.to_s) + end + end end end diff --git a/lib/rdf/n3/terminals.rb b/lib/rdf/n3/terminals.rb index 4195009..5497eea 100644 --- a/lib/rdf/n3/terminals.rb +++ b/lib/rdf/n3/terminals.rb @@ -64,14 +64,17 @@ module Terminals # 25 STRING_LITERAL_LONG_QUOTE = /"""(?:(?:"|"")?(?:[^"\\]|#{ECHAR}|#{UCHAR}))*"""/um.freeze - BASE = /base/i.freeze - PREFIX = /prefix/i.freeze + # 28t + PREFIX = /@?prefix/ui.freeze + # 29t + BASE = /@?base/ui.freeze QUICK_VAR_NAME = /\?#{PN_LOCAL}/.freeze # 161s WS = /(?:\s|(?:#[^\n\r]*))+/um.freeze # 162s ANON = /\[#{WS}*\]/um.freeze - + + FORALL = /@forAll/u.freeze end end \ No newline at end of file diff --git a/script/tc b/script/tc index 454a094..b2dd006 100755 --- a/script/tc +++ b/script/tc @@ -59,7 +59,7 @@ def run_tc(man, tc, **options) logger: logger }.merge(options) - reader = RDF::Reader.for(tc.action).new(tc.input, **options) + reader = RDF::N3::Reader.new(tc.input, **options) graph = RDF::Repository.new result = nil diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 806b825..30c069a 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -391,7 +391,7 @@ it "should generate rdf:type for '@a'", pending: 'deprecated' 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, format: :n3) + expect(parse(n3, base_uri: "http://a/b", validate: tru)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate inverse predicate for 'is xxx of'" do @@ -403,7 +403,7 @@ it "should generate inverse predicate for '@is xxx @of'", pending: 'deprecated' 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, format: :n3) + expect(parse(n3, base_uri: "http://a/b", validate: true)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate inverse predicate for '<- xxx'" do @@ -446,7 +446,7 @@ it "should generate predicate for '@has xxx'", pending: 'deprecated' 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, format: :n3) + expect(parse(n3, base_uri: "http://a/b", validate: tru)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should create log:implies predicate for '=>'" do @@ -779,6 +779,105 @@ end end + describe "property lists" do + it "should parse property list" do + n3 = %( + @prefix a: . + + a:b a:p1 "123" ; a:p1 "456" . + a:b a:p2 a:v1 ; a:p3 a:v2 . + ) + nt = %( + "123" . + "456" . + . + . + ) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) + end + end + + describe "lists" do + it "should parse empty list" do + n3 = %(@prefix :. :empty :set ().) + nt = %( + .) + 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 + n3 = %(@prefix :. :gregg :wrote ("RdfContext").) + nt = %( + _:bnode0 "RdfContext" . + _:bnode0 . + _:bnode0 . + ) + 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 + n3 = %(@prefix :. :gregg :name ("Gregg" "Barnum" "Kellogg").) + nt = %( + _:bnode0 "Gregg" . + _:bnode0 _:bnode1 . + _:bnode1 "Barnum" . + _:bnode1 _:bnode2 . + _:bnode2 "Kellogg" . + _:bnode2 . + _:bnode0 . + ) + 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 + n3 = %( + @prefix a: . + + ("1" "2" "3") . + # This is not a statement. + () . + ) + nt = %( + _:bnode0 "1" . + _:bnode0 _:bnode1 . + _:bnode1 "2" . + _:bnode1 _:bnode2 . + _:bnode2 "3" . + _:bnode2 . + ) + 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, format: :n3) + end + + it "should parse with compound items" do + n3 = %( + @prefix a: . + a:a a:p ( + [ a:p2 "v1" ] + + + ("inner list") + ) . + a:p "value" . + ) + g = parse(n3, base_uri: "http://a/b") + expect(g.subjects.to_a.length).to eq 8 + n = g.first_object(subject: RDF::URI.new("http://foo/a#a"), predicate: RDF::URI.new("http://foo/a#p")) + expect(n).to be_node + seq = RDF::List.new(subject: n, graph: g) + expect(seq.to_a.length).to eq 4 + expect(seq.first).to be_node + expect(seq.second).to eq RDF::URI.new("http://resource1") + expect(seq.third).to eq RDF::URI.new("http://resource2") + expect(seq.fourth).to be_node + end + end + context "property paths" do { "subject x!p": [ @@ -834,7 +933,7 @@ }.each do |title, (n3, res)| it title do expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(res, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected,slogger: logger, format: :n3) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, logger: logger, format: :n3) end end end @@ -929,23 +1028,24 @@ @repo = RDF::Repository.new parse(n3, repo: @repo, base_uri: "http://a/b") end + subject {@repo} it "assumption graph has 2 statements" do - tt = @repo.first(subject: RDF::URI.new("http://a/b#assumption"), predicate: RDF::OWL.sameAs) + tt = subject.first(subject: RDF::URI.new("http://a/b#assumption"), predicate: RDF::OWL.sameAs) expect(tt.object).to be_node - expect(@repo.query({graph_name: tt.object}).to_a.length).to eq 2 + expect(subject.query({graph_name: tt.object}).to_a.length).to eq 2 end it "conclusion graph has 1 statements" do - tt = @repo.first(subject: RDF::URI.new("http://a/b#conclusion"), predicate: RDF::OWL.sameAs) + tt = subject.first(subject: RDF::URI.new("http://a/b#conclusion"), predicate: RDF::OWL.sameAs) expect(tt.object).to be_node - expect(@repo.query({graph_name: tt.object}).to_a.length).to eq 1 + expect(subject.query({graph_name: tt.object}).to_a.length).to eq 1 end it "trivialTruth equivalent to empty graph" do - tt = @repo.first(subject: RDF::URI.new("http://a/b#trivialTruth"), predicate: RDF::OWL.sameAs) + tt = subject.first(subject: RDF::URI.new("http://a/b#trivialTruth"), predicate: RDF::OWL.sameAs) expect(tt.object).to be_node - @repo.query({graph_name: tt.object}) do |s| + subject.query({graph_name: tt.object}) do |s| puts "statement: #{s}" end end @@ -962,106 +1062,6 @@ end end - describe "property lists" do - it "should parse property list" do - n3 = %( - @prefix a: . - - a:b a:p1 "123" ; a:p1 "456" . - a:b a:p2 a:v1 ; a:p3 a:v2 . - ) - nt = %( - "123" . - "456" . - . - . - ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) - end - end - - describe "lists" do - it "should parse empty list" do - n3 = %(@prefix :. :empty :set ().) - nt = %( - .) - 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 - n3 = %(@prefix :. :gregg :wrote ("RdfContext").) - nt = %( - _:bnode0 "RdfContext" . - _:bnode0 . - _:bnode0 . - ) - 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 - n3 = %(@prefix :. :gregg :name ("Gregg" "Barnum" "Kellogg").) - nt = %( - _:bnode0 "Gregg" . - _:bnode0 _:bnode1 . - _:bnode1 "Barnum" . - _:bnode1 _:bnode2 . - _:bnode2 "Kellogg" . - _:bnode2 . - _:bnode0 . - ) - 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 - n3 = %( - @prefix a: . - - ("1" "2" "3") . - # This is not a statement. - () . - ) - nt = %( - _:bnode0 "1" . - _:bnode0 _:bnode1 . - _:bnode1 "2" . - _:bnode1 _:bnode2 . - _:bnode2 "3" . - _:bnode2 . - ) - 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, format: :n3) - end - - it "should parse with compound items" do - n3 = %( - @prefix a: . - a:a a:p ( - [ a:p2 "v1" ] - - - ("inner list") - ) . - a:p "value" . - ) - g = parse(n3, base_uri: "http://a/b") - expect(g.subjects.to_a.length).to eq 8 - n = g.first_object(subject: RDF::URI.new("http://foo/a#a"), predicate: RDF::URI.new("http://foo/a#p")) - expect(n).to be_node - seq = RDF::List.new(subject: n, graph: g) - expect(seq.to_a.length).to eq 4 - expect(seq.first).to be_node - expect(seq.second).to eq RDF::URI.new("http://resource1") - 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) diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index 1df1813..d03e621 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -18,7 +18,17 @@ describe m.label do m.entries.each do |t| specify "#{t.name}: #{t.comment}" do - #case t.name + case t.name + when *%w(04test_metastaticP.n3) + skip("it was decided not to allow this") + when *%w(04test_icalQ002.n3 04test_query-survey-11.n3) + pending("datatype (^^) tag and @language for quick-vars") + when *%w(07test_utf8.n3) + pending("invalid byte sequence in UTF-8") + when *%(01etc_skos-extra-rules.n3 07test_pd_hes_theory.n3) + pending("@ keywords") + when *%w(cwm_syntax_neg-literal-predicate.n3) + pending("should be negative test") #when *%w(n3_10004 n3_10007 n3_10014 n3_10015 n3_10017) # pending("Reification not supported") #when *%w(n3_10013) @@ -27,9 +37,9 @@ # pending("Verified test results are incorrect") #when *%w(n3_10009 n3_10018 n3_20002) # skip("Not allowed with new grammar") - #when *%w(n3_10021) - # skip("stack overflow") - #end + when *%w(07test_bg-test-1000) + skip("stack overflow") + end t.logger = logger t.logger.info t.inspect @@ -67,7 +77,8 @@ elsif t.syntax? expect { repo << reader - repo.dump(:nquads).to produce("not this", t.logger) + require 'byebug'; byebug + expect(repo.count).to produce("not this", t) }.to raise_error(RDF::ReaderError) else expect(repo).not_to be_equivalent_graph(output_repo, t) From 2d4f2d0d8761087b50d784f22faa8a09161acb6c Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 26 Jul 2020 12:09:43 -0700 Subject: [PATCH 027/193] Remove unused PEG and Meta files. --- Rakefile | 12 +-- lib/rdf/n3.rb | 1 - lib/rdf/n3/meta.rb | 217 --------------------------------------------- 3 files changed, 1 insertion(+), 229 deletions(-) delete mode 100644 lib/rdf/n3/meta.rb diff --git a/Rakefile b/Rakefile index 491e5c0..22d0356 100644 --- a/Rakefile +++ b/Rakefile @@ -13,7 +13,7 @@ namespace :gem do end namespace :etc do - ETC_FILES = %w{etc/n3.sxp etc/n3.peg.sxp} + ETC_FILES = %w{etc/n3.sxp} desc 'Remove generated files in etc' task :clean do %x(rm #{ETC_FILES.join(' ')}) @@ -26,13 +26,3 @@ end file "etc/n3.sxp" => "etc/n3.ebnf" do |t| %x{ebnf -o #{t.name} #{t.source}} end - -file "etc/n3.peg.sxp" => "etc/n3.ebnf" do |t| - %x{ebnf --peg -o #{t.name} #{t.source}} -end - -task :meta => %w{lib/rdf/n3/meta.rb} - -file "lib/rdf/n3/meta.rb" => "etc/n3.ebnf" do - %x(ebnf --peg -f rb --mod-name RDF::N3::Meta -o lib/rdf/n3/meta.rb etc/n3.ebnf) -end diff --git a/lib/rdf/n3.rb b/lib/rdf/n3.rb index b2037e8..23b7bd3 100644 --- a/lib/rdf/n3.rb +++ b/lib/rdf/n3.rb @@ -24,7 +24,6 @@ module N3 require 'rdf/n3/vocab' require 'rdf/n3/extensions' require 'rdf/n3/refinements' - autoload :Meta, 'rdf/n3/meta' autoload :Reader, 'rdf/n3/reader' autoload :Reasoner, 'rdf/n3/reasoner' autoload :Terminals, 'rdf/n3/terminals' diff --git a/lib/rdf/n3/meta.rb b/lib/rdf/n3/meta.rb deleted file mode 100644 index 706282a..0000000 --- a/lib/rdf/n3/meta.rb +++ /dev/null @@ -1,217 +0,0 @@ -# This file is automatically generated by ebnf version 2.1.0 -# Derived from etc/n3.ebnf -module RDF::N3::Meta - RULES = [ - EBNF::Rule.new(:n3Doc, "1", [:star, :_n3Doc_1]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_n3Doc_1, "1.1", [:alt, :_n3Doc_2, :sparqlDirective]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_n3Doc_2, "1.2", [:seq, :n3Statement, "."]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:n3Statement, "2", [:alt, :n3Directive, :triples, :existential, :universal]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:n3Directive, "3", [:alt, :prefixID, :base]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:sparqlDirective, "4", [:alt, :sparqlBase, :sparqlPrefix]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:sparqlBase, "5", [:seq, :BASE, :IRIREF]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:sparqlPrefix, "6", [:seq, :PREFIX, :PNAME_NS, :IRIREF]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:prefixID, "7", [:seq, "@prefix", :PNAME_NS, :IRIREF]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:base, "8", [:seq, "@base", :IRIREF]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:triples, "9", [:seq, :subject, :_triples_1]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_triples_1, "9.1", [:opt, :predicateObjectList]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:predicateObjectList, "10", [:seq, :verb, :objectList, :_predicateObjectList_1]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_predicateObjectList_1, "10.1", [:star, :_predicateObjectList_2]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_predicateObjectList_2, "10.2", [:seq, ";", :_predicateObjectList_3]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_predicateObjectList_3, "10.3", [:opt, :_predicateObjectList_4]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_predicateObjectList_4, "10.4", [:seq, :verb, :objectList]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:objectList, "11", [:seq, :object, :_objectList_1]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_objectList_1, "11.1", [:star, :_objectList_2]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_objectList_2, "11.2", [:seq, ",", :object]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:verb, "12", [:alt, :predicate, "a", :_verb_1, :_verb_2, :_verb_3, "<=", "=>", "="]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_verb_1, "12.1", [:seq, "has", :expression]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_verb_2, "12.2", [:seq, "is", :expression, "of"]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_verb_3, "12.3", [:seq, "<-", :expression]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:subject, "13", [:seq, :expression]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:predicate, "14", [:seq, :expression]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:object, "15", [:seq, :expression]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:expression, "16", [:seq, :path]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:path, "17", [:seq, :pathItem, :_path_1]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_path_1, "17.1", [:opt, :_path_2]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_path_2, "17.2", [:alt, :_path_3, :_path_4]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_path_3, "17.3", [:seq, "!", :path]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_path_4, "17.4", [:seq, "^", :path]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:pathItem, "18", [:alt, :iri, :blankNode, :quickVar, :collection, :blankNodePropertyList, :literal, :formula]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:literal, "19", [:alt, :rdfLiteral, :numericLiteral, :BOOLEAN_LITERAL]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:blankNodePropertyList, "20", [:seq, "[", :predicateObjectList, "]"]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:collection, "21", [:seq, "(", :_collection_1, ")"]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_collection_1, "21.1", [:star, :object]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:formula, "22", [:seq, "{", :_formula_1, "}"]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_formula_1, "22.1", [:opt, :formulaContent]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:formulaContent, "23", [:alt, :_formulaContent_1, :_formulaContent_2]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_formulaContent_1, "23.1", [:seq, :n3Statement, :_formulaContent_3]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_formulaContent_3, "23.3", [:opt, :_formulaContent_4]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_formulaContent_4, "23.4", [:seq, ".", :_formulaContent_5]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_formulaContent_5, "23.5", [:opt, :formulaContent]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_formulaContent_2, "23.2", [:seq, :sparqlDirective, :_formulaContent_6]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_formulaContent_6, "23.6", [:opt, :formulaContent]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:numericLiteral, "24", [:alt, :DOUBLE, :DECIMAL, :INTEGER]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:rdfLiteral, "25", [:seq, :STRING, :_rdfLiteral_1]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_rdfLiteral_1, "25.1", [:opt, :_rdfLiteral_2]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_rdfLiteral_2, "25.2", [:alt, :LANGTAG, :_rdfLiteral_3]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_rdfLiteral_3, "25.3", [:seq, "^^", :iri]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:iri, "26", [:alt, :IRIREF, :prefixedName]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:iriList, "27", [:seq, :iri, :_iriList_1]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_iriList_1, "27.1", [:star, :_iriList_2]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_iriList_2, "27.2", [:seq, ",", :iri]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:prefixedName, "28", [:alt, :PNAME_LN, :PNAME_NS]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:blankNode, "29", [:alt, :BLANK_NODE_LABEL, :ANON]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:quickVar, "30", [:seq, :QUICK_VAR_NAME]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:existential, "31", [:seq, "@forSome", :iriList]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:universal, "32", [:seq, "@forAll", :iriList]).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_terminals, nil, [:seq], kind: :terminals).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:BOOLEAN_LITERAL, "33", [:alt, "true", "false"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:STRING, "34", [:alt, :STRING_LITERAL_LONG_SINGLE_QUOTE, :STRING_LITERAL_LONG_QUOTE, :STRING_LITERAL_QUOTE, :STRING_LITERAL_SINGLE_QUOTE], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:IRIREF, "139s", [:seq, "<", :_IRIREF_1, ">"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_IRIREF_1, "139s.1", [:star, :_IRIREF_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_IRIREF_2, "139s.2", [:alt, :_IRIREF_3, :UCHAR, :WS], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_IRIREF_3, "139s.3", [:diff, :_IRIREF_4, :_IRIREF_5], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_IRIREF_4, "139s.4", [:range, "^<>\"{}|^`\\"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_IRIREF_5, "139s.5", [:range, "#x00-#x20"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:PNAME_NS, "140s", [:seq, :_PNAME_NS_1, ":"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PNAME_NS_1, "140s.1", [:opt, :PN_PREFIX], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:PNAME_LN, "141s", [:seq, :PNAME_NS, :PN_LOCAL], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:BLANK_NODE_LABEL, "142s", [:seq, "_:", :_BLANK_NODE_LABEL_1, :_BLANK_NODE_LABEL_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_BLANK_NODE_LABEL_1, "142s.1", [:alt, :PN_CHARS_U, :_BLANK_NODE_LABEL_3], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_BLANK_NODE_LABEL_3, "142s.3", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_BLANK_NODE_LABEL_2, "142s.2", [:opt, :_BLANK_NODE_LABEL_4], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_BLANK_NODE_LABEL_4, "142s.4", [:seq, :_BLANK_NODE_LABEL_5, :PN_CHARS], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_BLANK_NODE_LABEL_5, "142s.5", [:star, :_BLANK_NODE_LABEL_6], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_BLANK_NODE_LABEL_6, "142s.6", [:alt, :PN_CHARS, "."], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:LANGTAG, "145s", [:seq, "@", :_LANGTAG_1], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_LANGTAG_1, "145s.1", [:diff, :_LANGTAG_2, :_LANGTAG_3], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_LANGTAG_2, "145s.2", [:seq, :_LANGTAG_4, :_LANGTAG_5], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_LANGTAG_4, "145s.4", [:plus, :_LANGTAG_6], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_LANGTAG_6, "145s.6", [:range, "a-zA-Z"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_LANGTAG_5, "145s.5", [:star, :_LANGTAG_7], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_LANGTAG_7, "145s.7", [:seq, "-", :_LANGTAG_8], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_LANGTAG_8, "145s.8", [:plus, :_LANGTAG_9], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_LANGTAG_9, "145s.9", [:range, "a-zA-Z0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_LANGTAG_3, "145s.3", [:alt, "is", "has"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:INTEGER, "146s", [:plus, :_INTEGER_1], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_INTEGER_1, "146s.1", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:DECIMAL, "147s", [:seq, :_DECIMAL_1, ".", :_DECIMAL_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DECIMAL_1, "147s.1", [:star, :_DECIMAL_3], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DECIMAL_3, "147s.3", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DECIMAL_2, "147s.2", [:plus, :_DECIMAL_4], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DECIMAL_4, "147s.4", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:DOUBLE, "148s", [:alt, :_DOUBLE_1, :_DOUBLE_2, :_DOUBLE_3], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DOUBLE_1, "148s.1", [:seq, :_DOUBLE_4, ".", :_DOUBLE_5, :EXPONENT], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DOUBLE_4, "148s.4", [:plus, :_DOUBLE_6], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DOUBLE_6, "148s.6", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DOUBLE_5, "148s.5", [:star, :_DOUBLE_7], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DOUBLE_7, "148s.7", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DOUBLE_2, "148s.2", [:seq, ".", :_DOUBLE_8, :EXPONENT], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DOUBLE_8, "148s.8", [:plus, :_DOUBLE_9], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DOUBLE_9, "148s.9", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DOUBLE_3, "148s.3", [:seq, :_DOUBLE_10, :EXPONENT], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DOUBLE_10, "148s.10", [:plus, :_DOUBLE_11], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_DOUBLE_11, "148s.11", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:EXPONENT, "155s", [:seq, :_EXPONENT_1, :_EXPONENT_2, :_EXPONENT_3], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_EXPONENT_1, "155s.1", [:range, "eE"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_EXPONENT_2, "155s.2", [:opt, :_EXPONENT_4], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_EXPONENT_4, "155s.4", [:range, "+-"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_EXPONENT_3, "155s.3", [:plus, :_EXPONENT_5], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_EXPONENT_5, "155s.5", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:STRING_LITERAL_QUOTE, "156s", [:seq, "\"", :_STRING_LITERAL_QUOTE_1, "\""], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_QUOTE_1, "156s.1", [:star, :_STRING_LITERAL_QUOTE_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_QUOTE_2, "156s.2", [:alt, :_STRING_LITERAL_QUOTE_3, :ECHAR, :UCHAR], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_QUOTE_3, "156s.3", [:range, "^#x22#x5C#xA#xD"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:STRING_LITERAL_SINGLE_QUOTE, "157s", [:seq, "'", :_STRING_LITERAL_SINGLE_QUOTE_1, "'"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_SINGLE_QUOTE_1, "157s.1", [:star, :_STRING_LITERAL_SINGLE_QUOTE_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_SINGLE_QUOTE_2, "157s.2", [:alt, :_STRING_LITERAL_SINGLE_QUOTE_3, :ECHAR, :UCHAR], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_SINGLE_QUOTE_3, "157s.3", [:range, "^#x27#x5C#xA#xD"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:STRING_LITERAL_LONG_SINGLE_QUOTE, "158s", [:seq, "'''", :_STRING_LITERAL_LONG_SINGLE_QUOTE_1, "'''"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_1, "158s.1", [:star, :_STRING_LITERAL_LONG_SINGLE_QUOTE_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_2, "158s.2", [:seq, :_STRING_LITERAL_LONG_SINGLE_QUOTE_3, :_STRING_LITERAL_LONG_SINGLE_QUOTE_4], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_3, "158s.3", [:opt, :_STRING_LITERAL_LONG_SINGLE_QUOTE_5], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_5, "158s.5", [:alt, "'", "''"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_4, "158s.4", [:alt, :_STRING_LITERAL_LONG_SINGLE_QUOTE_6, :ECHAR, :UCHAR], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_SINGLE_QUOTE_6, "158s.6", [:range, "^'\\"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:STRING_LITERAL_LONG_QUOTE, "159s", [:seq, "\"\"\"", :_STRING_LITERAL_LONG_QUOTE_1, "\"\"\""], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_1, "159s.1", [:star, :_STRING_LITERAL_LONG_QUOTE_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_2, "159s.2", [:seq, :_STRING_LITERAL_LONG_QUOTE_3, :_STRING_LITERAL_LONG_QUOTE_4], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_3, "159s.3", [:opt, :_STRING_LITERAL_LONG_QUOTE_5], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_5, "159s.5", [:alt, "\"", "\"\""], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_4, "159s.4", [:alt, :_STRING_LITERAL_LONG_QUOTE_6, :ECHAR, :UCHAR], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_STRING_LITERAL_LONG_QUOTE_6, "159s.6", [:range, "^\"\\"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:UCHAR, "35", [:alt, :_UCHAR_1, :_UCHAR_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_UCHAR_1, "35.1", [:seq, "\\u", :HEX, :HEX, :HEX, :HEX], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_UCHAR_2, "35.2", [:seq, "\\U", :HEX, :HEX, :HEX, :HEX, :HEX, :HEX, :HEX, :HEX], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:ECHAR, "160s", [:seq, "\\", :_ECHAR_1], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_ECHAR_1, "160s.1", [:range, "tbnrf\\\"'"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:WS, "162s", [:alt, :_WS_1, :_WS_2, :_WS_3, :_WS_4], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_WS_1, "162s.1", [:hex, "#x20"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_WS_2, "162s.2", [:hex, "#x9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_WS_3, "162s.3", [:hex, "#xD"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_WS_4, "162s.4", [:hex, "#xA"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:ANON, "163s", [:seq, "[", :_ANON_1, "]"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_ANON_1, "163s.1", [:star, :WS], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:QUICK_VAR_NAME, "36", [:seq, "?", :PN_LOCAL], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:PN_CHARS_BASE, "164s", [:alt, :_PN_CHARS_BASE_1, :_PN_CHARS_BASE_2, :_PN_CHARS_BASE_3, :_PN_CHARS_BASE_4, :_PN_CHARS_BASE_5, :_PN_CHARS_BASE_6, :_PN_CHARS_BASE_7, :_PN_CHARS_BASE_8, :_PN_CHARS_BASE_9, :_PN_CHARS_BASE_10, :_PN_CHARS_BASE_11, :_PN_CHARS_BASE_12, :_PN_CHARS_BASE_13, :_PN_CHARS_BASE_14], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_1, "164s.1", [:range, "A-Z"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_2, "164s.2", [:range, "a-z"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_3, "164s.3", [:range, "#x00C0-#x00D6"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_4, "164s.4", [:range, "#x00D8-#x00F6"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_5, "164s.5", [:range, "#x00F8-#x02FF"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_6, "164s.6", [:range, "#x0370-#x037D"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_7, "164s.7", [:range, "#x037F-#x1FFF"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_8, "164s.8", [:range, "#x200C-#x200D"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_9, "164s.9", [:range, "#x2070-#x218F"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_10, "164s.10", [:range, "#x2C00-#x2FEF"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_11, "164s.11", [:range, "#x3001-#xD7FF"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_12, "164s.12", [:range, "#xF900-#xFDCF"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_13, "164s.13", [:range, "#xFDF0-#xFFFD"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_BASE_14, "164s.14", [:range, "#x10000-#xEFFFF"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:PN_CHARS_U, "165s", [:alt, :PN_CHARS_BASE, "_"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:PN_CHARS, "167s", [:alt, :PN_CHARS_U, "-", :_PN_CHARS_1, :_PN_CHARS_2, :_PN_CHARS_3, :_PN_CHARS_4], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_1, "167s.1", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_2, "167s.2", [:hex, "#x00B7"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_3, "167s.3", [:range, "#x0300-#x036F"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_CHARS_4, "167s.4", [:range, "#x203F-#x2040"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:BASE, "37", [:seq, :_BASE_1, :_BASE_2, :_BASE_3, :_BASE_4], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_BASE_1, "37.1", [:alt, "B", "b"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_BASE_2, "37.2", [:alt, "A", "a"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_BASE_3, "37.3", [:alt, "S", "s"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_BASE_4, "37.4", [:alt, "E", "e"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:PREFIX, "38", [:seq, :_PREFIX_1, :_PREFIX_2, :_PREFIX_3, :_PREFIX_4, :_PREFIX_5, :_PREFIX_6], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PREFIX_1, "38.1", [:alt, "P", "p"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PREFIX_2, "38.2", [:alt, "R", "r"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PREFIX_3, "38.3", [:alt, "E", "e"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PREFIX_4, "38.4", [:alt, "F", "f"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PREFIX_5, "38.5", [:alt, "I", "i"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PREFIX_6, "38.6", [:alt, "X", "x"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:PN_PREFIX, "168s", [:seq, :PN_CHARS_BASE, :_PN_PREFIX_1], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_PREFIX_1, "168s.1", [:opt, :_PN_PREFIX_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_PREFIX_2, "168s.2", [:seq, :_PN_PREFIX_3, :PN_CHARS], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_PREFIX_3, "168s.3", [:star, :_PN_PREFIX_4], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_PREFIX_4, "168s.4", [:alt, :PN_CHARS, "."], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:PN_LOCAL, "169s", [:seq, :_PN_LOCAL_1, :_PN_LOCAL_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_LOCAL_1, "169s.1", [:alt, :PN_CHARS_U, ":", :_PN_LOCAL_3, :PLX], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_LOCAL_3, "169s.3", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_LOCAL_2, "169s.2", [:opt, :_PN_LOCAL_4], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_LOCAL_4, "169s.4", [:seq, :_PN_LOCAL_5, :_PN_LOCAL_6], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_LOCAL_5, "169s.5", [:star, :_PN_LOCAL_7], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_LOCAL_7, "169s.7", [:alt, :PN_CHARS, ".", ":", :PLX], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_LOCAL_6, "169s.6", [:alt, :PN_CHARS, ":", :PLX], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:PLX, "170s", [:alt, :PERCENT, :PN_LOCAL_ESC], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:PERCENT, "171s", [:seq, "%", :HEX, :HEX], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:HEX, "172s", [:alt, :_HEX_1, :_HEX_2, :_HEX_3], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_HEX_1, "172s.1", [:range, "0-9"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_HEX_2, "172s.2", [:range, "A-F"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_HEX_3, "172s.3", [:range, "a-f"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:PN_LOCAL_ESC, "173s", [:seq, "\\", :_PN_LOCAL_ESC_1], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_PN_LOCAL_ESC_1, "173s.1", [:alt, "_", "~", ".", "-", "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=", "/", "?", "#", "@", "%"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:COMMENT, "39", [:seq, :_COMMENT_1, :_COMMENT_2], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_COMMENT_1, "39.1", [:diff, "#", "#x"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_COMMENT_2, "39.2", [:star, :_COMMENT_3], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_COMMENT_3, "39.3", [:range, "^#xA#xC#xD"], kind: :terminal).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:_pass, nil, [:star, :__pass_1], kind: :pass).extend(EBNF::PEG::Rule), - EBNF::Rule.new(:__pass_1, nil, [:alt, :WS, :COMMENT]).extend(EBNF::PEG::Rule), - ] -end - From dbf6d9f14d2067308b15b314483aca101ffeb98d Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 26 Jul 2020 12:10:07 -0700 Subject: [PATCH 028/193] Simplify `ANON` terminal, which was causing the Scanner to hang. --- lib/rdf/n3/terminals.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdf/n3/terminals.rb b/lib/rdf/n3/terminals.rb index 5497eea..7eaf727 100644 --- a/lib/rdf/n3/terminals.rb +++ b/lib/rdf/n3/terminals.rb @@ -73,7 +73,7 @@ module Terminals # 161s WS = /(?:\s|(?:#[^\n\r]*))+/um.freeze # 162s - ANON = /\[#{WS}*\]/um.freeze + ANON = /\[\s*\]/u.freeze FORALL = /@forAll/u.freeze end From 990d84756e4204e3d409cc1632f69aaae30b45e7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 26 Jul 2020 12:10:30 -0700 Subject: [PATCH 029/193] Improve diagnostic output. --- lib/rdf/n3/reader.rb | 34 +++++++++++++++++++++++++++++----- script/tc | 2 +- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index d7feb2f..97bfa77 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -491,7 +491,7 @@ def read_blankNodePropertyList if token === '[' prod(:blankNodePropertyList, %{]}) do @lexer.shift - log_info("blankNodePropertyList", depth: options[:depth]) {"token: #{token.inspect}"} + progress("blankNodePropertyList", depth: options[:depth], token: token) node = bnode read_predicateObjectList(node) error("blankNodePropertyList", "Expected closing ']'") unless @lexer.first === ']' @@ -512,7 +512,7 @@ def read_collection prod(:collection, %{)}) do @lexer.shift token = @lexer.first - log_info("collection", depth: options[:depth]) {"token: #{token.inspect}"} + progress("collection", depth: options[:depth]) {"token: #{token.inspect}"} objects = [] while @lexer.first.value != ')' && (object = read_path) objects << object @@ -844,7 +844,7 @@ def add_var_to_formula(bn, name, var) def prod(production, recover_to = []) @prod_stack << {prod: production, recover_to: recover_to} @options[:depth] += 1 - log_recover("#{production}(start)", depth: options[:depth]) {"token: #{@lexer.first.inspect}"} + recover("#{production}(start)", depth: options[:depth], token: @lexer.first) yield rescue EBNF::LL1::Lexer::Error, SyntaxError, Recovery => e # Lexer encountered an illegal token or the parser encountered @@ -890,11 +890,35 @@ def prod(production, recover_to = []) # Skip that token to get something reasonable to start the next production with @lexer.shift ensure - log_info("#{production}(finish)", depth: options[:depth]) + progress("#{production}(finish)", depth: options[:depth]) @options[:depth] -= 1 @prod_stack.pop end + def progress(*args, &block) + lineno = (options[:token].lineno if options[:token].respond_to?(:lineno)) || (@lexer && @lexer.lineno) + opts = args.last.is_a?(Hash) ? args.pop : {} + opts[:level] ||= 1 + opts[:lineno] ||= lineno + log_info(*args, **opts, &block) + end + + def recover(*args, &block) + lineno = (options[:token].lineno if options[:token].respond_to?(:lineno)) || (@lexer && @lexer.lineno) + opts = args.last.is_a?(Hash) ? args.pop : {} + opts[:level] ||= 1 + opts[:lineno] ||= lineno + log_recover(*args, **opts, &block) + end + + def debug(*args, &block) + lineno = (options[:token].lineno if options[:token].respond_to?(:lineno)) || (@lexer && @lexer.lineno) + opts = args.last.is_a?(Hash) ? args.pop : {} + opts[:level] ||= 0 + opts[:lineno] ||= lineno + log_debug(*args, **opts, &block) + end + ## # Error information, used as level `0` debug messages. # @@ -909,7 +933,7 @@ def error(*args) ctx = "" ctx += "(found #{options[:token].inspect})" if options[:token] ctx += ", production = #{options[:production].inspect}" if options[:production] - lineno = (options[:token].lineno if options[:token].respond_to?(:lineno)) || @lexer.lineno + lineno = (options[:token].lineno if options[:token].respond_to?(:lineno)) || (@lexer && @lexer.lineno) log_error(*args, ctx, lineno: lineno, token: options[:token], diff --git a/script/tc b/script/tc index b2dd006..7b04c0b 100755 --- a/script/tc +++ b/script/tc @@ -47,7 +47,7 @@ def run_tc(man, tc, **options) logger = options[:live] ? Logger.new(STDERR) : RDF::Spec.logger logger.level = options[:level] - logger.formatter = lambda {|severity, datetime, progname, msg| "#{severity}: #{msg}\n"} + logger.formatter = lambda {|severity, datetime, progname, msg| "%5s %s\n" % [severity, msg]} start = Time.now From dea40a0df50c081d0ea6ba210bf8726ca4e079fe Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 26 Jul 2020 14:59:35 -0700 Subject: [PATCH 030/193] Allow empty formulae. --- lib/rdf/n3/reader.rb | 4 +++- spec/reader_spec.rb | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 97bfa77..ec9f2b8 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -569,9 +569,11 @@ def read_formula # # @return [void] def read_formulaContent + return if @lexer.first === '}' # Allow empty formula prod(:formulaContent, %w(. })) do loop do - error("read_formulaContent", "Unexpected end of file") unless token = @lexer.first + token = @lexer.first + error("read_formulaContent", "Unexpected end of file") unless token case token.type when :BASE, :PREFIX read_directive || error("Failed to parse directive", production: :directive, token: token) diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 30c069a..9b00300 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -1005,6 +1005,30 @@ expect(variables.uniq.count).to produce(1, logger) end + { + "empty subject" => { + input: %({} .), + expect: %({} .) + }, + "empty object" => { + input: %( {} .), + expect: %( {} .) + }, + "as subject with constant content" => { + input: %({ } .), + expect: %({ } .), + }, + "as object with constant content" => { + input: %( { } .), + expect: %( { } .), + }, + }.each do |name, params| + it name do + result = parse(params[:expect], base_uri: "http://a/b", logger: false) + expect(parse(params[:input], base_uri: "http://a/b")).to be_equivalent_graph(result, logger: logger, format: :n3) + end + end + context "contexts" do before(:each) do n3 = %( From e11deb833ad01761a78e0890567ff430f2a78705 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 26 Jul 2020 15:00:39 -0700 Subject: [PATCH 031/193] Skip grammar tests other than cwm_ for speed. --- spec/suite_helper.rb | 6 +++--- spec/suite_parser_spec.rb | 30 +++++++++++------------------- spec/suite_reasoner_spec.rb | 4 ++-- spec/writer_spec.rb | 3 ++- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 53b51fe..a974d65 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -143,11 +143,11 @@ def expected end def positive_test? - attributes['@type'].to_s.match(/N3Positive/) || attributes['@type'].to_s.match(/N3Eval/) + attributes['@type'].to_s.match?(/N3Positive|N3Eval|N3Reason/) end def negative_test? - attributes['@type'].to_s.match(/N3Negative/) + attributes['@type'].to_s.match?(/N3Negative/) end def evaluate? @@ -163,7 +163,7 @@ def syntax? end def inspect - super.sub('>', "\n" + + super.sub(/>$/, "\n" + " positive?: #{positive_test?.inspect}\n" + " syntax?: #{syntax?.inspect}\n" + " eval?: #{evaluate?.inspect}\n" + diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index d03e621..fda7e75 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -17,27 +17,19 @@ Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/grammar/tests/N3Tests/manifest.ttl") do |m| describe m.label do m.entries.each do |t| + # Skip non-cwm entries, for now + next unless t.name.start_with?('cwm') specify "#{t.name}: #{t.comment}" do case t.name - when *%w(04test_metastaticP.n3) - skip("it was decided not to allow this") - when *%w(04test_icalQ002.n3 04test_query-survey-11.n3) - pending("datatype (^^) tag and @language for quick-vars") - when *%w(07test_utf8.n3) - pending("invalid byte sequence in UTF-8") - when *%(01etc_skos-extra-rules.n3 07test_pd_hes_theory.n3) - pending("@ keywords") - when *%w(cwm_syntax_neg-literal-predicate.n3) - pending("should be negative test") - #when *%w(n3_10004 n3_10007 n3_10014 n3_10015 n3_10017) - # pending("Reification not supported") - #when *%w(n3_10013) - # pending("numeric representation") - #when *%w(n3_10003 n3_10006) - # pending("Verified test results are incorrect") - #when *%w(n3_10009 n3_10018 n3_20002) - # skip("Not allowed with new grammar") - when *%w(07test_bg-test-1000) + #when *%w(04test_metastaticP.n3) + # skip("it was decided not to allow this") + #when *%w(04test_icalQ002.n3 04test_query-survey-11.n3) + # pending("datatype (^^) tag and @language for quick-vars") + when *%w(cwm_math_trigo-test.n3) + pending("dependency on prefixes") + when *%w(cwm_syntax_numbers.n3) + pending("number representation") + when *%w(cwm_syntax_too-nested.n3) skip("stack overflow") end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 38bb922..9350bf3 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -74,8 +74,8 @@ end else expect { - graph << reader - expect(graph.dump(:nquads)).to produce("not this", t) + repo << reader + expect(repo.dump(:nquads)).to produce("not this", t) }.to raise_error(RDF::ReaderError) end end diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index bae1deb..adcdc10 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -621,9 +621,10 @@ 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| + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/grammar/tests/N3Tests/manifest.ttl") do |m| describe m.comment do m.entries.each do |t| + next unless t.name.start_with?('cwm') # no horror shows right now. next unless t.positive_test? && t.evaluate? specify "#{t.name}: #{t.comment} (action)" do case t.name From f9b133ff8e03a83e663882db8cb9dfecbe8b339f Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 26 Jul 2020 15:06:48 -0700 Subject: [PATCH 032/193] Allow triples to have a subject without a predicateObjectList. --- lib/rdf/n3/reader.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index ec9f2b8..d48e8ec 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -288,16 +288,15 @@ def read_directive def read_triples prod(:triples, %w{.}) do error("read_triples", "Unexpected end of file") unless token = @lexer.first - case token.type || token.value + subject = case token.type || token.value when '[' # blankNodePropertyList predicateObjectList? - subject = read_blankNodePropertyList || error("Failed to parse blankNodePropertyList", production: :triples, token: @lexer.first) - read_predicateObjectList(subject) || subject + read_blankNodePropertyList || error("Failed to parse blankNodePropertyList", production: :triples, token: @lexer.first) else # subject predicateObjectList - subject = read_path || error("Failed to parse subject", production: :triples, token: @lexer.first) - read_predicateObjectList(subject) || error("Expected predicateObjectList", production: :triples, token: @lexer.first) + read_path || error("Failed to parse subject", production: :triples, token: @lexer.first) end + read_predicateObjectList(subject) || subject end end From d8091f26f752faf6d0201cffbeca84a4a8eda494 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 27 Jul 2020 17:11:16 -0700 Subject: [PATCH 033/193] Check for missing prefix. --- lib/rdf/n3/reader.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index d48e8ec..61f6cc6 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -728,6 +728,9 @@ def process_pname(value) iri = if prefix(prefix) #debug('process_pname(ns)', depth: @options[:depth]) {"#{prefix(prefix)}, #{name}"} ns(prefix, name) + elsif prefix && !prefix.empty? + error("process_pname", "Use of undefined prefix #{prefix.inspect}") + ns(nil, name) else #debug('process_pname(default_ns)', name, depth: @options[:depth]) namespace(nil, iri("#{base_uri}#")) unless prefix(nil) From 865f50ffe5d4bacd6fa9373b6fb193d2ad30c010 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 27 Jul 2020 17:11:27 -0700 Subject: [PATCH 034/193] Update test suite locations. --- script/tc | 9 ++++++--- spec/reader_spec.rb | 2 +- spec/suite_parser_spec.rb | 13 +++---------- spec/suite_reasoner_spec.rb | 3 ++- spec/suite_turtle_spec.rb | 3 ++- spec/writer_spec.rb | 38 +++++++++++++++++-------------------- 6 files changed, 31 insertions(+), 37 deletions(-) diff --git a/script/tc b/script/tc index 7b04c0b..d7661cb 100755 --- a/script/tc +++ b/script/tc @@ -45,6 +45,8 @@ def run_tc(man, tc, **options) STDERR.puts "\nExpected:\n" + tc.result if tc.result && tc.positiveTest? end + return if tc.approval == "rdft:Rejected" + logger = options[:live] ? Logger.new(STDERR) : RDF::Spec.logger logger.level = options[:level] logger.formatter = lambda {|severity, datetime, progname, msg| "%5s %s\n" % [severity, msg]} @@ -183,9 +185,10 @@ opts.each do |opt, arg| end manifests = %w( - grammar/tests/N3Tests/manifest.ttl - grammar/tests/TurtleTests/manifest.ttl - tests/manifest-reasoner.n3 + tests/N3Tests/manifest.ttl + tests/TurtleTests/manifest.ttl + tests/N3Tests/manifest-reasoner.ttl + tests/N3Tests/manifest-extended.ttl ).map {|r| "https://w3c.github.io/N3/#{r}"} earl_preamble(**options) if options[:earl] diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 9b00300..1740443 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -889,7 +889,7 @@ %(_:bnode0 :y2 :x2 . _:bnode0 :p2 "3" .) ], "alberts mother inverse of metor to auntieAnne": [ - %(:albert!fam:mother :mentor!:inverse :auntieAnne .), + %(:albert!:mother :mentor!:inverse :auntieAnne .), %( :albert :mother _:bnode0 . _:bnode0 _:pred0 :auntieAnne . diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index fda7e75..9ecaefa 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -14,25 +14,19 @@ require_relative 'suite_helper' - Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/grammar/tests/N3Tests/manifest.ttl") do |m| + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/tests/N3Tests/manifest.ttl") do |m| describe m.label do m.entries.each do |t| - # Skip non-cwm entries, for now - next unless t.name.start_with?('cwm') + next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do case t.name - #when *%w(04test_metastaticP.n3) - # skip("it was decided not to allow this") - #when *%w(04test_icalQ002.n3 04test_query-survey-11.n3) - # pending("datatype (^^) tag and @language for quick-vars") - when *%w(cwm_math_trigo-test.n3) - pending("dependency on prefixes") when *%w(cwm_syntax_numbers.n3) pending("number representation") when *%w(cwm_syntax_too-nested.n3) skip("stack overflow") end + t.logger = logger t.logger.info t.inspect t.logger.info "source:\n#{t.input}" @@ -69,7 +63,6 @@ elsif t.syntax? expect { repo << reader - require 'byebug'; byebug expect(repo.count).to produce("not this", t) }.to raise_error(RDF::ReaderError) else diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 9350bf3..253c032 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -13,9 +13,10 @@ !example.exception.is_a?(RSpec::Expectations::ExpectationNotMetError) end - Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/tests/manifest-reasoner.n3") do |m| + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/tests/N3Tests/manifest-reasoner.ttl") do |m| describe m.label do m.entries.each do |t| + next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do case t.id.split('#').last when *%w{listin bnode concat t2006} diff --git a/spec/suite_turtle_spec.rb b/spec/suite_turtle_spec.rb index fae3fec..5f8392e 100644 --- a/spec/suite_turtle_spec.rb +++ b/spec/suite_turtle_spec.rb @@ -6,9 +6,10 @@ describe "w3c turtle tests" do require 'suite_helper' - Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/grammar/tests/TurtleTests/manifest.ttl") do |m| + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/tests/TurtleTests/manifest.ttl") do |m| describe m.comment do m.entries.each do |t| + next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do t.logger = RDF::Spec.logger t.logger.info t.inspect diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index adcdc10..73cf84e 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -624,47 +624,43 @@ Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/grammar/tests/N3Tests/manifest.ttl") do |m| describe m.comment do m.entries.each do |t| - next unless t.name.start_with?('cwm') # no horror shows right now. - next unless t.positive_test? && t.evaluate? + next if t.negative_test? || t.rejected? 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_10017) + when *%w(cwm_syntax_path2.n3) pending "Investigate" - when *%w(n3_10013) + when *%w(cwm_syntax_numbers.n3) pending "Number syntax" - when *%w(n3_10009 n3_10018 n3_20002) - pending("Not allowed with new grammar") - when *%w(n3_10021) - pending("stack overflow") + when *%w(cwm_syntax_too-nested.n3) + skip("stack overflow") + when *%w(manifest.ttl) + skip("too long") end + logger.info t.inspect logger.info "source: #{t.input}" - repo = parse(t.input, base_uri: t.base) + repo = parse(t.input, base_uri: t.base, logger: false) n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true, logger: logger) logger.info "serialized: #{n3}" g2 = parse(n3, base_uri: t.base, logger: logger) expect(g2).to be_equivalent_graph(repo, logger: logger) end + next if t.syntax? || t.reason? 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_10017) + when *%w(cwm_syntax_path2.n3) pending "Investigate" - when *%w(n3_10013) + when *%w(cwm_syntax_numbers.n3) pending "Number syntax" - when *%w(n3_10009 n3_20002) - pending("Not allowed with new grammar") - when *%w(n3_10021) - pending("stack overflow") + when *%w(cwm_syntax_too-nested.n3) + skip("stack overflow") 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) + repo = parse(t.expected, base_uri: t.base, format: format, logger: false) n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true, logger: logger) logger.info "serialized: #{n3}" g2 = parse(n3, base_uri: t.base, logger: logger) @@ -685,7 +681,7 @@ def parse(input, format: :n3, **options) # 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: [], format: options.fetch(:input_format, :n3)) + g = ntstr.is_a?(RDF::Enumerable) ? ntstr : parse(ntstr, base_uri: base_uri, prefixes: prefixes, validate: false, logger: false, 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 772ec38b2aa40886df08d1d179f8ba6ce457d434 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 29 Jul 2020 12:08:18 -0700 Subject: [PATCH 035/193] Don't assume NTriples format for test results in script/tc. --- script/tc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/tc b/script/tc index d7661cb..6377c07 100755 --- a/script/tc +++ b/script/tc @@ -87,7 +87,7 @@ def run_tc(man, tc, **options) if tc.evaluate? && result.nil? begin - output_graph = RDF::Repository.load(tc.result, format: :ntriples, base_uri: tc.base) + output_graph = RDF::Repository.load(tc.result, base_uri: tc.base) result = graph.isomorphic_with?(output_graph) ? "passed" : "failed" rescue Exception => e STDERR.puts "Unexpected exception reading result: #{e.inspect}" From 351c94348e2650dca35428f6a9e16a0d9c3f5e4d Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 29 Jul 2020 14:10:11 -0700 Subject: [PATCH 036/193] Fix N3Test location in write specs. --- spec/writer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 73cf84e..6b7dace 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -621,7 +621,7 @@ describe "w3c n3 parser tests" do require_relative 'suite_helper' - Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/grammar/tests/N3Tests/manifest.ttl") do |m| + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/tests/N3Tests/manifest.ttl") do |m| describe m.comment do m.entries.each do |t| next if t.negative_test? || t.rejected? From 89506fb53d3375039ae1449d29353a586607603f Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 30 Jul 2020 16:17:36 -0700 Subject: [PATCH 037/193] Incremental writer improvements. --- lib/rdf/n3/writer.rb | 79 ++++++++++++++++++++++++++++++-------------- spec/writer_spec.rb | 4 +-- 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 751286f..765b312 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -58,6 +58,9 @@ class Writer < RDF::Writer # @return [RDF::Graph] Graph being serialized attr_accessor :graph + # @return [Array] formulae names + attr_accessor :formula_names + ## # N3 Writer options # @see http://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Writer#options-class_method @@ -159,6 +162,8 @@ def write_epilogue start_document + @formula_names = repo.graph_names(unique: true) + with_graph(nil) do count = 0 order_subjects.each do |subject| @@ -169,9 +174,12 @@ def write_epilogue end # Output any formulae not already serialized using owl:sameAs - repo.graph_names.each do |graph_name| + formula_names.each do |graph_name| next if graph_done?(graph_name) + # Add graph_name to @formulae + @formulae[graph_name] = true + log_debug {"formula(#{graph_name})"} @output.write("\n#{indent}") p_term(graph_name, :subject) @@ -332,7 +340,17 @@ def top_classes; [RDF::RDFS.Class]; end # Defines order of predicates to to emit at begninning of a resource description. Defaults to # [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 + def predicate_order + [ + RDF.type, + RDF::RDFS.label, + RDF::RDFS.comment, + RDF::URI("http://purl.org/dc/terms/title"), + RDF::URI("http://purl.org/dc/terms/description"), + RDF::OWL.sameAs, + RDF::N3::Log.implies + ] + end # Order subjects for output. Override this to output subjects in another order. # @@ -351,14 +369,18 @@ def order_subjects # Add distinguished classes top_classes.each do |class_uri| graph.query({predicate: RDF.type, object: class_uri}). - map {|st| st.subject}. - sort. - uniq. - each do |subject| - log_debug("order_subjects") {subject.to_sxp} - subjects << subject - seen[subject] = true - end + map {|st| st.subject}.sort.uniq.each do |subject| + log_debug("order_subjects") {subject.to_sxp} + subjects << subject + seen[subject] = true + end + end + + # Add formulae which are subjects in this graph + @formulae.each_key do |bn| + next unless @subjects.has_key?(bn) + subjects << bn + seen[bn] = true end # Mark as seen lists that are part of another list @@ -466,8 +488,8 @@ def quoted(string) # 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} - return @lists[l] && @lists[l].valid? + log_debug("is_valid_list?") {l.inspect + ' ' + (!!@lists[l] && @lists[l].valid?).inspect} + return !!@lists[l] && @lists[l].valid? end def do_list(l, position) @@ -516,9 +538,9 @@ def path(resource, position) log_debug("path") do "#{resource.to_sxp}, " + "pos: #{position}, " + - "{}?: #{formula?(resource, position)}, " + - "()?: #{is_valid_list?(resource)}, " + - "[]?: #{blankNodePropertyList?(resource, position)}, " + + "{}?: #{formula?(resource, position).inspect}, " + + "()?: #{is_valid_list?(resource).inspect}, " + + "[]?: #{blankNodePropertyList?(resource, position).inspect}, " + "rc: #{ref_count(resource)}" end raise RDF::WriterError, "Cannot serialize resource '#{resource}'" unless @@ -613,12 +635,7 @@ def blankNodePropertyList(resource, position) # Can subject be represented as a formula? def formula?(resource, position) - (resource.node? || position == :graph_name) && - 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) + !!@formulae[resource] end def formula(resource, position) @@ -653,7 +670,12 @@ def triples(subject) end def statement(subject, count) - log_debug("statement") {"#{subject.to_sxp}, bnodePL?: #{blankNodePropertyList?(subject, :subject)}"} + log_debug("statement") do + "#{subject.to_sxp}, " + + "{}?: #{formula?(subject, :subject).inspect}, " + + "()?: #{is_valid_list?(subject).inspect}, " + + "[]?: #{blankNodePropertyList?(subject, :subject).inspect}, " + end subject_done(subject) blankNodePropertyList(subject, :subject) || triples(subject) @output.puts if count > 0 || graph.graph_name @@ -718,10 +740,18 @@ def with_graph(graph_name) old_serialized, @serialized = @serialized, {} old_subjects, @subjects = @subjects, {} old_graph, @graph = @graph, repo.project_graph(graph_name) + old_formulae, @formulae = @formulae, {} graph_done(graph_name) - graph.each {|statement| preprocess_graph_statement(statement)} + graph.each do |statement| + preprocess_graph_statement(statement) + [statement.subject, statement.object].select(&:node?).each do |resource| + @formulae[resource] = true if + formula_names.include?(resource) || + resource.id.start_with?('.form_') + end + end # Remove lists that are referenced and have non-list properties; # these are legal, but can't be serialized as lists @@ -730,9 +760,10 @@ def with_graph(graph_name) list.subjects.any? {|elt| !resource_in_single_graph?(elt)} end + # Record nodes in subject or object yield ensure - @graph, @lists, @references, @serialized, @subjects = old_graph, old_lists, old_references, old_serialized, old_subjects + @graph, @lists, @references, @serialized, @subjects, @formulae = old_graph, old_lists, old_references, old_serialized, old_subjects, old_formulae end end end diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 6b7dace..530a28f 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -489,13 +489,13 @@ "empty subject" => { input: %({} .), regexp: [ - %r(\[ \] \.) + %r({} \.) ] }, "empty object" => { input: %( {} .), regexp: [ - %r( \[\] \.) + %r( {} \.) ] }, "as subject with constant content" => { From 00d06faa73c80c53052b59d66bb121f765e8ea8e Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 31 Jul 2020 13:41:55 -0700 Subject: [PATCH 038/193] Shrink variable names in output. --- lib/rdf/n3/writer.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 765b312..d906079 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -229,6 +229,13 @@ def get_pname(resource) nil end + # if resource is a variable (universal or extential), map to a shorter name + if (@universals.include?(resource) || @existentials.include?(resource)) && + resource.fragment.match(/^([^_]+)_[^_]+_([^_]+)$/) + sn, seq = $1, $2 + pname = @uri_to_pname.values.include?(sn) ? ":#{sn}_#{seq.to_i}" : ":#{sn}" + end + # Make sure pname is a valid pname if pname md = PNAME_LN.match(pname) || PNAME_NS.match(pname) @@ -236,8 +243,6 @@ def get_pname(resource) end @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. From c934bc83cd49eadbbf93850f434cbb979c335ab7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 2 Aug 2020 13:16:56 -0700 Subject: [PATCH 039/193] Use base_uri for null prefix when serializing. --- lib/rdf/n3/writer.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index d906079..ed27270 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -112,6 +112,9 @@ def initialize(output = $stdout, **options, &block) @uri_to_pname = {} @uri_to_prefix = {} super do + if base_uri + @uri_to_prefix[base_uri.to_s.end_with?('#', '/') ? base_uri : RDF::URI("#{base_uri}#")] = nil + end reset if block_given? case block.arity @@ -300,7 +303,7 @@ def format_literal(literal, **options) # @param [Hash{Symbol => Object}] options # @return [String] def format_uri(uri, **options) - md = uri.relativize(base_uri) + md = uri == base_uri ? '' : uri.relativize(base_uri) log_debug("relativize") {"#{uri.to_sxp} => #{md.inspect}"} if md != uri.to_s md != uri.to_s ? "<#{md}>" : (get_pname(uri) || "<#{uri}>") end @@ -366,7 +369,7 @@ def order_subjects subjects = [] # Start with base_uri - if base_uri && @subjects.keys.include?(base_uri) + if base_uri && @subjects.keys.select(&:uri?).include?(base_uri) subjects << base_uri seen[base_uri] = true end From 4873aff36f8c74cf4ca6b17a2d1835da96d79aa6 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 2 Aug 2020 13:18:02 -0700 Subject: [PATCH 040/193] Shorten variable names on output. --- lib/rdf/n3/writer.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index ed27270..fca937b 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -233,8 +233,8 @@ def get_pname(resource) end # if resource is a variable (universal or extential), map to a shorter name - if (@universals.include?(resource) || @existentials.include?(resource)) && - resource.fragment.match(/^([^_]+)_[^_]+_([^_]+)$/) + if (@universals + @existentials).include?(resource) && + resource.to_s.match(/#([^_]+)_[^_]+_([^_]+)$/) sn, seq = $1, $2 pname = @uri_to_pname.values.include?(sn) ? ":#{sn}_#{seq.to_i}" : ":#{sn}" end @@ -315,7 +315,15 @@ def format_uri(uri, **options) # @param [Hash{Symbol => Object}] options # @return [String] def format_node(node, **options) - options[:unique_bnodes] ? node.to_unique_base : node.to_base + if node.id.match(/^([^_]+)_[^_]+_([^_]+)$/) + sn, seq = $1, $2.to_i + seq = nil if seq == 0 + "_:#{sn}#{seq}" + elsif options[:unique_bnodes] + node.to_unique_base + else + node.to_base + end end protected @@ -328,6 +336,7 @@ def start_document @output.write("@prefix #{prefix}: <#{prefixes[prefix]}> .\n") end + # Universals and extentials at top-level unless @universals.empty? log_debug {"start_document: universals #{@universals.inspect}"} terms = @universals.map {|v| format_uri(RDF::URI(v.name.to_s))} From f77b37104855e45ec5ea58b110f8f73dd7bab017 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 2 Aug 2020 13:18:15 -0700 Subject: [PATCH 041/193] List writing tweaks. --- lib/rdf/n3/writer.rb | 84 ++++++++++++++++++-------------------------- spec/writer_spec.rb | 78 ++++++++++++++++++++++------------------ 2 files changed, 78 insertions(+), 84 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index fca937b..e781de5 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -403,11 +403,10 @@ def order_subjects # Mark as seen lists that are part of another list @lists.values.map(&:statements). flatten.each do |st| - seen[st.object] = true if @lists.has_key?(st.object) + seen[st.object] = true if @lists.key?(st.object) end - # List elements which are bnodes should not be targets for top-level serialization - list_elements = @lists.values.map(&:to_a).flatten.select(&:node?).compact + list_elements = [] # Lists may be top-level elements # Sort subjects by resources over bnodes, ref_counts and the subject URI itself recursable = (@subjects.keys - list_elements). @@ -461,6 +460,7 @@ def preprocess_graph_statement(statement) # Collect lists if statement.predicate == RDF.first l = RDF::List.new(subject: statement.subject, graph: graph) + log_debug("list #{l.inspect} invalid!") unless l.valid? @lists[statement.subject] = l if l.valid? end @@ -504,35 +504,36 @@ def quoted(string) 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 + ' ' + (!!@lists[l] && @lists[l].valid?).inspect} - return !!@lists[l] && @lists[l].valid? - end - - 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 + def collection?(l) + log_debug("collection?") {l.inspect + ' ' + (@lists.key?(l)).inspect} + return @lists.key?(l) end 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_sxp}, #{position}"} + return false if !collection?(node) + log_debug("collection") do + "#{node.to_sxp}, " + + "pos: #{position}, " + + "rc: #{ref_count(node)}" + end + # return false if position == :subject && ref_count(node) > 0 # recursive lists @output.write("(") - log_depth {do_list(node, position)} + log_depth do + list = @lists[node] + log_debug("collection") {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 @output.write(')') end @@ -556,7 +557,7 @@ def path(resource, position) "#{resource.to_sxp}, " + "pos: #{position}, " + "{}?: #{formula?(resource, position).inspect}, " + - "()?: #{is_valid_list?(resource).inspect}, " + + "()?: #{collection?(resource).inspect}, " + "[]?: #{blankNodePropertyList?(resource, position).inspect}, " + "rc: #{ref_count(resource)}" end @@ -612,7 +613,7 @@ def predicateObjectList(subject, from_bpl = false) end prop_list = sort_properties(properties) - prop_list -= [RDF.first.to_s, RDF.rest.to_s] if @lists.include?(subject) + prop_list -= [RDF.first.to_s, RDF.rest.to_s] if @lists.key?(subject) log_debug("predicateObjectList") {prop_list.inspect} return 0 if prop_list.empty? @@ -632,7 +633,7 @@ def predicateObjectList(subject, from_bpl = false) def blankNodePropertyList?(resource, position) resource.node? && !formula?(resource, position) && - !is_valid_list?(resource) && + !collection?(resource) && (!is_done?(resource) || position == :subject) && ref_count(resource) == (position == :object ? 1 : 0) && resource_in_single_graph?(resource) && @@ -644,7 +645,7 @@ 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(2)}" : "") + (position == :object ? ']' : '] .')) true @@ -661,9 +662,9 @@ def formula(resource, position) log_debug("formula") {resource.to_sxp} subject_done(resource) @output.write('{') + count = 0 log_depth do with_graph(resource) do - count = 0 order_subjects.each do |subject| unless is_done?(subject) statement(subject, count) @@ -672,7 +673,7 @@ def formula(resource, position) end end end - @output.write((graph.count > 1 ? "\n#{indent}" : "") + '}') + @output.write((count > 0 ? "#{indent}" : "") + '}') true end @@ -690,7 +691,7 @@ def statement(subject, count) log_debug("statement") do "#{subject.to_sxp}, " + "{}?: #{formula?(subject, :subject).inspect}, " + - "()?: #{is_valid_list?(subject).inspect}, " + + "()?: #{collection?(subject).inspect}, " + "[]?: #{blankNodePropertyList?(subject, :subject).inspect}, " end subject_done(subject) @@ -698,14 +699,6 @@ def statement(subject, count) @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 - # @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) @@ -770,13 +763,6 @@ def with_graph(graph_name) end end - # 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 - # Record nodes in subject or object yield ensure diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 530a28f..983cc73 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -118,7 +118,7 @@ }, "reuses BNode labels by default" => { input: %(@prefix ex: . _:a ex:b _:a .), - regexp: [%r(^\s*_:a(_\d+_\d+) ex:b _:a\1 \.$)] + regexp: [%r(^\s*_:a ex:b _:a \.$)] }, "standard prefixes" => { input: %( @@ -198,6 +198,12 @@ %r(ex:b a ex:Thing \.), ] }, + "embedded list": { + input: %{((:q)) a :Thing .}, + regexp: [ + %r{\(\(:q\)\) a :Thing \.} + ] + }, "owl:unionOf list": { input: %( @prefix ex: . @@ -256,24 +262,24 @@ ], 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 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 . @@ -513,7 +519,7 @@ "implies" => { input: %({ _:x :is _:x } => {_:x :is _:x } .), regexp: [ - %r({\s+_:x(_\d+_\d+) :is _:x\1 \.\s+} => {\s+_:x(_\d+_\d+) :is _:x\2 \.\s+} \.)m + %r({\s+_:x(\d*) :is _:x\1 \.\s+} => {\s+_:x(\d*) :is _:x\2 \.\s+} \.)m ] }, "formula simple" => { @@ -576,8 +582,8 @@ ), regexp: [ %r{\(17\) a :TestCase \.}, - %r{\(:x_\d+_\d+\) a :TestCase \.}, - %r{:x_\d+_\d+ a :RESULT \.}, + %r{\(:x\) a :TestCase \.}, + %r{:x a :RESULT \.}, ] }, }.each do |name, params| @@ -592,22 +598,22 @@ "@forAll": { input: %(@forAll :o. :s :p :o .), regexp: [ - %r(@forAll :o_\d+_\d+ \.), - %r(:s :p :o_\d+_\d+ \.), + %r(@forAll :o \.), + %r(:s :p :o \.), ] }, "@forSome": { input: %(@forSome :o. :s :p :o .), regexp: [ - %r(@forSome :o_\d+_\d+ \.), - %r(:s :p :o_\d+_\d+ \.), + %r(@forSome :o \.), + %r(:s :p :o \.), ] }, "?o": { input: %(:s :p ?o .), regexp: [ - %r(@forAll :o_\d+_\d+ \.), - %r(:s :p :o_\d+_\d+ \.), + %r(@forAll :o \.), + %r(:s :p :o \.), ] }, }.each do |name, params| @@ -627,7 +633,10 @@ next if t.negative_test? || t.rejected? specify "#{t.name}: #{t.comment} (action)" do case t.name - when *%w(cwm_syntax_path2.n3) + when *%w(cwm_syntax_path2.n3 cwm_includes_concat.n3 + cwm_includes_quantifiers.n3 cwm_includes_quantifiers_limited.n3 + cwm_includes_t11.n3 cwm_list_append.n3 cwm_list_builtin_generated_match-ref.n3 + ) pending "Investigate" when *%w(cwm_syntax_numbers.n3) pending "Number syntax" @@ -640,10 +649,10 @@ logger.info t.inspect logger.info "source: #{t.input}" repo = parse(t.input, base_uri: t.base, logger: false) + logger.info("sxp: #{repo.statements.map(&:to_sxp).join("\n")}") n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true, logger: logger) - logger.info "serialized: #{n3}" - g2 = parse(n3, base_uri: t.base, logger: logger) - expect(g2).to be_equivalent_graph(repo, logger: logger) + g2 = parse(n3, base_uri: t.base, logger: false) + expect(g2.isomorphic?(repo)).to produce(true, logger) end next if t.syntax? || t.reason? @@ -662,9 +671,8 @@ format = detect_format(t.expected) repo = parse(t.expected, base_uri: t.base, format: format, logger: false) n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true, logger: logger) - logger.info "serialized: #{n3}" - g2 = parse(n3, base_uri: t.base, logger: logger) - expect(g2).to be_equivalent_graph(repo, logger: logger) + g2 = parse(n3, base_uri: t.base, logger: false) + expect(g2.isomorphic?(repo)).to produce(true, logger) end end end From ae834f6aed499a9a273e19ede58c88c88c91dfd4 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 2 Aug 2020 15:59:07 -0700 Subject: [PATCH 042/193] Internalize list management in writer, as variables in N3 make using normal RDF::List logic fail. --- lib/rdf/n3/writer.rb | 81 +++++++++++++++++++++++++++++++------------- spec/writer_spec.rb | 30 ++++++++++------ 2 files changed, 78 insertions(+), 33 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index e781de5..8925d9e 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -212,7 +212,7 @@ def get_pname(resource) #log_debug {"get_pname(#{resource}), std?}"} pname = case - when @uri_to_pname.has_key?(uri) + when @uri_to_pname.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 @@ -395,16 +395,15 @@ def order_subjects # Add formulae which are subjects in this graph @formulae.each_key do |bn| - next unless @subjects.has_key?(bn) + next unless @subjects.key?(bn) subjects << bn seen[bn] = true end # Mark as seen lists that are part of another list - @lists.values.map(&:statements). - flatten.each do |st| - seen[st.object] = true if @lists.key?(st.object) - end + @lists.values.flatten.each do |v| + seen[v] = true if @lists.key?(v) + end list_elements = [] # Lists may be top-level elements @@ -456,18 +455,6 @@ def preprocess_graph_statement(statement) @subjects[statement.subject] ||= {} @subjects[statement.subject][statement.predicate] ||= 0 @subjects[statement.subject][statement.predicate] += 1 - - # Collect lists - if statement.predicate == RDF.first - l = RDF::List.new(subject: statement.subject, graph: graph) - log_debug("list #{l.inspect} invalid!") unless l.valid? - @lists[statement.subject] = l if l.valid? - end - - if statement.object == RDF.nil || statement.subject == RDF.nil - # Add an entry for the list tail - @lists[RDF.nil] ||= RDF::List[] - end end # Returns indent string multiplied by the depth @@ -523,13 +510,13 @@ def collection(node, position) list = @lists[node] log_debug("collection") {list.inspect} subject_done(RDF.nil) + subject_done(node) index = 0 - list.each_statement do |st| - next unless st.predicate == RDF.first - log_debug {" list this: #{st.subject} first: #{st.object}[#{position}]"} + list.each do |li| + log_debug {" list first: #{li}[#{position}]"} @output.write(" ") if index > 0 - path(st.object, position) - subject_done(st.subject) + path(li, position) + subject_done(li) position = :object index += 1 end @@ -754,6 +741,7 @@ def with_graph(graph_name) graph_done(graph_name) + lists = {} graph.each do |statement| preprocess_graph_statement(statement) [statement.subject, statement.object].select(&:node?).each do |resource| @@ -761,6 +749,53 @@ def with_graph(graph_name) formula_names.include?(resource) || resource.id.start_with?('.form_') end + + # Collect list elements + if [RDF.first, RDF.rest].include?(statement.predicate) && statement.subject.node? + lists[statement.subject] ||= {} + lists[statement.subject][statement.predicate] = statement.object + end + end + + # Remove list entries after head with more than two properties (other than rdf:type) + rests = lists.values.map {|props| props[RDF.rest]} + + # Remove non-head lists that have too many properties + rests.select do |bn| + pc = 0 + @subjects.fetch(bn, {}).each do |pred, count| + next if pred == RDF.type + pc += count + end + lists.delete(bn) if pc > 2 + end + + # Values for this list element, recursive + def list_values(bn, lists) + raise "no list" unless lists.has_key?(bn) + first, rest = lists[bn][RDF.first], lists[bn][RDF.rest] + (rest == RDF.nil ? [] : list_values(rest, lists)).unshift(first) + rescue + lists.delete(bn) + raise $! + end + + # Create value arrays for each entry + lists.each do |bn, props| + begin + @lists[bn] = list_values(bn, lists) + rescue + # Skip this list element, if it raises an exception + lists.delete(bn) + end + end + + # Mark all remaining rests done + rests.each {|bn| subject_done(bn) if lists.include?(bn)} + + # Remove entries that are referenced as rdf:rest of some entry + lists.each do |bn, props| + @lists.delete(props[RDF.rest]) end # Record nodes in subject or object diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 983cc73..9782e58 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -7,11 +7,11 @@ describe RDF::N3::Writer do let(:logger) {RDF::Spec.logger} - after(:each) do |example| - puts logger.to_s if - example.exception && - !example.exception.is_a?(RSpec::Expectations::ExpectationNotMetError) - end + #after(:each) do |example| + # puts logger.to_s if + # example.exception && + # !example.exception.is_a?(RSpec::Expectations::ExpectationNotMetError) + #end it_behaves_like 'an RDF::Writer' do let(:writer) {RDF::N3::Writer.new(StringIO.new)} @@ -633,16 +633,26 @@ next if t.negative_test? || t.rejected? specify "#{t.name}: #{t.comment} (action)" do case t.name - when *%w(cwm_syntax_path2.n3 cwm_includes_concat.n3 + when *%w(cwm_syntax_path2.n3 cwm_includes_quantifiers.n3 cwm_includes_quantifiers_limited.n3 - cwm_includes_t11.n3 cwm_list_append.n3 cwm_list_builtin_generated_match-ref.n3 + cwm_list_append.n3 cwm_includes_builtins.n3 + cwm_list_unify5-ref.n3 cwm_other_classes.n3 + cwm_other_lists-simple.n3 cwm_syntax_qual-after-user.n3 + cwm_other_lists.n3 # empty list with extra properties + cwm_includes_concat.n3 cwm_includes_t11.n3 ) pending "Investigate" - when *%w(cwm_syntax_numbers.n3) + when *%w(cwm_syntax_numbers.n3 cwm_math_trigo.ref.n3 + cwm_syntax_decimal.n3 cwm_syntax_decimal-ref.n3 + cwm_syntax_boolean.n3) pending "Number syntax" - when *%w(cwm_syntax_too-nested.n3) + when *%w(cwm_other_anon-prop.n3 cwm_other_filter-bnodes.n3 cwm_syntax_bad-preds-formula.n3 + cwm_reason_danc.n3) + pending "Anonymous properties" + when *%w(cwm_syntax_too-nested.n3 cwm_list_unify4.n3) skip("stack overflow") - when *%w(manifest.ttl) + when *%w(manifest.ttl cwm_math_math-test.n3 cwm_other_daml-ex.n3 + cwm_math_math-test.n3 cwm_syntax_this-quantifiers-ref.n3) skip("too long") end From 74f562c66d35512275354f213583830fcc87bc28 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 3 Aug 2020 12:57:28 -0700 Subject: [PATCH 043/193] Use an extended array instead of a repository for reader/writer tests. --- spec/reader_spec.rb | 8 ++++---- spec/suite_parser_spec.rb | 6 +++--- spec/suite_turtle_spec.rb | 6 +++--- spec/writer_spec.rb | 21 ++++++++------------- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 1740443..3928eda 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -395,19 +395,19 @@ end it "should generate inverse predicate for 'is xxx of'" do - n3 = %("value" is :prop of :b . :b :prop "value" .) + n3 = %("value" is :prop of :b .) nt = %( "value" .) 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'", pending: 'deprecated' do - n3 = %("value" @is :prop @of :b . :b :prop "value" .) + n3 = %("value" @is :prop @of :b .) nt = %( "value" .) expect(parse(n3, base_uri: "http://a/b", validate: true)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should generate inverse predicate for '<- xxx'" do - n3 = %("value" <- :prop :b . :b :prop "value" .) + n3 = %("value" <- :prop :b .) nt = %( "value" .) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end @@ -1275,7 +1275,7 @@ def parse(input, **options) validate: false, canonicalize: false, }.merge(options) - repo = options[:repo] || RDF::Repository.new + repo = options[:repo] || [].extend(RDF::Enumerable, RDF::Queryable) RDF::N3::Reader.new(input, **options).each_statement do |statement| repo << statement end diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index 9ecaefa..2a56012 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -37,7 +37,7 @@ validate: true, logger: t.logger) - repo = RDF::Repository.new + repo = [].extend(RDF::Enumerable, RDF::Queryable) output_repo = if t.evaluate? begin @@ -50,7 +50,7 @@ if t.positive_test? begin - repo << reader + reader.each_statement {|st| repo << st} rescue Exception => e expect(e.message).to produce("Not exception #{e.inspect}", t) end @@ -62,7 +62,7 @@ end elsif t.syntax? expect { - repo << reader + reader.each_statement {|st| repo << st} expect(repo.count).to produce("not this", t) }.to raise_error(RDF::ReaderError) else diff --git a/spec/suite_turtle_spec.rb b/spec/suite_turtle_spec.rb index 5f8392e..c85431b 100644 --- a/spec/suite_turtle_spec.rb +++ b/spec/suite_turtle_spec.rb @@ -21,11 +21,11 @@ validate: true, logger: t.logger) - graph = RDF::Repository.new + graph = [].extend(RDF::Enumerable, RDF::Queryable) if t.positive_test? begin - graph << reader + reader.each_statement {|st| graph << st} rescue Exception => e expect(e.message).to produce("Not exception #{e.inspect}", t) end @@ -38,7 +38,7 @@ end else expect { - graph << reader + reader.each_statement {|st| graph << st} expect(graph.dump(:ntriples)).to produce("not this", t) }.to raise_error(RDF::ReaderError) end diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 9782e58..c3be49a 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -636,18 +636,17 @@ when *%w(cwm_syntax_path2.n3 cwm_includes_quantifiers.n3 cwm_includes_quantifiers_limited.n3 cwm_list_append.n3 cwm_includes_builtins.n3 - cwm_list_unify5-ref.n3 cwm_other_classes.n3 + cwm_list_unify5-ref.n3 cwm_other_lists-simple.n3 cwm_syntax_qual-after-user.n3 cwm_other_lists.n3 # empty list with extra properties - cwm_includes_concat.n3 cwm_includes_t11.n3 + cwm_includes_concat.n3 new_syntax_inverted_properties.n3 ) pending "Investigate" - when *%w(cwm_syntax_numbers.n3 cwm_math_trigo.ref.n3 - cwm_syntax_decimal.n3 cwm_syntax_decimal-ref.n3 - cwm_syntax_boolean.n3) + when *%w(cwm_math_trigo.ref.n3 + cwm_syntax_decimal.n3 cwm_syntax_decimal-ref.n3) pending "Number syntax" when *%w(cwm_other_anon-prop.n3 cwm_other_filter-bnodes.n3 cwm_syntax_bad-preds-formula.n3 - cwm_reason_danc.n3) + cwm_reason_danc.n3 cwm_syntax_path2.n3) pending "Anonymous properties" when *%w(cwm_syntax_too-nested.n3 cwm_list_unify4.n3) skip("stack overflow") @@ -668,10 +667,6 @@ next if t.syntax? || t.reason? specify "#{t.name}: #{t.comment} (result)" do case t.name - when *%w(cwm_syntax_path2.n3) - pending "Investigate" - when *%w(cwm_syntax_numbers.n3) - pending "Number syntax" when *%w(cwm_syntax_too-nested.n3) skip("stack overflow") end @@ -690,9 +685,9 @@ end unless ENV['CI'] def parse(input, format: :n3, **options) - repo = RDF::Repository.new + repo = [].extend(RDF::Enumerable, RDF::Queryable) reader = RDF::Reader.for(format) - repo << reader.new(input, **options) + reader.new(input, **options).each_statement {|st| repo << st} repo end @@ -701,7 +696,7 @@ 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: false, 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 + g.each_statement {|st| writer << st} end if $verbose require 'cgi' From 4024ac10a9da12281eda33e7ab57f47d8a287fe6 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 3 Aug 2020 13:19:45 -0700 Subject: [PATCH 044/193] Don't presume predicate is a URI in writer. --- lib/rdf/n3/refinements.rb | 2 +- lib/rdf/n3/writer.rb | 22 +++++++++++----------- spec/writer_spec.rb | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb index d39133b..a7f4863 100644 --- a/lib/rdf/n3/refinements.rb +++ b/lib/rdf/n3/refinements.rb @@ -27,7 +27,7 @@ module RDF::N3::Refinements # @return [Boolean] def valid? has_subject? && subject.term? && subject.valid? && - has_predicate? && predicate.resource? && predicate.valid? && + has_predicate? && predicate.term? && predicate.valid? && has_object? && object.term? && object.valid? && (has_graph? ? (graph_name.resource? && graph_name.valid?) : true) end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 8925d9e..cfbfef8 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -250,20 +250,20 @@ def get_pname(resource) # 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 - # @return [Array}] Ordered list of properties. Uses predicate_order. + # @param [Hash{RDF::Term => Array}] properties A hash of Property to Resource mappings + # @return [Array}] Ordered list of properties. Uses predicate_order. 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 + next unless properties.key?(prop) + prop_list << prop end properties.keys.sort.each do |prop| - next if prop_list.include?(prop.to_s) - prop_list << prop.to_s + next if prop_list.include?(prop) + prop_list << prop end log_debug {"sort_properties: #{prop_list.join(', ')}"} @@ -591,16 +591,16 @@ def predicateObjectList(subject, from_bpl = false) 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 + (properties[st.predicate] ||= []) << st.object end else @graph.query({subject: subject}) do |st| - (properties[st.predicate.to_s] ||= []) << st.object + (properties[st.predicate] ||= []) << st.object end end prop_list = sort_properties(properties) - prop_list -= [RDF.first.to_s, RDF.rest.to_s] if @lists.key?(subject) + prop_list -= [RDF.first, RDF.rest] if @lists.key?(subject) log_debug("predicateObjectList") {prop_list.inspect} return 0 if prop_list.empty? @@ -608,7 +608,7 @@ def predicateObjectList(subject, from_bpl = false) prop_list.each_with_index do |prop, i| begin @output.write(";\n#{indent(2)}") if i > 0 - predicate(RDF::URI.intern(prop)) + predicate(prop) @output.write(" ") objectList(properties[prop]) end @@ -634,7 +634,7 @@ def blankNodePropertyList(resource, position) subject_done(resource) @output.write((position == :subject ? "\n#{indent}[" : '[')) num_props = log_depth {predicateObjectList(resource, true)} - @output.write((num_props > 1 ? "\n#{indent(2)}" : "") + (position == :object ? ']' : '] .')) + @output.write((num_props > 1 ? "\n#{indent(2)}" : "") + (position == :subject ? '] .' : ']')) true end diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index c3be49a..2262f68 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -645,8 +645,8 @@ when *%w(cwm_math_trigo.ref.n3 cwm_syntax_decimal.n3 cwm_syntax_decimal-ref.n3) pending "Number syntax" - when *%w(cwm_other_anon-prop.n3 cwm_other_filter-bnodes.n3 cwm_syntax_bad-preds-formula.n3 - cwm_reason_danc.n3 cwm_syntax_path2.n3) + when *%w(cwm_syntax_bad-preds-formula.n3 + cwm_syntax_path2.n3) pending "Anonymous properties" when *%w(cwm_syntax_too-nested.n3 cwm_list_unify4.n3) skip("stack overflow") From 320e5c7cbf2e3091e0a0e410afc884c27922e362 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 4 Aug 2020 15:09:00 -0700 Subject: [PATCH 045/193] Add test for whitespace in IRIs. --- lib/rdf/n3/reader.rb | 2 +- spec/reader_spec.rb | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 61f6cc6..e5d0eaa 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -604,7 +604,7 @@ def read_formulaContent def read_iri token = @lexer.first case token && token.type - when :IRIREF then prod(:iri) {process_iri(@lexer.shift.value[1..-2].gsub(/\s/, ''))} + when :IRIREF then prod(:iri) {process_iri(@lexer.shift.value[1..-2].gsub(/\s+/m, ''))} when :PNAME_LN, :PNAME_NS then prod(:prefixedName) {process_pname(*@lexer.shift.value)} end end diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 3928eda..bcac198 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -690,6 +690,27 @@ end end + describe "IRIs" do + { + "with newlines": { + n3: %( + a :testcase . + ), + ttl: %( + a . + ) + }, + }.each do |name, params| + it name do + expected = parse(params[:ttl], base_uri: "http://a/b", logger: false) + expect(parse(params[:n3], base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + end + end + end + describe "BNodes" do it "should create BNode for identifier with '_' prefix" do n3 = %(@prefix a: . _:a a:p a:v .) @@ -1227,7 +1248,7 @@ %(:y :p1 "xy.z"^^xsd:double .), %(:y :p1 "+1.0z"^^xsd:double .), %(:a :b .), - %(:a "literal value" :b .), + #%(:a "literal value" :b .), %(@keywords prefix. :e prefix :f .), ].each do |n3| it "should raise ReaderError for '#{n3}'" do From 5627686f096001b6c0966f0fdf093a117f5ec60e Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 4 Aug 2020 15:09:45 -0700 Subject: [PATCH 046/193] Add extended parser tests and filter for particularly slow tests. --- script/tc | 19 ++++---- spec/spec_helper.rb | 4 +- spec/suite_extended_spec.rb | 87 +++++++++++++++++++++++++++++++++++++ spec/suite_helper.rb | 60 +++++++++++++++++++++++++ spec/suite_parser_spec.rb | 2 + 5 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 spec/suite_extended_spec.rb diff --git a/script/tc b/script/tc index 6377c07..bf02bbe 100755 --- a/script/tc +++ b/script/tc @@ -37,14 +37,14 @@ def earl_preamble(**options) end def run_tc(man, tc, **options) - STDERR.write "run #{tc.name} " + STDERR.write "test #{tc.name} " if options[:verbose] STDERR.puts "\nTestCase: #{tc.inspect}" STDERR.puts "\nInput:\n" + tc.input STDERR.puts "\nExpected:\n" + tc.result if tc.result && tc.positiveTest? end - + return if tc.approval == "rdft:Rejected" logger = options[:live] ? Logger.new(STDERR) : RDF::Spec.logger @@ -66,7 +66,9 @@ def run_tc(man, tc, **options) graph = RDF::Repository.new result = nil - if tc.positive_test? + if !options[:slow] && tc.slow? + result = "untested" + elsif tc.positive_test? begin graph << reader rescue Exception => e @@ -128,13 +130,14 @@ def run_tc(man, tc, **options) options[:results][result] ||= 0 options[:results][result] += 1 - STDERR.puts "#{"test result:" unless options[:quiet]} #{result} #{"(#{secs} seconds)" unless options[:quiet] || secs < 3}." + STDERR.puts "#{"test result:" unless options[:quiet]} #{result} #{"(#{secs} seconds)" unless options[:quiet] || secs < 1}." end options = { output: STDOUT, results: {}, - level: Logger::WARN + level: Logger::WARN, + slow: true # Run slow tests by default } opts = GetoptLong.new( @@ -144,7 +147,7 @@ opts = GetoptLong.new( ["--live", GetoptLong::NO_ARGUMENT], ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT], ["--quiet", "-q", GetoptLong::NO_ARGUMENT], - ["--skip-long", "-s", GetoptLong::NO_ARGUMENT], + ["--skip-slow", "-s", GetoptLong::NO_ARGUMENT], ["--validate", GetoptLong::NO_ARGUMENT], ["--verbose", "-v", GetoptLong::NO_ARGUMENT], ) @@ -158,7 +161,7 @@ def help(**options) puts " --ntriples: Run N-Triples tests" puts " --output: Output to specified file" puts " --quiet: Minimal output" - puts " --skip-long: Avoid files taking too much time" + puts " --skip-slow: Avoid files taking too much time" puts " --validate: Validate input" puts " --verbose: Verbose processing" puts " --help,-?: This message" @@ -178,7 +181,7 @@ opts.each do |opt, arg| when '--quiet' options[:quiet] = true options[:level] = Logger::FATAL - when '--skip-long' then options[:skip] = true + when '--skip-slow' then options[:slow] = false when '--validate' then options[:validate] = true when '--verbose' then options[:verbose] = true end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 460c8cc..29f9173 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -27,9 +27,7 @@ ::RSpec.configure do |c| c.filter_run focus: true c.run_all_when_everything_filtered = true - c.exclusion_filter = { - ruby: lambda { |version| !(RUBY_VERSION.to_s =~ /^#{version.to_s}/) }, - } + c.filter_run_excluding slow: true unless ENV['SLOW'] end # Heuristically detect the input stream diff --git a/spec/suite_extended_spec.rb b/spec/suite_extended_spec.rb new file mode 100644 index 0000000..8eb23d9 --- /dev/null +++ b/spec/suite_extended_spec.rb @@ -0,0 +1,87 @@ +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 + let(:logger) {RDF::Spec.logger} + + after(:each) do |example| + puts logger.to_s if + example.exception && + !example.exception.is_a?(RSpec::Expectations::ExpectationNotMetError) + end + + require_relative 'suite_helper' + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/tests/N3Tests/manifest-extended.ttl") do |m| + describe m.label do + m.entries.each do |t| + next if t.approval == 'rdft:Rejected' + specify "#{t.name}: #{t.comment}", slow: t.slow? do + case t.name + when *%w(07test_utf8.n3) + pending("invalid byte sequence in UTF-8") + when *%w(01etc_skos-extra-rules.n3 01etc_skos-rules.n3 07test_pd_hes_theory.n3) + pending("@keywords") + when *%w(01etc_train_model.n3 04test_icalQ002.n3 04test_icalR.n3 04test_LanguageQ.n3 + 04test_LanguageQ.n3 04test_query-survey-11.n3 04test_query-survey-13.n3 + 04test_icalQ001.n3) + pending("variable filter syntax") + when *%w(04test_ontology-for-data-model.n3) + pending("invalid literal") + #when *%w(cwm_syntax_numbers.n3) + # pending("number representation") + #when *%w(space-in-uri) + # pending("space in URIs") + #when *%w(cwm_syntax_too-nested.n3) + # skip("stack overflow") + end + + + t.logger = 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: true, + logger: t.logger) + + repo = [].extend(RDF::Enumerable, RDF::Queryable) + + output_repo = if t.evaluate? + begin + 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) + end + end + + if t.positive_test? + begin + reader.each_statement {|st| repo << st} + rescue Exception => e + expect(e.message).to produce("Not exception #{e.inspect}", t) + end + + if t.evaluate? + expect(repo).to be_equivalent_graph(output_repo, t) + else + expect(repo).to be_enumerable + end + elsif t.syntax? + expect { + reader.each_statement {|st| repo << st} + expect(repo.count).to produce("not this", t) + }.to raise_error(RDF::ReaderError) + else + expect(repo).not_to be_equivalent_graph(output_repo, t) + end + end + end + end + end + end +end unless ENV['CI'] \ No newline at end of file diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index a974d65..0c67df1 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -117,6 +117,62 @@ def entries end class Entry < JSON::LD::Resource + # Slow tests, skipped by default + SLOW = %w( + 01etc_10tt_proof.n3 + 01etc_4color_proof.n3 + 01etc_bankSW.n3 + 01etc_biE.n3 + 01etc_bmi_proof.n3 + 01etc_data.n3 + 01etc_easter-proof.n3 + 01etc_easterE.n3 + 01etc_fcm_proof.n3 + 01etc_fgcm_proof.n3 + 01etc_fibE.n3 + 01etc_gedcom-proof.n3 + 01etc_gps-proof2.n3 + 01etc_graph-1000.n3 + 01etc_graph-10000.n3 + 01etc_mmln-gv-mln.n3 + 01etc_mmln-gv-proof.n3 + 01etc_mq_proof.n3 + 01etc_palindrome-proof.n3 + 01etc_palindrome2-proof.n3 + 01etc_pi-proof.n3 + 01etc_polynomial.n3 + 01etc_proof-1000.n3 + 01etc_proof-10000.n3 + 01etc_proof-2-1000.n3 + 01etc_proof-2-10000.n3 + 01etc_randomsample-proof.n3 + 01etc_result.n3 + 01etc_rifE.n3 + 01etc_swet_proof.n3 + 01etc_takE.n3 + 01etc_test-dl-1000.n3 + 01etc_test-dt-1000.n3 + 01etc_test-proof-1000.n3 + 01etc_test_proof.n3 + 01etc_train_model.n3 + 04test_not-galen.n3 + 04test_radlex.n3 + 05smml_FACTSboxgeometrydetection.n3 + 05smml_FACTShousewallsmeshed.n3 + 05smml_FACTSlinkfaceedgestoobjects.n3 + 05smml_FACTSlinkfacestoobjects.n3 + 05smml_FACTStriangleedges.n3 + 07test_bd-result-1000.n3 + 07test_biR.n3 + 07test_fgcm_proof.n3 + 07test_graph-10000.n3 + 07test_gv-mln.n3 + 07test_path-1024-3.n3 + 07test_path-256-3.n3 + 07test_pd_hes_result.n3 + 07test_test-strela-1000.n3 + ) + attr_accessor :logger # For debug output formatting @@ -133,6 +189,10 @@ def name id.to_s.split('#').last end + def slow? + SLOW.include?(name) + end + # Alias data and query def input @input ||= RDF::Util::File.open_file(action) {|f| f.read} diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index 2a56012..a3f51f7 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -22,6 +22,8 @@ case t.name when *%w(cwm_syntax_numbers.n3) pending("number representation") + when *%w(space-in-uri) + pending("space in URIs") when *%w(cwm_syntax_too-nested.n3) skip("stack overflow") end From 35fe4ddd28021de13ae2a267a6d12af4a81b424b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 4 Aug 2020 17:17:21 -0700 Subject: [PATCH 047/193] Add `RDF::Term#sameTerm?` which is like `#eql?`, but requires variables to be another variable with the same name. This is used in the writer when iterating through statements to find matching resources. --- lib/rdf/n3/refinements.rb | 23 +++++++++++++++++++++++ lib/rdf/n3/writer.rb | 19 +++++-------------- script/parse | 7 ++++++- spec/suite_parser_spec.rb | 3 +-- spec/suite_turtle_spec.rb | 5 +++++ spec/writer_spec.rb | 5 ++++- 6 files changed, 44 insertions(+), 18 deletions(-) diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb index a7f4863..e2bbd11 100644 --- a/lib/rdf/n3/refinements.rb +++ b/lib/rdf/n3/refinements.rb @@ -70,4 +70,27 @@ def valid? false end end + + # @!parse + # # Refinements on RDF::Term + # class ::RDF::Term + # # Adds `#sameTerm?` which is the same as `#eql?`, except for variables. + # # @return [Boolean] + # def sameTerm?; end + # end + refine ::RDF::Term do + ## + # Is this the same term? Like `#eql?`, but no variable matching + def sameTerm?(other) + eql?(other) + end + end + + refine ::RDF::Query::Variable do + ## + # True if the other is the same variable + def sameTerm?(other) + other.is_a?(::RDF::Query::Variable) && name.eql?(other.name) + end + end end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index cfbfef8..150587c 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -266,7 +266,6 @@ def sort_properties(properties) prop_list << prop end - log_debug {"sort_properties: #{prop_list.join(', ')}"} prop_list end @@ -304,7 +303,7 @@ def format_literal(literal, **options) # @return [String] def format_uri(uri, **options) md = uri == base_uri ? '' : uri.relativize(base_uri) - log_debug("relativize") {"#{uri.to_sxp} => #{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 @@ -492,7 +491,6 @@ def quoted(string) # Checks if l is a valid RDF list, i.e. no nodes have other properties. def collection?(l) - log_debug("collection?") {l.inspect + ' ' + (@lists.key?(l)).inspect} return @lists.key?(l) end @@ -588,20 +586,13 @@ def objectList(objects) # @return [Integer] the number of properties serialized def predicateObjectList(subject, from_bpl = false) properties = {} - if subject.variable? - # Can't query on variable - @graph.enum_statement.select {|s| s.subject.equal?(subject)}.each do |st| - (properties[st.predicate] ||= []) << st.object - end - else - @graph.query({subject: subject}) do |st| - (properties[st.predicate] ||= []) << st.object - end + @graph.enum_statement.select {|s| s.subject.sameTerm?(subject)}.each do |st| + (properties[st.predicate] ||= []) << st.object end prop_list = sort_properties(properties) prop_list -= [RDF.first, RDF.rest] if @lists.key?(subject) - log_debug("predicateObjectList") {prop_list.inspect} + log_debug("predicateObjectList") { "subject: #{subject.to_sxp}, properties: #{prop_list.join(', ')}" } return 0 if prop_list.empty? @output.write("\n#{indent(2)}") if properties.keys.length > 1 && from_bpl @@ -721,7 +712,7 @@ def resource_in_single_graph?(resource) if resource.variable? graph_names = @repo. enum_statement. - select {|st| st.subject.equal?(resource) || st.object.equal?(resource)}. + select {|st| st.subject.sameTerm?(resource) || st.object.sameTerm?(resource)}. map(&:graph_name) else graph_names = @repo.query({subject: resource}).map(&:graph_name) diff --git a/script/parse b/script/parse index 398c315..460dfa5 100755 --- a/script/parse +++ b/script/parse @@ -57,8 +57,13 @@ def run(input, **options) end else reader = reader_class.new(input, **options[:parser_options]) - repo = RDF::Repository.new << reader + repo = [].extend(RDF::Enumerable, RDF::Queryable) + reader.each_statement {|st| repo << st} num = repo.count + if options[:output_format] == :n3 + # Extra debugging + options[:logger].debug SXP::Generator.string(repo.to_sxp_bin) + end options[:output].puts repo.dump(options[:output_format], prefixes: reader.prefixes, standard_prefixes: true, logger: options[:logger]) end if options[:profile] diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index a3f51f7..9e12f93 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -22,13 +22,12 @@ case t.name when *%w(cwm_syntax_numbers.n3) pending("number representation") - when *%w(space-in-uri) + when *%w(cwm_syntax_space-in-uri.n3) pending("space in URIs") when *%w(cwm_syntax_too-nested.n3) skip("stack overflow") end - t.logger = logger t.logger.info t.inspect t.logger.info "source:\n#{t.input}" diff --git a/spec/suite_turtle_spec.rb b/spec/suite_turtle_spec.rb index c85431b..be88555 100644 --- a/spec/suite_turtle_spec.rb +++ b/spec/suite_turtle_spec.rb @@ -11,6 +11,11 @@ m.entries.each do |t| next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do + case t.name + when *%w(turtle-syntax-bad-struct-05 turtle-syntax-bad-kw-05 turtle-syntax-bad-struct-15) + pending("n3 allows odd predicates") + end + t.logger = RDF::Spec.logger t.logger.info t.inspect t.logger.info "source:\n#{t.input}" diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 2262f68..7c5a70e 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -640,6 +640,7 @@ cwm_other_lists-simple.n3 cwm_syntax_qual-after-user.n3 cwm_other_lists.n3 # empty list with extra properties cwm_includes_concat.n3 new_syntax_inverted_properties.n3 + cwm_other_dec-div.n3 cwm_syntax_sep-term.n3 ) pending "Investigate" when *%w(cwm_math_trigo.ref.n3 @@ -658,7 +659,7 @@ logger.info t.inspect logger.info "source: #{t.input}" repo = parse(t.input, base_uri: t.base, logger: false) - logger.info("sxp: #{repo.statements.map(&:to_sxp).join("\n")}") + logger.info("sxp: #{SXP::Generator.string(repo.to_sxp_bin)}") n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true, logger: logger) g2 = parse(n3, base_uri: t.base, logger: false) expect(g2.isomorphic?(repo)).to produce(true, logger) @@ -667,6 +668,8 @@ next if t.syntax? || t.reason? specify "#{t.name}: #{t.comment} (result)" do case t.name + when *%w(cwm_syntax_path2.n3) + pending "Investigate" when *%w(cwm_syntax_too-nested.n3) skip("stack overflow") end From 7c990d52ad0956d35641315b986392276bf099d7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 5 Aug 2020 15:47:30 -0700 Subject: [PATCH 048/193] Add some canonicalization test cases. --- spec/reader_spec.rb | 76 +++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index bcac198..287db09 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -1188,43 +1188,45 @@ end describe "canonicalization" do -# { -# "" => "", -# "" => "", -# "" => "", -# -# "" => "", -# "" => "", -# -# "" => "", -# "" => "", -# "" => "", -# "" => "", -# -# "" => "", -# "" => "", -# "" => "", -# "" => "", -# -# "" => "", -# "" => "", -# "" => "", -# "" => "", -# -# "" => "", -# "" => "", -# "" => "", -# "" => "", -# -# "" => "", -# "" => "", -# }.each_pair do |input, result| -# 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, format: :n3) -# end -# end + { + "" => "", + "" => "", + "" => "", + + "" => "", + "" => "", + + "" => "", + "" => "", + "" => "", + + "" => "", + "" => "", + "" => "", + "" => "", + + "" => "", + "" => "", + "" => "", + "" => "", + + "" => "", + + %("+1"^^xsd:integer) => %("1"^^), + %(+1) => %("1"^^), + %(.1) => %("0.1"^^), + %(123.E+1) => %("1.23E3"^^), + %(true) => %("true"^^), + %("lang"@EN) => %("lang"@en), + %("""lang"""@EN) => %("lang"@en), + %("""+1"""^^xsd:integer) => %("1"^^), + }.each_pair do |input, result| + it "returns #{result} given #{input}" do + n3 = %(@prefix xsd: . #{input} .) + nt = %( #{result} .) + expect(parse(n3, base_uri: "http://a/b", canonicalize: true)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) + end + end { %("+1"^^xsd:integer) => %("1"^^), From e2d2e7cdb0c6cb3f1efa353d93666e618b75d536 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 5 Aug 2020 15:52:57 -0700 Subject: [PATCH 049/193] Re-enable whitespace in IRIREF tests. --- spec/suite_extended_spec.rb | 6 ------ spec/suite_parser_spec.rb | 2 -- 2 files changed, 8 deletions(-) diff --git a/spec/suite_extended_spec.rb b/spec/suite_extended_spec.rb index 8eb23d9..cf1b3b5 100644 --- a/spec/suite_extended_spec.rb +++ b/spec/suite_extended_spec.rb @@ -29,12 +29,6 @@ pending("variable filter syntax") when *%w(04test_ontology-for-data-model.n3) pending("invalid literal") - #when *%w(cwm_syntax_numbers.n3) - # pending("number representation") - #when *%w(space-in-uri) - # pending("space in URIs") - #when *%w(cwm_syntax_too-nested.n3) - # skip("stack overflow") end diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index 9e12f93..9f66456 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -22,8 +22,6 @@ case t.name when *%w(cwm_syntax_numbers.n3) pending("number representation") - when *%w(cwm_syntax_space-in-uri.n3) - pending("space in URIs") when *%w(cwm_syntax_too-nested.n3) skip("stack overflow") end From 1d249615aac197d1309b557f211f2c0758fc2dc7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 6 Aug 2020 13:37:08 -0700 Subject: [PATCH 050/193] Canonicalize numeric literals if option set. --- lib/rdf/n3/reader.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index e5d0eaa..de60938 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -439,15 +439,15 @@ def read_path def read_literal error("Unexpected end of file", production: :literal) unless token = @lexer.first case token.type || token.value - when :INTEGER then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.integer)} + when :INTEGER then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.integer, canonicalize: canonicalize?)} when :DECIMAL prod(:literal) do value = @lexer.shift.value value = "0#{value}" if value.start_with?(".") - literal(value, datatype: RDF::XSD.decimal) + literal(value, datatype: RDF::XSD.decimal, canonicalize: canonicalize?) end - when :DOUBLE then prod(:literal) {literal(@lexer.shift.value.sub(/\.([eE])/, '.0\1'), datatype: RDF::XSD.double)} - when "true", "false" then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.boolean)} + when :DOUBLE then prod(:literal) {literal(@lexer.shift.value.sub(/\.([eE])/, '.0\1'), datatype: RDF::XSD.double, canonicalize: canonicalize?)} + when "true", "false" then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.boolean, canonicalize: canonicalize?)} when :STRING_LITERAL_QUOTE, :STRING_LITERAL_SINGLE_QUOTE prod(:literal) do value = @lexer.shift.value[1..-2] @@ -809,7 +809,7 @@ def literal(value, **options) "validate: #{validate?.inspect}, " + "c14n?: #{canonicalize?.inspect}" end - RDF::Literal.new(value, validate: validate?, canonicalize: canonicalize?, **options) + RDF::Literal.new(value, validate: validate?, canonicalize: canonicalize?, **options) rescue ArgumentError => e error("Argument Error #{e.message}", production: :literal, token: @lexer.first) end From eb5ad08a00877776e5db35d3c647d9e980872703 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 6 Aug 2020 13:44:48 -0700 Subject: [PATCH 051/193] Remove check that bnode is in a single graph, which is redundant for N3. --- lib/rdf/n3/writer.rb | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 150587c..2c72f71 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -614,7 +614,6 @@ def blankNodePropertyList?(resource, position) !collection?(resource) && (!is_done?(resource) || position == :subject) && ref_count(resource) == (position == :object ? 1 : 0) && - resource_in_single_graph?(resource) && !repo.has_graph?(resource) end @@ -708,19 +707,6 @@ def graph_done(graph_name) @graphs[graph_name] = true end - def resource_in_single_graph?(resource) - if resource.variable? - graph_names = @repo. - enum_statement. - select {|st| st.subject.sameTerm?(resource) || st.object.sameTerm?(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 - # Process a graph projection def with_graph(graph_name) old_lists, @lists = @lists, {} From 2a7425eb42a7b6fb3dcc28eadd7662914868dbe4 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 10 Aug 2020 14:38:57 -0700 Subject: [PATCH 052/193] update log:implies to not require the result to be in the queryable, but that all universal variables in the antecedent are contained in the solution. --- lib/rdf/n3/algebra/log/implies.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 4935f5c..e9c8998 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -26,6 +26,13 @@ def execute(queryable, solutions:, **options) @queryable = queryable log_debug {"logImplies"} @solutions = log_depth {operands.first.execute(queryable, solutions: solutions, **options)} + log_debug {"(logImplies solutions pre-filter) #{@solutions.to_sxp}"} + + # filter solutions where not all variables in antecedant are bound. + vars = operands.first.universal_vars + @solutions = @solutions.filter do |solution| + vars.all? {|v| solution.bound?(v)} + end log_debug {"(logImplies solutions) #{@solutions.to_sxp}"} # Return original solutions, without bindings @@ -50,23 +57,16 @@ def each(&block) return end - # Graph based on solutions from subject - subject_graph = log_depth {RDF::Graph.new {|g| g << subject}} - # Use solutions from subject for object object.solutions = @solutions # 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, graph_name: graph_name)) - end + log_debug("(logImplies implication true; solutions: #{solutions.to_sxp})") + # Yield statements into the default graph + log_depth do + object.each do |statement| + block.call(RDF::Statement.from(statement.to_triple, inferred: true, graph_name: graph_name)) end - else - log_debug("(logImplies implication false)") end end From 35fe7893707af7e3349da6844352badd0b03ed73 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 10 Aug 2020 14:59:30 -0700 Subject: [PATCH 053/193] Implement most remaining string builtins: * containsIgnoringCase * equalIgnoringCase * format * greaterThan * lessThan * matches * notEqualIgnoringCase * notGreaterThan * notLessThan * notMatches * replace * scrape Ignores `encodeForURI`, `encodeForFrag`, and `containsRoughly`. --- README.md | 65 ++++++++++--------- lib/rdf/n3/algebra.rb | 27 ++++---- lib/rdf/n3/algebra/formula.rb | 30 ++++++--- lib/rdf/n3/algebra/notImplemented.rb | 11 ++++ lib/rdf/n3/algebra/str/concatenation.rb | 51 +++++++++++++++ lib/rdf/n3/algebra/str/contains.rb | 9 --- .../n3/algebra/str/containsIgnoringCase.rb | 21 +++++- lib/rdf/n3/algebra/str/endsWith.rb | 9 --- lib/rdf/n3/algebra/str/equalIgnoringCase.rb | 21 +++++- lib/rdf/n3/algebra/str/format.rb | 51 ++++++++++++++- 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 | 21 +++++- lib/rdf/n3/algebra/str/notGreaterThan.rb | 9 --- lib/rdf/n3/algebra/str/notLessThan.rb | 9 --- lib/rdf/n3/algebra/str/notMatches.rb | 26 +++++++- lib/rdf/n3/algebra/str/replace.rb | 58 +++++++++++++++-- lib/rdf/n3/algebra/str/scrape.rb | 55 +++++++++++++++- lib/rdf/n3/algebra/str/startsWith.rb | 56 ---------------- lib/rdf/n3/extensions.rb | 16 +++++ lib/rdf/n3/reasoner.rb | 24 ++++++- script/tc | 23 ++++++- spec/suite_reasoner_spec.rb | 36 +++++----- 24 files changed, 437 insertions(+), 218 deletions(-) create mode 100644 lib/rdf/n3/algebra/notImplemented.rb delete mode 100644 lib/rdf/n3/algebra/str/contains.rb delete mode 100644 lib/rdf/n3/algebra/str/endsWith.rb delete mode 100644 lib/rdf/n3/algebra/str/greaterThan.rb delete mode 100644 lib/rdf/n3/algebra/str/lessThan.rb delete mode 100644 lib/rdf/n3/algebra/str/matches.rb delete mode 100644 lib/rdf/n3/algebra/str/notGreaterThan.rb delete mode 100644 lib/rdf/n3/algebra/str/notLessThan.rb delete mode 100644 lib/rdf/n3/algebra/str/startsWith.rb diff --git a/README.md b/README.md index 3686dba..a6ed12d 100755 --- a/README.md +++ b/README.md @@ -62,21 +62,42 @@ Reasoning is discussed in the [Design Issues][] document. #### RDF List vocabulary - * list:append (not implemented yet - See {RDF::N3::Algebra::List::Append}) - * list:in (not implemented yet - See {RDF::N3::Algebra::List::In}) - * list:last (not implemented yet - See {RDF::N3::Algebra::List::Last}) - * list:member (not implemented yet - See {RDF::N3::Algebra::List::Member}) + * `list:append` (not implemented yet - See {RDF::N3::Algebra::List::Append}) + * `list:in` (not implemented yet - See {RDF::N3::Algebra::List::In}) + * `list:last` (not implemented yet - See {RDF::N3::Algebra::List::Last}) + * `list:member` (not implemented yet - See {RDF::N3::Algebra::List::Member}) #### RDF Log vocabulary - * log:conclusion (not implemented yet - See {RDF::N3::Algebra::Log::Conclusion}) - * log:conjunction (not implemented yet - See {RDF::N3::Algebra::Log::Conjunction}) - * log:equalTo (See {RDF::N3::Algebra::Log::EqualTo}) - * log:implies (See {RDF::N3::Algebra::Log::Implies}) - * log:includes (See {RDF::N3::Algebra::Log::Includes}) - * log:notEqualTo (not implemented yet - See {RDF::N3::Algebra::Log::NotEqualTo}) - * log:notIncludes (not implemented yet - See {RDF::N3::Algebra::Log::NotIncludes}) - * log:outputString (not implemented yet - See {RDF::N3::Algebra::Log::OutputString}) + * `log:conclusion` (not implemented yet - See {RDF::N3::Algebra::Log::Conclusion}) + * `log:conjunction` (not implemented yet - See {RDF::N3::Algebra::Log::Conjunction}) + * `log:equalTo` (See {RDF::N3::Algebra::Log::EqualTo}) + * `log:implies` (See {RDF::N3::Algebra::Log::Implies}) + * `log:includes` (See {RDF::N3::Algebra::Log::Includes}) + * `log:notEqualTo` (not implemented yet - See {RDF::N3::Algebra::Log::NotEqualTo}) + * `log:notIncludes` (not implemented yet - See {RDF::N3::Algebra::Log::NotIncludes}) + * `log:outputString` (not implemented yet - See {RDF::N3::Algebra::Log::OutputString}) + +#### RDF String vocabulary + + * `string:concatenation` (See {RDF::N3::Algebra::Str::Concatenation}) + * `string:contains` (See {SPARQL::Algebra::Operator::Contains}) + * `string:containsIgnoringCase` (See {RDF::N3::Algebra::Str::ContainsIgnoringCase}) + * `string:endsWith` (See {SPARQL::Algebra::Operator::StrEnds}) + * `string:equalIgnoringCase` (See {RDF::N3::Algebra::Str::EqualIgnoringCase}) + * `string:format` (See {RDF::N3::Algebra::Str::Format}) + * `string:greaterThan` (See {SPARQL::Algebra::Operator::GreaterThan}) + * `string:lessThan` (See {SPARQL::Algebra::Operator::LessThan}) + * `string:matches` (See {SPARQL::Algebra::Operator::Regex}) + * `string:notEqualIgnoringCase` (See {RDF::N3::Algebra::Str::NotEqualIgnoringCase}) + * `string:notGreaterThan` (See {SPARQL::Algebra::Operator::LessThanOrEqual}) + * `string:notLessThan` (See {SPARQL::Algebra::Operator::GreaterThanOrEqual}) + * `string:notMatches` (See {RDF::N3::Algebra::Str::NotMatches}) + * `string:replace` (See {RDF::N3::Algebra::Str::Replace}) + * `string:scrape` (See {RDF::N3::Algebra::Str::Scrape}) + * `string:startsWith` (See {SPARQL::Algebra::Operator::StrStarts}) + +### 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: @@ -108,11 +129,6 @@ results in: 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. These rules are processed through a [Parsing Expression Grammar][PEG] parser implemented in the [EBNF gem][]. - -The [meta.rb][file:lib/rdf/n3/reader/meta.rb] file is generated from {file:etc/n3.ebnf N3 EBNF grammar} using a rake task. - ## Dependencies * [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.1, >= 3.1.4) * [EBNF][EBNF gem] (~> 2.1) @@ -128,26 +144,11 @@ Full documentation available on [RubyDoc.info](https://rubydoc.info/github/ruby- * {RDF::N3::Writer} * {RDF::N3::Algebra} * {RDF::N3::Algebra::Formula} - * {RDF::N3::Algebra::List::Append} - * {RDF::N3::Algebra::List::In} - * {RDF::N3::Algebra::List::Last} - * {RDF::N3::Algebra::List::Member} - * {RDF::N3::Algebra::Log::Conclusion} - * {RDF::N3::Algebra::Log::Conjunction} - * {RDF::N3::Algebra::Log::EqualTo} - * {RDF::N3::Algebra::Log::Implies} - * {RDF::N3::Algebra::Log::Includes} - * {RDF::N3::Algebra::Log::NotEqualTo} - * {RDF::N3::Algebra::Log::NotIncludes} - * {RDF::N3::Algebra::Log::OutputString} ### Additional vocabularies -* {RDF::N3::Log} * {RDF::N3::Rei} * {RDF::N3::Crypto} -* {RDF::N3::List} * {RDF::N3::Math} -* {RDF::N3::Str} * {RDF::N3::Time} ## Resources diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 8b4300c..1235790 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -8,6 +8,7 @@ module RDF::N3 # @author [Gregg Kellogg](http://greggkellogg.net/) module Algebra autoload :Formula, 'rdf/n3/algebra/formula' + autoload :NotImplemented, 'rdf/n3/algebra/notImplemented' module List autoload :Append, 'rdf/n3/algebra/list/append' @@ -49,21 +50,13 @@ module Math 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) @@ -81,6 +74,7 @@ def for(uri) RDF::N3::Log.notEqualTo => Log::NotEqualTo, RDF::N3::Log.notIncludes => Log::NotIncludes, RDF::N3::Log.outputString => Log::OutputString, + RDF::N3::Log.supports => NotImplemented, RDF::N3::Math.absoluteValue => Math::AbsoluteValue, RDF::N3::Math.difference => Math::Difference, @@ -101,21 +95,22 @@ def for(uri) RDF::N3::Math.sum => Math::Sum, RDF::N3::Str.concatenation => Str::Concatenation, - RDF::N3::Str.contains => Str::Contains, + RDF::N3::Str.contains => SPARQL::Algebra::Operator::Contains, RDF::N3::Str.containsIgnoringCase => Str::ContainsIgnoringCase, - RDF::N3::Str.endsWith => Str::EndsWith, + RDF::N3::Str.containsRoughly => NotImplemented, + RDF::N3::Str.endsWith => SPARQL::Algebra::Operator::StrEnds, 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.greaterThan => SPARQL::Algebra::Operator::GreaterThan, + RDF::N3::Str.lessThan => SPARQL::Algebra::Operator::LessThan, + RDF::N3::Str.matches => SPARQL::Algebra::Operator::Regex, RDF::N3::Str.notEqualIgnoringCase => Str::NotEqualIgnoringCase, - RDF::N3::Str.notGreaterThan => Str::NotGreaterThan, - RDF::N3::Str.notLessThan => Str::NotLessThan, + RDF::N3::Str.notGreaterThan => SPARQL::Algebra::Operator::LessThanOrEqual, + RDF::N3::Str.notLessThan => SPARQL::Algebra::Operator::GreaterThanOrEqual, RDF::N3::Str.notMatches => Str::NotMatches, RDF::N3::Str.replace => Str::Replace, RDF::N3::Str.scrape => Str::Scrape, - RDF::N3::Str.startsWith => Str::StartsWith, + RDF::N3::Str.startsWith => SPARQL::Algebra::Operator::StrStarts, }[uri] end module_function :for diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 03b44f1..4fa1359 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -27,26 +27,29 @@ class Formula < SPARQL::Algebra::Operator # @return [RDF::Solutions] distinct solutions 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 - bindings = solutions.bindings - log_debug {"(formula bindings) #{bindings.map {|k,v| RDF::Query::Variable.new(k,v)}.to_sxp}"} + log_debug {"(formula bindings) #{solutions.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? ? solutions : queryable.query(@query, solutions: solutions, bindings: bindings, **options) - # Merge solution sets + @solutions = if @query.patterns.empty? + solutions + else + solutions.merge(queryable.query(@query, solutions: solutions, **options)) + end + # Reject solutions which include variables as values - @solutions = @solutions - .merge(options[:solutions]) - .filter {|s| s.enum_value.none?(&:variable?)} + @solutions = @solutions.filter {|s| s.enum_value.none?(&:variable?)} # Use our solutions for sub-ops # Join solutions from other operands log_depth do sub_ops.each do |op| - @solutions = op.execute(queryable, solutions: @solutions) + @solutions = if op.executable? + op.execute(queryable, solutions: @solutions) + else + op.evaluate(@solutions.bindings) == RDF::Literal::TRUE ? @solutions : RDF::Query::Solutions.new + end end end log_debug {"(formula solutions) #{@solutions.to_sxp}"} @@ -171,6 +174,12 @@ def sub_ops @sub_ops ||= operands.reject {|op| op.is_a?(RDF::Statement)} end + ## + # Universal vars in this formula and sub-formulae + def universal_vars + @universals ||= (patterns.vars + sub_ops.vars).reject(&:existential?).uniq + end + ## # Existential vars in this formula def existential_vars @@ -178,6 +187,7 @@ def existential_vars end def to_sxp_bin + raise "a formula can't contain itself" if operands.include?(self) [:formula, graph_name].compact + operands.map(&:to_sxp_bin) end diff --git a/lib/rdf/n3/algebra/notImplemented.rb b/lib/rdf/n3/algebra/notImplemented.rb new file mode 100644 index 0000000..d37bbf8 --- /dev/null +++ b/lib/rdf/n3/algebra/notImplemented.rb @@ -0,0 +1,11 @@ +require 'rdf' + +module RDF::N3::Algebra + # + # A Notation3 Formula combines a graph with a BGP query. + class NotImplemented < SPARQL::Algebra::Operator + def initialize(*args, predicate:, **options) + raise NotImplementedError, "The #{predicate} operator is not implemented" + end + end +end \ No newline at end of file diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb index ac8b500..a387098 100644 --- a/lib/rdf/n3/algebra/str/concatenation.rb +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -1,9 +1,60 @@ module RDF::N3::Algebra::Str ## # The subject is a list of strings. The object is calculated as a concatenation of those strings. + # + # @example + # ("a" "b") string:concatenation :s class Concatenation < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :strConcatenation + + ## + # The string:concatenation operator takes a list of terms evaluating to strings and either binds the result of concatenating them to the output variable, removes a solution that does equal. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + # @raise [TypeError] if operands are not compatible + def execute(queryable, solutions:, **options) + list = operand(0) + result = operand(1) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + + raise TypeError, "operand is not a list" unless list.list? && list.valid? + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + bound_entries = list.to_a.map {|op| op.evaluate(solution.bindings)} + + if bound_entries.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + rhs = bound_entries.map(&:value).join("") + + if result.variable? + solution.merge(result.to_sym => RDF::Literal(rhs)) + elsif result != rhs + nil + else + solution + end + end + end.compact) + end + + ## + # Does not yield statements. + # + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + def each(&block) + end end end diff --git a/lib/rdf/n3/algebra/str/contains.rb b/lib/rdf/n3/algebra/str/contains.rb deleted file mode 100644 index 6f38c1e..0000000 --- a/lib/rdf/n3/algebra/str/contains.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 index 01c00ce..cc8c6f7 100644 --- a/lib/rdf/n3/algebra/str/containsIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/containsIgnoringCase.rb @@ -1,9 +1,26 @@ 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 SPARQL::Algebra::Evaluatable include RDF::Util::Logger NAME = :strContainsIgnoringCase + + ## + # True iff the subject string contains the object string, with the comparison done ignoring the difference between upper case and lower case characters. + # + # @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.downcase.include?(right.to_s.downcase) then RDF::Literal::TRUE + else RDF::Literal::FALSE + end + end end end diff --git a/lib/rdf/n3/algebra/str/endsWith.rb b/lib/rdf/n3/algebra/str/endsWith.rb deleted file mode 100644 index f7ec877..0000000 --- a/lib/rdf/n3/algebra/str/endsWith.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 index fe51875..0b82ef5 100644 --- a/lib/rdf/n3/algebra/str/equalIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/equalIgnoringCase.rb @@ -1,9 +1,26 @@ 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 SPARQL::Algebra::Evaluatable include RDF::Util::Logger NAME = :strEqualIgnoringCase + + ## + # True iff the subject string is the same as object string ignoring differences between upper and lower case. + # + # @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.downcase == right.to_s.downcase then RDF::Literal::TRUE + else RDF::Literal::FALSE + end + end end end diff --git a/lib/rdf/n3/algebra/str/format.rb b/lib/rdf/n3/algebra/str/format.rb index 61724bf..e7b2161 100644 --- a/lib/rdf/n3/algebra/str/format.rb +++ b/lib/rdf/n3/algebra/str/format.rb @@ -1,9 +1,56 @@ 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 SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :strFormat + + ## + # 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. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + # @raise [TypeError] if operands are not compatible + def execute(queryable, solutions:, **options) + list = operand(0) + result = operand(1) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + + raise TypeError, "operand is not a list" unless list.list? && list.valid? + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + bound_entries = list.to_a.map {|op| op.evaluate(solution.bindings)} + + if bound_entries.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + format, *args = bound_entries.map(&:value) + str = format % args + + if result.variable? + solution.merge(result.to_sym => str) + elsif result != str + nil + else + solution + end + end + end.compact) + end + + ## + # Does not yield statements. + # + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + def each(&block) + end end end diff --git a/lib/rdf/n3/algebra/str/greaterThan.rb b/lib/rdf/n3/algebra/str/greaterThan.rb deleted file mode 100644 index 488606a..0000000 --- a/lib/rdf/n3/algebra/str/greaterThan.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index fabbb90..0000000 --- a/lib/rdf/n3/algebra/str/lessThan.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index cc5b6e7..0000000 --- a/lib/rdf/n3/algebra/str/matches.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 index 7795f6c..039bf1d 100644 --- a/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb @@ -1,9 +1,26 @@ 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 SPARQL::Algebra::Evaluatable include RDF::Util::Logger NAME = :strNotEqualIgnoringCase + + ## + # True iff the subject string is the NOT same as object string ignoring differences between upper and lower case. + # + # @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.downcase != right.to_s.downcase then RDF::Literal::TRUE + else RDF::Literal::FALSE + end + end end end diff --git a/lib/rdf/n3/algebra/str/notGreaterThan.rb b/lib/rdf/n3/algebra/str/notGreaterThan.rb deleted file mode 100644 index fba338c..0000000 --- a/lib/rdf/n3/algebra/str/notGreaterThan.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 81ca196..0000000 --- a/lib/rdf/n3/algebra/str/notLessThan.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 index 64ca466..db272f2 100644 --- a/lib/rdf/n3/algebra/str/notMatches.rb +++ b/lib/rdf/n3/algebra/str/notMatches.rb @@ -1,9 +1,31 @@ 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 SPARQL::Algebra::Evaluatable include RDF::Util::Logger NAME = :strNotMatches + + ## + # The subject string; the object is a regular expression in the perl, python style. It is true iff the string does NOT match the regexp. + # + # @param [RDF::Literal] text + # a simple literal + # @param [RDF::Literal] pattern + # a simple literal + # @return [RDF::Literal::Boolean] `true` or `false` + # @raise [TypeError] if any operand is unbound + # @raise [TypeError] if any operand is not a simple literal + def apply(text, pattern) + # @see https://www.w3.org/TR/xpath-functions/#regex-syntax + raise TypeError, "expected a plain RDF::Literal, but got #{text.inspect}" unless text.is_a?(RDF::Literal) && text.plain? + text = text.to_s + # TODO: validate text syntax + + # @see https://www.w3.org/TR/xpath-functions/#regex-syntax + raise TypeError, "expected a plain RDF::Literal, but got #{pattern.inspect}" unless pattern.is_a?(RDF::Literal) && pattern.plain? + pattern = pattern.to_s + + RDF::Literal(!Regexp.new(pattern).match?(text)) + end end end diff --git a/lib/rdf/n3/algebra/str/replace.rb b/lib/rdf/n3/algebra/str/replace.rb index bc2ec47..4a5a8a7 100644 --- a/lib/rdf/n3/algebra/str/replace.rb +++ b/lib/rdf/n3/algebra/str/replace.rb @@ -1,12 +1,60 @@ 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 SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :strReplace + + ## + # 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 replaced string. + # + # @example + # ("fofof bar", "of", "baz") string:replace "fbazbaz bar" + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + # @raise [TypeError] if operands are not compatible + def execute(queryable, solutions:, **options) + list = operand(0) + result = operand(1) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + + raise TypeError, "operand is not a list" unless list.list? && list.valid? + raise TypeError, "list must have exactly three entries" unless list.length == 3 + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + bound_entries = list.to_a.map {|op| op.evaluate(solution.bindings)} + + if bound_entries.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + input, old_str, new_str = bound_entries + output = input.to_s.gsub(old_str.to_s, new_str.to_s) + + if result.variable? + solution.merge(result.to_sym => RDF::Literal(output)) + elsif result != output + nil + else + solution + end + end + end.compact) + end + + ## + # Does not yield statements. + # + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + def each(&block) + end end end diff --git a/lib/rdf/n3/algebra/str/scrape.rb b/lib/rdf/n3/algebra/str/scrape.rb index a941651..cc458fa 100644 --- a/lib/rdf/n3/algebra/str/scrape.rb +++ b/lib/rdf/n3/algebra/str/scrape.rb @@ -1,9 +1,60 @@ 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 SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :strScrape + + ## + # 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 the part of the first string which matches the group. + # + # @example + # ("abcdef" "ab(..)ef") string:scrape "cd" + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + # @raise [TypeError] if operands are not compatible + def execute(queryable, solutions:, **options) + list = operand(0) + result = operand(1) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + + raise TypeError, "operand is not a list" unless list.list? && list.valid? + raise TypeError, "list must have exactly two entries" unless list.length == 2 + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + bound_entries = list.to_a.map {|op| op.evaluate(solution.bindings)} + + if bound_entries.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + input, regex = bound_entries + md = Regexp.new(regex.to_s).match(input.to_s) + + if result.variable? && md && md[1] + solution.merge(result.to_sym => RDF::Literal(md[1])) + elsif !md || result != md[1] + nil + else + solution + end + end + end.compact) + end + + ## + # Does not yield statements. + # + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + def each(&block) + end end end diff --git a/lib/rdf/n3/algebra/str/startsWith.rb b/lib/rdf/n3/algebra/str/startsWith.rb deleted file mode 100644 index cda654c..0000000 --- a/lib/rdf/n3/algebra/str/startsWith.rb +++ /dev/null @@ -1,56 +0,0 @@ -module RDF::N3::Algebra::Str - ## - # True iff the subject string starts with the object string. - class StartsWith < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - - NAME = :strStartsWith - - ## - # The string:startsWith operator corresponds to the XPath fn:starts-with function. The arguments must be argument compatible otherwise an error is raised. - # - # For constant inputs that evaulate to true, the original solutions are returned. - # - # For constant inputs that evaluate to false, the empty solution set is returned. XXX - # - # 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 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 - - ## - # 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; parent.graph_name; end - end -end diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index ac26e07..97fe7eb 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -22,6 +22,22 @@ def contain?(other) end end + class RDF::List + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + to_a.to_sxp_bin + end + + ## + # Returns an S-Expression (SXP) representation + # + # @return [String] + def to_sxp + to_sxp_bin.to_sxp + end + end + class RDF::Statement # Transform Statement into an SXP # @return [Array] diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index b145b05..5c0f24d 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -66,7 +66,7 @@ def initialize(input, **options, &block) else RDF::Repository.new end - log_debug("reasoner: expression", **options) {SXP::Generator.string(formula.to_sxp_bin)} + log_debug("reasoner: expression") {SXP::Generator.string(formula.to_sxp_bin)} if block_given? case block.arity @@ -260,7 +260,7 @@ def formula 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, parent: form, **@options) + form.operands << klass.new(fs, fo, parent: form, predicate: pattern.predicate, **@options) else # Add formulae as direct operators if formulae.has_key?(pattern.subject) @@ -274,6 +274,26 @@ def formula end end + # Bind formula operands which are lists to RDF::List + formulae.each do |gn, form| + form_graph = RDF::Graph.new do |g| + form.operands.each do |op| + g << op if op.is_a?(RDF::Statement) + end + end + form.operands.each do |op| + next unless op.is_a?(SPARQL::Algebra::Operator) + op.operands.map! do |operand| + if operand.is_a?(RDF::Node) + ln = RDF::List.new(subject: operand, graph: form_graph) + ln.valid? ? ln : operand + else + operand + end + end + end + end + # Formula is that without a graph name formulae[nil] end diff --git a/script/tc b/script/tc index bf02bbe..916eed3 100755 --- a/script/tc +++ b/script/tc @@ -42,7 +42,7 @@ def run_tc(man, tc, **options) if options[:verbose] STDERR.puts "\nTestCase: #{tc.inspect}" STDERR.puts "\nInput:\n" + tc.input - STDERR.puts "\nExpected:\n" + tc.result if tc.result && tc.positiveTest? + STDERR.puts "\nExpected:\n" + tc.expected if tc.result && tc.positive_test? end return if tc.approval == "rdft:Rejected" @@ -95,6 +95,27 @@ def run_tc(man, tc, **options) STDERR.puts "Unexpected exception reading result: #{e.inspect}" result = "failed" end + elsif tc.reason? && result.nil? + reasoner = RDF::N3::Reasoner.new(graph, **options) + + repo = RDF::Repository.new + + begin + reasoner.execute(logger: logger, think: !!tc.options['think']) + if tc.options["filter"] + repo << reasoner.conclusions + elsif tc.options["data"] + repo << reasoner.data + else + repo << reasoner + end + rescue Exception => e + STDERR.puts "Unexpected exception reasoning: #{e.inspect}" + result = "failed" + end + + output_graph = RDF::Repository.load(tc.result, base_uri: tc.base) + result = repo.isomorphic_with?(output_graph) ? "passed" : "failed" else result ||= "passed" end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 253c032..4c99ab8 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -7,11 +7,11 @@ require_relative 'suite_helper' let(:logger) {RDF::Spec.logger} - after(:each) do |example| - puts logger.to_s if - example.exception && - !example.exception.is_a?(RSpec::Expectations::ExpectationNotMetError) - end + #after(:each) do |example| + # puts logger.to_s if + # example.exception && + # !example.exception.is_a?(RSpec::Expectations::ExpectationNotMetError) + #end Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/N3/tests/N3Tests/manifest-reasoner.ttl") do |m| describe m.label do @@ -19,23 +19,17 @@ next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do case t.id.split('#').last - when *%w{listin bnode concat t2006} + when *%w{cwm_includes_listin cwm_includes_bnode cwm_includes_concat + cwm_includes_conjunction cwm_includes_conclusion_simple + cwm_list_append cwm_list_last} pending "support for lists" - when *%w{t1018b2 t103 t104 t105 concat} - pending "support for string" - when *%w{t06proof} + when *%w{cwm_unify_unify1 cwm_unify_unify2} + pending "reason over formulae" + when *%w{cwm_reason_t6} pending "support for math" - when *%w{t01} - 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" - when *%w{t553 t554} - pending "support for inference over quoted graphs" - when *%w{t2005} - pending "something else" - when *%w{t2004u5 t2007 t12} + when *%w{cwm_supports_simple cwm_string_roughly cwm_string_uriEncode} + pending "Uses unsupported builtin" + when *%w{cwm_reason_t2 cwm_list_builtin_generated_match cwm_reason_double} skip("Not allowed with new grammar") end @@ -47,7 +41,7 @@ base_uri: t.base, canonicalize: false, validate: false, - logger: t.logger) + logger: false) reasoner = RDF::N3::Reasoner.new(reader, base_uri: t.base, From 9589d8cccffc40c5afba0310c68d230a1dd10d9c Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 11 Aug 2020 17:31:19 -0700 Subject: [PATCH 054/193] list:in, which added different methods for evaluating bound lists, and folding lists into formulae as resources. --- lib/rdf/n3/algebra/formula.rb | 27 ++++++++----- lib/rdf/n3/algebra/list/in.rb | 47 +++++++++++++++++++++- lib/rdf/n3/algebra/log/implies.rb | 9 ++--- lib/rdf/n3/extensions.rb | 27 +++++++++++++ lib/rdf/n3/reasoner.rb | 2 + lib/rdf/n3/refinements.rb | 7 ++++ script/tc | 1 + spec/reasoner_spec.rb | 65 +++++++++++++++++++++++++++++++ spec/suite_reasoner_spec.rb | 2 + 9 files changed, 170 insertions(+), 17 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 4fa1359..9abb076 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -26,8 +26,8 @@ class Formula < SPARQL::Algebra::Operator # optional initial solutions for chained queries # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options) - log_debug {"formula #{graph_name} #{operands.to_sxp}"} - log_debug {"(formula bindings) #{solutions.bindings.map {|k,v| RDF::Query::Variable.new(k,v)}.to_sxp}"} + log_debug("formula #{graph_name}") {operands.to_sxp} + log_debug("(formula bindings)") { solutions.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! @@ -35,7 +35,16 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new @solutions = if @query.patterns.empty? solutions else - solutions.merge(queryable.query(@query, solutions: solutions, **options)) + these_solutions = queryable.query(@query, solutions: solutions, **options) + # Replace blank node bindings with lists, where those blank nodes are associated with lists + these_solutions.map! do |solution| + RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)| + l = RDF::List.new(subject: value, graph: queryable) unless value.list? + value = l if l && l.valid? + memo.merge(name => value) + end) + end + solutions.merge(these_solutions) end # Reject solutions which include variables as values @@ -52,7 +61,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new end end end - log_debug {"(formula solutions) #{@solutions.to_sxp}"} + 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?('$$')} @@ -71,11 +80,11 @@ def each(&block) # If there are no solutions, create a single solution RDF::Query::Solutions(RDF::Query::Solution.new) end - log_debug {"formula #{graph_name} each #{@solutions.to_sxp}"} + log_debug("formula #{graph_name} each") {@solutions.to_sxp} # Yield constant statements/patterns constants.each do |pattern| - log_debug {"(formula constant) #{pattern.to_sxp}"} + #log_debug {"(formula constant) #{pattern.to_sxp}"} block.call(RDF::Statement.from(pattern, graph_name: graph_name)) end @@ -87,7 +96,7 @@ def each(&block) solution[var.name] ||= RDF::Node.intern(var.name.to_s.sub(/^\$+/, '')) end - log_debug {"(formula apply) #{solution.to_sxp} to BGP"} + log_debug("(formula apply)") {solution.to_sxp} # Yield each variable statement which is constant after applying solution patterns.each do |pattern| terms = {} @@ -105,11 +114,11 @@ def each(&block) statement.predicate.literal? || statement.subject.is_a?(SPARQL::Algebra::Operator) || statement.object.is_a?(SPARQL::Algebra::Operator) - log_debug {"(formula skip) #{statement.to_sxp}"} + log_debug("(formula skip)") {statement.to_sxp} next end - log_debug {"(formula add) #{statement.to_sxp}"} + #log_debug {"(formula add) #{statement.to_sxp}"} block.call(statement) end end diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index 10aed00..2c0c77c 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -1,9 +1,52 @@ 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 SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :listIn + + ## + ## + # Evaluates this operator using the given variable `bindings`. + # If the first operand is a variable, it creates a solution for each element in the list. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + # @raise [TypeError] if operands are not compatible + def execute(queryable, solutions:, **options) + @solutions = RDF::Query::Solutions(solutions.map do |solution| + subject = operand(0) + list = operand(1) + + case list + when RDF::Node, RDF::Query::Variable + # Attempt to bind a node to a list + list = list.evaluate(solution.bindings) + when RDF::List + # Attempt to bind list elements + list = list.to_a.map {|op| op.evaluate(solution.bindings)} + end + log_debug(NAME) {"subject: #{subject.to_sxp}, list: #{list.to_sxp}"} + + if list.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + if subject.variable? + # Bind all list entries to this solution, creates an array of solutions + list.to_a.map do |term| + solution.merge(subject.to_sym => term) + end + elsif list.to_a.include?(subject) + solution + else + nil + end + end + end.flatten.compact) + end end end diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index e9c8998..6a81069 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -48,14 +48,11 @@ def execute(queryable, solutions:, **options) # @yieldreturn [void] ignored def each(&block) @solutions ||= RDF::Query::Solutions.new - log_debug {"logImplies each #{@solutions.to_sxp}"} subject, object = operands - if @solutions.empty? - # Some evalaluatable operand evaluated to false - log_debug("(logImplies implication false - no solutions)") - return - end + return if @solutions.empty? + + log_debug {"logImplies each #{@solutions.to_sxp}"} # Use solutions from subject for object object.solutions = @solutions diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index 97fe7eb..0082c0b 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -23,6 +23,19 @@ def contain?(other) end class RDF::List + ## + # Evaluates the list using the given variable `bindings`. + # + # @param [RDF::Query::Solution] bindings + # a query solution containing zero or more variable bindings + # @param [Hash{Symbol => Object}] options ({}) + # options passed from query + # @return [RDF::List] + # @see SPARQL::Algebra::Expression.evaluate + def evaluate(bindings, **options) + RDF::List[*to_a.map {|o| o.evaluate(bindings, **options)}] + end + # Transform Statement into an SXP # @return [Array] def to_sxp_bin @@ -54,6 +67,20 @@ def to_sxp end end + class RDF::Node + # Either binds to variable, or returns itself. + # + # @param [RDF::Query::Solution] bindings + # a query solution containing zero or more variable bindings + # @param [Hash{Symbol => Object}] options ({}) + # options passed from query + # @return [RDF::Term] + # def evaluate(bindings, **options); end + def evaluate(bindings, **options) + bindings.fetch(self.id.to_sym, self) + end + end + class RDF::Query::Solution # Transform Statement into an SXP # @return [Array] diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 5c0f24d..0a546a5 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -287,6 +287,8 @@ def formula if operand.is_a?(RDF::Node) ln = RDF::List.new(subject: operand, graph: form_graph) ln.valid? ? ln : operand + elsif operand.is_a?(RDF::URI) && operand == RDF.nil + RDF::List::NIL else operand end diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb index e2bbd11..344693e 100644 --- a/lib/rdf/n3/refinements.rb +++ b/lib/rdf/n3/refinements.rb @@ -86,6 +86,13 @@ def sameTerm?(other) end end + # @!parse + # # Refinements on RDF::Query::Variable + # class ::RDF::Query::Variable + # # Adds `#sameTerm?` which is the same as `#eql?`, except for variables. + # # @return [Boolean] + # def sameTerm?; end + # end refine ::RDF::Query::Variable do ## # True if the other is the same variable diff --git a/script/tc b/script/tc index 916eed3..9841c6d 100755 --- a/script/tc +++ b/script/tc @@ -113,6 +113,7 @@ def run_tc(man, tc, **options) STDERR.puts "Unexpected exception reasoning: #{e.inspect}" result = "failed" end + STDERR.puts "\nResult: #{repo.dump(:n3)}" if options[:verbose] output_graph = RDF::Repository.load(tc.result, base_uri: tc.base) result = repo.isomorphic_with?(output_graph) ? "passed" : "failed" diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 7a73e21..5cb1f13 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -84,6 +84,71 @@ end end + context "n3:list" do + context "list:in" do + { + "1 in (1)": { + input: %( + @prefix list: . + + { 1 list:in (1) } => { :test4a a :SUCCESS }. + ), + expect: %( + :test4a a :SUCCESS. + ) + }, + "1 in ( 1 2 3 4 5)": { + input: %( + @prefix list: . + + { 1 list:in ( 1 2 3 4 5 ) } => { :test4a a :SUCCESS }. + ), + expect: %( + :test4a a :SUCCESS. + ) + }, + "1 in ()": { + input: %( + @prefix list: . + + { 1 list:in () } => { :trap1 a :FAILURE }. + ), + expect: %( + ) + }, + "2 in ( 1 2 3 4 5)": { + input: %( + @prefix list: . + + { 2 list:in ( 1 2 3 4 5 ) } => { :test4b a :SUCCESS }. + ), + expect: %( + :test4b a :SUCCESS. + ) + }, + "thing1 :prop1": { + input: %( + @prefix list: . + + :thing1 :prop1 ( :test5a :test5b :test5c ) . + { ?item list:in [ is :prop1 of :thing1 ] } => { ?item a :SUCCESS } . + ), + expect: %( + :test5a a :SUCCESS. + :test5b a :SUCCESS. + :test5c a :SUCCESS. + ) + } + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end + end + context "n3:string" do context "string:startsWith" do { diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 4c99ab8..f2e2213 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -62,6 +62,8 @@ rescue Exception => e expect(e.message).to produce("Not exception #{e.inspect}: #{e.backtrace.join("\n")}", t) end + + t.logger.info "result:\n#{repo.dump(:n3)}" 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) From 75517226468d2dd7698771d2d028cc6e3634c8bf Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 12 Aug 2020 13:53:57 -0700 Subject: [PATCH 055/193] Added implementations for list:in, let:member, and list:last. --- README.md | 6 ++-- lib/rdf/n3/algebra/list/in.rb | 10 +++++-- lib/rdf/n3/algebra/list/last.rb | 47 +++++++++++++++++++++++++++++- lib/rdf/n3/algebra/list/member.rb | 48 ++++++++++++++++++++++++++++++- spec/suite_reasoner_spec.rb | 2 +- 5 files changed, 104 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a6ed12d..b76953e 100755 --- a/README.md +++ b/README.md @@ -63,9 +63,9 @@ Reasoning is discussed in the [Design Issues][] document. #### RDF List vocabulary * `list:append` (not implemented yet - See {RDF::N3::Algebra::List::Append}) - * `list:in` (not implemented yet - See {RDF::N3::Algebra::List::In}) - * `list:last` (not implemented yet - See {RDF::N3::Algebra::List::Last}) - * `list:member` (not implemented yet - See {RDF::N3::Algebra::List::Member}) + * `list:in` (See {RDF::N3::Algebra::List::In}) + * `list:last` (See {RDF::N3::Algebra::List::Last}) + * `list:member` (See {RDF::N3::Algebra::List::Member}) #### RDF Log vocabulary diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index 2c0c77c..c305a76 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -1,4 +1,9 @@ module RDF::N3::Algebra::List + ## + # Iff the object is a list and the subject is in that list, then this is true. + # + # @example + # { 1 list:in ( 1 2 3 4 5 ) } => { :test4a a :SUCCESS }. class In < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Query include SPARQL::Algebra::Update @@ -7,7 +12,6 @@ class In < SPARQL::Algebra::Operator::Binary NAME = :listIn - ## ## # Evaluates this operator using the given variable `bindings`. # If the first operand is a variable, it creates a solution for each element in the list. @@ -23,7 +27,7 @@ def execute(queryable, solutions:, **options) case list when RDF::Node, RDF::Query::Variable - # Attempt to bind a node to a list + # Attempt to bind a node or variable to a list list = list.evaluate(solution.bindings) when RDF::List # Attempt to bind list elements @@ -31,7 +35,7 @@ def execute(queryable, solutions:, **options) end log_debug(NAME) {"subject: #{subject.to_sxp}, list: #{list.to_sxp}"} - if list.any? {|op| op.variable? && op.unbound?} + if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements solution else diff --git a/lib/rdf/n3/algebra/list/last.rb b/lib/rdf/n3/algebra/list/last.rb index 301f655..2001915 100644 --- a/lib/rdf/n3/algebra/list/last.rb +++ b/lib/rdf/n3/algebra/list/last.rb @@ -1,11 +1,56 @@ module RDF::N3::Algebra::List ## - # Iff the suject is a list and the obbject is the last thing that list, then this is true. + # Iff the suject is a list and the object is the last thing that list, then this is true. The object can be calculated as a function of the list. + # + # @example + # { ( 1 2 3 4 5 6 ) list:last 6 } => { :test1 a :SUCCESS }. # # The object can be calculated as a function of the list. class Last < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :listLast + + ## + # Evaluates this operator using the given variable `bindings`. + # If the last operand is a variable, it creates a solution for each element in the list. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + # @raise [TypeError] if operands are not compatible + def execute(queryable, solutions:, **options) + @solutions = RDF::Query::Solutions(solutions.map do |solution| + list = operand(0) + result = operand(1) + + case list + when RDF::Node, RDF::Query::Variable + # Attempt to bind a node or variable to a list + list = list.evaluate(solution.bindings) + when RDF::List + # Attempt to bind list elements + list = list.to_a.map {|op| op.evaluate(solution.bindings)} + end + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + + #require 'byebug'; byebug + if list.to_a.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + if result.variable? + solution.merge(result.to_sym => list.last) + elsif list.last == result + solution + else + nil + end + end + end.compact) + end end end diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index 9633743..27e65e9 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -1,7 +1,53 @@ module RDF::N3::Algebra::List ## - # Iff the subject is a list and the obbject is in that list, then this is true. + # Iff the subject is a list and the object is in that list, then this is true. class Member < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + NAME = :listMember + + ## + # Evaluates this operator using the given variable `bindings`. + # If the last operand is a variable, it creates a solution for each element in the list. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + # @raise [TypeError] if operands are not compatible + def execute(queryable, solutions:, **options) + @solutions = RDF::Query::Solutions(solutions.map do |solution| + list = operand(0) + result = operand(1) + + case list + when RDF::Node, RDF::Query::Variable + # Attempt to bind a node or variable to a list + list = list.evaluate(solution.bindings) + when RDF::List + # Attempt to bind list elements + list = list.to_a.map {|op| op.evaluate(solution.bindings)} + end + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + + if list.to_a.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + if result.variable? + # Bind all list entries to this solution, creates an array of solutions + list.to_a.map do |term| + solution.merge(result.to_sym => term) + end + elsif list.to_a.include?(result) + solution + else + nil + end + end + end.flatten.compact) + end end end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index f2e2213..4793a3e 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -21,7 +21,7 @@ case t.id.split('#').last when *%w{cwm_includes_listin cwm_includes_bnode cwm_includes_concat cwm_includes_conjunction cwm_includes_conclusion_simple - cwm_list_append cwm_list_last} + cwm_list_append} pending "support for lists" when *%w{cwm_unify_unify1 cwm_unify_unify2} pending "reason over formulae" From 56eafaf8394557274e5de7604c607ef89736e01e Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 15 Aug 2020 13:42:39 -0700 Subject: [PATCH 056/193] Add Queryable#as_list and List#variable?. --- lib/rdf/n3/algebra/formula.rb | 17 +++++------ lib/rdf/n3/algebra/log/implies.rb | 4 +-- lib/rdf/n3/extensions.rb | 48 ++++++++++++++++++++++++++++--- lib/rdf/n3/writer.rb | 12 ++++---- 4 files changed, 59 insertions(+), 22 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 9abb076..577b70c 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -36,11 +36,11 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new solutions else these_solutions = queryable.query(@query, solutions: solutions, **options) - # Replace blank node bindings with lists, where those blank nodes are associated with lists these_solutions.map! do |solution| RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)| - l = RDF::List.new(subject: value, graph: queryable) unless value.list? - value = l if l && l.valid? + # Replace blank node bindings with lists, where those blank nodes are associated with lists + l = queryable.as_list(value) unless value.list? + value = l if l memo.merge(name => value) end) end @@ -100,8 +100,8 @@ def each(&block) # 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) + [:subject, :predicate, :object].each do |part| + terms[part] = case o = pattern.send(part) when RDF::Query::Variable then solution[o] else o end @@ -110,15 +110,12 @@ def each(&block) statement = RDF::Statement.from(terms) # Sanity checking on statement - if statement.variable? || - statement.predicate.literal? || - statement.subject.is_a?(SPARQL::Algebra::Operator) || - statement.object.is_a?(SPARQL::Algebra::Operator) + if statement.variable? log_debug("(formula skip)") {statement.to_sxp} next end - #log_debug {"(formula add) #{statement.to_sxp}"} + #log_debug("(formula add)") {statement.to_sxp} block.call(statement) end end diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 6a81069..2ef772c 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -26,14 +26,14 @@ def execute(queryable, solutions:, **options) @queryable = queryable log_debug {"logImplies"} @solutions = log_depth {operands.first.execute(queryable, solutions: solutions, **options)} - log_debug {"(logImplies solutions pre-filter) #{@solutions.to_sxp}"} + log_debug("(logImplies solutions pre-filter)") {@solutions.to_sxp} # filter solutions where not all variables in antecedant are bound. vars = operands.first.universal_vars @solutions = @solutions.filter do |solution| vars.all? {|v| solution.bound?(v)} end - log_debug {"(logImplies solutions) #{@solutions.to_sxp}"} + log_debug("(logImplies solutions)") {@solutions.to_sxp} # Return original solutions, without bindings solutions diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index 0082c0b..c18123e 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -22,7 +22,23 @@ def contain?(other) end end - class RDF::List + module Queryable + ## + # Return the RDF::List representation of the resource from self, with any recursive lists represented as RDF::List. + # + # @param [RDF::Resource] subject + # @return [RDF::List, RDF::Resource] returns either the original resource, or a list based on that resource + def as_list(subject) + return subject unless subject.node? || subject.uri? && subject == RDF.nil + ln = RDF::List.new(subject: subject, graph: self) + return subject unless ln.valid? + + # Return a new list, outside of this queryable, with any embedded lists also expanded + RDF::List.new(subject: subject, values: ln.to_a.map {|li| self.as_list(li)}) + end + end + + class List ## # Evaluates the list using the given variable `bindings`. # @@ -36,6 +52,14 @@ def evaluate(bindings, **options) RDF::List[*to_a.map {|o| o.evaluate(bindings, **options)}] end + ## + # A list is variable if any of its members are variable? + # + # @return [Boolean] + def variable? + to_a.any?(&:variable?) + end + # Transform Statement into an SXP # @return [Array] def to_sxp_bin @@ -51,7 +75,7 @@ def to_sxp end end - class RDF::Statement + class Statement # Transform Statement into an SXP # @return [Array] def to_sxp_bin @@ -67,7 +91,15 @@ def to_sxp end end - class RDF::Node + module Term + ## + # Is this the same term? Like `#eql?`, but no variable matching + def sameTerm?(other) + eql?(other) + end + end + + class Node # Either binds to variable, or returns itself. # # @param [RDF::Query::Solution] bindings @@ -81,7 +113,7 @@ def evaluate(bindings, **options) end end - class RDF::Query::Solution + class Query::Solution # Transform Statement into an SXP # @return [Array] def to_sxp_bin @@ -96,4 +128,12 @@ def to_sxp to_sxp_bin.to_sxp end end + + class Query::Variable + ## + # True if the other is the same variable + def sameTerm?(other) + other.is_a?(::RDF::Query::Variable) && name.eql?(other.name) + end + end end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 2c72f71..8ce37b0 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -159,7 +159,7 @@ def write_epilogue self.reset - log_debug {"\nserialize: repo: #{repo.size}"} + log_debug("\nserialize: repo:") {repo.size} preprocess @@ -330,20 +330,20 @@ def format_node(node, **options) def start_document @output.write("@base <#{base_uri}> .\n") unless base_uri.to_s.empty? - log_debug {"start_document: prefixes #{prefixes.inspect}"} + log_debug("start_document: prefixes") { prefixes.inspect} prefixes.keys.sort_by(&:to_s).each do |prefix| @output.write("@prefix #{prefix}: <#{prefixes[prefix]}> .\n") end # Universals and extentials at top-level unless @universals.empty? - log_debug {"start_document: universals #{@universals.inspect}"} + log_debug("start_document: universals") { @universals.inspect} terms = @universals.map {|v| format_uri(RDF::URI(v.name.to_s))} @output.write("@forAll #{terms.join(', ')} .\n") end unless @existentials.empty? - log_debug {"start_document: universals #{@existentials.inspect}"} + log_debug("start_document: existentials") { @existentials.inspect} terms = @existentials.map {|v| format_uri(RDF::URI(v.name.to_s))} @output.write("@forSome #{terms.join(', ')} .\n") end @@ -437,7 +437,7 @@ def preprocess # prefixes. # @param [Statement] statement def preprocess_statement(statement) - #log_debug {"preprocess: #{statement.inspect}"} + #log_debug("preprocess") {statement.inspect} # Pre-fetch pnames, to fill prefixes get_pname(statement.subject) @@ -511,7 +511,7 @@ def collection(node, position) subject_done(node) index = 0 list.each do |li| - log_debug {" list first: #{li}[#{position}]"} + log_debug("(list first)") {"#{li}[#{position}]"} @output.write(" ") if index > 0 path(li, position) subject_done(li) From ad3251960826fb7c5f282d600a8d74f3f89f738a Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 15 Aug 2020 13:46:17 -0700 Subject: [PATCH 057/193] Refine List#each_statement to be recursive if an element is, itself, a List. --- lib/rdf/n3/refinements.rb | 53 +++++++++++++++++++++------------------ spec/reasoner_spec.rb | 2 +- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb index 344693e..c63d844 100644 --- a/lib/rdf/n3/refinements.rb +++ b/lib/rdf/n3/refinements.rb @@ -71,33 +71,38 @@ def valid? end end - # @!parse - # # Refinements on RDF::Term - # class ::RDF::Term - # # Adds `#sameTerm?` which is the same as `#eql?`, except for variables. - # # @return [Boolean] - # def sameTerm?; end - # end - refine ::RDF::Term do + refine ::RDF::List do + # Allow a list to be treated as a term in a statement. + include ::RDF::Term + ## - # Is this the same term? Like `#eql?`, but no variable matching - def sameTerm?(other) - eql?(other) + # Refine each_statement to recursively emit statements from embedded lists. + # + # @example + # RDF::List[1, 2, 3].each_statement do |statement| + # puts statement.inspect + # end + # + # @return [Enumerator] + # @see RDF::Enumerable#each_statement + def each_statement(&block) + return enum_statement unless block_given? + + each_subject do |subject| + graph.query({subject: subject}) do |statement| + if statement.object.list? + block.call(RDF::Statement.from(statement.subject, statement.predicate, statement.object.subject)) + statement.object.each_statement(&block) + else + block.call(statement) + end + end + end end end - # @!parse - # # Refinements on RDF::Query::Variable - # class ::RDF::Query::Variable - # # Adds `#sameTerm?` which is the same as `#eql?`, except for variables. - # # @return [Boolean] - # def sameTerm?; end - # end - refine ::RDF::Query::Variable do - ## - # True if the other is the same variable - def sameTerm?(other) - other.is_a?(::RDF::Query::Variable) && name.eql?(other.name) - end + refine ::RDF::Graph do + # Allow a graph to be treated as a term in a statement. + include ::RDF::Term end end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 5cb1f13..1a5f5db 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -10,7 +10,7 @@ { "r1" => { input: %( - @forAll :a, :b, :c, :x, :y, :z. + @forAll :a, :b. ( "one" "two" ) a :whatever. { (:a :b) a :whatever } log:implies { :a a :SUCCESS. :b a :SUCCESS }. ), From c9f5580e00be2ac866db3a248f807b369c7b98b7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 20 Aug 2020 15:06:25 -0700 Subject: [PATCH 058/193] Update RDF::N3::List to subclass RDF::List, but provide accessors for list namespace. Uses local storage for list elements, and allows recusive lists. Enumerating statements is recursive. --- README.md | 5 + lib/rdf/n3.rb | 1 + lib/rdf/n3/extensions.rb | 34 ++-- lib/rdf/n3/list.rb | 407 ++++++++++++++++++++++++++++++++++++++ lib/rdf/n3/reader.rb | 16 +- lib/rdf/n3/reasoner.rb | 33 ++-- lib/rdf/n3/refinements.rb | 30 --- lib/rdf/n3/vocab.rb | 5 - script/parse | 10 +- script/tc | 22 ++- spec/reader_spec.rb | 8 + 11 files changed, 483 insertions(+), 88 deletions(-) create mode 100644 lib/rdf/n3/list.rb diff --git a/README.md b/README.md index b76953e..0b2bea3 100755 --- a/README.md +++ b/README.md @@ -129,6 +129,11 @@ results in: 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. +### Query +Formulae are typically used to query the knowledge-base, which is set from the base-formula/default graph. A formula is composed of both constant statements, and variable statements. When running as a query, such as for the antecedent formula in `log:implies`, statements including either explicit variables or blank nodes are treated as query patterns and are used to query the knowledge base to create a solution set, which is used either prove the formula correct, or create bindings passed to the consequent formula. + +Blank nodes associated with rdf:List statements used as part of a built-in are made _non-distinguished_ existential variables, and patters containing these variables become optional. If they are not bound as part of the query, the implicitly are bound as the original blank nodes defined within the formula, which allows for both constant list arguments, list arguments that contain variables, or arguments which are variables expanding to lists. + ## Dependencies * [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.1, >= 3.1.4) * [EBNF][EBNF gem] (~> 2.1) diff --git a/lib/rdf/n3.rb b/lib/rdf/n3.rb index 23b7bd3..ec2187d 100644 --- a/lib/rdf/n3.rb +++ b/lib/rdf/n3.rb @@ -24,6 +24,7 @@ module N3 require 'rdf/n3/vocab' require 'rdf/n3/extensions' require 'rdf/n3/refinements' + autoload :List, 'rdf/n3/list' autoload :Reader, 'rdf/n3/reader' autoload :Reasoner, 'rdf/n3/reasoner' autoload :Terminals, 'rdf/n3/terminals' diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index c18123e..d656bb2 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -22,22 +22,6 @@ def contain?(other) end end - module Queryable - ## - # Return the RDF::List representation of the resource from self, with any recursive lists represented as RDF::List. - # - # @param [RDF::Resource] subject - # @return [RDF::List, RDF::Resource] returns either the original resource, or a list based on that resource - def as_list(subject) - return subject unless subject.node? || subject.uri? && subject == RDF.nil - ln = RDF::List.new(subject: subject, graph: self) - return subject unless ln.valid? - - # Return a new list, outside of this queryable, with any embedded lists also expanded - RDF::List.new(subject: subject, values: ln.to_a.map {|li| self.as_list(li)}) - end - end - class List ## # Evaluates the list using the given variable `bindings`. @@ -46,10 +30,14 @@ class List # a query solution containing zero or more variable bindings # @param [Hash{Symbol => Object}] options ({}) # options passed from query - # @return [RDF::List] + # @return [RDF::N3::List] # @see SPARQL::Algebra::Expression.evaluate def evaluate(bindings, **options) - RDF::List[*to_a.map {|o| o.evaluate(bindings, **options)}] + # if values are constant, simply return ourselves + return self if to_a.none? {|li| li.node? || li.variable?} + # Create a new list subject using a combination of the current subject and a hash of the binding values + subj = "#{subject.id}_#{bindings.values.sort.hash}" + RDF::N3::List.new(subject: RDF::Node.intern(subj), values: to_a.map {|o| o.evaluate(bindings, **options)}) end ## @@ -91,6 +79,16 @@ def to_sxp end end + module Value + ## + # Returns `true` if `self` is a {RDF::N3::Formula}. + # + # @return [Boolean] + def formula? + false + end + end + module Term ## # Is this the same term? Like `#eql?`, but no variable matching diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb new file mode 100644 index 0000000..0138b31 --- /dev/null +++ b/lib/rdf/n3/list.rb @@ -0,0 +1,407 @@ +module RDF::N3 + ## + # Sub-class of RDF::List which uses a native representation of values and allows recursive lists. + # + # Also serves as the vocabulary URI for expanding other methods + class List < RDF::List + URI = RDF::URI("http://www.w3.org/2000/10/swap/list#") + + # Returns a vocubulary term + def self.method_missing(property, *args, &block) + property = RDF::Vocabulary.camelize(property.to_s) + if args.empty? && !to_s.empty? + RDF::Vocabulary::Term.intern("#{URI}#{property}", attributes: {}) + else + super + end + end + + ## + # Returns the base URI for this vocabulary. + # + # @return [URI] + def self.to_uri + URI + end + + ## + # Attempts to create an RDF::N3::List from subject, or returns the node as is, if unable. + # + # @param [RDF::Resource] subject + # @return [RDF::List, RDF::Resource] returns either the original resource, or a list based on that resource + def self.try_list(subject, graph) + return subject unless subject.node? || subject.uri? && subject == RDF.nil + ln = RDF::List.new(subject: subject, graph: graph) + return subject unless ln.valid? + + # Return a new list, outside of this queryable, with any embedded lists also expanded + values = ln.to_a.map {|li| try_list(li, graph)} + RDF::N3::List.new(subject: subject, graph: graph, values: values) + end + + ## + # Initializes a newly-constructed list. + # + # Instantiates a new list based at `subject`, which **must** be an RDF::Node. List may be initialized using passed `values`. + # + # @example add constructed list to existing graph + # l = RDF::N3::List(values: (1, 2, 3)) + # g = RDF::Graph.new << l + # g.count # => l.count + # + # If values is not provided, but subject and graph are, then will attempt to recursively represent lists. + # + # @param [RDF::Resource] subject (RDF.nil) + # Subject should be an {RDF::Node}, not a {RDF::URI}. A list with an IRI head will not validate, but is commonly used to detect if a list is valid. + # @param [RDF::Graph] graph (RDF::Graph.new) + # @param [Array] values + # Any values which are not terms are coerced to `RDF::Literal`. + # @yield [list] + # @yieldparam [RDF::List] list + def initialize(subject: nil, graph: nil, values: nil, &block) + @subject = subject || (Array(values).empty? ? RDF.nil : RDF::Node.new) + @graph = graph + @valid = true + + @values = case + when values + values + when subject && graph + ln = RDF::List.new(subject: subject, graph: graph) + @valid = ln.valid? + ln.to_a.map {|li| self.class.try_list(li, graph)} + else + [] + end + end + + ## + # Lists are valid, unless established via RDF::List, in which case they are only valid if the RDF::List is valid. + # + # @return [Boolean] + def valid?; @valid; end + + ## + # @see RDF::Value#== + def ==(other) + case other + when Array, RDF::List then to_a == other.to_a + else + false + end + end + + ## + # Element Assignment — Sets the element at `index`, or replaces a subarray from the `start` index for `length` elements, or replaces a subarray specified by the `range` of indices. + # + # @overload []=(index, term) + # Replaces the element at `index` with `term`. + # @param [Integer] index + # @param [RDF::Term] term + # A non-RDF::Term is coerced to a Literal. + # @return [RDF::Term] + # @raise [IndexError] + # + # @overload []=(start, length, value) + # Replaces a subarray from the `start` index for `length` elements with `value`. Value is a {RDF::Term}, Array of {RDF::Term}, or {RDF::List}. + # @param [Integer] start + # @param [Integer] length + # @param [RDF::Term, Array, RDF::List] value + # A non-RDF::Term is coerced to a Literal. + # @return [RDF::Term, RDF::List] + # @raise [IndexError] + # + # @overload []=(range, value) + # Replaces a subarray from the `start` index for `length` elements with `value`. Value is a {RDF::Term}, Array of {RDF::Term}, or {RDF::List}. + # @param [Range] range + # @param [RDF::Term, Array, RDF::List] value + # A non-RDF::Term is coerced to a Literal. + # @return [RDF::Term, RDF::List] + # @raise [IndexError] + def []=(*args) + value = case args.last + when Array then args.last + when RDF::List then args.last.to_a + else [args.last] + end + + case args.length + when 3 + start, length = args[0], args[1] + @value[start, length] = value + case args.first + when Integer + raise ArgumentError, "Index form of []= takes a single term" if args.last.is_a?(Array) + @value[args.first] = case args.last + when RDF::N3::List then args.last + when RDF::List then RDF::N3::List.new(values: args.last.to_a) + when Array then RDF::N3::List.new(values: args.last.to_a) + else args.last + end + when Range + @value[args.first] = value + else + raise ArgumentError, "Index form of must use an integer or range" + end + else + raise ArgumentError, "List []= takes one or two index values" + end + end + + ## + # Appends an element to the head of this list. Existing references are not updated, as the list subject changes as a side-effect. + # + # @example + # RDF::List[].unshift(1).unshift(2).unshift(3) #=> RDF::List[3, 2, 1] + # + # @param [RDF::Term, Array, RDF::List] value + # A non-RDF::Term is coerced to a Literal + # @return [RDF::List] + # @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-unshift + # + def unshift(value) + value = normalize_value(value) + @values.unshift(value) + @subject = nil + + return self + end + + ## + # Removes and returns the element at the head of this list. + # + # @example + # RDF::List[1,2,3].shift #=> 1 + # + # @return [RDF::Term] + # @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-shift + def shift + return nil if empty? + @subject = nil + @values.shift + end + + ## + # Empties this list + # + # @example + # RDF::List[1, 2, 2, 3].clear #=> RDF::List[] + # + # @return [RDF::List] + # @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-clear + def clear + @values.clear + @subject = nil + self + end + + ## + # Appends an element to the tail of this list. + # + # @example + # RDF::List[] << 1 << 2 << 3 #=> RDF::List[1, 2, 3] + # + # @param [RDF::Term] value + # @return [RDF::List] + # @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-3C-3C + def <<(value) + value = normalize_value(value) + @subject = nil + @values << value + self + end + + ## + # Returns `true` if this list is empty. + # + # @example + # RDF::List[].empty? #=> true + # RDF::List[1, 2, 3].empty? #=> false + # + # @return [Boolean] + # @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-empty-3F + def empty? + @values.empty? + end + + ## + # Returns the length of this list. + # + # @example + # RDF::List[].length #=> 0 + # RDF::List[1, 2, 3].length #=> 3 + # + # @return [Integer] + # @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-length + def length + @values.length + end + + ## + # Returns the index of the first element equal to `value`, or `nil` if + # no match was found. + # + # @example + # RDF::List['a', 'b', 'c'].index('a') #=> 0 + # RDF::List['a', 'b', 'c'].index('d') #=> nil + # + # @param [RDF::Term] value + # @return [Integer] + # @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-index + def index(value) + @values.index(value) + end + + ## + # Returns element at `index` with default. + # + # @example + # RDF::List[1, 2, 3].fetch(0) #=> RDF::Literal(1) + # RDF::List[1, 2, 3].fetch(4) #=> IndexError + # RDF::List[1, 2, 3].fetch(4, nil) #=> nil + # RDF::List[1, 2, 3].fetch(4) { |n| n*n } #=> 16 + # + # @return [RDF::Term, nil] + # @see http://ruby-doc.org/core-1.9/classes/Array.html#M000420 + def fetch(*args, &block) + @values.fetch(*args, default) + end + + ## + # Returns the element at `index`. + # + # @example + # RDF::List[1, 2, 3].at(0) #=> 1 + # RDF::List[1, 2, 3].at(4) #=> nil + # + # @return [RDF::Term, nil] + # @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-at + def at(index) + @values.at(index) + end + + ## + # Returns the first element in this list. + # + # @example + # RDF::List[*(1..10)].first #=> RDF::Literal(1) + # + # @return [RDF::Term] + def first + @values.first + end + + ## + # Returns the last element in this list. + # + # @example + # RDF::List[*(1..10)].last #=> RDF::Literal(10) + # + # @return [RDF::Term] + # @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-last + def last + @values.last + end + + ## + # Returns a list containing all but the first element of this list. + # + # @example + # RDF::List[1, 2, 3].rest #=> RDF::List[2, 3] + # + # @return [RDF::List] + def rest + self.class.new(values: values[1..-1]) + end + + ## + # Returns a list containing the last element of this list. + # + # @example + # RDF::List[1, 2, 3].tail #=> RDF::List[3] + # + # @return [RDF::List] + def tail + self.class.new(values: values[-1..-1]) + end + + ## + # Yields each element in this list. + # + # @example + # RDF::List[1, 2, 3].each do |value| + # puts value.inspect + # end + # + # @return [Enumerator] + # @see http://ruby-doc.org/core-1.9/classes/Enumerable.html + def each(&block) + return to_enum unless block_given? + + @values.each(&block) + end + + ## + # Yields each statement constituting this list. Uses actual statements if a graph was set, otherwise, the saved values. + # + # This will recursively get statements for sub-lists as well. + # + # @example + # RDF::List[1, 2, 3].each_statement do |statement| + # puts statement.inspect + # end + # + # @return [Enumerator] + # @see RDF::Enumerable#each_statement + def each_statement(&block) + return enum_statement unless block_given? + + if graph + RDF::List.new(subject: subject, graph: graph).each_statement(&block) + elsif @values.length > 0 + # Create a subject for each entry based on the subject bnode + subjects = (0..(@values.count-1)).map {|ndx| ndx > 0 ? RDF::Node.intern("#{subject.id}_#{ndx}") : subject} + *values, last = @values + while !values.empty? + subj = subjects.shift + block.call(RDF::Statement(subj, RDF.first, values.shift)) + block.call(RDF::Statement(subj, RDF.rest, subjects.first)) + end + subj = subjects.shift + block.call(RDF::Statement(subj, RDF.first, last)) + block.call(RDF::Statement(subj, RDF.rest, RDF.nil)) + end + + # If a graph was used, also get statements from sub-lists + @values.select(&:list?).each {|li| li.each_statement(&block)} if graph + end + + ## + # Yields each subject term constituting this list along with sub-lists. + # + # @example + # RDF::List[1, 2, 3].each_subject do |subject| + # puts subject.inspect + # end + # + # @return [Enumerator] + # @see RDF::Enumerable#each + def each_subject(&block) + return enum_subject unless block_given? + + each_statement {|st| block.call(st.subject) if st.predicate == RDF.rest} + end + + ## + # Returns the elements in this list as an array. + # + # @example + # RDF::List[].to_a #=> [] + # RDF::List[1, 2, 3].to_a #=> [RDF::Literal(1), RDF::Literal(2), RDF::Literal(3)] + # + # @return [Array] + def to_a + @values + end + end +end diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index de60938..add34a3 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -12,7 +12,7 @@ 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. + # 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 `$` or `?`, 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 @@ -83,13 +83,13 @@ def initialize(input = $stdin, **options, &block) # 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) + namespace(:crypto, RDF::N3::Crypto.to_uri) + namespace(:list, RDF::N3::List.to_uri) + namespace(:log, RDF::N3::Log.to_uri) + namespace(:math, RDF::N3::Math.to_uri) + namespace(:rei, RDF::N3::Rei.to_uri) + #namespace(:string, RDF::N3::String.to_uri) + namespace(:time, RDF::N3::Time.to_uri) end progress("validate") {validate?.inspect} progress("canonicalize") {canonicalize?.inspect} diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 0a546a5..dfc2714 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -50,7 +50,7 @@ def self.open(file) # RDF::N3::Reader.open("rules.n3") {|r| reasoner << r} # reasoner.each_triple {} # - # @param [RDF::Enumerable] input (nil) + # @param [RDF::Mutable] 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) @@ -100,6 +100,7 @@ def insert_statement(statement) # # @param [Hash{Symbol => Object}] options # @option options [Boolean] :apply + # @option options [Boolean] :rules # @option options [Boolean] :think # @yield [statement] # @yieldparam [RDF::Statement] statement @@ -112,6 +113,7 @@ def execute(**options, &block) count = 0 log_info("reasoner: think start") { "count: #{count}"} while @mutable.count > count + log_info("reasoner: think do") { "count: #{count}"} count = @mutable.count dataset = RDF::Graph.new << @mutable.project_graph(nil) log_depth {formula.execute(dataset, **options)} @@ -120,11 +122,11 @@ def execute(**options, &block) log_info("reasoner: think end") { "count: #{count}"} else # Run one iteration - log_info("reasoner: apply start") { "count: #{count}"} + log_info("reasoner: rules start") { "count: #{count}"} dataset = RDF::Graph.new << @mutable.project_graph(nil) log_depth {formula.execute(dataset, **options)} @mutable << formula - log_info("reasoner: apply end") { "count: #{count}"} + log_info("reasoner: rules end") { "count: #{count}"} end log_debug("reasoner: datastore") {@mutable.to_sxp} @@ -247,14 +249,22 @@ def formula memo.merge(graph_name => Algebra::Formula.new(graph_name: graph_name, **@options)) end + # Create `queryable` as a repo subset of `mutable` excluding built-ins and statements with a variable subject or predicate. This is useful for extracting lists. + queryable = RDF::Repository.new + # 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 + if statement.subject.constant? && statement.predicate.constant? + queryable << statement + end + # A graph name indicates a formula. - form = formulae[pattern.graph_name] + graph_name = pattern.graph_name + form = formulae[graph_name] # Formulae may be the subject or object of a known operator if klass = Algebra.for(pattern.predicate) @@ -274,24 +284,21 @@ def formula end end - # Bind formula operands which are lists to RDF::List + # Create a graph for each formula, containing statements and built-in operands formulae.each do |gn, form| form_graph = RDF::Graph.new do |g| + # Graph initialized with non-built-in statements form.operands.each do |op| g << op if op.is_a?(RDF::Statement) end end form.operands.each do |op| next unless op.is_a?(SPARQL::Algebra::Operator) + # Bind built-in operands which are constant lists to RDF::N3::List op.operands.map! do |operand| - if operand.is_a?(RDF::Node) - ln = RDF::List.new(subject: operand, graph: form_graph) - ln.valid? ? ln : operand - elsif operand.is_a?(RDF::URI) && operand == RDF.nil - RDF::List::NIL - else - operand - end + # Use a List object, if it is constant + ln = RDF::N3::List.try_list(operand, queryable) unless !operand.is_a?(RDF::Term) || operand.list? + ln || operand end end end diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb index c63d844..58507ab 100644 --- a/lib/rdf/n3/refinements.rb +++ b/lib/rdf/n3/refinements.rb @@ -71,36 +71,6 @@ def valid? end end - refine ::RDF::List do - # Allow a list to be treated as a term in a statement. - include ::RDF::Term - - ## - # Refine each_statement to recursively emit statements from embedded lists. - # - # @example - # RDF::List[1, 2, 3].each_statement do |statement| - # puts statement.inspect - # end - # - # @return [Enumerator] - # @see RDF::Enumerable#each_statement - def each_statement(&block) - return enum_statement unless block_given? - - each_subject do |subject| - graph.query({subject: subject}) do |statement| - if statement.object.list? - block.call(RDF::Statement.from(statement.subject, statement.predicate, statement.object.subject)) - statement.object.each_statement(&block) - else - block.call(statement) - end - end - end - end - end - refine ::RDF::Graph do # Allow a graph to be treated as a term in a statement. include ::RDF::Term diff --git a/lib/rdf/n3/vocab.rb b/lib/rdf/n3/vocab.rb index 18a2dff..64285c6 100644 --- a/lib/rdf/n3/vocab.rb +++ b/lib/rdf/n3/vocab.rb @@ -4,11 +4,6 @@ module RDF::N3 # class Crypto < RDF::Vocabulary; end const_set("Crypto", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/crypto#"))) - # @!parse - # # List namespace - # class List < RDF::Vocabulary; end - const_set("List", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/list#"))) - # @!parse # # Log namespace # class Log < RDF::Vocabulary; end diff --git a/script/parse b/script/parse index 460dfa5..8b4d23d 100755 --- a/script/parse +++ b/script/parse @@ -18,11 +18,11 @@ def run(input, **options) start = Time.new num = 0 Profiler__::start_profile if options[:profile] - if options[:think] + if options[:think] || options[:rules] # Parse into a new reasoner and evaluate reader_class.new(input, **options[:parser_options].merge(logger: nil)) do |reader| - reasoner = RDF::N3::Reasoner.new(reader, options[:parser_options]) - reasoner.reason!(options) + reasoner = RDF::N3::Reasoner.new(reader, **options[:parser_options]) + reasoner.reason!(**options) repo = RDF::Repository.new if options[:conclusions] repo << reasoner.conclusions @@ -77,8 +77,8 @@ rescue RDF::ReaderError => e STDERR.puts "Backtrace: " + e.backtrace.join("\n ") if $verbose exit(1) rescue Exception => e - STDERR.puts "Error: #{e.message}" - STDERR.puts "Backtrace: " + e.backtrace.join("\n ") if $verbose + STDERR.puts "Error: #{e}" + STDERR.puts "Backtrace: " + e.backtrace.join("\n ") exit(1) end diff --git a/script/tc b/script/tc index 9841c6d..6b78edf 100755 --- a/script/tc +++ b/script/tc @@ -72,7 +72,9 @@ def run_tc(man, tc, **options) begin graph << reader rescue Exception => e - STDERR.puts "Unexpected exception: #{e.inspect}" if options[:verbose] + if options[:verbose] + STDERR.puts "Unexpected exception: #{e.inspect}\n#{e.backtrace.join("\n")}" + end result = "failed" end else @@ -92,7 +94,9 @@ def run_tc(man, tc, **options) output_graph = RDF::Repository.load(tc.result, base_uri: tc.base) result = graph.isomorphic_with?(output_graph) ? "passed" : "failed" rescue Exception => e - STDERR.puts "Unexpected exception reading result: #{e.inspect}" + if options[:verbose] + STDERR.puts "Unexpected exception: #{e.inspect}\n#{e.backtrace.join("\n")}" + end result = "failed" end elsif tc.reason? && result.nil? @@ -110,7 +114,9 @@ def run_tc(man, tc, **options) repo << reasoner end rescue Exception => e - STDERR.puts "Unexpected exception reasoning: #{e.inspect}" + if options[:verbose] + STDERR.puts "Unexpected exception: #{e.inspect}\n#{e.backtrace.join("\n")}" + end result = "failed" end STDERR.puts "\nResult: #{repo.dump(:n3)}" if options[:verbose] @@ -121,16 +127,14 @@ def run_tc(man, tc, **options) result ||= "passed" end - rescue Interrupt + rescue Interrupt => e STDERR.puts "(interrupt)" + STDERR.puts "Backtrace: " + e.backtrace.join("\n ") if $verbose exit 1 rescue Exception => e STDERR.puts "#{"exception:" unless options[:quiet]}: #{e}" - if options[:quiet] - return - else - raise - end + return if options[:quiet] + STDERR.puts "Backtrace: " + e.backtrace.join("\n ") if $verbose end STDERR.puts options[:logger] if options[:verbose] && !options[:live] diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 287db09..b54a29f 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -897,6 +897,14 @@ expect(seq.third).to eq RDF::URI.new("http://resource2") expect(seq.fourth).to be_node end + + it "should use exactly the same object when referencing a list" do + n3 = %(:thing :prop ( 4 ) .) + g = parse(n3) + n1 = g.first_object(predicate: RDF::URI("#prop")) + n2 = g.first_subject(predicate: RDF.first) + expect(n1.object_id).to eq n2.object_id + end end context "property paths" do From bdfde886fac364c242c98d71dd45ea2b1c6735cb Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 20 Aug 2020 15:11:02 -0700 Subject: [PATCH 059/193] Evaluate list operands within each solution, binding to that solution. --- lib/rdf/n3/algebra/list/in.rb | 14 +++++--------- lib/rdf/n3/algebra/list/last.rb | 14 ++++---------- lib/rdf/n3/algebra/list/member.rb | 12 +++--------- lib/rdf/n3/algebra/str/concatenation.rb | 17 +++++++++-------- lib/rdf/n3/algebra/str/format.rb | 13 +++++++------ lib/rdf/n3/algebra/str/replace.rb | 19 ++++++++++--------- lib/rdf/n3/algebra/str/scrape.rb | 16 ++++++++-------- 7 files changed, 46 insertions(+), 59 deletions(-) diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index c305a76..341dc13 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -23,17 +23,13 @@ class In < SPARQL::Algebra::Operator::Binary def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| subject = operand(0) - list = operand(1) + # Might be a variable or node evaluating to a list in queryable, or might be a list with variables + list = operand(1).evaluate(solution.bindings) + # If it evaluated to a BNode, re-expand as a list + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - case list - when RDF::Node, RDF::Query::Variable - # Attempt to bind a node or variable to a list - list = list.evaluate(solution.bindings) - when RDF::List - # Attempt to bind list elements - list = list.to_a.map {|op| op.evaluate(solution.bindings)} - end log_debug(NAME) {"subject: #{subject.to_sxp}, list: #{list.to_sxp}"} + raise TypeError, "operand is not a list" unless list.list? && list.valid? if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements diff --git a/lib/rdf/n3/algebra/list/last.rb b/lib/rdf/n3/algebra/list/last.rb index 2001915..e8ca832 100644 --- a/lib/rdf/n3/algebra/list/last.rb +++ b/lib/rdf/n3/algebra/list/last.rb @@ -24,20 +24,14 @@ class Last < SPARQL::Algebra::Operator::Binary # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| - list = operand(0) + list = operand(0).evaluate(solution.bindings) + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + result = operand(1) - case list - when RDF::Node, RDF::Query::Variable - # Attempt to bind a node or variable to a list - list = list.evaluate(solution.bindings) - when RDF::List - # Attempt to bind list elements - list = list.to_a.map {|op| op.evaluate(solution.bindings)} - end log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + raise TypeError, "operand is not a list" unless list.list? && list.valid? - #require 'byebug'; byebug if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements solution diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index 27e65e9..ec10749 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -19,18 +19,12 @@ class Member < SPARQL::Algebra::Operator::Binary # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| - list = operand(0) + list = operand(0).evaluate(solution.bindings) + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) result = operand(1) - case list - when RDF::Node, RDF::Query::Variable - # Attempt to bind a node or variable to a list - list = list.evaluate(solution.bindings) - when RDF::List - # Attempt to bind list elements - list = list.to_a.map {|op| op.evaluate(solution.bindings)} - end log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + raise TypeError, "operand is not a list" unless list.list? && list.valid? if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb index a387098..bc0bd85 100644 --- a/lib/rdf/n3/algebra/str/concatenation.rb +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -20,21 +20,22 @@ class Concatenation < SPARQL::Algebra::Operator::Binary # @return [RDF::Query::Solutions] # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) - list = operand(0) result = operand(1) - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - - raise TypeError, "operand is not a list" unless list.list? && list.valid? - @solutions = RDF::Query::Solutions(solutions.map do |solution| - bound_entries = list.to_a.map {|op| op.evaluate(solution.bindings)} + # Might be a variable or node evaluating to a list in queryable, or might be a list with variables + list = operand(0).evaluate(solution.bindings) + # If it evaluated to a BNode, re-expand as a list + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + raise TypeError, "operand is not a list" unless list.list? && list.valid? - if bound_entries.any? {|op| op.variable? && op.unbound?} + if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements solution else - rhs = bound_entries.map(&:value).join("") + rhs = list.to_a.map(&:value).join("") if result.variable? solution.merge(result.to_sym => RDF::Literal(rhs)) diff --git a/lib/rdf/n3/algebra/str/format.rb b/lib/rdf/n3/algebra/str/format.rb index e7b2161..29eb395 100644 --- a/lib/rdf/n3/algebra/str/format.rb +++ b/lib/rdf/n3/algebra/str/format.rb @@ -18,18 +18,19 @@ def execute(queryable, solutions:, **options) list = operand(0) result = operand(1) - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + @solutions = RDF::Query::Solutions(solutions.map do |solution| + list = operand(0).evaluate(solution.bindings) + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - raise TypeError, "operand is not a list" unless list.list? && list.valid? + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - @solutions = RDF::Query::Solutions(solutions.map do |solution| - bound_entries = list.to_a.map {|op| op.evaluate(solution.bindings)} + raise TypeError, "operand is not a list" unless list.list? && list.valid? - if bound_entries.any? {|op| op.variable? && op.unbound?} + if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements solution else - format, *args = bound_entries.map(&:value) + format, *args = list.to_a.map(&:value) str = format % args if result.variable? diff --git a/lib/rdf/n3/algebra/str/replace.rb b/lib/rdf/n3/algebra/str/replace.rb index 4a5a8a7..9b69468 100644 --- a/lib/rdf/n3/algebra/str/replace.rb +++ b/lib/rdf/n3/algebra/str/replace.rb @@ -18,22 +18,23 @@ class Replace < SPARQL::Algebra::Operator::Binary # @return [RDF::Query::Solutions] # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) - list = operand(0) result = operand(1) + @solutions = RDF::Query::Solutions(solutions.map do |solution| + # Might be a variable or node evaluating to a list in queryable, or might be a list with variables + list = operand(0).evaluate(solution.bindings) + # If it evaluated to a BNode, re-expand as a list + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - - raise TypeError, "operand is not a list" unless list.list? && list.valid? - raise TypeError, "list must have exactly three entries" unless list.length == 3 + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - @solutions = RDF::Query::Solutions(solutions.map do |solution| - bound_entries = list.to_a.map {|op| op.evaluate(solution.bindings)} + raise TypeError, "operand is not a list" unless list.list? && list.valid? + raise TypeError, "list must have exactly three entries" unless list.length == 3 - if bound_entries.any? {|op| op.variable? && op.unbound?} + if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements solution else - input, old_str, new_str = bound_entries + input, old_str, new_str = list.to_a output = input.to_s.gsub(old_str.to_s, new_str.to_s) if result.variable? diff --git a/lib/rdf/n3/algebra/str/scrape.rb b/lib/rdf/n3/algebra/str/scrape.rb index cc458fa..f00aa42 100644 --- a/lib/rdf/n3/algebra/str/scrape.rb +++ b/lib/rdf/n3/algebra/str/scrape.rb @@ -18,22 +18,22 @@ class Scrape < SPARQL::Algebra::Operator::Binary # @return [RDF::Query::Solutions] # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) - list = operand(0) result = operand(1) - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + @solutions = RDF::Query::Solutions(solutions.map do |solution| + list = operand(0).evaluate(solution.bindings) + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - raise TypeError, "operand is not a list" unless list.list? && list.valid? - raise TypeError, "list must have exactly two entries" unless list.length == 2 + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - @solutions = RDF::Query::Solutions(solutions.map do |solution| - bound_entries = list.to_a.map {|op| op.evaluate(solution.bindings)} + raise TypeError, "operand is not a list" unless list.list? && list.valid? + raise TypeError, "list must have exactly two entries" unless list.length == 2 - if bound_entries.any? {|op| op.variable? && op.unbound?} + if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements solution else - input, regex = bound_entries + input, regex = list.to_a md = Regexp.new(regex.to_s).match(input.to_s) if result.variable? && md && md[1] From e95f3d8d33c1b795ead5e6481bf115324adc34f3 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 20 Aug 2020 15:12:10 -0700 Subject: [PATCH 060/193] Implemented list:append, which requires binding a generated list to a solution, and expanding these in the consequent. --- lib/rdf/n3/algebra/list/append.rb | 42 ++++++++++++++- spec/reasoner_spec.rb | 86 +++++++++++++++++++++++++++---- spec/suite_reasoner_spec.rb | 4 +- 3 files changed, 120 insertions(+), 12 deletions(-) diff --git a/lib/rdf/n3/algebra/list/append.rb b/lib/rdf/n3/algebra/list/append.rb index d9be709..fb4ddcb 100644 --- a/lib/rdf/n3/algebra/list/append.rb +++ b/lib/rdf/n3/algebra/list/append.rb @@ -1,13 +1,53 @@ 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. + # Iff the subject is a list of lists and the concatenation of all those lists is the object, then this is true. The object can be calculated as a function of the subject. + # # @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 SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :listAppend + + ## + # Evaluates this operator using the given variable `bindings`. + # If the last operand is a variable, it creates a solution for each element in the list. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + # @raise [TypeError] if operands are not compatible + def execute(queryable, solutions:, **options) + @solutions = RDF::Query::Solutions(solutions.map do |solution| + list = operand(0).evaluate(solution.bindings) + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + result = operand(1) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + raise TypeError, "operand is not a list: #{list.to_sxp}" unless list.list? && list.valid? + raise TypeError, "operand is not a list of lists: #{list.to_sxp}" unless list.to_a.all? {|li| li.list?} + + if list.to_a.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + flattened = list.to_a.map(&:to_a).flatten + if result.variable? + # Bind a new list based on the values, whos subject use made up from original list subjects + subj = RDF::Node.intern(list.map(&:subject).hash) + solution.merge(result.to_sym => RDF::N3::List.new(subject: subj, values: list.to_a.map(&:to_a).flatten)) + elsif result.to_a == flattened + solution + else + nil + end + end + end.compact) + end end end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 1a5f5db..a7c808f 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -128,8 +128,6 @@ }, "thing1 :prop1": { input: %( - @prefix list: . - :thing1 :prop1 ( :test5a :test5b :test5c ) . { ?item list:in [ is :prop1 of :thing1 ] } => { ?item a :SUCCESS } . ), @@ -147,6 +145,64 @@ end end end + + context "list:append" do + { + "(1 2 3 4 5) (6) const": { + input: %( + { ((1 2 3 4 5) (6)) list:append (1 2 3 4 5 6)} => {:test1 a :success}. + ), + expect: %( + :test1 a :success. + ) + }, + "(1 2 3 4 5) (6) var": { + input: %( + { ((1 2 3 4 5) (6)) list:append ?item} => {:test2 :is ?item}. + ), + expect: %( + :test2 :is (1 2 3 4 5 6). + ) + }, + "() (1) const": { + input: %( + { (() (1)) list:append (1)} => {:test3 a :success}. + ), + expect: %( + :test3 a :success. + ) + }, + "() (1) var": { + input: %( + { (() (1)) list:append ?item} => {:test4 :is ?item}. + ), + expect: %( + :test4 :is (1). + ) + }, + "thing1 :prop1": { + input: %( + :thing1 :prop1 ( 1 2 3 ) . + :thing2 :prop1 ( 4 ) . + { + ([is :prop1 of :thing1] + [is :prop1 of :thing2]) list:append ?item + } => { + :test5 :is ?item + } . + ), + expect: %( + :test5 :is (1 2 3 4). + ) + } + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end end context "n3:string" do @@ -155,19 +211,31 @@ "literal starts with literal" => { input: %( @prefix string: . - :a :b :c . {"abc" string:startsWith "a"} => {:test a :Success}. ), - expect: %( - :a :b :c . - :test a :Success. - ) - } + expect: %(:test a :Success.) + }, + "ext starts with literal" => { + input: %( + @prefix string: . + :abc :value "abc" . + {[ is :value of :abc] string:startsWith "a"} => {:test a :Success}. + ), + expect: %(:test a :Success.) + }, + "literal starts with ext" => { + input: %( + @prefix string: . + :a :value "a" . + {"abc" string:startsWith [is :value of :a]} => {:test a :Success}. + ), + expect: %(:test a :Success.) + }, }.each do |name, options| it name do logger.info "input: #{options[:input]}" expected = parse(options[:expect]) - expect(reason(options[:input])).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) end end end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 4793a3e..288d6c8 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -21,9 +21,9 @@ case t.id.split('#').last when *%w{cwm_includes_listin cwm_includes_bnode cwm_includes_concat cwm_includes_conjunction cwm_includes_conclusion_simple - cwm_list_append} + } pending "support for lists" - when *%w{cwm_unify_unify1 cwm_unify_unify2} + when *%w{cwm_unify_unify1} pending "reason over formulae" when *%w{cwm_reason_t6} pending "support for math" From 69b160b1989cd9a2c8a05a1e07e070358e70980c Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 20 Aug 2020 15:13:05 -0700 Subject: [PATCH 061/193] Treat a formula as an RDF::Term (responding true to `#formula?`. --- lib/rdf/n3/algebra/formula.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 577b70c..6dca8e2 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -4,6 +4,7 @@ module RDF::N3::Algebra # # A Notation3 Formula combines a graph with a BGP query. class Formula < SPARQL::Algebra::Operator + include RDF::Term include SPARQL::Algebra::Query include SPARQL::Algebra::Update include RDF::Enumerable @@ -68,6 +69,14 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new variable_names.empty? ? @solutions : @solutions.dup.project(*variable_names) end + ## + # Returns `true` if `self` is a {RDF::N3::Formula}. + # + # @return [Boolean] + def formula? + true + end + ## # Yields each statement from this formula bound to previously determined solutions. # From 49ae76669c0013f111c3ae22c8ef8bd52a578130 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 20 Aug 2020 15:15:04 -0700 Subject: [PATCH 062/193] Optimize query, as some list statements associated with builtin operands are now considered optional. --- lib/rdf/n3/algebra/formula.rb | 80 +++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 6dca8e2..a837177 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -31,20 +31,21 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new log_debug("(formula bindings)") { solutions.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! + @query ||= RDF::Query.new(patterns) @solutions = if @query.patterns.empty? solutions else - these_solutions = queryable.query(@query, solutions: solutions, **options) + these_solutions = queryable.query(@query, solutions: solutions, optimize: true, **options) these_solutions.map! do |solution| RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)| - # Replace blank node bindings with lists, where those blank nodes are associated with lists - l = queryable.as_list(value) unless value.list? - value = l if l + # Replace blank node bindings with lists, where those blank nodes are associated with lists. + l = RDF::N3::List.try_list(value, queryable) + value = l if l.constant? memo.merge(name => value) end) end + log_debug("(formula solutions)") { these_solutions.to_sxp} solutions.merge(these_solutions) end @@ -57,15 +58,17 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new sub_ops.each do |op| @solutions = if op.executable? op.execute(queryable, solutions: @solutions) - else - op.evaluate(@solutions.bindings) == RDF::Literal::TRUE ? @solutions : RDF::Query::Solutions.new + else # Evaluatable + @solutions.all? {|s| op.evaluate(s.bindings) == RDF::Literal::TRUE} ? + @solutions : + RDF::Query::Solutions.new end end end 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?('$$')} + # Only return solutions with universal variables + variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?('$')} variable_names.empty? ? @solutions : @solutions.dup.project(*variable_names) end @@ -91,14 +94,13 @@ def each(&block) end log_debug("formula #{graph_name} each") {@solutions.to_sxp} - # Yield constant statements/patterns - constants.each do |pattern| - #log_debug {"(formula constant) #{pattern.to_sxp}"} - block.call(RDF::Statement.from(pattern, graph_name: graph_name)) + # Yield constant statements + constants.each do |statement| + #log_debug {"(formula constant) #{statement.to_sxp}"} + block.call(RDF::Statement.from(statement, graph_name: graph_name)) 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| @@ -111,7 +113,14 @@ def each(&block) terms = {} [:subject, :predicate, :object].each do |part| terms[part] = case o = pattern.send(part) - when RDF::Query::Variable then solution[o] + when RDF::Query::Variable + if solution[o] && solution[o].list? + solution[o].each_statement(&block) + # Bind the list subject, and emit list statements + solution[o].subject + else + solution[o] + end else o end end @@ -146,24 +155,33 @@ def graph_name; @options[:graph_name]; end ## # Statements memoizer def statements - # BNodes in statements are non-distinguished existential variables - @statements ||= operands. - select {|op| op.is_a?(RDF::Statement)}. - map do |pattern| - - # 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, existential: true, distinguished: false) - else o + # BNodes in statements are existential variables. If part of a built-in, they become non-distinguished, creating optional patterns, and may become bound to themselves. + @statements ||= begin + statements = operands.select {|op| op.is_a?(RDF::Statement)} + + # Find statements associated with lists referenced from a built-in. Blank nodes which are related to these operands are marked non-distinguished, so they become optional patterns. + op_lists = sub_ops.map(&:operands).flatten.select(&:list?) + op_list_subjects = op_lists.map(&:subjects).flatten + + statements.map do |pattern| + # Map nodes to existential variables (except when in top-level formula). They are non-distinguished if associated with a built-in. + if graph_name + terms = {} + has_nd_var = op_list_subjects.include?(pattern.subject) + [:subject, :predicate, :object].each do |r| + terms[r] = case o = pattern.send(r) + when RDF::Node + RDF::Query::Variable.new(o.id, existential: true) + else + o + end end - end - RDF::Query::Pattern.from(terms) - else - RDF::Query::Pattern.from(pattern) + # A pattern with a non-destinguished variable becomes optional, so that it will bind to itself, if not matched in queryable. + RDF::Query::Pattern.from(terms, optional: has_nd_var) + else + RDF::Query::Pattern.from(pattern) + end end end end From b8f1d1f0a759137e19b35dd69e951bdb1ea8a0e9 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 20 Aug 2020 16:27:51 -0700 Subject: [PATCH 063/193] Add `:list_terms` as a reader option to emit first-class list objects as statement terms. --- lib/rdf/n3/list.rb | 3 ++ lib/rdf/n3/reader.rb | 17 ++++++--- spec/reader_spec.rb | 84 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index 0138b31..e0c6822 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -4,6 +4,9 @@ module RDF::N3 # # Also serves as the vocabulary URI for expanding other methods class List < RDF::List + # Allow a list to be treated as a term in a statement. + include ::RDF::Term + URI = RDF::URI("http://www.w3.org/2000/10/swap/list#") # Returns a vocubulary term diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index add34a3..9e8d56d 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -56,6 +56,8 @@ class Reader < RDF::Reader # whether to canonicalize parsed literals and URIs. # @option options [Hash] :prefixes (Hash.new) # the prefix mappings to use (not supported by all readers) + # @option options [Hash] :list_terms (false) + # represent collections as an `RDF::Term`, rather than an rdf:first/rest ladder. # @return [reader] # @yield [reader] `self` # @yieldparam [RDF::Reader] reader @@ -505,6 +507,7 @@ def read_blankNodePropertyList # # [21] collection ::= '(' object* ')' # + # If the `list_terms` option is given, the resulting resource is a list, otherwise, it is the list subject, and the first/rest entries are also emitted. # @return [RDF::Node] def read_collection if @lexer.first === '(' @@ -516,13 +519,17 @@ def read_collection while @lexer.first.value != ')' && (object = read_path) objects << object end - list = RDF::List.new(values: objects) - list.each_statement do |statement| - add_statement("collection", *statement.to_a) - end error("collection", "Expected closing ')'") unless @lexer.first === ')' @lexer.shift - list.subject + list = RDF::N3::List.new(values: objects) + if options[:list_terms] + list + else + list.each_statement do |statement| + add_statement("collection", *statement.to_a) + end + list.subject + end end end end diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index b54a29f..d599815 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -818,7 +818,7 @@ end end - describe "lists" do + describe "collections" do it "should parse empty list" do n3 = %(@prefix :. :empty :set ().) nt = %( @@ -827,11 +827,11 @@ end it "should parse list with single element" do - n3 = %(@prefix :. :gregg :wrote ("RdfContext").) + n3 = %(@prefix :. :gregg :edited ("JSON-LD").) nt = %( - _:bnode0 "RdfContext" . + _:bnode0 "JSON-LD" . _:bnode0 . - _:bnode0 . + _:bnode0 . ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end @@ -869,7 +869,7 @@ 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 + it "should add property to empty 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, format: :n3) @@ -905,6 +905,80 @@ n2 = g.first_subject(predicate: RDF.first) expect(n1.object_id).to eq n2.object_id end + + context "as terms" do + it "should parse list with single element" do + n3 = %(:gregg :edited ("JSON-LD").) + nt = %( + _:bnode0 "JSON-LD" . + _:bnode0 . + _:bnode0 . + ) + g = parse(n3, list_terms: true) + expect(g.count).to eql 1 + + statement = g.statements.first + expect(statement.object).to be_list + + list = statement.object + expect(list.subject).to be_node + expect(list.length).to eql 1 + expect(list.first).to eql RDF::Literal("JSON-LD") + end + end + + it "should parse list with multiple elements" do + n3 = %(:gregg :name ("Gregg" "Barnum" "Kellogg").) + g = parse(n3, list_terms: true) + expect(g.count).to eql 1 + + statement = g.statements.first + expect(statement.object).to be_list + + list = statement.object + expect(list.subject).to be_node + expect(list.length).to eql 3 + expect(list.to_a).to include(RDF::Literal("Gregg"), RDF::Literal("Barnum"), RDF::Literal("Kellogg")) + end + + it "should add property to empty list" do + n3 = %(@prefix a: . () a:prop "nilProp" .) + g = parse(n3, list_terms: true) + expect(g.count).to eql 1 + + statement = g.statements.first + expect(statement.subject).to be_list + expect(statement.subject).to be_empty + end + + it "should parse with compound items" do + n3 = %( + @prefix a: . + a:a a:p ( + [ a:p2 "v1" ] + + + ("inner list") + ) . + a:p "value" . + ) + g = parse(n3, list_terms: true) + expect(g.subjects.to_a.length).to eq 3 + list = g.first_object(subject: RDF::URI.new("http://foo/a#a"), predicate: RDF::URI.new("http://foo/a#p")) + expect(list).to be_list + expect(list.count).to eq 4 + l1, l2, l3, l4 = list.to_a + expect(l1).to be_node + expect(l2).to eq RDF::URI.new("http://resource1") + expect(l3).to eq RDF::URI.new("http://resource2") + expect(l4).to be_list + + n = g.first_object(subject: l1, predicate: RDF::URI.new("http://foo/a#p2")) + expect(n).to eq RDF::Literal("v1") + + expect(l4.count).to be 1 + expect(l4.first).to eq RDF::Literal("inner list") + end end context "property paths" do From cfeab07c52cea44e064eed4f199abe64653532ea Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 20 Aug 2020 16:48:03 -0700 Subject: [PATCH 064/193] Write from statements including native list terms. --- lib/rdf/n3/writer.rb | 4 ++-- spec/writer_spec.rb | 32 +++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 8ce37b0..bd07527 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -491,7 +491,7 @@ def quoted(string) # Checks if l is a valid RDF list, i.e. no nodes have other properties. def collection?(l) - return @lists.key?(l) + return @lists.key?(l) || l.list? end def collection(node, position) @@ -505,7 +505,7 @@ def collection(node, position) @output.write("(") log_depth do - list = @lists[node] + list = node.list? ? node : @lists[node] log_debug("collection") {list.inspect} subject_done(RDF.nil) subject_done(node) diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 7c5a70e..01e7cba 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -154,41 +154,49 @@ end end - describe "lists" do + describe "collections" do { "bare list": { input: %(@prefix ex: . (ex:a ex:b) .), - regexp: [%r(^\(\s*ex:a ex:b\s*\) \.$)] + regexp: [%r(^\(\s*ex:a ex:b\s*\) \.$)], + list_terms: true }, "literal list": { input: %(@prefix ex: . ex:a ex:b ( "apple" "banana" ) .), - regexp: [%r(^ex:a ex:b \(\s*"apple" "banana"\s*\) \.$)] + regexp: [%r(^ex:a ex:b \(\s*"apple" "banana"\s*\) \.$)], + list_terms: true }, "empty list": { input: %(@prefix ex: . ex:a ex:b () .), regexp: [%r(^ex:a ex:b \(\s*\) \.$)], - prefixes: { "" => RDF::Vocab::FOAF} + prefixes: { "" => RDF::Vocab::FOAF}, + list_terms: true }, "should generate empty list(2)" => { input: %(@prefix : . :emptyList = () .), regexp: [%r(^:emptyList (<.*sameAs>|owl:sameAs|=) \(\) \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} + prefixes: { "" => "http://xmlns.com/foaf/0.1/"}, + list_terms: true }, "empty list as subject": { input: %(@prefix ex: . () ex:a ex:b .), - regexp: [%r(^\(\s*\) ex:a ex:b \.$)] + regexp: [%r(^\(\s*\) ex:a ex:b \.$)], + list_terms: true }, "list as subject": { input: %(@prefix ex: . (ex:a) ex:b ex:c .), - regexp: [%r(^\(\s*ex:a\s*\) ex:b ex:c \.$)] + regexp: [%r(^\(\s*ex:a\s*\) ex:b ex:c \.$)], + list_terms: true }, "list of empties": { input: %(@prefix ex: . [ex:listOf2Empties (() ())] .), - regexp: [%r(\[\s*ex:listOf2Empties \(\s*\(\s*\) \(\s*\)\s*\)\s*\] \.$)] + regexp: [%r(\[\s*ex:listOf2Empties \(\s*\(\s*\) \(\s*\)\s*\)\s*\] \.$)], + list_terms: true }, "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*\]\)\] \.$)] + regexp: [%r(\[\s*ex:twoAnons \(\s*\[\s*a ex:mother\s*\] \[\s*a ex:father\s*\]\)\] \.$)], + list_terms: true }, "list subjects": { input: %(@prefix ex: . (ex:a ex:b) . ex:a a ex:Thing . ex:b a ex:Thing .), @@ -196,13 +204,15 @@ %r(\(ex:a ex:b\) \.), %r(ex:a a ex:Thing \.), %r(ex:b a ex:Thing \.), - ] + ], + list_terms: true }, "embedded list": { input: %{((:q)) a :Thing .}, regexp: [ %r{\(\(:q\)\) a :Thing \.} - ] + ], + list_terms: true }, "owl:unionOf list": { input: %( From e96b63b75b1f86de186c67a57c679c42d8c65193 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 22 Aug 2020 10:33:12 -0700 Subject: [PATCH 065/193] Fix List#to_sxp. --- lib/rdf/n3/extensions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index d656bb2..26e2670 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -59,7 +59,7 @@ def to_sxp_bin # # @return [String] def to_sxp - to_sxp_bin.to_sxp + to_a.to_sxp_bin.to_sxp end end From 21fc3183aa8ba398b94f9e5d433236998b214eb4 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 22 Aug 2020 10:35:21 -0700 Subject: [PATCH 066/193] In formula, duplicate solutions before filtering, which led to inconsistant behavior. --- lib/rdf/n3/algebra/formula.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index a837177..8b99660 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -27,16 +27,17 @@ class Formula < SPARQL::Algebra::Operator # optional initial solutions for chained queries # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options) - log_debug("formula #{graph_name}") {operands.to_sxp} + log_debug("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin} log_debug("(formula bindings)") { solutions.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) + @query ||= RDF::Query.new(patterns).optimize! + log_debug("(formula query)") { @query.patterns.to_sxp} - @solutions = if @query.patterns.empty? + solutions = if @query.patterns.empty? solutions else - these_solutions = queryable.query(@query, solutions: solutions, optimize: true, **options) + these_solutions = queryable.query(@query, solutions: solutions, **options) these_solutions.map! do |solution| RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)| # Replace blank node bindings with lists, where those blank nodes are associated with lists. @@ -50,7 +51,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new end # Reject solutions which include variables as values - @solutions = @solutions.filter {|s| s.enum_value.none?(&:variable?)} + @solutions = solutions.dup.filter {|s| s.enum_value.none?(&:variable?)} # Use our solutions for sub-ops # Join solutions from other operands From 8b623716d4d8bc0b08065b432de44d627f48e651 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 22 Aug 2020 14:57:15 -0700 Subject: [PATCH 067/193] Create RDF::N3::Repository which allows native lists as terms. --- lib/rdf/n3.rb | 13 +- lib/rdf/n3/reasoner.rb | 11 +- lib/rdf/n3/refinements.rb | 2 +- lib/rdf/n3/repository.rb | 288 ++++++++++++++++++++++++++++++++++++ lib/rdf/n3/writer.rb | 2 +- script/parse | 7 +- script/tc | 11 +- spec/reader_spec.rb | 8 +- spec/reasoner_spec.rb | 5 +- spec/repository_spec.rb | 72 +++++++++ spec/suite_extended_spec.rb | 2 +- spec/suite_helper.rb | 2 +- spec/suite_parser_spec.rb | 2 +- spec/suite_reasoner_spec.rb | 5 +- 14 files changed, 399 insertions(+), 31 deletions(-) create mode 100644 lib/rdf/n3/repository.rb create mode 100644 spec/repository_spec.rb diff --git a/lib/rdf/n3.rb b/lib/rdf/n3.rb index ec2187d..b8ccbe4 100644 --- a/lib/rdf/n3.rb +++ b/lib/rdf/n3.rb @@ -24,11 +24,12 @@ module N3 require 'rdf/n3/vocab' require 'rdf/n3/extensions' require 'rdf/n3/refinements' - autoload :List, 'rdf/n3/list' - autoload :Reader, 'rdf/n3/reader' - autoload :Reasoner, 'rdf/n3/reasoner' - autoload :Terminals, 'rdf/n3/terminals' - autoload :VERSION, 'rdf/n3/version' - autoload :Writer, 'rdf/n3/writer' + autoload :List, 'rdf/n3/list' + autoload :Reader, 'rdf/n3/reader' + autoload :Reasoner, 'rdf/n3/reasoner' + autoload :Repository, 'rdf/n3/repository' + autoload :Terminals, 'rdf/n3/terminals' + autoload :VERSION, 'rdf/n3/version' + autoload :Writer, 'rdf/n3/writer' end end \ No newline at end of file diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index dfc2714..c653a85 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -51,6 +51,7 @@ def self.open(file) # reasoner.each_triple {} # # @param [RDF::Mutable] input (nil) + # Input should be parsed N3 using native lists (see `:list_terms` option to {RDF::N3::Reader#initialize}) # @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) @@ -62,8 +63,8 @@ def initialize(input, **options, &block) @options = options @mutable = case input when RDF::Mutable then input - when RDF::Enumerable then RDF::Repository.new {|r| r << input} - else RDF::Repository.new + when RDF::Enumerable then RDF::N3::Repository.new {|r| r << input} + else RDF::N3::Repository.new end log_debug("reasoner: expression") {SXP::Generator.string(formula.to_sxp_bin)} @@ -79,7 +80,7 @@ def initialize(input, **options, &block) ## # Returns a copy of this reasoner def dup - repo = RDF::Repository.new {|r| r << @mutable} + repo = RDF::N3::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 @@ -250,7 +251,7 @@ def formula end # Create `queryable` as a repo subset of `mutable` excluding built-ins and statements with a variable subject or predicate. This is useful for extracting lists. - queryable = RDF::Repository.new + queryable = RDF::N3::Repository.new # Add patterns to appropiate formula based on graph_name, # and replace subject and object bnodes which identify @@ -303,6 +304,8 @@ def formula end end + log_info("reasoner formula") {SXP::Generator.string formulae[nil].to_sxp_bin} + # Formula is that without a graph name formulae[nil] end diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb index 58507ab..aa3b8da 100644 --- a/lib/rdf/n3/refinements.rb +++ b/lib/rdf/n3/refinements.rb @@ -63,7 +63,7 @@ def validate! # @return [Boolean] `true` or `false` def valid? (has_subject? ? (subject.term? || subject.variable?) && subject.valid? : true) && - (has_predicate? ? (predicate.resource? || predicate.variable?) && predicate.valid? : true) && + (has_predicate? ? (predicate.term? || predicate.variable?) && predicate.valid? : true) && (has_object? ? (object.term? || object.variable?) && object.valid? : true) && (has_graph? ? (graph_name.resource? || graph_name.variable?) && graph_name.valid? : true) rescue NoMethodError diff --git a/lib/rdf/n3/repository.rb b/lib/rdf/n3/repository.rb new file mode 100644 index 0000000..5338315 --- /dev/null +++ b/lib/rdf/n3/repository.rb @@ -0,0 +1,288 @@ +module RDF::N3 + ## + # Sub-class of RDF::Repository which allows for native lists in different positions. + class Repository < RDF::Repository + DEFAULT_GRAPH = false + + ## + # Initializes this repository instance. + # + # @param [URI, #to_s] uri (nil) + # @param [String, #to_s] title (nil) + # @param [Hash{Symbol => Object}] options + # @option options [Boolean] :with_graph_name (true) + # Indicates that the repository supports named graphs, otherwise, + # only the default graph is supported. + # @option options [Boolean] :with_validity (true) + # Indicates that the repository supports named validation. + # @option options [Boolean] :transaction_class (DEFAULT_TX_CLASS) + # Specifies the RDF::Transaction implementation to use in this Repository. + # @yield [repository] + # @yieldparam [Repository] repository + def initialize(uri: nil, title: nil, **options, &block) + @data = options.delete(:data) || {} + super do + if block_given? + case block.arity + when 1 then block.call(self) + else instance_eval(&block) + end + end + end + end + + ## + # Returns `true` if this respository supports the given `feature`. + # + # This repository supports list_terms. + def supports?(feature) + case feature.to_sym + when :list_terms then true + when :rdfstar then true + when :snapshots then false + else super + end + end + + ## + # @private + # @see RDF::Countable#count + def count + count = 0 + @data.each do |_, ss| + ss.each do |_, ps| + ps.each { |_, os| count += os.size } + end + end + count + end + + ## + # @private + # @see RDF::Enumerable#has_graph? + def has_graph?(graph) + @data.has_key?(graph) + end + + ## + # @private + # @see RDF::Enumerable#each_graph + def graph_names(options = nil, &block) + @data.keys.reject { |g| g == DEFAULT_GRAPH }.to_a + end + + ## + # @private + # @see RDF::Enumerable#each_graph + def each_graph(&block) + if block_given? + @data.each_key do |gn| + yield RDF::Graph.new(graph_name: (gn == DEFAULT_GRAPH ? nil : gn), data: self) + end + end + enum_graph + end + + ## + # @private + # @see RDF::Enumerable#has_statement? + def has_statement?(statement) + has_statement_in?(@data, statement) + end + + ## + # @private + # @see RDF::Enumerable#each_statement + def each_statement(&block) + if block_given? + @data.each do |g, ss| + ss.each do |s, ps| + ps.each do |p, os| + os.each do |o, object_options| + yield RDF::Statement.new(s, p, o, object_options.merge(graph_name: g.equal?(DEFAULT_GRAPH) ? nil : g)) + end + end + end + end + end + enum_statement + end + alias_method :each, :each_statement + + ## + # @see Mutable#apply_changeset + def apply_changeset(changeset) + data = @data + changeset.deletes.each do |del| + if del.constant? + data = delete_from(data, del) + else + # we need this condition to handle wildcard statements + query_pattern(del) { |stmt| data = delete_from(data, stmt) } + end + end + changeset.inserts.each { |ins| data = insert_to(data, ins) } + @data = data + end + + ## + # @see RDF::Dataset#isolation_level + def isolation_level + :serializable + end + + protected + + ## + # Match elements with `eql?`, not `==` + # + # `graph_name` of `false` matches default graph. Unbound variable matches + # non-false graph name. + # + # Matches terms which are native lists. + # + # @private + # @see RDF::Queryable#query_pattern + def query_pattern(pattern, **options, &block) + if block_given? + graph_name = pattern.graph_name + subject = pattern.subject + predicate = pattern.predicate + object = pattern.object + + cs = @data.has_key?(graph_name) ? { graph_name => @data[graph_name] } : @data + + cs.each do |c, ss| + next unless graph_name.nil? || + graph_name == DEFAULT_GRAPH && !c || + graph_name.eql?(c) + + ss = if subject.nil? || subject.is_a?(RDF::Query::Variable) + ss + elsif subject.is_a?(RDF::N3::List) + # Match subjects which are eql lists + ss.keys.select {|s| s.list? && subject.eql?(s)}.inject({}) do |memo, li| + memo.merge(li => ss[li]) + end + elsif subject.is_a?(RDF::Query::Pattern) + # Match subjects which are statements matching this pattern + ss.keys.select {|s| s.statement? && subject.eql?(s)}.inject({}) do |memo, st| + memo.merge(st => ss[st]) + end + elsif ss.has_key?(subject) + { subject => ss[subject] } + else + [] + end + ss.each do |s, ps| + ps = if predicate.nil? || predicate.is_a?(RDF::Query::Variable) + ps + elsif predicate.is_a?(RDF::N3::List) + # Match predicates which are eql lists + ps.keys.select {|p| p.list? && predicate.eql?(p)}.inject({}) do |memo, li| + memo.merge(li => ps[li]) + end + elsif ps.has_key?(predicate) + { predicate => ps[predicate] } + else + [] + end + ps.each do |p, os| + os.each do |o, object_options| + next unless object.nil? || object.eql?(o) + yield RDF::Statement.new(s, p, o, object_options.merge(graph_name: c.equal?(DEFAULT_GRAPH) ? nil : c)) + end + end + end + end + else + enum_for(:query_pattern, pattern, **options) + end + end + + ## + # @private + # @see RDF::Mutable#insert + def insert_statement(statement) + @data = insert_to(@data, statement) + end + + ## + # @private + # @see RDF::Mutable#delete + def delete_statement(statement) + @data = delete_from(@data, statement) + end + + ## + # @private + # @see RDF::Mutable#clear + def clear_statements + @data = @data.clear + end + + ## + # @private + # @return [Hash] + def data + @data + end + + ## + # @private + # @return [Hash] + def data=(hash) + @data = hash + end + + private + + ## + # @private + # @see #has_statement + def has_statement_in?(data, statement) + s, p, o, g = statement.to_quad + g ||= DEFAULT_GRAPH + + data.has_key?(g) && + data[g].has_key?(s) && + data[g][s].has_key?(p) && + data[g][s][p].has_key?(o) + end + + ## + # @private + # @return [Hash] a new, updated hash + def insert_to(data, statement) + raise ArgumentError, "Statement #{statement.inspect} is incomplete" if statement.incomplete? + + unless has_statement_in?(data, statement) + s, p, o, c = statement.to_quad + c ||= DEFAULT_GRAPH + + data = data.has_key?(c) ? data.dup : data.merge(c => {}) + data[c] = data[c].has_key?(s) ? data[c].dup : data[c].merge(s => {}) + data[c][s] = data[c][s].has_key?(p) ? data[c][s].dup : data[c][s].merge(p => {}) + data[c][s][p] = data[c][s][p].merge(o => statement.options) + end + data + end + + ## + # @private + # @return [Hash] a new, updated hash + def delete_from(data, statement) + if has_statement_in?(data, statement) + s, p, o, g = statement.to_quad + g = DEFAULT_GRAPH unless supports?(:graph_name) + g ||= DEFAULT_GRAPH + + os = data[g][s][p].dup.delete(o) + ps = os.empty? ? data[g][s].dup.delete_if {|k,v| k == p} : data[g][s].merge(p => os) + ss = ps.empty? ? data[g].dup.delete_if {|k,v| k == s} : data[g].merge(s => ps) + return ss.empty? ? data.dup.delete_if {|k,v| k == g} : data.merge(g => ss) + end + data + end + end +end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index bd07527..92daf79 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -108,7 +108,7 @@ def self.options # @yield [writer] # @yieldparam [RDF::Writer] writer def initialize(output = $stdout, **options, &block) - @repo = RDF::Repository.new + @repo = RDF::N3::Repository.new @uri_to_pname = {} @uri_to_prefix = {} super do diff --git a/script/parse b/script/parse index 8b4d23d..720100e 100755 --- a/script/parse +++ b/script/parse @@ -23,7 +23,7 @@ def run(input, **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 + repo = RDF::N3::Repository.new if options[:conclusions] repo << reasoner.conclusions elsif options[:data] @@ -93,11 +93,12 @@ parser_options = { } options = { - parser_options: parser_options, + input_format: :n3, + list_terms: true, logger: logger, output: STDOUT, output_format: :n3, - input_format: :n3, + parser_options: parser_options, } input = nil diff --git a/script/tc b/script/tc index 6b78edf..8f90809 100755 --- a/script/tc +++ b/script/tc @@ -63,7 +63,7 @@ def run_tc(man, tc, **options) reader = RDF::N3::Reader.new(tc.input, **options) - graph = RDF::Repository.new + graph = RDF::N3::Repository.new result = nil if !options[:slow] && tc.slow? @@ -91,7 +91,7 @@ def run_tc(man, tc, **options) if tc.evaluate? && result.nil? begin - output_graph = RDF::Repository.load(tc.result, base_uri: tc.base) + output_graph = RDF::N3::Repository.load(tc.result, base_uri: tc.base) result = graph.isomorphic_with?(output_graph) ? "passed" : "failed" rescue Exception => e if options[:verbose] @@ -102,7 +102,7 @@ def run_tc(man, tc, **options) elsif tc.reason? && result.nil? reasoner = RDF::N3::Reasoner.new(graph, **options) - repo = RDF::Repository.new + repo = RDF::N3::Repository.new begin reasoner.execute(logger: logger, think: !!tc.options['think']) @@ -121,7 +121,7 @@ def run_tc(man, tc, **options) end STDERR.puts "\nResult: #{repo.dump(:n3)}" if options[:verbose] - output_graph = RDF::Repository.load(tc.result, base_uri: tc.base) + output_graph = RDF::N3::Repository.load(tc.result, base_uri: tc.base) result = repo.isomorphic_with?(output_graph) ? "passed" : "failed" else result ||= "passed" @@ -160,9 +160,10 @@ def run_tc(man, tc, **options) end options = { + level: Logger::WARN, + list_terms: true, output: STDOUT, results: {}, - level: Logger::WARN, slow: true # Run slow tests by default } diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index d599815..51fd911 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -1043,7 +1043,7 @@ end describe "formulae" do - before(:each) { @repo = RDF::Repository.new } + before(:each) { @repo = RDF::N3:: Repository.new } it "creates an RDF::Node instance for formula" do n3 = %(:a :b {} .) @@ -1057,7 +1057,7 @@ n3 = %(:a :b {[:c :d]} .) 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")} + expected = RDF::N3:: Repository.new {|r| r << RDF::TriG::Reader.new(trig, base_uri: "http://a/b")} expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end @@ -1078,7 +1078,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")} + expected = RDF::N3:: Repository.new {|r| r << RDF::TriG::Reader.new(trig, base_uri: "http://a/b")} expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end @@ -1152,7 +1152,7 @@ # ENDS ) - @repo = RDF::Repository.new + @repo = RDF::N3:: Repository.new parse(n3, repo: @repo, base_uri: "http://a/b") end subject {@repo} diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index a7c808f..6e3a55e 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -243,7 +243,7 @@ # Parse N3 input into a repository def parse(input, **options) - repo = options[:repo] || RDF::Repository.new + repo = options[:repo] || RDF::N3:: Repository.new RDF::N3::Reader.new(input, **options).each_statement do |statement| repo << statement end @@ -252,9 +252,10 @@ def parse(input, **options) # Reason over input, returning a repo def reason(input, base_uri: 'http://example.com/', filter: false, data: true, think: true, **options) + options[:list_terms] ||= true input = parse(input, **options) if input.is_a?(String) reasoner = RDF::N3::Reasoner.new(input, base_uri: base_uri) - repo = RDF::Repository.new + repo = RDF::N3:: Repository.new reasoner.execute(logger: logger, think: think) if filter diff --git a/spec/repository_spec.rb b/spec/repository_spec.rb new file mode 100644 index 0000000..ad2708b --- /dev/null +++ b/spec/repository_spec.rb @@ -0,0 +1,72 @@ +require_relative 'spec_helper' +require 'rdf/spec/repository' + +describe RDF::N3::Repository do + # @see lib/rdf/spec/repository.rb in rdf-spec + it_behaves_like 'an RDF::Repository' do + let(:repository) { RDF::N3::Repository.new } + end + + it { is_expected.not_to be_durable } + + let(:list_subject) {RDF::Statement.new(RDF::N3::List[RDF::Literal('a'), RDF::Literal('b')], RDF::URI('p'), RDF::Literal('o'))} + let(:list_object) {RDF::Statement.new(RDF::URI('s'), RDF::URI('p'), RDF::N3::List[RDF::Literal('a'), RDF::Literal('b')])} + + it "maintains arbitrary options" do + repository = RDF::N3::Repository.new(foo: :bar) + expect(repository.options).to have_key(:foo) + expect(repository.options[:foo]).to eq :bar + end + + describe '#query_pattern' do + before { subject.insert(*(RDF::Spec.quads + [list_subject, list_object])) } + + it "finds a list subject constant" do + pattern = RDF::Query::Pattern.new(list_subject.subject, nil, nil) + solutions = [] + subject.send(:query_pattern, pattern) {|s| solutions << s} + + expect(solutions.size).to eq 1 + end + end + + describe '#insert_to' do + it "inserts a statement with a list subject" do + subject << list_subject + expect(subject.count).to eql 1 + expect(subject.statements.first).to eql list_subject + end + + it "inserts a statement with a list object" do + subject << list_object + expect(subject.count).to eql 1 + expect(subject.statements.first).to eql list_object + end + end + + describe '#has_statement' do + it "detects a statement with a list subject" do + subject << list_subject + expect(subject).to have_statement(list_subject) + end + + it "detects a statement with a list object" do + subject << list_object + expect(subject).to have_statement(list_object) + end + end + + describe '#delete_from' do + it "deletes a statement with a list subject" do + subject << list_subject + subject.delete(list_subject) + expect(subject.count).to eql 0 + end + + it "deletes a statement with a list object" do + subject << list_object + subject.delete(list_object) + expect(subject.count).to eql 0 + end + end +end diff --git a/spec/suite_extended_spec.rb b/spec/suite_extended_spec.rb index cf1b3b5..a0f4dee 100644 --- a/spec/suite_extended_spec.rb +++ b/spec/suite_extended_spec.rb @@ -47,7 +47,7 @@ output_repo = if t.evaluate? begin format = detect_format(t.expected) - RDF::Repository.load(t.result, format: format, base_uri: t.accept) + RDF::N3:: Repository.load(t.result, format: format, base_uri: t.accept) rescue Exception => e expect(e.message).to produce("Exception loading output #{e.inspect}", t) end diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 0c67df1..ee3bcfc 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -102,7 +102,7 @@ module SuiteTest class Manifest < JSON::LD::Resource def self.open(file) #puts "open: #{file}" - g = RDF::Repository.load(file) + g = RDF::N3:: Repository.load(file) JSON::LD::API.fromRDF(g) do |expanded| JSON::LD::API.frame(expanded, FRAME) do |framed| yield Manifest.new(framed) diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb index 9f66456..0d0273f 100644 --- a/spec/suite_parser_spec.rb +++ b/spec/suite_parser_spec.rb @@ -41,7 +41,7 @@ output_repo = if t.evaluate? begin format = detect_format(t.expected) - RDF::Repository.load(t.result, format: format, base_uri: t.accept) + RDF::N3:: Repository.load(t.result, format: format, base_uri: t.accept) rescue Exception => e expect(e.message).to produce("Exception loading output #{e.inspect}", t) end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 288d6c8..a3ecc87 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -40,6 +40,7 @@ reader = RDF::N3::Reader.new(t.input, base_uri: t.base, canonicalize: false, + list_terms: true, validate: false, logger: false) @@ -47,7 +48,7 @@ base_uri: t.base, logger: t.logger) - repo = RDF::Repository.new + repo = RDF::N3:: Repository.new if t.positive_test? begin @@ -65,7 +66,7 @@ t.logger.info "result:\n#{repo.dump(:n3)}" if t.evaluate? || t.reason? - output_repo = RDF::Repository.load(t.result, format: :n3, base_uri: t.base) + output_repo = RDF::N3:: Repository.load(t.result, format: :n3, base_uri: t.base) expect(repo).to be_equivalent_graph(output_repo, t) else end From 874a0f6677bb501b0ace21dd0ae575b5fd0f99de Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 24 Aug 2020 15:51:11 -0700 Subject: [PATCH 068/193] Coerce list values to RDF terms. --- lib/rdf/n3/list.rb | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index e0c6822..c3f0687 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -68,7 +68,16 @@ def initialize(subject: nil, graph: nil, values: nil, &block) @values = case when values - values + values.map do |v| + # Convert values, as necessary. + case v + when RDF::Value then v.to_term + when Symbol then RDF::Node.intern(v) + when Array then RDF::N3::List.new(values: v) + when nil then nil + else RDF::Literal.new(v) + end + end when subject && graph ln = RDF::List.new(subject: subject, graph: graph) @valid = ln.valid? @@ -126,29 +135,39 @@ def []=(*args) when Array then args.last when RDF::List then args.last.to_a else [args.last] + end.map do |v| + # Convert values, as necessary. + case v + when RDF::Value then v.to_term + when Symbol then RDF::Node.intern(v) + when Array then RDF::N3::List.new(values: v) + when nil then nil + else RDF::Literal.new(v) + end end - case args.length + ret = case args.length when 3 start, length = args[0], args[1] - @value[start, length] = value + @subject = nil if start == 0 + @values[start, length] = value + when 2 case args.first when Integer raise ArgumentError, "Index form of []= takes a single term" if args.last.is_a?(Array) - @value[args.first] = case args.last - when RDF::N3::List then args.last - when RDF::List then RDF::N3::List.new(values: args.last.to_a) - when Array then RDF::N3::List.new(values: args.last.to_a) - else args.last - end + @values[args.first] = value.first when Range - @value[args.first] = value + @values[args.first] = value else raise ArgumentError, "Index form of must use an integer or range" end else raise ArgumentError, "List []= takes one or two index values" end + + @subject = RDF.nil if @values.empty? + @subject ||= RDF::Node.new + ret # Returns inserted values end ## From 874a96a14d9e50c845dc39cbae4ba3c7a9e7559c Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 24 Aug 2020 15:54:02 -0700 Subject: [PATCH 069/193] Fix list methods: `#fetch`, `#rest`, `#tail`, and `#each_statement`. Add methods to coerce to a pattern, and evaluate that pattern: `#has_nodes?`, `#to_existential`, `#eql?`, `#variable?`, `#variables`, `#var_values`, `#evaluate`, `#solution`. Add more comprehensive list specs. --- lib/rdf/n3/list.rb | 162 +++++++++++- spec/list_spec.rb | 647 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 803 insertions(+), 6 deletions(-) create mode 100644 spec/list_spec.rb diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index c3f0687..27fe95e 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -286,7 +286,7 @@ def index(value) # @return [RDF::Term, nil] # @see http://ruby-doc.org/core-1.9/classes/Array.html#M000420 def fetch(*args, &block) - @values.fetch(*args, default) + @values.fetch(*args, &block) end ## @@ -333,7 +333,7 @@ def last # # @return [RDF::List] def rest - self.class.new(values: values[1..-1]) + self.class.new(values: @values[1..-1]) end ## @@ -344,7 +344,7 @@ def rest # # @return [RDF::List] def tail - self.class.new(values: values[-1..-1]) + self.class.new(values: @values[-1..-1]) end ## @@ -386,16 +386,17 @@ def each_statement(&block) *values, last = @values while !values.empty? subj = subjects.shift - block.call(RDF::Statement(subj, RDF.first, values.shift)) + value = values.shift + block.call(RDF::Statement(subj, RDF.first, value.list? ? value.subject : value)) block.call(RDF::Statement(subj, RDF.rest, subjects.first)) end subj = subjects.shift - block.call(RDF::Statement(subj, RDF.first, last)) + block.call(RDF::Statement(subj, RDF.first, last.list? ? last.subject : last)) block.call(RDF::Statement(subj, RDF.rest, RDF.nil)) end # If a graph was used, also get statements from sub-lists - @values.select(&:list?).each {|li| li.each_statement(&block)} if graph + @values.select(&:list?).each {|li| li.each_statement(&block)} end ## @@ -414,6 +415,29 @@ def each_subject(&block) each_statement {|st| block.call(st.subject) if st.predicate == RDF.rest} end + ## + # Does this list, or any recusive list have any blank node members? + # + # @return [Boolean] + def has_nodes? + @values.any? {|e| e.node? || e.list? && e.has_nodes?} + end + + ## + # Substitutes blank node members with existential variables, recusively. + # + # @return [RDF::N3::List] + def to_existential + values = @values.map do |e| + case e + when RDF::Node then RDF::Query::Variable.new(e.id, existential: true) + when RDF::N3::List then e.to_existential + else e + end + end + RDF::N3::List.new(values: values) + end + ## # Returns the elements in this list as an array. # @@ -425,5 +449,131 @@ def each_subject(&block) def to_a @values end + + ## + # Checks pattern equality against another list, considering nesting. + # + # @param [List, Array] other + # @return [Boolean] + def eql?(other) + other = RDF::N3::List[*other] if other.is_a?(Array) + return false if !other.is_a?(RDF::List) || count != other.count + @values.each_with_index do |li, ndx| + case li + when RDF::Query::Pattern, RDF::N3::List + return false unless li.eql?(other.at(ndx)) + else + return false unless li == other.at(ndx) + end + end + true + end + + ## + # A list is variable if any of its members are variable? + # + # @return [Boolean] + def variable? + @values.any?(&:variable?) + end + + ## + # Returns all variables in this list. + # + # Note: this returns a hash containing distinct variables only. + # + # @return [Hash{Symbol => Variable}] + def variables + @values.inject({}) do |hash, li| + li.respond_to?(:variables) ? hash.merge(li.variables) : hash + end + end + + ## + # Returns the number of variables in this list, recursively. + # + # @return [Integer] + def variable_count + variables.length + end + + ## + # Returns all values the list in the same pattern position + # + # @param [Symbol] var + # @param [RDF::N3::List] list + # @return [Array] + def var_values(var, list) + results = [] + @values.each_index do |ndx| + maybe_var = @values[ndx] + next unless maybe_var.respond_to?(:var_values) + results.append(maybe_var.var_values(var, list.at(ndx))) + end + results.flatten.compact + end + + ## + # Evaluates the list using the given variable `bindings`. + # + # @param [Hash{Symbol => RDF::Term}] bindings + # a query solution containing zero or more variable bindings + # @param [Hash{Symbol => Object}] options ({}) + # options passed from query + # @return [RDF::N3::List] + # @see SPARQL::Algebra::Expression.evaluate + def evaluate(bindings, **options) + # if values are constant, simply return ourselves + return self if to_a.none? {|li| li.node? || li.variable?} + bindings = bindings.to_h unless bindings.is_a?(Hash) + # Create a new list subject using a combination of the current subject and a hash of the binding values + subj = "#{subject.id}_#{bindings.values.sort.hash}" + RDF::N3::List.new(subject: RDF::Node.intern(subj), values: to_a.map {|o| o.evaluate(bindings, **options)}) + end + + + ## + # Returns a query solution constructed by binding any variables in this list with the corresponding terms in the given `list`. + # + # @param [RDF::N3::List] list + # a native list with patterns to bind. + # @return [RDF::Query::Solution] + # @see RDF::Query::Pattern#solution + def solution(list) + RDF::Query::Solution.new do |solution| + @values.each_with_index do |li, ndx| + if li.respond_to?(:solution) + solution.merge!(li.solution(list[ndx])) + elsif li.is_a?(RDF::Query::Variable) + solution[li.to_sym] = list[ndx] + end + end + end + end + + ## + # Returns the base representation of this term. + # + # @return [Sring] + def to_base + "(#{@values.map(&:to_base).join(' ')})" + end + + private + + ## + # Normalizes `Array` to `RDF::List` and `nil` to `RDF.nil`. + # + # @param value [Object] + # @return [RDF::Value, Object] normalized value + def normalize_value(value) + case value + when RDF::Value then value.to_term + when Array then RDF::N3::List.new(values: value) + when Symbol then RDF::Node.intern(value) + when nil then nil + else RDF::Literal.new(value) + end + end end end diff --git a/spec/list_spec.rb b/spec/list_spec.rb new file mode 100644 index 0000000..068f533 --- /dev/null +++ b/spec/list_spec.rb @@ -0,0 +1,647 @@ +# coding: utf-8 +require_relative 'spec_helper' + +describe RDF::N3::List do + + let(:empty) {RDF::List::NIL} + let(:abc) {described_class[RDF::Literal.new('a'), RDF::Literal.new('b'), RDF::Literal.new('c')]} + let(:nodes) {described_class[RDF::Node.new('a'), RDF::Node.new('b'), RDF::Node.new('c')]} + let(:ten) {described_class[*(1..10)]} + let(:pattern) {described_class[RDF::Query::Variable.new("a"), RDF::Query::Variable.new("b"), RDF::Literal("c")]} + + describe ".to_uri" do + specify {expect(described_class.to_uri).to eq RDF::N3::List::URI} + end + + describe "vocabulary accessors" do + specify {expect(described_class.append).to be_a(RDF::Vocabulary::Term)} + specify {expect(described_class.append).to eql RDF::N3::List::URI.+("append")} + end + + describe ".try_list" do + end + + describe "[]" do + context "without arguments" do + it "constructs a new empty list" do + expect(described_class[]).to be_an(described_class) + expect(described_class[]).to be_empty + expect(described_class[]).to eq RDF::List::NIL + end + end + + context "with arguments" do + it "constructs a new non-empty list" do + expect(described_class[1, 2, 3]).to be_an(described_class) + expect(described_class[1, 2, 3]).not_to be_empty + end + + it "accepts list arguments" do + expect { described_class[described_class[]] }.not_to raise_error + end + + it "accepts blank node arguments" do + expect { described_class[RDF::Node.new] }.not_to raise_error + end + + it "accepts URI arguments" do + expect { described_class[RDF.nil] }.not_to raise_error + end + + it "accepts nil arguments" do + expect { described_class[nil] }.not_to raise_error + end + + it "accepts literal arguments" do + expect { described_class[RDF::Literal.new("Hello, world!", language: :en)] }.not_to raise_error + end + + it "accepts boolean arguments" do + expect { described_class[true, false] }.not_to raise_error + end + + it "accepts string arguments" do + expect { described_class["foo", "bar"] }.not_to raise_error + end + + it "accepts integer arguments" do + expect { described_class[1, 2, 3] }.not_to raise_error + end + end + end + + describe "#initialize" do + context "with subject and graph" do + let(:graph) {RDF::Graph.new} + it "initializes pre-existing list" do + n = RDF::Node.new + graph.insert(RDF::Statement(n, RDF.first, "foo")) + graph.insert(RDF::Statement(n, RDF.rest, RDF.nil)) + described_class.new(subject: n, graph: graph).valid? + expect(described_class.new(subject: n, graph: graph)).to be_valid + end + end + + context "without subject or graph" do + end + + context "with subject, graph and no values" do + end + + context "with subject and values" do + end + end + + describe "#[]" do + it "accepts one argument" do + expect { empty[0] }.not_to raise_error + end + + it "rejects fewer arguments" do + expect { empty.__send__(:[]) }.to raise_error(ArgumentError) + end + + it "returns a value for valid indexes" do + expect(ten[0]).to be_a_value + end + + it "returns nil for invalid indexes" do + expect(empty[0]).to be_nil + expect(ten[20]).to be_nil + end + + context "with start index and a length" do + it "accepts two arguments" do + expect { ten[0, 9] }.not_to raise_error + end + + it "returns a value" do + expect(ten[0, 9]).to be_a_value + end + end + + context "with a range" do + it "accepts one argument" do + expect { ten[0..9] }.not_to raise_error + end + end + end + + describe "#[]=" do + it "accepts one integer argument" do + expect { ten[0] = 0 }.not_to raise_error + end + + it "accepts two integer arguments" do + expect { ten[0, 0] = 0 }.not_to raise_error + end + + it "accepts a range argument" do + expect { ten[0..1] = 0 }.not_to raise_error + end + + it "rejects fewer arguments" do + expect { ten[] = 0 }.to raise_error(ArgumentError) + end + + it "rejects extra arguments" do + expect { ten[0, 1, 2] = 0 }.to raise_error(ArgumentError) + end + + context "with index" do + it "rejects string index" do + expect { ten["1"] = 0 }.to raise_error(ArgumentError) + end + + { + "a[4] = '4'" => { + initial: [], + index: 4, + value: "4", + result: [nil, nil, nil, nil, "4"] + }, + "a[-1] = 'Z'" => { + initial: ["A", "4"], + index: -1, + value: "Z", + result: ["A", "Z"] + }, + }.each do |name, props| + it name do + list = described_class[*props[:initial]] + list[props[:index]] = props[:value] + expect(list).to eq described_class[*props[:result]] + end + end + end + + context "with start and length" do + { + "a[0, 3] = [ 'a', 'b', 'c' ]" => { + initial: [nil, nil, nil, nil, "4"], + start: 0, + length: 3, + value: [ 'a', 'b', 'c' ], + result: ["a", "b", "c", nil, "4"] + }, + "a[0, 2] = '?'" => { + initial: ["a", 1, 2, nil, "4"], + start: 0, + length: 2, + value: "?", + result: ["?", 2, nil, "4"] + }, + "a[0, 0] = [ 1, 2 ]" => { + initial: ["A"], + start: 0, + length: 0, + value: [ 1, 2 ], + result: [1, 2, "A"] + }, + "a[3, 0] = 'B'" => { + initial: [1, 2, "A"], + start: 3, + length: 0, + value: "B", + result: [1, 2, "A", "B"] + }, + "lorem[0, 5] = []" => { + initial: ['lorem' 'ipsum' 'dolor' 'sit' 'amet'], + start: 0, + length: 5, + value: [], + result: [] + }, + }.each do |name, props| + it name do + list = described_class[*props[:initial]] + list[props[:start], props[:length]] = props[:value] + expect(list).to eq described_class[*props[:result]] + end + end + + it "sets subject to rdf:nil when list is emptied" do + list = described_class[%(lorem ipsum dolor sit amet)] + list[0,5] = [] + expect(list).to eq described_class[] + expect(list.subject).to eq RDF.nil + end + end + + context "with range" do + { + "a[1..2] = [ 1, 2 ]" => { + initial: ["a", "b", "c", nil, "4"], + range: (1..2), + value: [ 1, 2 ], + result: ["a", 1, 2, nil, "4"] + }, + "a[0..2] = 'A'" => { + initial: ["?", 2, nil, "4"], + range: (0..2), + value: "A", + result: ["A", "4"] + }, + "a[1..-1] = nil" => { + initial: ["A", "Z"], + range: (1..-1), + value: nil, + result: ["A", nil] + }, + "a[1..-1] = []" => { + initial: ["A", nil], + range: (1..-1), + value: [], + result: ["A"] + }, + }.each do |name, props| + it name do + list = described_class[*props[:initial]] + list[props[:range]] = props[:value] + expect(list).to eq described_class[*props[:result]] + end + end + end + end + + describe "#<<" do + it "accepts one argument" do + expect { ten << 11 }.not_to raise_error + end + + it "rejects fewer arguments" do + expect { ten.__send__(:<<) }.to raise_error(ArgumentError) + end + + it "appends the new value at the tail of the list" do + ten << 11 + expect(ten.last).to eq RDF::Literal.new(11) + end + + it "increments the length of the list by one" do + ten << 11 + expect(ten.length).to eq 11 + end + + it "returns self" do + expect(ten << 11).to equal(ten) + end + end + + describe "#shift" do + it "returns the first element from the list" do + expect(ten.shift).to eq RDF::Literal.new(1) + end + + it "removes the first element from the list" do + ten.shift + expect(ten).to eq described_class[2, 3, 4, 5, 6, 7, 8, 9, 10] + end + + it "should return nil from an empty list" do + expect(empty.shift).to be_nil + end + end + + describe "#unshift" do + it "adds element to beginning of list" do + ten.unshift(0) + expect(ten).to eq described_class[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + end + + it "should return the new list" do + expect(ten.unshift(0)).to eq described_class[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + end + end + + describe "#clear" do + it "empties list" do + expect(ten.clear).to eq described_class[] + end + end + + describe "#eql?" do + it "requires an argument" do + expect { empty.send(:eql?) }.to raise_error(ArgumentError) + end + + it "returns true when given the same list" do + expect(ten).to eql ten + end + + it "returns true when comparing a list to its contents" do + expect(ten).to eql ten.to_a + end + + it "does not equal different list" do + expect(abc).not_to eql ten + end + + it "pattern matches list" do + expect(pattern).to eql abc + end + + it "does not match of different size" do + expect(pattern).not_to eql ten + end + end + + describe "#empty?" do + it "requires no arguments" do + expect { empty.empty? }.not_to raise_error + end + + it "returns a boolean" do + expect(empty).to be_empty + expect(abc).not_to be_empty + expect(ten).not_to be_empty + end + end + + describe "#length" do + it "requires no arguments" do + expect { empty.length }.not_to raise_error + end + + it "returns an integer" do + expect(empty.length).to be_an(Integer) + end + + it "returns the length of the list" do + expect(empty.length).to eq 0 + expect(abc.length).to eq 3 + expect(ten.length).to eq 10 + end + end + + describe "#size" do + it "aliases #length" do + expect(empty.size).to eq empty.length + expect(ten.size).to eq ten.length + end + end + + describe "#index" do + it "accepts one argument" do + expect { ten.index(nil) }.not_to raise_error + end + end + + describe "#fetch" do + it "requires one argument" do + expect { ten.fetch }.to raise_error(ArgumentError) + expect { ten.fetch(0) }.not_to raise_error + end + + it "returns a value" do + expect(ten.fetch(0)).to be_a_value + end + + it "returns the value at the given index" do + expect(ten.fetch(0)).to eq RDF::Literal.new(1) + expect(ten.fetch(9)).to eq RDF::Literal.new(10) + end + + it "raises IndexError for invalid indexes" do + expect { ten.fetch(20) }.to raise_error(IndexError) + end + + describe "with a default value" do + it "accepts two arguments" do + expect { ten.fetch(0, nil) }.not_to raise_error + end + + it "returns the second argument for invalid indexes" do + expect { ten.fetch(20, nil) }.not_to raise_error + expect(ten.fetch(20, true)).to eq true + end + end + + describe "with a block" do + it "yields to the given block for invalid indexes" do + expect { ten.fetch(20) { |index| } }.not_to raise_error + expect(ten.fetch(20) { |index| true }).to be_truthy + end + end + end + + describe "#at" do + it "accepts one argument" do + expect { ten.at(0) }.not_to raise_error + end + end + + describe "#last" do + it "requires no arguments" do + expect { ten.last }.not_to raise_error + end + end + + describe "#rest" do + it "requires no arguments" do + expect { ten.rest }.not_to raise_error + end + end + + describe "#tail" do + it "requires no arguments" do + expect { ten.tail }.not_to raise_error + end + end + + describe "#each_subject" do + describe "without a block" do + it "requires no arguments" do + expect { ten.each_subject }.not_to raise_error + end + + it "returns an enumerator" do + expect(abc.each_subject).to be_an_enumerator + end + end + + describe "with a block" do + it "requires no arguments" do + expect { ten.each_subject { |subject| } }.not_to raise_error + end + + it "yields all subject terms in the list" do + expect {|b| ten.each_subject(&b)}.to yield_control.exactly(10).times + end + end + end + + describe "#each" do + describe "without a block" do + it "requires no arguments" do + expect { ten.each }.not_to raise_error + end + + it "returns an enumerator" do + expect(abc.each_subject).to be_an_enumerator + end + end + + describe "with a block" do + it "requires no arguments" do + expect { ten.each { |value| } }.not_to raise_error + end + + it "yields the correct number of times" do + expect(abc.each.count).to eq 3 + expect(ten.each.count).to eq 10 + end + end + end + + describe "#each_statement" do + describe "without a block" do + it "requires no arguments" do + expect { ten.each_statement }.not_to raise_error + end + + it "returns an enumerator" do + expect(abc.each_subject).to be_an_enumerator + end + end + + describe "with a block" do + it "requires no arguments" do + expect { ten.each_statement { |statement| } }.not_to raise_error + end + + it "yields the correct number of times" do + expect(abc.each_statement.count).to eq 3 * 2 + expect(ten.each_statement.count).to eq 10 * 2 + end + + it "yields statements" do + expect {|b| ten.each_statement(&b)}.to yield_control.at_least(10).times + ten.each_statement do |statement| + expect(statement).to be_a_statement + end + end + end + + describe "with embedded statement" do + subject {RDF::N3::List['a', RDF::N3::List['b'], 'c']} + + it "yields the correct number of times" do + expect(subject.each_statement.count).to eq 8 + end + + it "does not include statements with embedded lists" do + statements = subject.each_statement.to_a + entries = statements.select {|st| st.predicate == RDF.first}.map(&:object) + entries.each do |e| + expect(e).not_to be_list + end + end + end + end + + describe "#has_nodes?" do + it "finds list with nodes" do + expect(nodes).to have_nodes + end + + it "rejects list with nodes" do + expect(abc).not_to have_nodes + end + end + + describe "#to_existential" do + it "creates existential vars for list having nodes" do + expect(nodes.to_existential).to all(be_variable) + end + end + + describe "#variable?" do + it "rejects list with nodes" do + expect(nodes).not_to be_variable + end + + it "rejects list with URIs" do + expect(abc).not_to be_variable + end + + it "finds list with existentials" do + expect(nodes.to_existential).to be_variable + end + end + + describe "#variables" do + it "finds no variables in constant list" do + expect(abc.variables).to be_empty + end + + it "finds no variables in node list" do + expect(nodes.variables).to be_empty + end + + it "finds variables in existential list" do + expect(nodes.to_existential.variables).to all(be_variable) + end + end + + describe "#var_values" do + it "returns an empty array with constant pattern" do + pattern = described_class.new(values: %w(a b c)) + list = described_class.new(values: %w(a b c)) + expect(pattern.var_values(:x, list)).to be_empty + end + + it "returns an empty array with no matching variable" do + pattern = described_class.new(values: [RDF::Query::Variable.new(:a), RDF::Query::Variable.new(:b), RDF::Query::Variable.new(:c)]) + list = described_class.new(values: %w(a b c)) + expect(pattern.var_values(:x, list)).to be_empty + end + + it "returns matching value" do + pattern = described_class.new(values: [RDF::Query::Variable.new(:a), RDF::Query::Variable.new(:b), RDF::Query::Variable.new(:c)]) + list = described_class.new(values: %w(a b c)) + expect(pattern.var_values(:a, list)).to include(RDF::Literal('a')) + end + + it "returns matching values when multiple" do + pattern = described_class.new(values: [RDF::Query::Variable.new(:a), RDF::Query::Variable.new(:a), RDF::Query::Variable.new(:a)]) + list = described_class.new(values: %w(a b c)) + expect(pattern.var_values(:a, list)).to include(RDF::Literal('a'), RDF::Literal('b'), RDF::Literal('c')) + end + + it "returns matching values recursively" do + pattern = described_class.new(values: [ + RDF::Query::Variable.new(:a), + described_class.new(values: [RDF::Query::Variable.new(:a)]), + RDF::Query::Variable.new(:a)]) + list = described_class.new(values: ["a", described_class.new(values: ["b"]), "c"]) + expect(pattern.var_values(:a, list)).to include(RDF::Literal('a'), RDF::Literal('b'), RDF::Literal('c')) + end + end + + describe "#evaluate" do + let(:constant) {RDF::N3::List[RDF::URI("A"), RDF::URI("B")]} + let(:nodes) {described_class[RDF::Node.new('a'), RDF::Node.new('b')]} + let(:vars) {RDF::N3::List[RDF::Query::Variable.new("a"), RDF::Query::Variable.new("b")]} + let(:bindings) {RDF::Query::Solution.new(a: RDF::URI("A"), b: RDF::URI("B"))} + + it "returns itself if not variable" do + expect(constant.evaluate(bindings)).to eq constant + end + + it "returns bound list if nodes" do + expect(nodes.evaluate(bindings)).to eq constant + end + + it "returns bound list if variable" do + expect(vars.evaluate(bindings)).to eq constant + end + end + + describe "#solution" do + subject {pattern.solution(abc)} + + specify("pattern[:a] #=> list[0]") { expect(subject[:a]).to eq abc[0]} + specify("pattern[:b] #=> list[1]") { expect(subject[:b]).to eq abc[1]} + end +end From 5369b3741f5d5d48edf6b82d47584e5213ac865b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 24 Aug 2020 15:57:00 -0700 Subject: [PATCH 070/193] Add extensions: * `RDF::Enumerable#existentials`, * `RDF::Enumerable#universals`, * `RDF::Enumerable#contain?` * `RDF::List#variable?` * `RDF::Value#formula?` * `RDF::Term#sameTerm?` * `RDF::Node#evaluate` * `RDF::Query::Pattern#eql?` * `RDF::Query::Variable#sameTerm?` --- lib/rdf/n3/extensions.rb | 46 +++++++++----- spec/extensions_spec.rb | 131 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 17 deletions(-) create mode 100644 spec/extensions_spec.rb diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index 26e2670..e86eb85 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -23,23 +23,6 @@ def contain?(other) end class List - ## - # Evaluates the list using the given variable `bindings`. - # - # @param [RDF::Query::Solution] bindings - # a query solution containing zero or more variable bindings - # @param [Hash{Symbol => Object}] options ({}) - # options passed from query - # @return [RDF::N3::List] - # @see SPARQL::Algebra::Expression.evaluate - def evaluate(bindings, **options) - # if values are constant, simply return ourselves - return self if to_a.none? {|li| li.node? || li.variable?} - # Create a new list subject using a combination of the current subject and a hash of the binding values - subj = "#{subject.id}_#{bindings.values.sort.hash}" - RDF::N3::List.new(subject: RDF::Node.intern(subj), values: to_a.map {|o| o.evaluate(bindings, **options)}) - end - ## # A list is variable if any of its members are variable? # @@ -111,6 +94,35 @@ def evaluate(bindings, **options) end end + class Query::Pattern + ## + # Checks pattern equality against a statement, considering nesting an lists. + # + # * A pattern which has a pattern as a subject or an object, matches + # a statement having a statement as a subject or an object using {#eql?}. + # + # @param [Statement] other + # @return [Boolean] + # + # @see RDF::URI#== + # @see RDF::Node#== + # @see RDF::Literal#== + # @see RDF::Query::Variable#== + def eql?(other) + return false unless other.is_a?(RDF::Statement) && (self.graph_name || false) == (other.graph_name || false) + + [:subject, :predicate, :object].each do |part| + case o = self.send(part) + when RDF::Query::Pattern, RDF::List + return false unless o.eql?(other.send(part)) + else + return false unless o == other.send(part) + end + end + true + end + end + class Query::Solution # Transform Statement into an SXP # @return [Array] diff --git a/spec/extensions_spec.rb b/spec/extensions_spec.rb new file mode 100644 index 0000000..fe5531f --- /dev/null +++ b/spec/extensions_spec.rb @@ -0,0 +1,131 @@ +# coding: utf-8 +require_relative 'spec_helper' + +describe "RDF::Enmerable" do + let(:g1) {RDF::Graph.new {|g| g.insert(*RDF::Spec.triples)}} + let(:g2) {RDF::Graph.new {|g| g.insert(*RDF::Spec.triples[0..(RDF::Spec.triples.length/2)])}} + describe "#contain?" do + it "contains itself" do + expect(g1).to be_contain(g1) + end + + it "contains a subset" do + expect(g1).to be_contain(g2) + end + + it "does not contain a superset" do + expect(g2).not_to be_contain(g1) + end + end +end + +describe RDF::List do + let(:constant) {RDF::List[RDF::URI("A"), RDF::URI("B")]} + let(:nodes) {RDF::List[RDF::Node.new("a"), RDF::Node.new("b")]} + let(:vars) {RDF::List[RDF::Query::Variable.new("a"), RDF::Query::Variable.new("b")]} + + describe "#variable?" do + context "constant" do + subject {constant} + specify {is_expected.not_to be_variable} + specify {is_expected.to be_constant} + end + + context "nodes" do + subject {nodes} + specify {is_expected.not_to be_variable} + specify {is_expected.to be_constant} + end + + context "vars" do + subject {vars} + specify {is_expected.to be_variable} + specify {is_expected.not_to be_constant} + end + end +end + +describe RDF::Value do + describe "#formula?" do + { + RDF::Node.new("a") => false, + RDF::Literal.new("a") => false, + RDF::URI("a") => false, + RDF::Graph.new => false, + RDF::List[RDF::URI("a")] => false, + RDF::Statement.new(RDF::URI("s"), RDF::URI("p"), RDF::URI("o")) => false, + RDF::N3::Algebra::Formula.new => true + }.each do |term, is_formula| + context term.class.to_s do + if is_formula + specify {expect(term).to be_formula} + else + specify {expect(term).not_to be_formula} + end + end + end + end +end + +describe RDF::Term do + describe "#sameTerm?" do + { + "lita lita": [RDF::Literal.new("a"), RDF::Literal.new("a"), true], + "lita litb": [RDF::Literal.new("a"), RDF::Literal.new("b"), false], + "lita nodea": [RDF::Literal.new("a"), RDF::Node.intern("a"), false], + "lita uria": [RDF::Literal.new("a"), RDF::URI("a"), false], + "lita vara": [RDF::Literal.new("a"), RDF::Query::Variable.new("a"), false], + + "nodea nodea": [RDF::Node.intern("a"), RDF::Node.intern("a"), true], + "nodea nodeb": [RDF::Node.intern("a"), RDF::Node.intern("b"), false], + + "uria uria": [RDF::URI("a"), RDF::URI("a"), true], + "uria urib": [RDF::URI("a"), RDF::URI("b"), false], + + "vara vara": [RDF::Query::Variable.new("a"), RDF::Query::Variable.new("a"), true], + "vara varb": [RDF::Query::Variable.new("a"), RDF::Query::Variable.new("b"), false], + }.each do |term, (a, b, tf)| + context term do + if tf + specify {expect(a).to be_sameTerm(b)} + else + specify {expect(a).not_to be_sameTerm(b)} + end + end + end + end +end + +describe RDF::Node do + describe "#evaluate" do + let(:node) {RDF::Node.intern("a")} + + it "returns itself if not bound" do + expect(node.evaluate({})).to eq node + end + + it "returns bound value if bound" do + expect(node.evaluate({a: RDF::URI("a")})).to eq RDF::URI("a") + end + end +end + +describe RDF::Query::Pattern do + describe "#eql?" do + let(:stmt1) {RDF::Statement.new(RDF::N3::List[RDF::URI("a"), RDF::URI("b")], RDF::URI("p"), RDF::N3::List[RDF::URI("d"), RDF::URI("e")])} + let(:stmt2) {RDF::Statement.new(RDF::N3::List[RDF::URI("a"), RDF::URI("b")], RDF::URI("p"), RDF::URI("o"))} + let(:pat1) {RDF::Query::Pattern.new(RDF::N3::List[RDF::URI("a"), RDF::URI("b")], RDF::URI("p"), RDF::N3::List[RDF::URI("d"), RDF::URI("e")])} + + it "equals itself" do + expect(pat1).to eql pat1 + end + + it "equals matching statement" do + expect(pat1).to eql stmt1 + end + + it "does not equal non-matching statement" do + expect(pat1).not_to eql stmt2 + end + end +end From 7dadac2aaa67c9246ec27887ad425df532bda873 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 24 Aug 2020 16:01:42 -0700 Subject: [PATCH 071/193] Add N3::Repository which holds native lists, queryies over list contents, and supports `#each_expanded_statement` to retrieve statements resulting from expanding native lists. --- lib/rdf/n3/repository.rb | 29 +++++++++++++++++++++++++ spec/repository_spec.rb | 47 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/lib/rdf/n3/repository.rb b/lib/rdf/n3/repository.rb index 5338315..21c45d3 100644 --- a/lib/rdf/n3/repository.rb +++ b/lib/rdf/n3/repository.rb @@ -109,6 +109,35 @@ def each_statement(&block) end alias_method :each, :each_statement + ## + # Projects statements with lists expanded to first/rest chains + # + # @yield [RDF::Statement] + def each_expanded_statement(&block) + if block_given? + each_statement do |st| + if st.subject.list? + st.subject.each_statement(&block) + st.subject = st.subject.subject + end + if st.object.list? + st.object.each_statement(&block) + st.object = st.object.subject + end + block.call(st) + end + end + enum_for(:each_expanded_statement) unless block_given? + end + + ## + # Returns the expanded statements for this repository + # + # @return [Array] + def expanded_statements + each_expanded_statement.to_a + end + ## # @see Mutable#apply_changeset def apply_changeset(changeset) diff --git a/spec/repository_spec.rb b/spec/repository_spec.rb index ad2708b..d061b1c 100644 --- a/spec/repository_spec.rb +++ b/spec/repository_spec.rb @@ -69,4 +69,51 @@ expect(subject.count).to eql 0 end end + + describe '#each_expanded_statement' do + context "with standard quads" do + before {subject << RDF::Spec.quads} + it {is_expected.to respond_to(:each_expanded_statement)} + its(:each_expanded_statement) {is_expected.to be_an_enumerator} + its(:each_expanded_statement) {expect(subject.each_expanded_statement.to_a).to all(be_statement)} + end + + { + "straight triple": { + input: RDF::N3::Repository.new {|g| g << RDF::Statement(RDF::URI('s'), RDF::URI('p'), RDF::URI('o'))}, + result: RDF::Repository.new {|r| r << RDF::Statement(RDF::URI('s'), RDF::URI('p'), RDF::URI('o'))} + }, + "list subject": { + input: RDF::N3::Repository.new {|r| r << RDF::Statement(RDF::N3::List['a'], RDF::URI('p'), RDF::URI('o'))}, + result: RDF::Repository.new { |r| + r << RDF::Statement(RDF::Node.intern(:l1), RDF::URI('p'), RDF::URI('o')) + r << RDF::Statement(RDF::Node.intern(:l1), RDF.first, 'a') + r << RDF::Statement(RDF::Node.intern(:l1), RDF.rest, RDF.nil) + } + }, + "list object": { + input: RDF::N3::Repository.new {|r| r << RDF::Statement(RDF::URI('s'), RDF::URI('p'), RDF::N3::List['a'])}, + result: RDF::Repository.new { |r| + r << RDF::Statement(RDF::URI('s'), RDF::URI('p'), RDF::Node.intern(:l1)) + r << RDF::Statement(RDF::Node.intern(:l1), RDF.first, 'a') + r << RDF::Statement(RDF::Node.intern(:l1), RDF.rest, RDF.nil) + } + }, + "embedded list": { + input: RDF::N3::Repository.new {|r| r << RDF::Statement(RDF::URI('s'), RDF::URI('p'), RDF::N3::List[RDF::N3::List['a']])}, + result: RDF::Repository.new { |r| + r << RDF::Statement(RDF::URI('s'), RDF::URI('p'), RDF::Node.intern(:l1)) + r << RDF::Statement(RDF::Node.intern(:l1), RDF.first, RDF::Node.intern(:l2)) + r << RDF::Statement(RDF::Node.intern(:l1), RDF.rest, RDF.nil) + r << RDF::Statement(RDF::Node.intern(:l2), RDF.first, 'a') + r << RDF::Statement(RDF::Node.intern(:l2), RDF.rest, RDF.nil) + } + }, + }.each do |name, params| + it name do + expanded = RDF::Repository.new {|r| r << params[:input].each_expanded_statement} + expect(expanded).to be_isomorphic_with(params[:result]) + end + end + end end From 381c9e55af78b5c1044466fbe8ab289d2f8e51b2 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 24 Aug 2020 16:02:14 -0700 Subject: [PATCH 072/193] Use native lists and N3::Repository for reasoning. --- lib/rdf/n3/algebra/formula.rb | 19 ++++++++++--------- lib/rdf/n3/reasoner.rb | 8 ++++---- script/tc | 10 +++++++++- spec/reasoner_spec.rb | 14 ++++++++++---- spec/suite_reasoner_spec.rb | 11 +++++++++-- spec/writer_spec.rb | 23 ++++++++++++++++++++++- 6 files changed, 64 insertions(+), 21 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 8b99660..b5f19ca 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -46,7 +46,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new memo.merge(name => value) end) end - log_debug("(formula solutions)") { these_solutions.to_sxp} + log_debug("(formula query solutions)") { these_solutions.to_sxp} solutions.merge(these_solutions) end @@ -66,7 +66,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new end end end - log_debug("(formula solutions)") {@solutions.to_sxp} + log_debug("(formula sub-op solutions)") {@solutions.to_sxp} # Only return solutions with universal variables variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?('$')} @@ -160,17 +160,14 @@ def statements @statements ||= begin statements = operands.select {|op| op.is_a?(RDF::Statement)} - # Find statements associated with lists referenced from a built-in. Blank nodes which are related to these operands are marked non-distinguished, so they become optional patterns. - op_lists = sub_ops.map(&:operands).flatten.select(&:list?) - op_list_subjects = op_lists.map(&:subjects).flatten - statements.map do |pattern| - # Map nodes to existential variables (except when in top-level formula). They are non-distinguished if associated with a built-in. if graph_name terms = {} - has_nd_var = op_list_subjects.include?(pattern.subject) [:subject, :predicate, :object].each do |r| terms[r] = case o = pattern.send(r) + when RDF::N3::List + # Substitute blank node members with existential variables, recusively. + o.has_nodes? ? o.to_existential : o when RDF::Node RDF::Query::Variable.new(o.id, existential: true) else @@ -179,7 +176,7 @@ def statements end # A pattern with a non-destinguished variable becomes optional, so that it will bind to itself, if not matched in queryable. - RDF::Query::Pattern.from(terms, optional: has_nd_var) + RDF::Query::Pattern.from(terms) else RDF::Query::Pattern.from(pattern) end @@ -220,6 +217,10 @@ def existential_vars @existentials ||= patterns.vars.select(&:existential?) end + def to_s + to_sxp + end + def to_sxp_bin raise "a formula can't contain itself" if operands.include?(self) [:formula, graph_name].compact + diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index c653a85..fdcf7f0 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -116,7 +116,7 @@ def execute(**options, &block) while @mutable.count > count log_info("reasoner: think do") { "count: #{count}"} count = @mutable.count - dataset = RDF::Graph.new << @mutable.project_graph(nil) + dataset = @mutable.project_graph(nil) log_depth {formula.execute(dataset, **options)} @mutable << formula end @@ -124,13 +124,13 @@ def execute(**options, &block) else # Run one iteration log_info("reasoner: rules start") { "count: #{count}"} - dataset = RDF::Graph.new << @mutable.project_graph(nil) + dataset = @mutable.project_graph(nil) log_depth {formula.execute(dataset, **options)} @mutable << formula log_info("reasoner: rules end") { "count: #{count}"} end - log_debug("reasoner: datastore") {@mutable.to_sxp} + log_debug("reasoner: datastore") {SXP::Generator.string @mutable.statements.to_sxp_bin} conclusions(&block) if block_given? self @@ -287,7 +287,7 @@ def formula # Create a graph for each formula, containing statements and built-in operands formulae.each do |gn, form| - form_graph = RDF::Graph.new do |g| + form_graph = RDF::N3::Repository.new(with_graph_name: false) do |g| # Graph initialized with non-built-in statements form.operands.each do |op| g << op if op.is_a?(RDF::Statement) diff --git a/script/tc b/script/tc index 8f90809..0836d23 100755 --- a/script/tc +++ b/script/tc @@ -122,7 +122,15 @@ def run_tc(man, tc, **options) STDERR.puts "\nResult: #{repo.dump(:n3)}" if options[:verbose] output_graph = RDF::N3::Repository.load(tc.result, base_uri: tc.base) - result = repo.isomorphic_with?(output_graph) ? "passed" : "failed" + + # Check against expanded triples from repo + expanded_repo = RDF::Repository.new do |r| + repo.each_expanded_statement do |st| + r << st + end + end + + result = expanded_repo.isomorphic_with?(output_graph) ? "passed" : "failed" else result ||= "passed" end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 6e3a55e..fd2c23a 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -78,7 +78,8 @@ it name do logger.info "input: #{options[:input]}" expected = parse(options[:expect]) - expect(reason(options[:input])).to be_equivalent_graph(expected, logger: logger) + result = reason(options[:input]) + expect(reason(options[:input])).to be_equivalent_graph(expected, logger: logger, format: :n3) end end end @@ -252,8 +253,7 @@ def parse(input, **options) # Reason over input, returning a repo def reason(input, base_uri: 'http://example.com/', filter: false, data: true, think: true, **options) - options[:list_terms] ||= true - input = parse(input, **options) if input.is_a?(String) + input = parse(input, list_terms: true, **options) if input.is_a?(String) reasoner = RDF::N3::Reasoner.new(input, base_uri: base_uri) repo = RDF::N3:: Repository.new @@ -265,6 +265,12 @@ def reason(input, base_uri: 'http://example.com/', filter: false, data: true, th else repo << reasoner end - repo + + # Expand results with embedded lists to ease comparison + RDF::Repository.new do |r| + repo.each_expanded_statement do |st| + r << st + end + end end end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index a3ecc87..2d88f6f 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -66,8 +66,15 @@ t.logger.info "result:\n#{repo.dump(:n3)}" if t.evaluate? || t.reason? - output_repo = RDF::N3:: Repository.load(t.result, format: :n3, base_uri: t.base) - expect(repo).to be_equivalent_graph(output_repo, t) + output_repo = RDF:: Repository.load(t.result, format: :n3, base_uri: t.base) + + # Check against expanded triples from repo + expanded_repo = RDF::Repository.new do |r| + repo.each_expanded_statement do |st| + r << st + end + end + expect(expanded_repo).to be_equivalent_graph(output_repo, t) else end else diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 01e7cba..b15f956 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -633,6 +633,27 @@ end end + describe "results" do + { + "r1": { + input: %( + ( "one" "two" ) a :whatever. + "one" a :SUCCESS. + "two" a :SUCCESS. + ), + regexp: [ + %r(\(\s*"one"\s+"two"\s*\) a :whatever\s*\.), + %r("one" a :SUCCESS \.), + %r("two" a :SUCCESS \.), + ] + }, + }.each do |name, params| + it name do + serialize(params[:input], params[:regexp], list_terms: true, **params) + end + end + end + # W3C TriG Test suite describe "w3c n3 parser tests" do require_relative 'suite_helper' @@ -645,7 +666,7 @@ case t.name when *%w(cwm_syntax_path2.n3 cwm_includes_quantifiers.n3 cwm_includes_quantifiers_limited.n3 - cwm_list_append.n3 cwm_includes_builtins.n3 + cwm_includes_builtins.n3 cwm_list_unify5-ref.n3 cwm_other_lists-simple.n3 cwm_syntax_qual-after-user.n3 cwm_other_lists.n3 # empty list with extra properties From 911ffe651363d52a3a5a9699ad1cf998a9964ac8 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 24 Aug 2020 17:16:37 -0700 Subject: [PATCH 073/193] Turn operator operands which are blank nodes into existential variables. --- lib/rdf/n3/algebra/formula.rb | 18 ++++++++++-- lib/rdf/n3/list.rb | 7 +++++ spec/reasoner_spec.rb | 52 +++++++++++++++++++++++++++++++++++ spec/suite_reasoner_spec.rb | 10 +++---- 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index b5f19ca..2c5386a 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -156,7 +156,7 @@ def graph_name; @options[:graph_name]; end ## # Statements memoizer def statements - # BNodes in statements are existential variables. If part of a built-in, they become non-distinguished, creating optional patterns, and may become bound to themselves. + # BNodes in statements are existential variables. @statements ||= begin statements = operands.select {|op| op.is_a?(RDF::Statement)} @@ -202,7 +202,21 @@ def patterns # Non-statement operands memoizer def sub_ops # operands that aren't statements, ordered by their graph_name - @sub_ops ||= operands.reject {|op| op.is_a?(RDF::Statement)} + @sub_ops ||= operands.reject {|op| op.is_a?(RDF::Statement)}.map do |op| + # Substitute nodes for existential variables in operator operands + op.operands.map! do |o| + case o + when RDF::N3::List + # Substitute blank node members with existential variables, recusively. + o.has_nodes? ? o.to_existential : o + when RDF::Node + RDF::Query::Variable.new(o.id, existential: true) + else + o + end + end + op + end end ## diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index 27fe95e..01af9e9 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -477,6 +477,13 @@ def variable? @values.any?(&:variable?) end + ## + # Return the variables contained this list + # @return [Array] + def vars + @values.vars + end + ## # Returns all variables in this list. # diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index fd2c23a..a526cf4 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -204,6 +204,58 @@ end end end + + context "list:member" do + { + "1 in (1 2 3 4 5)": { + input: %( + { ( 1 2 3 4 5 ) list:member 1 } => { :test4a a :SUCCESS }. + ), + expect: %( + :test4a a :SUCCESS . + ) + }, + "?x in (1 2 3 4 5)": { + input: %( + { ( 1 2 3 4 5 ) list:member ?x } => { :test4a :is ?x }. + ), + expect: %( + :test4a :is 1 . + :test4a :is 2 . + :test4a :is 3 . + :test4a :is 4 . + :test4a :is 5 . + ) + }, + "Pythag 3 5": { + input: %( + { ((3) (5))!list:member list:member ?z } => { ?z a :Pythagorean }. + ), + expect: %( + 3 a :Pythagorean. + 5 a :Pythagorean. + ) + }, + #"Pythag 3 4 5 5 12 13": { + # input: %( + # { ((3 4 5) (5 12 13))!list:member list:member ?z } => { ?z a :Pythagorean }. + # ), + # expect: %( + # 3 a :Pythagorean. + # 4 a :Pythagorean. + # 5 a :Pythagorean. + # 12 a :Pythagorean. + # 13 a :Pythagorean. + # ) + #}, + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end end context "n3:string" do diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 2d88f6f..264ed48 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -19,11 +19,11 @@ next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do case t.id.split('#').last - when *%w{cwm_includes_listin cwm_includes_bnode cwm_includes_concat - cwm_includes_conjunction cwm_includes_conclusion_simple - } - pending "support for lists" - when *%w{cwm_unify_unify1} + when *%w{cwm_includes_conjunction} + pending "log:conjunction" + when *%w{cwm_includes_bnode} + pending "log:includes" + when *%w{cwm_includes_conclusion_simple cwm_unify_unify1} pending "reason over formulae" when *%w{cwm_reason_t6} pending "support for math" From edf1b4be09e6481909bbf1bd5cb1ec6c907d6a55 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 25 Aug 2020 12:12:45 -0700 Subject: [PATCH 074/193] Replace Array#append with Array#push in N3::List for Ruby 2.4 compatibility. --- lib/rdf/n3/list.rb | 2 +- spec/suite_reasoner_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index 01af9e9..878bf55 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -515,7 +515,7 @@ def var_values(var, list) @values.each_index do |ndx| maybe_var = @values[ndx] next unless maybe_var.respond_to?(:var_values) - results.append(maybe_var.var_values(var, list.at(ndx))) + results.push(*Array(maybe_var.var_values(var, list.at(ndx)))) end results.flatten.compact end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 264ed48..e45b4fe 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -29,8 +29,8 @@ pending "support for math" when *%w{cwm_supports_simple cwm_string_roughly cwm_string_uriEncode} pending "Uses unsupported builtin" - when *%w{cwm_reason_t2 cwm_list_builtin_generated_match cwm_reason_double} - skip("Not allowed with new grammar") + when *%w{cwm_list_builtin_generated_match} + skip("List reification") end t.logger = logger From c091c375e21aa686c4573112988b82901938121b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 25 Aug 2020 12:33:37 -0700 Subject: [PATCH 075/193] Add list:first operator. --- README.md | 1 + lib/rdf/n3/algebra.rb | 2 ++ lib/rdf/n3/algebra/list/first.rb | 50 ++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 lib/rdf/n3/algebra/list/first.rb diff --git a/README.md b/README.md index 0b2bea3..5b082e8 100755 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ Reasoning is discussed in the [Design Issues][] document. #### RDF List vocabulary * `list:append` (not implemented yet - See {RDF::N3::Algebra::List::Append}) + * `list:first` (See {RDF::N3::Algebra::List::First}) * `list:in` (See {RDF::N3::Algebra::List::In}) * `list:last` (See {RDF::N3::Algebra::List::Last}) * `list:member` (See {RDF::N3::Algebra::List::Member}) diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 1235790..8dcf7f6 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -12,6 +12,7 @@ module Algebra module List autoload :Append, 'rdf/n3/algebra/list/append' + autoload :First, 'rdf/n3/algebra/list/first' autoload :In, 'rdf/n3/algebra/list/in' autoload :Last, 'rdf/n3/algebra/list/last' autoload :Member, 'rdf/n3/algebra/list/member' @@ -62,6 +63,7 @@ module Str def for(uri) { RDF::N3::List.append => List::Append, + RDF::N3::List.first => List::First, RDF::N3::List.in => List::In, RDF::N3::List.last => List::Last, RDF::N3::List.member => List::Member, diff --git a/lib/rdf/n3/algebra/list/first.rb b/lib/rdf/n3/algebra/list/first.rb new file mode 100644 index 0000000..a24f18f --- /dev/null +++ b/lib/rdf/n3/algebra/list/first.rb @@ -0,0 +1,50 @@ +module RDF::N3::Algebra::List + ## + # Iff the suject is a list and the object is the first thing that list, then this is true. The object can be calculated as a function of the list. + # + # @example + # { ( 1 2 3 4 5 6 ) list:first 1 } => { :test1 a :SUCCESS }. + # + # The object can be calculated as a function of the list. + class First < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :listFirst + + ## + # Evaluates this operator using the given variable `bindings`. + # If the last operand is a variable, it creates a solution for each element in the list. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + # @raise [TypeError] if operands are not compatible + def execute(queryable, solutions:, **options) + @solutions = RDF::Query::Solutions(solutions.map do |solution| + list = operand(0).evaluate(solution.bindings) + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + + result = operand(1) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + raise TypeError, "operand is not a list" unless list.list? && list.valid? + + if list.to_a.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + if result.variable? + solution.merge(result.to_sym => list.first) + elsif list.first == result + solution + else + nil + end + end + end.compact) + end + end +end From f8b55e78fe4a294c7603a844648327b3129faf28 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 25 Aug 2020 13:11:48 -0700 Subject: [PATCH 076/193] Remove some extraneous `#each` methods in string operators. --- lib/rdf/n3/algebra/str/concatenation.rb | 10 ---------- lib/rdf/n3/algebra/str/format.rb | 10 ---------- lib/rdf/n3/algebra/str/replace.rb | 10 ---------- lib/rdf/n3/algebra/str/scrape.rb | 10 ---------- 4 files changed, 40 deletions(-) diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb index bc0bd85..27fd43b 100644 --- a/lib/rdf/n3/algebra/str/concatenation.rb +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -47,15 +47,5 @@ def execute(queryable, solutions:, **options) end end.compact) end - - ## - # Does not yield statements. - # - # @yield [statement] - # each matching statement - # @yieldparam [RDF::Statement] solution - # @yieldreturn [void] ignored - def each(&block) - end end end diff --git a/lib/rdf/n3/algebra/str/format.rb b/lib/rdf/n3/algebra/str/format.rb index 29eb395..c9c6726 100644 --- a/lib/rdf/n3/algebra/str/format.rb +++ b/lib/rdf/n3/algebra/str/format.rb @@ -43,15 +43,5 @@ def execute(queryable, solutions:, **options) end end.compact) end - - ## - # Does not yield statements. - # - # @yield [statement] - # each matching statement - # @yieldparam [RDF::Statement] solution - # @yieldreturn [void] ignored - def each(&block) - end end end diff --git a/lib/rdf/n3/algebra/str/replace.rb b/lib/rdf/n3/algebra/str/replace.rb index 9b69468..8febe60 100644 --- a/lib/rdf/n3/algebra/str/replace.rb +++ b/lib/rdf/n3/algebra/str/replace.rb @@ -47,15 +47,5 @@ def execute(queryable, solutions:, **options) end end.compact) end - - ## - # Does not yield statements. - # - # @yield [statement] - # each matching statement - # @yieldparam [RDF::Statement] solution - # @yieldreturn [void] ignored - def each(&block) - end end end diff --git a/lib/rdf/n3/algebra/str/scrape.rb b/lib/rdf/n3/algebra/str/scrape.rb index f00aa42..9f956e0 100644 --- a/lib/rdf/n3/algebra/str/scrape.rb +++ b/lib/rdf/n3/algebra/str/scrape.rb @@ -46,15 +46,5 @@ def execute(queryable, solutions:, **options) end end.compact) end - - ## - # Does not yield statements. - # - # @yield [statement] - # each matching statement - # @yieldparam [RDF::Statement] solution - # @yieldreturn [void] ignored - def each(&block) - end end end From 988712aaed35a91a05ae32a088d4f5fdf0f2afd5 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 25 Aug 2020 14:26:50 -0700 Subject: [PATCH 077/193] Log error instead of raising TypeError on operand mismatches. --- lib/rdf/n3/algebra/list/append.rb | 11 ++++++++--- lib/rdf/n3/algebra/list/first.rb | 6 ++++-- lib/rdf/n3/algebra/list/in.rb | 6 ++++-- lib/rdf/n3/algebra/list/last.rb | 6 ++++-- lib/rdf/n3/algebra/list/member.rb | 6 ++++-- lib/rdf/n3/algebra/str/concatenation.rb | 6 ++++-- lib/rdf/n3/algebra/str/containsIgnoringCase.rb | 4 ++-- lib/rdf/n3/algebra/str/equalIgnoringCase.rb | 3 +-- lib/rdf/n3/algebra/str/format.rb | 7 ++++--- lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb | 3 +-- lib/rdf/n3/algebra/str/notMatches.rb | 6 ++---- lib/rdf/n3/algebra/str/replace.rb | 12 ++++++++---- lib/rdf/n3/algebra/str/scrape.rb | 11 ++++++++--- 13 files changed, 54 insertions(+), 33 deletions(-) diff --git a/lib/rdf/n3/algebra/list/append.rb b/lib/rdf/n3/algebra/list/append.rb index fb4ddcb..d3c07de 100644 --- a/lib/rdf/n3/algebra/list/append.rb +++ b/lib/rdf/n3/algebra/list/append.rb @@ -21,7 +21,6 @@ class Append < SPARQL::Algebra::Operator::Binary # @param [RDF::Queryable] queryable # @param [RDF::Query::Solutions] solutions # @return [RDF::Query::Solutions] - # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| list = operand(0).evaluate(solution.bindings) @@ -29,8 +28,14 @@ def execute(queryable, solutions:, **options) result = operand(1) log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - raise TypeError, "operand is not a list: #{list.to_sxp}" unless list.list? && list.valid? - raise TypeError, "operand is not a list of lists: #{list.to_sxp}" unless list.to_a.all? {|li| li.list?} + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end + unless list.to_a.all? {|li| li.list?} + log_error(NAME) {"operand is not a list of lists: #{list.to_sxp}"} + next + end if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements diff --git a/lib/rdf/n3/algebra/list/first.rb b/lib/rdf/n3/algebra/list/first.rb index a24f18f..df11b9f 100644 --- a/lib/rdf/n3/algebra/list/first.rb +++ b/lib/rdf/n3/algebra/list/first.rb @@ -21,7 +21,6 @@ class First < SPARQL::Algebra::Operator::Binary # @param [RDF::Queryable] queryable # @param [RDF::Query::Solutions] solutions # @return [RDF::Query::Solutions] - # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| list = operand(0).evaluate(solution.bindings) @@ -30,7 +29,10 @@ def execute(queryable, solutions:, **options) result = operand(1) log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - raise TypeError, "operand is not a list" unless list.list? && list.valid? + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index 341dc13..8434128 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -19,7 +19,6 @@ class In < SPARQL::Algebra::Operator::Binary # @param [RDF::Queryable] queryable # @param [RDF::Query::Solutions] solutions # @return [RDF::Query::Solutions] - # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| subject = operand(0) @@ -29,7 +28,10 @@ def execute(queryable, solutions:, **options) list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) log_debug(NAME) {"subject: #{subject.to_sxp}, list: #{list.to_sxp}"} - raise TypeError, "operand is not a list" unless list.list? && list.valid? + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements diff --git a/lib/rdf/n3/algebra/list/last.rb b/lib/rdf/n3/algebra/list/last.rb index e8ca832..5e48665 100644 --- a/lib/rdf/n3/algebra/list/last.rb +++ b/lib/rdf/n3/algebra/list/last.rb @@ -21,7 +21,6 @@ class Last < SPARQL::Algebra::Operator::Binary # @param [RDF::Queryable] queryable # @param [RDF::Query::Solutions] solutions # @return [RDF::Query::Solutions] - # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| list = operand(0).evaluate(solution.bindings) @@ -30,7 +29,10 @@ def execute(queryable, solutions:, **options) result = operand(1) log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - raise TypeError, "operand is not a list" unless list.list? && list.valid? + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index ec10749..b52b352 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -16,7 +16,6 @@ class Member < SPARQL::Algebra::Operator::Binary # @param [RDF::Queryable] queryable # @param [RDF::Query::Solutions] solutions # @return [RDF::Query::Solutions] - # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| list = operand(0).evaluate(solution.bindings) @@ -24,7 +23,10 @@ def execute(queryable, solutions:, **options) result = operand(1) log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - raise TypeError, "operand is not a list" unless list.list? && list.valid? + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb index 27fd43b..f0956dc 100644 --- a/lib/rdf/n3/algebra/str/concatenation.rb +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -18,7 +18,6 @@ class Concatenation < SPARQL::Algebra::Operator::Binary # @param [RDF::Queryable] queryable # @param [RDF::Query::Solutions] solutions # @return [RDF::Query::Solutions] - # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) result = operand(1) @@ -29,7 +28,10 @@ def execute(queryable, solutions:, **options) list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - raise TypeError, "operand is not a list" unless list.list? && list.valid? + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements diff --git a/lib/rdf/n3/algebra/str/containsIgnoringCase.rb b/lib/rdf/n3/algebra/str/containsIgnoringCase.rb index cc8c6f7..e66de41 100644 --- a/lib/rdf/n3/algebra/str/containsIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/containsIgnoringCase.rb @@ -13,11 +13,11 @@ class ContainsIgnoringCase < SPARQL::Algebra::Operator::Binary # @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}" + log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} + RDF::Literal::FALSE when left.to_s.downcase.include?(right.to_s.downcase) then RDF::Literal::TRUE else RDF::Literal::FALSE end diff --git a/lib/rdf/n3/algebra/str/equalIgnoringCase.rb b/lib/rdf/n3/algebra/str/equalIgnoringCase.rb index 0b82ef5..22eb7aa 100644 --- a/lib/rdf/n3/algebra/str/equalIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/equalIgnoringCase.rb @@ -13,11 +13,10 @@ class EqualIgnoringCase < SPARQL::Algebra::Operator::Binary # @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}" + log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} when left.to_s.downcase == right.to_s.downcase then RDF::Literal::TRUE else RDF::Literal::FALSE end diff --git a/lib/rdf/n3/algebra/str/format.rb b/lib/rdf/n3/algebra/str/format.rb index c9c6726..0dea284 100644 --- a/lib/rdf/n3/algebra/str/format.rb +++ b/lib/rdf/n3/algebra/str/format.rb @@ -13,7 +13,6 @@ class Format < SPARQL::Algebra::Operator::Binary # @param [RDF::Queryable] queryable # @param [RDF::Query::Solutions] solutions # @return [RDF::Query::Solutions] - # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) list = operand(0) result = operand(1) @@ -23,8 +22,10 @@ def execute(queryable, solutions:, **options) list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - - raise TypeError, "operand is not a list" unless list.list? && list.valid? + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements diff --git a/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb b/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb index 039bf1d..5f4a857 100644 --- a/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb @@ -13,11 +13,10 @@ class NotEqualIgnoringCase < SPARQL::Algebra::Operator::Binary # @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}" + log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} when left.to_s.downcase != right.to_s.downcase then RDF::Literal::TRUE else RDF::Literal::FALSE end diff --git a/lib/rdf/n3/algebra/str/notMatches.rb b/lib/rdf/n3/algebra/str/notMatches.rb index db272f2..88f7f9d 100644 --- a/lib/rdf/n3/algebra/str/notMatches.rb +++ b/lib/rdf/n3/algebra/str/notMatches.rb @@ -13,16 +13,14 @@ class NotMatches < SPARQL::Algebra::Operator::Binary # @param [RDF::Literal] pattern # a simple literal # @return [RDF::Literal::Boolean] `true` or `false` - # @raise [TypeError] if any operand is unbound - # @raise [TypeError] if any operand is not a simple literal def apply(text, pattern) # @see https://www.w3.org/TR/xpath-functions/#regex-syntax - raise TypeError, "expected a plain RDF::Literal, but got #{text.inspect}" unless text.is_a?(RDF::Literal) && text.plain? + log_error(NAME) {"expected a plain RDF::Literal, but got #{text.inspect}"} unless text.is_a?(RDF::Literal) && text.plain? text = text.to_s # TODO: validate text syntax # @see https://www.w3.org/TR/xpath-functions/#regex-syntax - raise TypeError, "expected a plain RDF::Literal, but got #{pattern.inspect}" unless pattern.is_a?(RDF::Literal) && pattern.plain? + log_error(NAME) {"expected a plain RDF::Literal, but got #{pattern.inspect}"} unless pattern.is_a?(RDF::Literal) && pattern.plain? pattern = pattern.to_s RDF::Literal(!Regexp.new(pattern).match?(text)) diff --git a/lib/rdf/n3/algebra/str/replace.rb b/lib/rdf/n3/algebra/str/replace.rb index 8febe60..540a997 100644 --- a/lib/rdf/n3/algebra/str/replace.rb +++ b/lib/rdf/n3/algebra/str/replace.rb @@ -16,7 +16,6 @@ class Replace < SPARQL::Algebra::Operator::Binary # @param [RDF::Queryable] queryable # @param [RDF::Query::Solutions] solutions # @return [RDF::Query::Solutions] - # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) result = operand(1) @solutions = RDF::Query::Solutions(solutions.map do |solution| @@ -26,9 +25,14 @@ def execute(queryable, solutions:, **options) list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - - raise TypeError, "operand is not a list" unless list.list? && list.valid? - raise TypeError, "list must have exactly three entries" unless list.length == 3 + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end + unless list.length == 3 + log_error(NAME) {"list must have exactly three entries: #{list.to_sxp}"} + next + end if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements diff --git a/lib/rdf/n3/algebra/str/scrape.rb b/lib/rdf/n3/algebra/str/scrape.rb index 9f956e0..267f8ca 100644 --- a/lib/rdf/n3/algebra/str/scrape.rb +++ b/lib/rdf/n3/algebra/str/scrape.rb @@ -16,7 +16,6 @@ class Scrape < SPARQL::Algebra::Operator::Binary # @param [RDF::Queryable] queryable # @param [RDF::Query::Solutions] solutions # @return [RDF::Query::Solutions] - # @raise [TypeError] if operands are not compatible def execute(queryable, solutions:, **options) result = operand(1) @@ -26,8 +25,14 @@ def execute(queryable, solutions:, **options) log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - raise TypeError, "operand is not a list" unless list.list? && list.valid? - raise TypeError, "list must have exactly two entries" unless list.length == 2 + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end + unless list.length == 2 + log_error(NAME) {"list must have exactly two entries: #{list.to_sxp}"} + next + end if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements From 7c4c5cf0af06933231a334d407453dd769d50fbe Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 26 Aug 2020 13:11:07 -0700 Subject: [PATCH 078/193] Math operators. --- README.md | 73 +++++---- lib/rdf/n3/algebra.rb | 29 ++-- lib/rdf/n3/algebra/math/absoluteValue.rb | 33 ++++ lib/rdf/n3/algebra/math/ceiling.rb | 42 ++++++ lib/rdf/n3/algebra/math/difference.rb | 50 ++++++ lib/rdf/n3/algebra/math/equalTo.rb | 9 -- lib/rdf/n3/algebra/math/exponentiation.rb | 45 ++++++ lib/rdf/n3/algebra/math/floor.rb | 42 ++++++ lib/rdf/n3/algebra/math/greaterThan.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 | 41 +++++ lib/rdf/n3/algebra/math/quotient.rb | 45 ++++++ lib/rdf/n3/algebra/math/remainder.rb | 45 ++++++ lib/rdf/n3/algebra/math/rounded.rb | 33 ++++ lib/rdf/n3/algebra/math/sum.rb | 45 ++++++ spec/reasoner_spec.rb | 176 ++++++++++++++++++++-- spec/suite_reasoner_spec.rb | 2 - 22 files changed, 644 insertions(+), 129 deletions(-) create mode 100644 lib/rdf/n3/algebra/math/ceiling.rb delete mode 100644 lib/rdf/n3/algebra/math/equalTo.rb create mode 100644 lib/rdf/n3/algebra/math/floor.rb delete mode 100644 lib/rdf/n3/algebra/math/greaterThan.rb delete mode 100644 lib/rdf/n3/algebra/math/lessThan.rb delete mode 100644 lib/rdf/n3/algebra/math/memberCount.rb delete mode 100644 lib/rdf/n3/algebra/math/negation.rb delete mode 100644 lib/rdf/n3/algebra/math/notEqualTo.rb delete mode 100644 lib/rdf/n3/algebra/math/notGreaterThan.rb delete mode 100644 lib/rdf/n3/algebra/math/notLessThan.rb diff --git a/README.md b/README.md index 5b082e8..71c29a7 100755 --- a/README.md +++ b/README.md @@ -62,41 +62,62 @@ Reasoning is discussed in the [Design Issues][] document. #### RDF List vocabulary - * `list:append` (not implemented yet - See {RDF::N3::Algebra::List::Append}) - * `list:first` (See {RDF::N3::Algebra::List::First}) - * `list:in` (See {RDF::N3::Algebra::List::In}) - * `list:last` (See {RDF::N3::Algebra::List::Last}) + * `list:append` (See {RDF::N3::Algebra::List::Append}) + * `list:first` (See {RDF::N3::Algebra::List::First}) + * `list:in` (See {RDF::N3::Algebra::List::In}) + * `list:last` (See {RDF::N3::Algebra::List::Last}) * `list:member` (See {RDF::N3::Algebra::List::Member}) #### RDF Log vocabulary - * `log:conclusion` (not implemented yet - See {RDF::N3::Algebra::Log::Conclusion}) - * `log:conjunction` (not implemented yet - See {RDF::N3::Algebra::Log::Conjunction}) - * `log:equalTo` (See {RDF::N3::Algebra::Log::EqualTo}) - * `log:implies` (See {RDF::N3::Algebra::Log::Implies}) - * `log:includes` (See {RDF::N3::Algebra::Log::Includes}) - * `log:notEqualTo` (not implemented yet - See {RDF::N3::Algebra::Log::NotEqualTo}) - * `log:notIncludes` (not implemented yet - See {RDF::N3::Algebra::Log::NotIncludes}) - * `log:outputString` (not implemented yet - See {RDF::N3::Algebra::Log::OutputString}) + * `log:conclusion` (not implemented yet - See {RDF::N3::Algebra::Log::Conclusion}) + * `log:conjunction` (not implemented yet - See {RDF::N3::Algebra::Log::Conjunction}) + * `log:equalTo` (See {RDF::N3::Algebra::Log::EqualTo}) + * `log:implies` (See {RDF::N3::Algebra::Log::Implies}) + * `log:includes` (See {RDF::N3::Algebra::Log::Includes}) + * `log:notEqualTo` (not implemented yet - See {RDF::N3::Algebra::Log::NotEqualTo}) + * `log:notIncludes` (not implemented yet - See {RDF::N3::Algebra::Log::NotIncludes}) + * `log:outputString` (not implemented yet - See {RDF::N3::Algebra::Log::OutputString}) + +#### RDF Math vocabulary + + * `math:absoluteValue` (See {RDF::N3::Algebra::Math::AbsoluteValue}) + * `math:ceiling` (See {RDF::N3::Algebra::Math::Ceiling}) + * `math:difference` (See {RDF::N3::Algebra::Math::Difference}) + * `math:equalTo` (See {SPARQL::Algebra::Operator::Equal}) + * `math:exponentiation` (See {RDF::N3::Algebra::Math::Exponentiation}) + * `math:floor` (See {RDF::N3::Algebra::Math::Floor}) + * `math:greaterThan` (See {SPARQL::Algebra::Operator::GreaterThan}) + * `math:integerQuotient` (See {RDF::N3::Algebra::Math::IntegerQuotient}) + * `math:lessThan` (See {SPARQL::Algebra::Operator::LessThan}) + * `math:negation` (See {SPARQL::Algebra::Operator::Negate}) + * `math:notEqualTo` (See {SPARQL::Algebra::Operator::NotEqual}) + * `math:notGreaterThan` (See {SPARQL::Algebra::Operator::LessThanOrEqual}) + * `math:notLessThan` (See {SPARQL::Algebra::Operator::GreaterThanOrEqual}) + * `math:product` (See {RDF::N3::Algebra::Math::Product}) + * `math:quotient` (See {RDF::N3::Algebra::Math::Quotient}) + * `math:remainder` (See {RDF::N3::Algebra::Math::Remainder}) + * `math:rounded` (See {RDF::N3::Algebra::Math::Rounded}) + * `math:sum` (See {RDF::N3::Algebra::Math::Sum}) #### RDF String vocabulary - * `string:concatenation` (See {RDF::N3::Algebra::Str::Concatenation}) - * `string:contains` (See {SPARQL::Algebra::Operator::Contains}) + * `string:concatenation` (See {RDF::N3::Algebra::Str::Concatenation}) + * `string:contains` (See {SPARQL::Algebra::Operator::Contains}) * `string:containsIgnoringCase` (See {RDF::N3::Algebra::Str::ContainsIgnoringCase}) - * `string:endsWith` (See {SPARQL::Algebra::Operator::StrEnds}) - * `string:equalIgnoringCase` (See {RDF::N3::Algebra::Str::EqualIgnoringCase}) - * `string:format` (See {RDF::N3::Algebra::Str::Format}) - * `string:greaterThan` (See {SPARQL::Algebra::Operator::GreaterThan}) - * `string:lessThan` (See {SPARQL::Algebra::Operator::LessThan}) - * `string:matches` (See {SPARQL::Algebra::Operator::Regex}) + * `string:endsWith` (See {SPARQL::Algebra::Operator::StrEnds}) + * `string:equalIgnoringCase` (See {RDF::N3::Algebra::Str::EqualIgnoringCase}) + * `string:format` (See {RDF::N3::Algebra::Str::Format}) + * `string:greaterThan` (See {SPARQL::Algebra::Operator::GreaterThan}) + * `string:lessThan` (See {SPARQL::Algebra::Operator::LessThan}) + * `string:matches` (See {SPARQL::Algebra::Operator::Regex}) * `string:notEqualIgnoringCase` (See {RDF::N3::Algebra::Str::NotEqualIgnoringCase}) - * `string:notGreaterThan` (See {SPARQL::Algebra::Operator::LessThanOrEqual}) - * `string:notLessThan` (See {SPARQL::Algebra::Operator::GreaterThanOrEqual}) - * `string:notMatches` (See {RDF::N3::Algebra::Str::NotMatches}) - * `string:replace` (See {RDF::N3::Algebra::Str::Replace}) - * `string:scrape` (See {RDF::N3::Algebra::Str::Scrape}) - * `string:startsWith` (See {SPARQL::Algebra::Operator::StrStarts}) + * `string:notGreaterThan` (See {SPARQL::Algebra::Operator::LessThanOrEqual}) + * `string:notLessThan` (See {SPARQL::Algebra::Operator::GreaterThanOrEqual}) + * `string:notMatches` (See {RDF::N3::Algebra::Str::NotMatches}) + * `string:replace` (See {RDF::N3::Algebra::Str::Replace}) + * `string:scrape` (See {RDF::N3::Algebra::Str::Scrape}) + * `string:startsWith` (See {SPARQL::Algebra::Operator::StrStarts}) ### Formulae diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 8dcf7f6..80cd3b9 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -31,17 +31,11 @@ module Log module Math autoload :AbsoluteValue, 'rdf/n3/algebra/math/absoluteValue' + autoload :Ceiling, 'rdf/n3/algebra/math/ceiling' 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 :Floor, 'rdf/n3/algebra/math/floor' 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' @@ -79,22 +73,23 @@ def for(uri) RDF::N3::Log.supports => NotImplemented, RDF::N3::Math.absoluteValue => Math::AbsoluteValue, + RDF::N3::Math.ceiling => Math::Ceiling, RDF::N3::Math.difference => Math::Difference, - RDF::N3::Math.equalTo => Math::EqualTo, + RDF::N3::Math.equalTo => SPARQL::Algebra::Operator::Equal, RDF::N3::Math.exponentiation => Math::Exponentiation, - RDF::N3::Math.greaterThan => Math::GreaterThan, + RDF::N3::Math.floor => Math::Floor, + RDF::N3::Math.greaterThan => SPARQL::Algebra::Operator::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.lessThan => SPARQL::Algebra::Operator::LessThan, + RDF::N3::Math.negation => SPARQL::Algebra::Operator::Negate, + RDF::N3::Math.notEqualTo => SPARQL::Algebra::Operator::NotEqual, + RDF::N3::Math.notGreaterThan => SPARQL::Algebra::Operator::LessThanOrEqual, + RDF::N3::Math.notLessThan => SPARQL::Algebra::Operator::GreaterThanOrEqual, 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::Math[:sum] => Math::Sum, RDF::N3::Str.concatenation => Str::Concatenation, RDF::N3::Str.contains => SPARQL::Algebra::Operator::Contains, diff --git a/lib/rdf/n3/algebra/math/absoluteValue.rb b/lib/rdf/n3/algebra/math/absoluteValue.rb index f7287fe..88fb2b9 100644 --- a/lib/rdf/n3/algebra/math/absoluteValue.rb +++ b/lib/rdf/n3/algebra/math/absoluteValue.rb @@ -2,8 +2,41 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the absolute value of the subject. class AbsoluteValue < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :mathAbsoluteValue + + ## + # The math:sum operator takes string or number and calculates its absolute value. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal::Integer.new(num.value) unless num.is_a?(RDF::Literal::Numeric) + num = num.abs + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end end end diff --git a/lib/rdf/n3/algebra/math/ceiling.rb b/lib/rdf/n3/algebra/math/ceiling.rb new file mode 100644 index 0000000..9fc5d79 --- /dev/null +++ b/lib/rdf/n3/algebra/math/ceiling.rb @@ -0,0 +1,42 @@ +module RDF::N3::Algebra::Math + ## + # The object is calculated as the subject upwards to a whole number. + class Ceiling < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathCeiling + + ## + # The math:ceiling operator takes string or number and calculates its ceiling. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal::Double.new(num.value) unless num.is_a?(RDF::Literal::Numeric) + num = num.ceil + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/difference.rb b/lib/rdf/n3/algebra/math/difference.rb index 15f89c9..c336044 100644 --- a/lib/rdf/n3/algebra/math/difference.rb +++ b/lib/rdf/n3/algebra/math/difference.rb @@ -1,9 +1,59 @@ 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. + # + # @example + # { ("8" "3") math:difference ?x} => { ?x :valueOf "8 - 3" } . + # { ("8") math:difference ?x } => { ?x :valueOf "8 - (error?)" } . + # { (8 3) math:difference ?x} => { ?x :valueOf "8 - 3" } . class Difference < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :mathDifference + + ## + # The math:difference operator takes a pair of strings or numbers and calculates their difference. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + # Might be a variable or node evaluating to a list in queryable, or might be a list with variables + list = operand(0).evaluate(solution.bindings) + # If it evaluated to a BNode, re-expand as a list + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end + unless list.all?(&:literal?) && list.length == 2 + log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} + next + end + + if list.to_a.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:-) + + if result.variable? + solution.merge(result.to_sym => rhs) + elsif result != rhs + nil + else + solution + end + end + end.compact) + end end end diff --git a/lib/rdf/n3/algebra/math/equalTo.rb b/lib/rdf/n3/algebra/math/equalTo.rb deleted file mode 100644 index 0505047..0000000 --- a/lib/rdf/n3/algebra/math/equalTo.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 index 9eaf9bd..1cb6286 100644 --- a/lib/rdf/n3/algebra/math/exponentiation.rb +++ b/lib/rdf/n3/algebra/math/exponentiation.rb @@ -2,8 +2,53 @@ 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 SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :mathExponentiation + + ## + # The math:difference operator takes a pair of strings or numbers and calculates the exponent. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + # Might be a variable or node evaluating to a list in queryable, or might be a list with variables + list = operand(0).evaluate(solution.bindings) + # If it evaluated to a BNode, re-expand as a list + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end + unless list.all?(&:literal?) && list.length == 2 + log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} + next + end + + if list.to_a.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:**) + + if result.variable? + solution.merge(result.to_sym => rhs) + elsif result != rhs + nil + else + solution + end + end + end.compact) + end end end diff --git a/lib/rdf/n3/algebra/math/floor.rb b/lib/rdf/n3/algebra/math/floor.rb new file mode 100644 index 0000000..3487782 --- /dev/null +++ b/lib/rdf/n3/algebra/math/floor.rb @@ -0,0 +1,42 @@ +module RDF::N3::Algebra::Math + ## + # The object is calculated as the subject downwards to a whole number. + class Floor < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathFloor + + ## + # The math:floor operator takes string or number and calculates its floor. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal::Double.new(num.value) unless num.is_a?(RDF::Literal::Numeric) + num = num.floor + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/greaterThan.rb b/lib/rdf/n3/algebra/math/greaterThan.rb deleted file mode 100644 index 890675a..0000000 --- a/lib/rdf/n3/algebra/math/greaterThan.rb +++ /dev/null @@ -1,9 +0,0 @@ -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/lessThan.rb b/lib/rdf/n3/algebra/math/lessThan.rb deleted file mode 100644 index c37aac0..0000000 --- a/lib/rdf/n3/algebra/math/lessThan.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 8e26fae..0000000 --- a/lib/rdf/n3/algebra/math/memberCount.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 9538f42..0000000 --- a/lib/rdf/n3/algebra/math/negation.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index e97c3f3..0000000 --- a/lib/rdf/n3/algebra/math/notEqualTo.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index fa46ec1..0000000 --- a/lib/rdf/n3/algebra/math/notGreaterThan.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index 8bd3774..0000000 --- a/lib/rdf/n3/algebra/math/notLessThan.rb +++ /dev/null @@ -1,9 +0,0 @@ -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 index 6634b29..8fe5146 100644 --- a/lib/rdf/n3/algebra/math/product.rb +++ b/lib/rdf/n3/algebra/math/product.rb @@ -2,8 +2,49 @@ 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 SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :mathProduct + + ## + # The math:product operator takes a list of strings or numbers and calculates their sum. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + # Might be a variable or node evaluating to a list in queryable, or might be a list with variables + list = operand(0).evaluate(solution.bindings) + # If it evaluated to a BNode, re-expand as a list + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end + + if list.to_a.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:*) + + if result.variable? + solution.merge(result.to_sym => rhs) + elsif result != rhs + nil + else + solution + end + end + end.compact) + end end end diff --git a/lib/rdf/n3/algebra/math/quotient.rb b/lib/rdf/n3/algebra/math/quotient.rb index 5050c7e..d1fb45c 100644 --- a/lib/rdf/n3/algebra/math/quotient.rb +++ b/lib/rdf/n3/algebra/math/quotient.rb @@ -2,8 +2,53 @@ 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 SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :mathQuotient + + ## + # The math:quotient operator takes a pair of strings or numbers and calculates their quotient. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + # Might be a variable or node evaluating to a list in queryable, or might be a list with variables + list = operand(0).evaluate(solution.bindings) + # If it evaluated to a BNode, re-expand as a list + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end + unless list.all?(&:literal?) && list.length == 2 + log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} + next + end + + if list.to_a.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:/) + + if result.variable? + solution.merge(result.to_sym => rhs) + elsif result != rhs + nil + else + solution + end + end + end.compact) + end end end diff --git a/lib/rdf/n3/algebra/math/remainder.rb b/lib/rdf/n3/algebra/math/remainder.rb index 4d26d01..e86aae3 100644 --- a/lib/rdf/n3/algebra/math/remainder.rb +++ b/lib/rdf/n3/algebra/math/remainder.rb @@ -2,8 +2,53 @@ 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 SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :mathRemainder + + ## + # The math:quotient operator takes a pair of strings or numbers and calculates their quotient. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + # Might be a variable or node evaluating to a list in queryable, or might be a list with variables + list = operand(0).evaluate(solution.bindings) + # If it evaluated to a BNode, re-expand as a list + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end + unless list.all?(&:literal?) && list.length == 2 + log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} + next + end + + if list.to_a.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:%) + + if result.variable? + solution.merge(result.to_sym => rhs) + elsif result != rhs + nil + else + solution + end + end + end.compact) + end end end diff --git a/lib/rdf/n3/algebra/math/rounded.rb b/lib/rdf/n3/algebra/math/rounded.rb index 1c9ab50..d28775d 100644 --- a/lib/rdf/n3/algebra/math/rounded.rb +++ b/lib/rdf/n3/algebra/math/rounded.rb @@ -2,8 +2,41 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the subject rounded to the nearest integer. class Rounded < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :mathRounded + + ## + # The math:floor operator takes string or number and calculates its floor. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal::Double.new(num.value) unless num.is_a?(RDF::Literal::Numeric) + num = num.round + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end end end diff --git a/lib/rdf/n3/algebra/math/sum.rb b/lib/rdf/n3/algebra/math/sum.rb index f8b0fcb..ca42920 100644 --- a/lib/rdf/n3/algebra/math/sum.rb +++ b/lib/rdf/n3/algebra/math/sum.rb @@ -1,9 +1,54 @@ module RDF::N3::Algebra::Math ## # The subject is a list of numbers. The object is calculated as the arithmentic sum of those numbers. + # + # @example + # { ("3" "5") math:sum ?x } => { ?x :valueOf "3 + 5" } . + # { (3 5) math:sum ?x } => { ?x :valueOf "3 + 5 = 8" } . class Sum < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable include RDF::Util::Logger NAME = :mathSum + + ## + # The math:sum operator takes a list of strings or numbers and calculates their sum. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + # Might be a variable or node evaluating to a list in queryable, or might be a list with variables + list = operand(0).evaluate(solution.bindings) + # If it evaluated to a BNode, re-expand as a list + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + unless list.list? && list.valid? + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + next + end + + if list.to_a.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:+) + + if result.variable? + solution.merge(result.to_sym => rhs) + elsif result != rhs + nil + else + solution + end + end + end.compact) + end end end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index a526cf4..4cee15f 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -236,18 +236,170 @@ 5 a :Pythagorean. ) }, - #"Pythag 3 4 5 5 12 13": { - # input: %( - # { ((3 4 5) (5 12 13))!list:member list:member ?z } => { ?z a :Pythagorean }. - # ), - # expect: %( - # 3 a :Pythagorean. - # 4 a :Pythagorean. - # 5 a :Pythagorean. - # 12 a :Pythagorean. - # 13 a :Pythagorean. - # ) - #}, + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end + end + + context "n3:math" do + context "math:absoluteValue" do + { + '"1"': { + input: %( + { "1" math:absoluteValue 1 } => {:test1a a :SUCCESS}. + ), + expect: %( + :test1a a :SUCCESS . + ) + }, + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + if options[:exception] + expect {reason(options[:input], filter: true)}.to raise_error options[:exception] + else + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end + end + + context "math:ceiling" do + { + '"2.6"': { + input: %( + { "2.6" math:ceiling ?x} => { ?x :valueOf "ceiling(2.7)" } . + ), + expect: %( + 3 :valueOf "ceiling(2.7)" . + ) + }, + "-8.1": { + input: %( + { -8.1 math:ceiling ?x } => {:test2a :is ?x}. + ), + expect: %( + :test2a :is -8 . + ) + } + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end + + context "math:difference" do + { + '("8" "3")': { + input: %( + { ("8" "3") math:difference ?x} => { ?x :valueOf "8 - 3" } . + ), + expect: %( + 5 :valueOf "8 - 3" . + ) + }, + '("8")': { + input: %( + { ("8") math:difference ?x } => { ?x :valueOf "8 - (error?)" } . + ), + expect: %() + }, + '(8 3)': { + input: %( + { (8 3) math:difference ?x} => { ?x :valueOf "8 - 3" } . + ), + expect: %( + 5 :valueOf "8 - 3" . + ) + } + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end + + context "math:floor" do + { + '"2.6"': { + input: %( + { "2.6" math:floor ?x} => { ?x :valueOf "floor(2.7)" } . + ), + expect: %( + 2 :valueOf "floor(2.7)" . + ) + }, + '-8.1': { + input: %( + { -8.1 math:floor ?x } => {:test2a :is ?x}. + ), + expect: %( + :test2a :is -9 . + ) + } + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end + + context "math:greaterThan" do + { + '"008" > "70"': { + input: %( + { "008" math:greaterThan "70" } => { :test10 a :FAILURE }. + ), + expect: %() + }, + '"070" > "008"': { + input: %( + { "70" math:greaterThan "008" } => { :test10 a :success }. + ), + expect: %( + :test10 a :success . + ) + } + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end + + context "math:sum" do + { + '("3" "5")': { + input: %( + { ("3" "5") math:sum ?x } => { ?x :valueOf "3 + 5" } . + ), + expect: %( + 8 :valueOf "3 + 5" . + ) + }, + '(3 5 100)': { + input: %( + { (3 5 100) math:sum ?x } => { ?x :valueOf "3 + 5 + 100" } . + ), + expect: %( + 108 :valueOf "3 + 5 + 100" . + ) + }, }.each do |name, options| it name do logger.info "input: #{options[:input]}" diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index e45b4fe..7181cd0 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -25,8 +25,6 @@ pending "log:includes" when *%w{cwm_includes_conclusion_simple cwm_unify_unify1} pending "reason over formulae" - when *%w{cwm_reason_t6} - pending "support for math" when *%w{cwm_supports_simple cwm_string_roughly cwm_string_uriEncode} pending "Uses unsupported builtin" when *%w{cwm_list_builtin_generated_match} From adc9b3c6d83004d09cea2f6c9fb7da2f21a8728c Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 26 Aug 2020 13:31:08 -0700 Subject: [PATCH 079/193] Refactor operators taking a list subject to use ListOperator as a superclass. --- lib/rdf/n3/algebra.rb | 1 + lib/rdf/n3/algebra/list/append.rb | 63 +++++++------------ lib/rdf/n3/algebra/list/first.rb | 41 ++----------- lib/rdf/n3/algebra/list/last.rb | 36 ++--------- lib/rdf/n3/algebra/list_operator.rb | 70 ++++++++++++++++++++++ lib/rdf/n3/algebra/math/difference.rb | 66 ++++++++------------ lib/rdf/n3/algebra/math/exponentiation.rb | 66 ++++++++------------ lib/rdf/n3/algebra/math/integerQuotient.rb | 34 ++++++++++- lib/rdf/n3/algebra/math/product.rb | 49 ++++----------- lib/rdf/n3/algebra/math/quotient.rb | 67 ++++++++------------- lib/rdf/n3/algebra/math/remainder.rb | 66 ++++++++------------ lib/rdf/n3/algebra/math/sum.rb | 51 ++++------------ lib/rdf/n3/algebra/str/concatenation.rb | 45 ++------------ lib/rdf/n3/algebra/str/format.rb | 46 +++----------- lib/rdf/n3/algebra/str/replace.rb | 63 +++++++------------ lib/rdf/n3/algebra/str/scrape.rb | 63 +++++++------------ 16 files changed, 310 insertions(+), 517 deletions(-) create mode 100644 lib/rdf/n3/algebra/list_operator.rb diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 80cd3b9..c056c10 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -8,6 +8,7 @@ module RDF::N3 # @author [Gregg Kellogg](http://greggkellogg.net/) module Algebra autoload :Formula, 'rdf/n3/algebra/formula' + autoload :ListOperator, 'rdf/n3/algebra/list_operator' autoload :NotImplemented, 'rdf/n3/algebra/notImplemented' module List diff --git a/lib/rdf/n3/algebra/list/append.rb b/lib/rdf/n3/algebra/list/append.rb index d3c07de..8856462 100644 --- a/lib/rdf/n3/algebra/list/append.rb +++ b/lib/rdf/n3/algebra/list/append.rb @@ -6,53 +6,36 @@ module RDF::N3::Algebra::List # ( (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 SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Append < RDF::N3::Algebra::ListOperator NAME = :listAppend ## # Evaluates this operator using the given variable `bindings`. # If the last operand is a variable, it creates a solution for each element in the list. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - @solutions = RDF::Query::Solutions(solutions.map do |solution| - list = operand(0).evaluate(solution.bindings) - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - result = operand(1) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - unless list.to_a.all? {|li| li.list?} - log_error(NAME) {"operand is not a list of lists: #{list.to_sxp}"} - next - end + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + flattened = list.to_a.map(&:to_a).flatten + # Bind a new list based on the values, whos subject use made up from original list subjects + subj = RDF::Node.intern(list.map(&:subject).hash) + RDF::N3::List.new(subject: subj, values: flattened) + end - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - flattened = list.to_a.map(&:to_a).flatten - if result.variable? - # Bind a new list based on the values, whos subject use made up from original list subjects - subj = RDF::Node.intern(list.map(&:subject).hash) - solution.merge(result.to_sym => RDF::N3::List.new(subject: subj, values: list.to_a.map(&:to_a).flatten)) - elsif result.to_a == flattened - solution - else - nil - end - end - end.compact) + ## + # The list argument must be a pair of literals. + # + # @param [RDF::N3::List] list + # @return [Boolean] + # @see RDF::N3::ListOperator#validate + def validate(list) + if super && list.to_a.all? {|li| li.list?} + true + else + log_error(NAME) {"operand is not a list of lists: #{list.to_sxp}"} + false + end end end end diff --git a/lib/rdf/n3/algebra/list/first.rb b/lib/rdf/n3/algebra/list/first.rb index df11b9f..72286f1 100644 --- a/lib/rdf/n3/algebra/list/first.rb +++ b/lib/rdf/n3/algebra/list/first.rb @@ -6,47 +6,18 @@ module RDF::N3::Algebra::List # { ( 1 2 3 4 5 6 ) list:first 1 } => { :test1 a :SUCCESS }. # # The object can be calculated as a function of the list. - class First < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class First < RDF::N3::Algebra::ListOperator NAME = :listFirst ## # Evaluates this operator using the given variable `bindings`. # If the last operand is a variable, it creates a solution for each element in the list. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - @solutions = RDF::Query::Solutions(solutions.map do |solution| - list = operand(0).evaluate(solution.bindings) - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - result = operand(1) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - if result.variable? - solution.merge(result.to_sym => list.first) - elsif list.first == result - solution - else - nil - end - end - end.compact) + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + list.first end end end diff --git a/lib/rdf/n3/algebra/list/last.rb b/lib/rdf/n3/algebra/list/last.rb index 5e48665..620232b 100644 --- a/lib/rdf/n3/algebra/list/last.rb +++ b/lib/rdf/n3/algebra/list/last.rb @@ -6,7 +6,7 @@ module RDF::N3::Algebra::List # { ( 1 2 3 4 5 6 ) list:last 6 } => { :test1 a :SUCCESS }. # # The object can be calculated as a function of the list. - class Last < SPARQL::Algebra::Operator::Binary + class Last < RDF::N3::Algebra::ListOperator include SPARQL::Algebra::Query include SPARQL::Algebra::Update include RDF::Enumerable @@ -18,35 +18,11 @@ class Last < SPARQL::Algebra::Operator::Binary # Evaluates this operator using the given variable `bindings`. # If the last operand is a variable, it creates a solution for each element in the list. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - @solutions = RDF::Query::Solutions(solutions.map do |solution| - list = operand(0).evaluate(solution.bindings) - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - result = operand(1) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - if result.variable? - solution.merge(result.to_sym => list.last) - elsif list.last == result - solution - else - nil - end - end - end.compact) + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + list.last end end end diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb new file mode 100644 index 0000000..f7f5bac --- /dev/null +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -0,0 +1,70 @@ +module RDF::N3::Algebra + ## + # This is a generic operator where the subject is a list or binds to a list and the object is either a constant that equals the evaluation of the subject, or a variable to which the result is bound in a solution + class ListOperator < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :listOperator + + ## + # The operator takes a list and provides a mechanism for subclasses to operate over (and validate) that list argument. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + # Might be a variable or node evaluating to a list in queryable, or might be a list with variables + list = operand(0).evaluate(solution.bindings) + # If it evaluated to a BNode, re-expand as a list + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + + log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + next unless validate(list) + + if list.to_a.any? {|op| op.variable? && op.unbound?} + # Can't bind list elements + solution + else + rhs = evaluate(list) + + if result.variable? + solution.merge(result.to_sym => rhs) + elsif result != rhs + nil + else + solution + end + end + end.compact) + end + + ## + # Subclasses implement `evaluate`. + # + # @param [RDF::N3::List] list + # @return [RDF::Term] + def evaluate(list) + raise NotImplemented + end + + ## + # Subclasses may override or supplement validate to perform validation on the list subject + # + # @param [RDF::N3::List] list + # @return [Boolean] + def validate(list) + if list.list? && list.valid? + true + else + log_error(NAME) {"operand is not a list: #{list.to_sxp}"} + false + end + end + end +end diff --git a/lib/rdf/n3/algebra/math/difference.rb b/lib/rdf/n3/algebra/math/difference.rb index c336044..d96778f 100644 --- a/lib/rdf/n3/algebra/math/difference.rb +++ b/lib/rdf/n3/algebra/math/difference.rb @@ -6,54 +6,36 @@ module RDF::N3::Algebra::Math # { ("8" "3") math:difference ?x} => { ?x :valueOf "8 - 3" } . # { ("8") math:difference ?x } => { ?x :valueOf "8 - (error?)" } . # { (8 3) math:difference ?x} => { ?x :valueOf "8 - 3" } . - class Difference < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Difference < RDF::N3::Algebra::ListOperator NAME = :mathDifference ## # The math:difference operator takes a pair of strings or numbers and calculates their difference. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - # Might be a variable or node evaluating to a list in queryable, or might be a list with variables - list = operand(0).evaluate(solution.bindings) - # If it evaluated to a BNode, re-expand as a list - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - unless list.all?(&:literal?) && list.length == 2 - log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:-) + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + list.to_a.map do |li| + li.is_a?(RDF::Literal::Numeric) ? + li : + RDF::Literal::Integer.new(li.value) + end.reduce(&:-) + end - if result.variable? - solution.merge(result.to_sym => rhs) - elsif result != rhs - nil - else - solution - end - end - end.compact) + ## + # The list argument must be a pair of literals. + # + # @param [RDF::N3::List] list + # @return [Boolean] + # @see RDF::N3::ListOperator#validate + def validate(list) + if super && list.all?(&:literal?) && list.length == 2 + true + else + log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} + false + end end end end diff --git a/lib/rdf/n3/algebra/math/exponentiation.rb b/lib/rdf/n3/algebra/math/exponentiation.rb index 1cb6286..053e9c1 100644 --- a/lib/rdf/n3/algebra/math/exponentiation.rb +++ b/lib/rdf/n3/algebra/math/exponentiation.rb @@ -1,54 +1,36 @@ 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 SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Exponentiation < RDF::N3::Algebra::ListOperator NAME = :mathExponentiation ## # The math:difference operator takes a pair of strings or numbers and calculates the exponent. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - # Might be a variable or node evaluating to a list in queryable, or might be a list with variables - list = operand(0).evaluate(solution.bindings) - # If it evaluated to a BNode, re-expand as a list - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - unless list.all?(&:literal?) && list.length == 2 - log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:**) + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + list.to_a.map do |li| + li.is_a?(RDF::Literal::Numeric) ? + li : + RDF::Literal::Integer.new(li.value) + end.reduce(&:**) + end - if result.variable? - solution.merge(result.to_sym => rhs) - elsif result != rhs - nil - else - solution - end - end - end.compact) + ## + # The list argument must be a pair of literals. + # + # @param [RDF::N3::List] list + # @return [Boolean] + # @see RDF::N3::ListOperator#validate + def validate(list) + if super && list.all?(&:literal?) && list.length == 2 + true + else + log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} + false + end end end end diff --git a/lib/rdf/n3/algebra/math/integerQuotient.rb b/lib/rdf/n3/algebra/math/integerQuotient.rb index 5d1287b..a19b2e2 100644 --- a/lib/rdf/n3/algebra/math/integerQuotient.rb +++ b/lib/rdf/n3/algebra/math/integerQuotient.rb @@ -1,9 +1,37 @@ 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 - + class IntegerQuotient < RDF::N3::Algebra::ListOperator NAME = :mathIntegerQuotient + + ## + # The math:quotient operator takes a pair of strings or numbers and calculates their quotient. + # + # + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + RDF::Literal::Integer.new(list.to_a.map do |li| + li.is_a?(RDF::Literal::Integer) ? + li : + RDF::Literal::Integer.new(li.value) + end.reduce(&:/)) + end + + ## + # The list argument must be a pair of literals. + # + # @param [RDF::N3::List] list + # @return [Boolean] + # @see RDF::N3::ListOperator#validate + def validate(list) + if super && list.all?(&:literal?) && list.length == 2 + true + else + log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} + false + end + end end end diff --git a/lib/rdf/n3/algebra/math/product.rb b/lib/rdf/n3/algebra/math/product.rb index 8fe5146..79479c8 100644 --- a/lib/rdf/n3/algebra/math/product.rb +++ b/lib/rdf/n3/algebra/math/product.rb @@ -1,50 +1,21 @@ 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 SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Product < RDF::N3::Algebra::ListOperator NAME = :mathProduct ## # The math:product operator takes a list of strings or numbers and calculates their sum. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - # Might be a variable or node evaluating to a list in queryable, or might be a list with variables - list = operand(0).evaluate(solution.bindings) - # If it evaluated to a BNode, re-expand as a list - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:*) - - if result.variable? - solution.merge(result.to_sym => rhs) - elsif result != rhs - nil - else - solution - end - end - end.compact) + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + list.to_a.map do |li| + li.is_a?(RDF::Literal::Numeric) ? + li : + RDF::Literal::Integer.new(li.value) + end.reduce(&:*) end end end diff --git a/lib/rdf/n3/algebra/math/quotient.rb b/lib/rdf/n3/algebra/math/quotient.rb index d1fb45c..64c4275 100644 --- a/lib/rdf/n3/algebra/math/quotient.rb +++ b/lib/rdf/n3/algebra/math/quotient.rb @@ -1,54 +1,37 @@ 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 SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Quotient < RDF::N3::Algebra::ListOperator NAME = :mathQuotient ## # The math:quotient operator takes a pair of strings or numbers and calculates their quotient. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - # Might be a variable or node evaluating to a list in queryable, or might be a list with variables - list = operand(0).evaluate(solution.bindings) - # If it evaluated to a BNode, re-expand as a list - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - unless list.all?(&:literal?) && list.length == 2 - log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:/) + # + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + list.to_a.map do |li| + li.is_a?(RDF::Literal::Numeric) ? + li : + RDF::Literal::Integer.new(li.value) + end.reduce(&:/) + end - if result.variable? - solution.merge(result.to_sym => rhs) - elsif result != rhs - nil - else - solution - end - end - end.compact) + ## + # The list argument must be a pair of literals. + # + # @param [RDF::N3::List] list + # @return [Boolean] + # @see RDF::N3::ListOperator#validate + def validate(list) + if super && list.all?(&:literal?) && list.length == 2 + true + else + log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} + false + end end end end diff --git a/lib/rdf/n3/algebra/math/remainder.rb b/lib/rdf/n3/algebra/math/remainder.rb index e86aae3..853e927 100644 --- a/lib/rdf/n3/algebra/math/remainder.rb +++ b/lib/rdf/n3/algebra/math/remainder.rb @@ -1,54 +1,36 @@ 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 SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Remainder < RDF::N3::Algebra::ListOperator NAME = :mathRemainder ## # The math:quotient operator takes a pair of strings or numbers and calculates their quotient. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - # Might be a variable or node evaluating to a list in queryable, or might be a list with variables - list = operand(0).evaluate(solution.bindings) - # If it evaluated to a BNode, re-expand as a list - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - unless list.all?(&:literal?) && list.length == 2 - log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:%) + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + list.to_a.map do |li| + li.is_a?(RDF::Literal::Numeric) ? + li : + RDF::Literal::Integer.new(li.value) + end.reduce(&:%) + end - if result.variable? - solution.merge(result.to_sym => rhs) - elsif result != rhs - nil - else - solution - end - end - end.compact) + ## + # The list argument must be a pair of literals. + # + # @param [RDF::N3::List] list + # @return [Boolean] + # @see RDF::N3::ListOperator#validate + def validate(list) + if super && list.all?(&:literal?) && list.length == 2 + true + else + log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} + false + end end end end diff --git a/lib/rdf/n3/algebra/math/sum.rb b/lib/rdf/n3/algebra/math/sum.rb index ca42920..7ba0a8b 100644 --- a/lib/rdf/n3/algebra/math/sum.rb +++ b/lib/rdf/n3/algebra/math/sum.rb @@ -5,50 +5,21 @@ module RDF::N3::Algebra::Math # @example # { ("3" "5") math:sum ?x } => { ?x :valueOf "3 + 5" } . # { (3 5) math:sum ?x } => { ?x :valueOf "3 + 5 = 8" } . - class Sum < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Sum < RDF::N3::Algebra::ListOperator NAME = :mathSum ## - # The math:sum operator takes a list of strings or numbers and calculates their sum. + # Evaluates to the sum of the list elements # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - # Might be a variable or node evaluating to a list in queryable, or might be a list with variables - list = operand(0).evaluate(solution.bindings) - # If it evaluated to a BNode, re-expand as a list - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - rhs = list.to_a.map {|li| li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value)}.reduce(&:+) - - if result.variable? - solution.merge(result.to_sym => rhs) - elsif result != rhs - nil - else - solution - end - end - end.compact) + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + list.to_a.map do |li| + li.is_a?(RDF::Literal::Numeric) ? + li : + RDF::Literal::Integer.new(li.value) + end.reduce(&:+) end end end diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb index f0956dc..a5ff7f4 100644 --- a/lib/rdf/n3/algebra/str/concatenation.rb +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -4,50 +4,17 @@ module RDF::N3::Algebra::Str # # @example # ("a" "b") string:concatenation :s - class Concatenation < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Concatenation < RDF::N3::Algebra::ListOperator NAME = :strConcatenation ## # The string:concatenation operator takes a list of terms evaluating to strings and either binds the result of concatenating them to the output variable, removes a solution that does equal. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - # Might be a variable or node evaluating to a list in queryable, or might be a list with variables - list = operand(0).evaluate(solution.bindings) - # If it evaluated to a BNode, re-expand as a list - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - rhs = list.to_a.map(&:value).join("") - - if result.variable? - solution.merge(result.to_sym => RDF::Literal(rhs)) - elsif result != rhs - nil - else - solution - end - end - end.compact) + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + RDF::Literal(list.to_a.map(&:value).join("")) end end end diff --git a/lib/rdf/n3/algebra/str/format.rb b/lib/rdf/n3/algebra/str/format.rb index 0dea284..5bc81e5 100644 --- a/lib/rdf/n3/algebra/str/format.rb +++ b/lib/rdf/n3/algebra/str/format.rb @@ -1,48 +1,16 @@ module RDF::N3::Algebra::Str - class Format < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Format < RDF::N3::Algebra::ListOperator NAME = :strFormat ## # 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. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - list = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - list = operand(0).evaluate(solution.bindings) - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - format, *args = list.to_a.map(&:value) - str = format % args - - if result.variable? - solution.merge(result.to_sym => str) - elsif result != str - nil - else - solution - end - end - end.compact) + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + format, *args = list.to_a.map(&:value) + str = format % args end end end diff --git a/lib/rdf/n3/algebra/str/replace.rb b/lib/rdf/n3/algebra/str/replace.rb index 540a997..347177b 100644 --- a/lib/rdf/n3/algebra/str/replace.rb +++ b/lib/rdf/n3/algebra/str/replace.rb @@ -1,10 +1,5 @@ module RDF::N3::Algebra::Str - class Replace < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Replace < RDF::N3::Algebra::ListOperator NAME = :strReplace ## @@ -13,43 +8,27 @@ class Replace < SPARQL::Algebra::Operator::Binary # @example # ("fofof bar", "of", "baz") string:replace "fbazbaz bar" # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - result = operand(1) - @solutions = RDF::Query::Solutions(solutions.map do |solution| - # Might be a variable or node evaluating to a list in queryable, or might be a list with variables - list = operand(0).evaluate(solution.bindings) - # If it evaluated to a BNode, re-expand as a list - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - unless list.length == 3 - log_error(NAME) {"list must have exactly three entries: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - input, old_str, new_str = list.to_a - output = input.to_s.gsub(old_str.to_s, new_str.to_s) + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + format, *args = list.to_a.map(&:value) + input, old_str, new_str = list.to_a + RDF::Literal(input.to_s.gsub(old_str.to_s, new_str.to_s)) + end - if result.variable? - solution.merge(result.to_sym => RDF::Literal(output)) - elsif result != output - nil - else - solution - end - end - end.compact) + ## + # Subclasses may override or supplement validate to perform validation on the list subject + # + # @param [RDF::N3::List] list + # @return [Boolean] + def validate(list) + if super && list.length == 3 + true + else + log_error(NAME) {"list must have exactly three entries: #{list.to_sxp}"} + false + end end end end diff --git a/lib/rdf/n3/algebra/str/scrape.rb b/lib/rdf/n3/algebra/str/scrape.rb index 267f8ca..27533d8 100644 --- a/lib/rdf/n3/algebra/str/scrape.rb +++ b/lib/rdf/n3/algebra/str/scrape.rb @@ -1,10 +1,5 @@ module RDF::N3::Algebra::Str - class Scrape < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Scrape < RDF::N3::Algebra::ListOperator NAME = :strScrape ## @@ -13,43 +8,27 @@ class Scrape < SPARQL::Algebra::Operator::Binary # @example # ("abcdef" "ab(..)ef") string:scrape "cd" # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - list = operand(0).evaluate(solution.bindings) - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} - - unless list.list? && list.valid? - log_error(NAME) {"operand is not a list: #{list.to_sxp}"} - next - end - unless list.length == 2 - log_error(NAME) {"list must have exactly two entries: #{list.to_sxp}"} - next - end - - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - input, regex = list.to_a - md = Regexp.new(regex.to_s).match(input.to_s) + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + input, regex = list.to_a + md = Regexp.new(regex.to_s).match(input.to_s) + RDF::Literal(md[1]) if md + end - if result.variable? && md && md[1] - solution.merge(result.to_sym => RDF::Literal(md[1])) - elsif !md || result != md[1] - nil - else - solution - end - end - end.compact) + ## + # Subclasses may override or supplement validate to perform validation on the list subject + # + # @param [RDF::N3::List] list + # @return [Boolean] + def validate(list) + if super && list.length == 2 + true + else + log_error(NAME) {"list must have exactly two entries: #{list.to_sxp}"} + false + end end end end From 55172930b332a5bbba4b0a6a980ade3b80b2d535 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 26 Aug 2020 14:04:28 -0700 Subject: [PATCH 080/193] Implement math:negation, as semantics differ from SPARQL negate. --- lib/rdf/n3/algebra.rb | 3 ++- lib/rdf/n3/algebra/math/negation.rb | 42 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 lib/rdf/n3/algebra/math/negation.rb diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index c056c10..812853e 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -37,6 +37,7 @@ module Math autoload :Exponentiation, 'rdf/n3/algebra/math/exponentiation' autoload :Floor, 'rdf/n3/algebra/math/floor' autoload :IntegerQuotient, 'rdf/n3/algebra/math/integerQuotient' + autoload :Negation, 'rdf/n3/algebra/math/negation' autoload :Product, 'rdf/n3/algebra/math/product' autoload :Quotient, 'rdf/n3/algebra/math/quotient' autoload :Remainder, 'rdf/n3/algebra/math/remainder' @@ -82,7 +83,7 @@ def for(uri) RDF::N3::Math.greaterThan => SPARQL::Algebra::Operator::GreaterThan, RDF::N3::Math.integerQuotient => Math::IntegerQuotient, RDF::N3::Math.lessThan => SPARQL::Algebra::Operator::LessThan, - RDF::N3::Math.negation => SPARQL::Algebra::Operator::Negate, + RDF::N3::Math.negation => Math::Negation, RDF::N3::Math.notEqualTo => SPARQL::Algebra::Operator::NotEqual, RDF::N3::Math.notGreaterThan => SPARQL::Algebra::Operator::LessThanOrEqual, RDF::N3::Math.notLessThan => SPARQL::Algebra::Operator::GreaterThanOrEqual, diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb new file mode 100644 index 0000000..23a3917 --- /dev/null +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -0,0 +1,42 @@ +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 SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathNegation + + ## + # The math:negation operator takes may have either a bound subject or object. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + @solutions = RDF::Query::Solutions(solutions.map do |solution| + subject, object = operand(0), operand(1) + + log_debug(NAME) {"subject: #{subject.to_sxp}, object: #{object.to_sxp}"} + unless subject.literal? || object.literal? + log_error(NAME) {"subject or object are not literals: #{subject.inspect}, #{object.inspect}"} + next + end + + subject = RDF::Literal::Integer.new(subject.value) if subject.literal? && !subject.is_a?(RDF::Literal::Numeric) + object = RDF::Literal::Integer.new(object.value) if object.literal? && !object.is_a?(RDF::Literal::Numeric) + if subject.variable? + solution.merge(subject.to_sym => -object) + elsif object.variable? + solution.merge(object.to_sym => -subject) + elsif subject == -object + solution + else + nil + end + end.compact) + end + end +end From 18a3c2e2a4efb03694d726ebe540aebad20fc61b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 27 Aug 2020 10:05:26 -0700 Subject: [PATCH 081/193] Add math:equalTo, math:notEqualTo and list:length. --- README.md | 1 + lib/rdf/n3/algebra.rb | 8 ++++++-- lib/rdf/n3/algebra/list/length.rb | 28 +++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/equalTo.rb | 24 +++++++++++++++++++++++ lib/rdf/n3/algebra/math/notEqualTo.rb | 24 +++++++++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 lib/rdf/n3/algebra/list/length.rb create mode 100644 lib/rdf/n3/algebra/math/equalTo.rb create mode 100644 lib/rdf/n3/algebra/math/notEqualTo.rb diff --git a/README.md b/README.md index 71c29a7..b4ddf81 100755 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Reasoning is discussed in the [Design Issues][] document. * `list:first` (See {RDF::N3::Algebra::List::First}) * `list:in` (See {RDF::N3::Algebra::List::In}) * `list:last` (See {RDF::N3::Algebra::List::Last}) + * `list:length` (See {RDF::N3::Algebra::List::Length}) * `list:member` (See {RDF::N3::Algebra::List::Member}) #### RDF Log vocabulary diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 812853e..c08b16b 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -16,6 +16,7 @@ module List autoload :First, 'rdf/n3/algebra/list/first' autoload :In, 'rdf/n3/algebra/list/in' autoload :Last, 'rdf/n3/algebra/list/last' + autoload :Length, 'rdf/n3/algebra/list/length' autoload :Member, 'rdf/n3/algebra/list/member' end @@ -34,10 +35,12 @@ module Math autoload :AbsoluteValue, 'rdf/n3/algebra/math/absoluteValue' autoload :Ceiling, 'rdf/n3/algebra/math/ceiling' autoload :Difference, 'rdf/n3/algebra/math/difference' + autoload :EqualTo, 'rdf/n3/algebra/math/equalTo' autoload :Exponentiation, 'rdf/n3/algebra/math/exponentiation' autoload :Floor, 'rdf/n3/algebra/math/floor' autoload :IntegerQuotient, 'rdf/n3/algebra/math/integerQuotient' autoload :Negation, 'rdf/n3/algebra/math/negation' + autoload :NotEqualTo, 'rdf/n3/algebra/math/notEqualTo' autoload :Product, 'rdf/n3/algebra/math/product' autoload :Quotient, 'rdf/n3/algebra/math/quotient' autoload :Remainder, 'rdf/n3/algebra/math/remainder' @@ -62,6 +65,7 @@ def for(uri) RDF::N3::List.first => List::First, RDF::N3::List.in => List::In, RDF::N3::List.last => List::Last, + RDF::N3::List.length => List::Length, RDF::N3::List.member => List::Member, RDF::N3::Log.conclusion => Log::Conclusion, @@ -77,14 +81,14 @@ def for(uri) RDF::N3::Math.absoluteValue => Math::AbsoluteValue, RDF::N3::Math.ceiling => Math::Ceiling, RDF::N3::Math.difference => Math::Difference, - RDF::N3::Math.equalTo => SPARQL::Algebra::Operator::Equal, + RDF::N3::Math.equalTo => Math::EqualTo, RDF::N3::Math.exponentiation => Math::Exponentiation, RDF::N3::Math.floor => Math::Floor, RDF::N3::Math.greaterThan => SPARQL::Algebra::Operator::GreaterThan, RDF::N3::Math.integerQuotient => Math::IntegerQuotient, RDF::N3::Math.lessThan => SPARQL::Algebra::Operator::LessThan, RDF::N3::Math.negation => Math::Negation, - RDF::N3::Math.notEqualTo => SPARQL::Algebra::Operator::NotEqual, + RDF::N3::Math.notEqualTo => Math::NotEqualTo, RDF::N3::Math.notGreaterThan => SPARQL::Algebra::Operator::LessThanOrEqual, RDF::N3::Math.notLessThan => SPARQL::Algebra::Operator::GreaterThanOrEqual, RDF::N3::Math.product => Math::Product, diff --git a/lib/rdf/n3/algebra/list/length.rb b/lib/rdf/n3/algebra/list/length.rb new file mode 100644 index 0000000..bc61351 --- /dev/null +++ b/lib/rdf/n3/algebra/list/length.rb @@ -0,0 +1,28 @@ +module RDF::N3::Algebra::List + ## + # Iff the suject is a list and the object is the last thing that list, then this is true. The object can be calculated as a function of the list. + # + # @example + # { ( 1 2 3 4 5 6 ) list:length 6 } => { :test1 a :SUCCESS }. + # + # The object can be calculated as a function of the list. + class Length < RDF::N3::Algebra::ListOperator + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :listLength + + ## + # Evaluates this operator using the given variable `bindings`. + # If the last operand is a variable, it creates a solution for each element in the list. + # + # @param [RDF::N3::List] list + # @return [RDF::Term] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + list.length + end + 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..acf3424 --- /dev/null +++ b/lib/rdf/n3/algebra/math/equalTo.rb @@ -0,0 +1,24 @@ +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::Compare + include RDF::Util::Logger + + NAME = :'=' + + ## + # Returns TRUE if `term1` and `term2` are the same RDF term as defined in Resource Description Framework (RDF): Concepts and Abstract Syntax [CONCEPTS]; produces a type error if the arguments are both literal but are not the same RDF term *; returns FALSE otherwise. `term1` and `term2` are the same if any of the following is true: + # + # @param [RDF::Term] term1 + # an RDF term + # @param [RDF::Term] term2 + # an RDF term + # @return [RDF::Literal::Boolean] `true` or `false` + # @raise [TypeError] if either operand is not an RDF term or operands are not comperable + # + # @see RDF::Term#== + def apply(term1, term2) + RDF::Literal(term1 == term2) + end + 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..45661e9 --- /dev/null +++ b/lib/rdf/n3/algebra/math/notEqualTo.rb @@ -0,0 +1,24 @@ +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::Compare + include RDF::Util::Logger + + NAME = :'!=' + + ## + # Returns TRUE if `term1` and `term2` are the same RDF term as defined in Resource Description Framework (RDF): Concepts and Abstract Syntax [CONCEPTS]; produces a type error if the arguments are both literal but are not the same RDF term *; returns FALSE otherwise. `term1` and `term2` are the same if any of the following is true: + # + # @param [RDF::Term] term1 + # an RDF term + # @param [RDF::Term] term2 + # an RDF term + # @return [RDF::Literal::Boolean] `true` or `false` + # @raise [TypeError] if either operand is not an RDF term or operands are not comperable + # + # @see RDF::Term#== + def apply(term1, term2) + RDF::Literal(term1 == term2) + end + end +end From 3fcb753733f13f022453f0343bb709b0fb26b2d0 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 27 Aug 2020 10:06:53 -0700 Subject: [PATCH 082/193] Provide default values for math:sum and math:product when given an empty list (`0`, and `1`, respectively). --- lib/rdf/n3/algebra/math/product.rb | 2 +- lib/rdf/n3/algebra/math/sum.rb | 2 +- spec/reasoner_spec.rb | 75 ++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/lib/rdf/n3/algebra/math/product.rb b/lib/rdf/n3/algebra/math/product.rb index 79479c8..6db7fea 100644 --- a/lib/rdf/n3/algebra/math/product.rb +++ b/lib/rdf/n3/algebra/math/product.rb @@ -15,7 +15,7 @@ def evaluate(list) li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value) - end.reduce(&:*) + end.reduce(&:*) || RDF::Literal(1) # Empty list product is 1 end end end diff --git a/lib/rdf/n3/algebra/math/sum.rb b/lib/rdf/n3/algebra/math/sum.rb index 7ba0a8b..1a63001 100644 --- a/lib/rdf/n3/algebra/math/sum.rb +++ b/lib/rdf/n3/algebra/math/sum.rb @@ -19,7 +19,7 @@ def evaluate(list) li.is_a?(RDF::Literal::Numeric) ? li : RDF::Literal::Integer.new(li.value) - end.reduce(&:+) + end.reduce(&:+) || RDF::Literal(0) # Empty list sums to 0 end end end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 4cee15f..239ca0f 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -382,6 +382,41 @@ end end + context "math:product" do + { + '("5" "3" "2")': { + input: %( + { ("5" "3" "2") math:product ?x} => { ?x :valueOf "5 * 3 * 2" } . + ), + expect: %( + 30 :valueOf "5 * 3 * 2" . + ) + }, + '(5 3 2)': { + input: %( + { (5 3 2) math:product ?x} => { ?x :valueOf "5 * 3 * 2" } . + ), + expect: %( + 30 :valueOf "5 * 3 * 2" . + ) + }, + "()": { + input: %( + { () math:product ?x } => { ?x :valuOf " () math:product ?x --- should be 1" }. + ), + expect: %( + 1 :valuOf " () math:product ?x --- should be 1" . + ) + }, + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end + context "math:sum" do { '("3" "5")': { @@ -400,6 +435,46 @@ 108 :valueOf "3 + 5 + 100" . ) }, + "()": { + input: %( + { () math:sum ?x } => { ?x :valuOf " () math:sum ?x --- should be 0" }. + ), + expect: %( + 0 :valuOf " () math:sum ?x --- should be 0" . + ) + }, + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end + + context "nesting" do + { + "A nested rule": { + input: %( + { ?x is math:sum of (3 (8 3)!math:difference ) } + => { ?x :valueOf "3 + (8 - 3)" } . + ), + expect: %( + 8 :valueOf "3 + (8 - 3)" . + ) + }, + "Big test": { + input: %( + { ( ("7" "2")!math:quotient + (("7" "2")!math:remainder "10000000")!math:exponentiation + ("a" "b" "c" "d" "e")!list:length + ) math:sum ?x } => + { ?x :valueOf "(7 / 2) + ((7 % 2)^10000000) + 5 [should be 9.5e0]" } . + ), + expect: %( + 9.5e0 :valueOf "(7 / 2) + ((7 % 2)^10000000) + 5 [should be 9.5e0]" . + ) + }, }.each do |name, options| it name do logger.info "input: #{options[:input]}" From 01e84ce80e67a49b46e4883a0ef876932138800b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 27 Aug 2020 10:07:38 -0700 Subject: [PATCH 083/193] Use `NAME` from `self.class` for logging in listOperator. --- lib/rdf/n3/algebra/list_operator.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index f7f5bac..fd3e4d5 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -7,8 +7,6 @@ class ListOperator < SPARQL::Algebra::Operator::Binary include RDF::Enumerable include RDF::Util::Logger - NAME = :listOperator - ## # The operator takes a list and provides a mechanism for subclasses to operate over (and validate) that list argument. # @@ -24,7 +22,7 @@ def execute(queryable, solutions:, **options) # If it evaluated to a BNode, re-expand as a list list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + log_debug(self.class.const_get(:NAME)) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} next unless validate(list) if list.to_a.any? {|op| op.variable? && op.unbound?} From 14de2be6cd15b307171854ae181012e45c268954 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 27 Aug 2020 14:35:13 -0700 Subject: [PATCH 084/193] Use `rdf:nil` for `nil` list entries, rather than simply `nil`. --- lib/rdf/n3/list.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index 878bf55..e3f2689 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -74,7 +74,7 @@ def initialize(subject: nil, graph: nil, values: nil, &block) when RDF::Value then v.to_term when Symbol then RDF::Node.intern(v) when Array then RDF::N3::List.new(values: v) - when nil then nil + when nil then RDF.nil else RDF::Literal.new(v) end end @@ -141,7 +141,7 @@ def []=(*args) when RDF::Value then v.to_term when Symbol then RDF::Node.intern(v) when Array then RDF::N3::List.new(values: v) - when nil then nil + when nil then RDF.nil else RDF::Literal.new(v) end end @@ -165,6 +165,9 @@ def []=(*args) raise ArgumentError, "List []= takes one or two index values" end + # Fill any nil entries in @values with rdf:nil + @values.map! {|v| v || RDF.nil} + @subject = RDF.nil if @values.empty? @subject ||= RDF::Node.new ret # Returns inserted values @@ -578,7 +581,7 @@ def normalize_value(value) when RDF::Value then value.to_term when Array then RDF::N3::List.new(values: value) when Symbol then RDF::Node.intern(value) - when nil then nil + when nil then RDF.nil else RDF::Literal.new(value) end end From 67c2fc3f426f721dd652d2d9680e0ec10b487cf9 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 27 Aug 2020 14:35:32 -0700 Subject: [PATCH 085/193] list:length result must be a literal. --- lib/rdf/n3/algebra/list/length.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdf/n3/algebra/list/length.rb b/lib/rdf/n3/algebra/list/length.rb index bc61351..c415d1b 100644 --- a/lib/rdf/n3/algebra/list/length.rb +++ b/lib/rdf/n3/algebra/list/length.rb @@ -22,7 +22,7 @@ class Length < RDF::N3::Algebra::ListOperator # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - list.length + RDF::Literal(list.length) end end end From c236f1eec0df9ba57fbf335e2f98b5512d3b336e Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 27 Aug 2020 17:23:33 -0700 Subject: [PATCH 086/193] Add Literal#as_number (with Value#as_numer and Variable#as_number) to ease transformation of strings to numbers, based on DOUBLE/DECIMAL/INTEGER parsing. --- lib/rdf/n3/algebra/math/absoluteValue.rb | 3 +- lib/rdf/n3/algebra/math/ceiling.rb | 3 +- lib/rdf/n3/algebra/math/difference.rb | 6 +-- lib/rdf/n3/algebra/math/equalTo.rb | 1 + lib/rdf/n3/algebra/math/exponentiation.rb | 6 +-- lib/rdf/n3/algebra/math/floor.rb | 3 +- lib/rdf/n3/algebra/math/negation.rb | 4 +- lib/rdf/n3/algebra/math/notEqualTo.rb | 1 + lib/rdf/n3/algebra/math/product.rb | 6 +-- lib/rdf/n3/algebra/math/quotient.rb | 6 +-- lib/rdf/n3/algebra/math/remainder.rb | 6 +-- lib/rdf/n3/algebra/math/rounded.rb | 3 +- lib/rdf/n3/algebra/math/sum.rb | 6 +-- lib/rdf/n3/extensions.rb | 55 +++++++++++++++++++++++ 14 files changed, 69 insertions(+), 40 deletions(-) diff --git a/lib/rdf/n3/algebra/math/absoluteValue.rb b/lib/rdf/n3/algebra/math/absoluteValue.rb index 88fb2b9..a2b0248 100644 --- a/lib/rdf/n3/algebra/math/absoluteValue.rb +++ b/lib/rdf/n3/algebra/math/absoluteValue.rb @@ -26,8 +26,7 @@ def execute(queryable, solutions:, **options) next end - num = RDF::Literal::Integer.new(num.value) unless num.is_a?(RDF::Literal::Numeric) - num = num.abs + num = num.as_number.abs if result.variable? solution.merge(result.to_sym => num) diff --git a/lib/rdf/n3/algebra/math/ceiling.rb b/lib/rdf/n3/algebra/math/ceiling.rb index 9fc5d79..b29804e 100644 --- a/lib/rdf/n3/algebra/math/ceiling.rb +++ b/lib/rdf/n3/algebra/math/ceiling.rb @@ -26,8 +26,7 @@ def execute(queryable, solutions:, **options) next end - num = RDF::Literal::Double.new(num.value) unless num.is_a?(RDF::Literal::Numeric) - num = num.ceil + num = num.as_number.ceil if result.variable? solution.merge(result.to_sym => num) diff --git a/lib/rdf/n3/algebra/math/difference.rb b/lib/rdf/n3/algebra/math/difference.rb index d96778f..8870fcd 100644 --- a/lib/rdf/n3/algebra/math/difference.rb +++ b/lib/rdf/n3/algebra/math/difference.rb @@ -16,11 +16,7 @@ class Difference < RDF::N3::Algebra::ListOperator # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - list.to_a.map do |li| - li.is_a?(RDF::Literal::Numeric) ? - li : - RDF::Literal::Integer.new(li.value) - end.reduce(&:-) + list.to_a.map(&:as_number).reduce(&:-) end ## diff --git a/lib/rdf/n3/algebra/math/equalTo.rb b/lib/rdf/n3/algebra/math/equalTo.rb index acf3424..beb6d54 100644 --- a/lib/rdf/n3/algebra/math/equalTo.rb +++ b/lib/rdf/n3/algebra/math/equalTo.rb @@ -18,6 +18,7 @@ class EqualTo < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) + log_debug(NAME) { "term1: #{term1.to_sxp} == term2: #{term2.to_sxp} ? #{(term1 == term2).inspect}"} RDF::Literal(term1 == term2) end end diff --git a/lib/rdf/n3/algebra/math/exponentiation.rb b/lib/rdf/n3/algebra/math/exponentiation.rb index 053e9c1..1f361cf 100644 --- a/lib/rdf/n3/algebra/math/exponentiation.rb +++ b/lib/rdf/n3/algebra/math/exponentiation.rb @@ -11,11 +11,7 @@ class Exponentiation < RDF::N3::Algebra::ListOperator # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - list.to_a.map do |li| - li.is_a?(RDF::Literal::Numeric) ? - li : - RDF::Literal::Integer.new(li.value) - end.reduce(&:**) + list.to_a.map(&:as_number).reduce(&:**) end ## diff --git a/lib/rdf/n3/algebra/math/floor.rb b/lib/rdf/n3/algebra/math/floor.rb index 3487782..8559d73 100644 --- a/lib/rdf/n3/algebra/math/floor.rb +++ b/lib/rdf/n3/algebra/math/floor.rb @@ -26,8 +26,7 @@ def execute(queryable, solutions:, **options) next end - num = RDF::Literal::Double.new(num.value) unless num.is_a?(RDF::Literal::Numeric) - num = num.floor + num = num.as_number.floor if result.variable? solution.merge(result.to_sym => num) diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb index 23a3917..2853e5a 100644 --- a/lib/rdf/n3/algebra/math/negation.rb +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -25,8 +25,8 @@ def execute(queryable, solutions:, **options) next end - subject = RDF::Literal::Integer.new(subject.value) if subject.literal? && !subject.is_a?(RDF::Literal::Numeric) - object = RDF::Literal::Integer.new(object.value) if object.literal? && !object.is_a?(RDF::Literal::Numeric) + subject = subject.as_number + object = object.as_number if subject.variable? solution.merge(subject.to_sym => -object) elsif object.variable? diff --git a/lib/rdf/n3/algebra/math/notEqualTo.rb b/lib/rdf/n3/algebra/math/notEqualTo.rb index 45661e9..fd982a3 100644 --- a/lib/rdf/n3/algebra/math/notEqualTo.rb +++ b/lib/rdf/n3/algebra/math/notEqualTo.rb @@ -18,6 +18,7 @@ class NotEqualTo < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) + log_debug(NAME) { "term1: #{term1.to_sxp} != term2: #{term2.to_sxp} ? #{(term1 == term2).inspect}"} RDF::Literal(term1 == term2) end end diff --git a/lib/rdf/n3/algebra/math/product.rb b/lib/rdf/n3/algebra/math/product.rb index 6db7fea..4d371c7 100644 --- a/lib/rdf/n3/algebra/math/product.rb +++ b/lib/rdf/n3/algebra/math/product.rb @@ -11,11 +11,7 @@ class Product < RDF::N3::Algebra::ListOperator # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - list.to_a.map do |li| - li.is_a?(RDF::Literal::Numeric) ? - li : - RDF::Literal::Integer.new(li.value) - end.reduce(&:*) || RDF::Literal(1) # Empty list product is 1 + list.to_a.map(&:as_number).reduce(&:*) || RDF::Literal(1) # Empty list product is 1 end end end diff --git a/lib/rdf/n3/algebra/math/quotient.rb b/lib/rdf/n3/algebra/math/quotient.rb index 64c4275..6d25e6d 100644 --- a/lib/rdf/n3/algebra/math/quotient.rb +++ b/lib/rdf/n3/algebra/math/quotient.rb @@ -12,11 +12,7 @@ class Quotient < RDF::N3::Algebra::ListOperator # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - list.to_a.map do |li| - li.is_a?(RDF::Literal::Numeric) ? - li : - RDF::Literal::Integer.new(li.value) - end.reduce(&:/) + list.to_a.map(&:as_number).reduce(&:/) end ## diff --git a/lib/rdf/n3/algebra/math/remainder.rb b/lib/rdf/n3/algebra/math/remainder.rb index 853e927..a9aecb0 100644 --- a/lib/rdf/n3/algebra/math/remainder.rb +++ b/lib/rdf/n3/algebra/math/remainder.rb @@ -11,11 +11,7 @@ class Remainder < RDF::N3::Algebra::ListOperator # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - list.to_a.map do |li| - li.is_a?(RDF::Literal::Numeric) ? - li : - RDF::Literal::Integer.new(li.value) - end.reduce(&:%) + list.to_a.map(&:as_number).reduce(&:%) end ## diff --git a/lib/rdf/n3/algebra/math/rounded.rb b/lib/rdf/n3/algebra/math/rounded.rb index d28775d..2e516fd 100644 --- a/lib/rdf/n3/algebra/math/rounded.rb +++ b/lib/rdf/n3/algebra/math/rounded.rb @@ -26,8 +26,7 @@ def execute(queryable, solutions:, **options) next end - num = RDF::Literal::Double.new(num.value) unless num.is_a?(RDF::Literal::Numeric) - num = num.round + num = num.as_number.round if result.variable? solution.merge(result.to_sym => num) diff --git a/lib/rdf/n3/algebra/math/sum.rb b/lib/rdf/n3/algebra/math/sum.rb index 1a63001..9e266cd 100644 --- a/lib/rdf/n3/algebra/math/sum.rb +++ b/lib/rdf/n3/algebra/math/sum.rb @@ -15,11 +15,7 @@ class Sum < RDF::N3::Algebra::ListOperator # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - list.to_a.map do |li| - li.is_a?(RDF::Literal::Numeric) ? - li : - RDF::Literal::Integer.new(li.value) - end.reduce(&:+) || RDF::Literal(0) # Empty list sums to 0 + list.to_a.map(&:as_number).reduce(&:+) || RDF::Literal(0) # Empty list sums to 0 end end end diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index e86eb85..a2943c0 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require 'rdf' +require 'rdf/n3/terminals' # Monkey-patch RDF::Enumerable to add `:existentials` and `:univerals` accessors module RDF @@ -78,6 +79,47 @@ module Term def sameTerm?(other) eql?(other) end + + ## + # Parse the value as a numeric literal, or return 0. + # + # @return [RDF::Literal::Numeric] + def as_number + RDF::Literal(0) + end + end + + class Literal + include RDF::N3::Terminals + + ## + # Parse the value as a numeric literal, or return 0. + # + # @return [RDF::Literal::Numeric] + def as_number + return self if self.is_a?(RDF::Literal::Numeric) + case value + when DOUBLE then RDF::Literal::Double.new(value) + when DECIMAL then RDF::Literal::Decimal.new(value) + when INTEGER then RDF::Literal::Integer.new(value) + else + RDF::Literal(0) + end + end + + class Double + ## + # Returns the SXP representation of this object. + # + # @return [String] + def to_sxp + case + when nan? then 'nan.0' + when infinite? then (infinite? > 0 ? '+inf.0' : '-inf.0') + else canonicalize.to_s.downcase + end + end + end end class Node @@ -145,5 +187,18 @@ class Query::Variable def sameTerm?(other) other.is_a?(::RDF::Query::Variable) && name.eql?(other.name) end + + ## + # Parse the value as a numeric literal, or return 0. + # + # @return [RDF::Literal::Numeric] + def as_number + RDF::Literal(0) + end + + def to_sxp + prefix = distinguished? ? (existential? ? '$' : '?') : (existential? ? '$$' : '??') + unbound? ? "#{prefix}#{name}" : "#{prefix}#{name}=#{value.to_sxp}" + end end end From 4ca141aa6103b5a407b99daa121924557c326f48 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 27 Aug 2020 17:25:23 -0700 Subject: [PATCH 087/193] * Always canonicalize terms in string:concatenate. * Canonicalize Double to lower-case. * Canonicalize native numbers when parsing. --- lib/rdf/n3/algebra/str/concatenation.rb | 2 +- lib/rdf/n3/reader.rb | 8 +-- lib/rdf/n3/refinements.rb | 8 +++ spec/reasoner_spec.rb | 83 ++++++++++++++++++++++++- 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb index a5ff7f4..93a777c 100644 --- a/lib/rdf/n3/algebra/str/concatenation.rb +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -14,7 +14,7 @@ class Concatenation < RDF::N3::Algebra::ListOperator # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - RDF::Literal(list.to_a.map(&:value).join("")) + RDF::Literal(list.to_a.map{|li| li.is_a?(RDF::Literal::Double) ? li.canonicalize.to_s.downcase : li.canonicalize}.join("")) end end end diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 9e8d56d..2f00e79 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -441,15 +441,15 @@ def read_path def read_literal error("Unexpected end of file", production: :literal) unless token = @lexer.first case token.type || token.value - when :INTEGER then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.integer, canonicalize: canonicalize?)} + when :INTEGER then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.integer, canonicalize: true)} when :DECIMAL prod(:literal) do value = @lexer.shift.value value = "0#{value}" if value.start_with?(".") - literal(value, datatype: RDF::XSD.decimal, canonicalize: canonicalize?) + literal(value, datatype: RDF::XSD.decimal, canonicalize: true) end - when :DOUBLE then prod(:literal) {literal(@lexer.shift.value.sub(/\.([eE])/, '.0\1'), datatype: RDF::XSD.double, canonicalize: canonicalize?)} - when "true", "false" then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.boolean, canonicalize: canonicalize?)} + when :DOUBLE then prod(:literal) {literal(@lexer.shift.value.sub(/\.([eE])/, '.0\1'), datatype: RDF::XSD.double, canonicalize: true)} + when "true", "false" then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.boolean, canonicalize: true)} when :STRING_LITERAL_QUOTE, :STRING_LITERAL_SINGLE_QUOTE prod(:literal) do value = @lexer.shift.value[1..-2] diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb index aa3b8da..da253c4 100644 --- a/lib/rdf/n3/refinements.rb +++ b/lib/rdf/n3/refinements.rb @@ -71,6 +71,14 @@ def valid? end end + refine ::RDF::Literal::Double do + ## + # Canonicalizes to lower-case 'e' + def to_s + super.downcase + end + end + refine ::RDF::Graph do # Allow a graph to be treated as a term in a statement. include ::RDF::Term diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 239ca0f..125b323 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -452,7 +452,7 @@ end end - context "nesting" do + context "math-test" do { "A nested rule": { input: %( @@ -469,14 +469,91 @@ (("7" "2")!math:remainder "10000000")!math:exponentiation ("a" "b" "c" "d" "e")!list:length ) math:sum ?x } => - { ?x :valueOf "(7 / 2) + ((7 % 2)^10000000) + 5 [should be 9.5e0]" } . + { ?x :valueOf "(7 / 2) + ((7 % 2)^10000000) + 5 [should be 9.5]" } . ), expect: %( - 9.5e0 :valueOf "(7 / 2) + ((7 % 2)^10000000) + 5 [should be 9.5e0]" . + 9.5 :valueOf "(7 / 2) + ((7 % 2)^10000000) + 5 [should be 9.5]" . + ) + }, + "Combinatorial test - worksWith": { + input: %( + "3.1415926" a :testValue. + 3.1415926 a :testValue. + "1729" a :testValue. + 1729 a :testValue. + "0" a :testValue. + 0 a :testValue. + "1.0e7" a :testValue. + 1.0e7 a :testValue. + { ?x a :testValue. ?y a :testValue. + (?x [ is math:difference of (?y ?x)]) math:sum ?y } => {?x :worksWith ?y}. + ), + expect: %( + "1.0e7" :worksWith 1.0e7 . + "1729" :worksWith 3.1415926, 1.0e7, 0, 1729 . + 1729 :worksWith 3.1415926, 1.0e7, 0, 1729 . + "3.1415926" :worksWith 3.1415926, 1.0e7 . + 3.1415926 :worksWith 3.1415926, 1.0e7 . + 1.0e7 :worksWith 1.0e7 . + "0" :worksWith 3.1415926, 1.0e7, 0, 1729 . + 0 :worksWith 3.1415926, 1.0e7, 0, 1729 . + ), + pending: true + }, + "Combinatorial test - SumDifferenceFAILS": { + input: %( + "3.1415926" a :testValue. + 3.1415926 a :testValue. + "1729" a :testValue. + 1729 a :testValue. + "0" a :testValue. + 0 a :testValue. + "1.0e7" a :testValue. + 1.0e7 a :testValue. + { ?x a :testValue. ?y a :testValue. + ?z is math:sum of (?x (?y ?x)!math:difference). + ?z math:notEqualTo ?y } => {(?x ?y) :SumDifferenceFAILS ?z}. + ), + expect: %() + }, + "Combinatorial test - concatenation": { + input: %( + @prefix string: . + "3.1415926" a :testValue. + 3.1415926 a :testValue. + "1729" a :testValue. + 1729 a :testValue. + "0" a :testValue. + 0 a :testValue. + "1.0e7" a :testValue. + 1.0e7 a :testValue. + { ?x a :testValue. ?y a :testValue. + (?x ?y) math:sum ?z. + (?x " + " ?y " = " ?z ) string:concatenation ?s + } => { ?s a :RESULT }. + ), + expect: %( + "0 + 0 = 0" a :RESULT . + "0 + 1.0e7 = 1.0e7" a :RESULT . + "0 + 1729 = 1729" a :RESULT . + "0 + 3.1415926 = 3.1415926" a :RESULT . + "1.0e7 + 0 = 1.0e7" a :RESULT . + "1.0e7 + 1.0e7 = 2.0e7" a :RESULT . + "1.0e7 + 1729 = 1.0001729e7" a :RESULT . + "1.0e7 + 3.1415926 = 1.00000031415926e7" a :RESULT . + "1729 + 0 = 1729" a :RESULT . + "1729 + 1.0e7 = 1.0001729e7" a :RESULT . + "1729 + 1729 = 3458" a :RESULT . + "1729 + 3.1415926 = 1732.1415926" a :RESULT . + "3.1415926 + 0 = 3.1415926" a :RESULT . + "3.1415926 + 1.0e7 = 1.00000031415926e7" a :RESULT . + "3.1415926 + 1729 = 1732.1415926" a :RESULT . + "3.1415926 + 3.1415926 = 6.2831852" a :RESULT . ) }, }.each do |name, options| it name do + pending(options[:pending]) if options[:pending] logger.info "input: #{options[:input]}" expected = parse(options[:expect]) expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) From 4e3a796cd736e6f44241855090109c0c56f83664 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 29 Aug 2020 16:09:15 -0700 Subject: [PATCH 088/193] math: remainder only operates on integers. --- lib/rdf/n3/algebra/math/remainder.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rdf/n3/algebra/math/remainder.rb b/lib/rdf/n3/algebra/math/remainder.rb index a9aecb0..6536313 100644 --- a/lib/rdf/n3/algebra/math/remainder.rb +++ b/lib/rdf/n3/algebra/math/remainder.rb @@ -21,10 +21,10 @@ def evaluate(list) # @return [Boolean] # @see RDF::N3::ListOperator#validate def validate(list) - if super && list.all?(&:literal?) && list.length == 2 + if super && list.all? {|li| li.as_number.is_a?(RDF::Literal::Integer)} && list.length == 2 true else - log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} + log_error(NAME) {"list is not a pair of integers: #{list.to_sxp}"} false end end From 095ea6b154485d1c4471fc877a31d678a02597a7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 30 Aug 2020 15:00:45 -0700 Subject: [PATCH 089/193] Include constants in formula patterns, so that an antecedent constant statement must appear in the knowledge base for the antecedent to be true. --- lib/rdf/n3/algebra/formula.rb | 16 ++-------------- script/parse | 5 +++++ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 2c5386a..f0fc2c4 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -95,12 +95,6 @@ def each(&block) end log_debug("formula #{graph_name} each") {@solutions.to_sxp} - # Yield constant statements - constants.each do |statement| - #log_debug {"(formula constant) #{statement.to_sxp}"} - block.call(RDF::Statement.from(statement, graph_name: graph_name)) - end - # Yield patterns by binding variables @solutions.each do |solution| # Bind blank nodes to the solution when it doesn't contain a solution for an existential variable @@ -158,6 +152,7 @@ def graph_name; @options[:graph_name]; end def statements # BNodes in statements are existential variables. @statements ||= begin + # Operations/Builtins are not statements. statements = operands.select {|op| op.is_a?(RDF::Statement)} statements.map do |pattern| @@ -184,18 +179,11 @@ def statements 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?) + @patterns ||= statements end ## diff --git a/script/parse b/script/parse index 720100e..c8bcd40 100755 --- a/script/parse +++ b/script/parse @@ -19,6 +19,7 @@ def run(input, **options) num = 0 Profiler__::start_profile if options[:profile] if options[:think] || options[:rules] + STDERR.puts "Reason" if $verbose # Parse into a new reasoner and evaluate reader_class.new(input, **options[:parser_options].merge(logger: nil)) do |reader| reasoner = RDF::N3::Reasoner.new(reader, **options[:parser_options]) @@ -35,6 +36,7 @@ def run(input, **options) 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] + STDERR.puts "Parse nt/quiet" if $verbose reader_class.new(input, **options[:parser_options]).each do |statement| num += 1 if options[:errors] && statement.invalid? @@ -46,16 +48,19 @@ def run(input, **options) end end elsif options[:output_format] == :sxp + STDERR.puts "Parse to SXP" if $verbose reader_class.new(input, **options[:parser_options]) do |reader| reasoner = RDF::N3::Reasoner.new(reader) SXP::Generator.print(reasoner.to_sxp_bin) end elsif options[:output_format] == :inspect + STDERR.puts "Parse to inspect" if $verbose reader_class.new(input, **options[:parser_options]).each do |statement| num += 1 options[:output].puts statement.inspect end else + STDERR.puts "Parse to #{options[:output_format]}" if $verbose reader = reader_class.new(input, **options[:parser_options]) repo = [].extend(RDF::Enumerable, RDF::Queryable) reader.each_statement {|st| repo << st} From a84b32b30f5ac0ef69c94cdeb30b8621902fad74 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 30 Aug 2020 15:55:41 -0700 Subject: [PATCH 090/193] Add trigonometric math operators. --- README.md | 12 ++++++++++ lib/rdf/n3/algebra.rb | 24 +++++++++++++++++++ lib/rdf/n3/algebra/math/acos.rb | 41 ++++++++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/acosh.rb | 41 ++++++++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/asin.rb | 41 ++++++++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/asinh.rb | 41 ++++++++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/atan.rb | 41 ++++++++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/atanh.rb | 41 ++++++++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/cos.rb | 41 ++++++++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/cosh.rb | 41 ++++++++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/sin.rb | 41 ++++++++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/sinh.rb | 41 ++++++++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/tan.rb | 41 ++++++++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/tanh.rb | 41 ++++++++++++++++++++++++++++++++ script/tc | 19 +++++++++++++-- spec/suite_reasoner_spec.rb | 2 ++ 16 files changed, 547 insertions(+), 2 deletions(-) create mode 100644 lib/rdf/n3/algebra/math/acos.rb create mode 100644 lib/rdf/n3/algebra/math/acosh.rb create mode 100644 lib/rdf/n3/algebra/math/asin.rb create mode 100644 lib/rdf/n3/algebra/math/asinh.rb create mode 100644 lib/rdf/n3/algebra/math/atan.rb create mode 100644 lib/rdf/n3/algebra/math/atanh.rb create mode 100644 lib/rdf/n3/algebra/math/cos.rb create mode 100644 lib/rdf/n3/algebra/math/cosh.rb create mode 100644 lib/rdf/n3/algebra/math/sin.rb create mode 100644 lib/rdf/n3/algebra/math/sinh.rb create mode 100644 lib/rdf/n3/algebra/math/tan.rb create mode 100644 lib/rdf/n3/algebra/math/tanh.rb diff --git a/README.md b/README.md index b4ddf81..42930ed 100755 --- a/README.md +++ b/README.md @@ -83,7 +83,15 @@ Reasoning is discussed in the [Design Issues][] document. #### RDF Math vocabulary * `math:absoluteValue` (See {RDF::N3::Algebra::Math::AbsoluteValue}) + * `math:acos` (See {RDF::N3::Algebra::Math::ACos}) + * `math:asin` (See {RDF::N3::Algebra::Math::ASin}) + * `math:atan` (See {RDF::N3::Algebra::Math::ATan}) + * `math:acosh` (See {RDF::N3::Algebra::Math::ACosH}) + * `math:asinh` (See {RDF::N3::Algebra::Math::ASinH}) + * `math:atanh` (See {RDF::N3::Algebra::Math::ATanH}) * `math:ceiling` (See {RDF::N3::Algebra::Math::Ceiling}) + * `math:cosh` (See {RDF::N3::Algebra::Math::CosH}) + * `math:cos` (See {RDF::N3::Algebra::Math::Cos}) * `math:difference` (See {RDF::N3::Algebra::Math::Difference}) * `math:equalTo` (See {SPARQL::Algebra::Operator::Equal}) * `math:exponentiation` (See {RDF::N3::Algebra::Math::Exponentiation}) @@ -99,7 +107,11 @@ Reasoning is discussed in the [Design Issues][] document. * `math:quotient` (See {RDF::N3::Algebra::Math::Quotient}) * `math:remainder` (See {RDF::N3::Algebra::Math::Remainder}) * `math:rounded` (See {RDF::N3::Algebra::Math::Rounded}) + * `math:sinh` (See {RDF::N3::Algebra::Math::SinH}) + * `math:sin` (See {RDF::N3::Algebra::Math::Sin}) * `math:sum` (See {RDF::N3::Algebra::Math::Sum}) + * `math:tanh` (See {RDF::N3::Algebra::Math::TanH}) + * `math:tan` (See {RDF::N3::Algebra::Math::Tan}) #### RDF String vocabulary diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index c08b16b..0e087f9 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -33,7 +33,15 @@ module Log module Math autoload :AbsoluteValue, 'rdf/n3/algebra/math/absoluteValue' + autoload :ACos, 'rdf/n3/algebra/math/acos' + autoload :ASin, 'rdf/n3/algebra/math/asin' + autoload :ATan, 'rdf/n3/algebra/math/atan' + autoload :ACosH, 'rdf/n3/algebra/math/acosh' + autoload :ASinH, 'rdf/n3/algebra/math/asinh' + autoload :ATanH, 'rdf/n3/algebra/math/atanh' autoload :Ceiling, 'rdf/n3/algebra/math/ceiling' + autoload :Cos, 'rdf/n3/algebra/math/cos' + autoload :CosH, 'rdf/n3/algebra/math/cosh' autoload :Difference, 'rdf/n3/algebra/math/difference' autoload :EqualTo, 'rdf/n3/algebra/math/equalTo' autoload :Exponentiation, 'rdf/n3/algebra/math/exponentiation' @@ -45,7 +53,11 @@ module Math autoload :Quotient, 'rdf/n3/algebra/math/quotient' autoload :Remainder, 'rdf/n3/algebra/math/remainder' autoload :Rounded, 'rdf/n3/algebra/math/rounded' + autoload :Sin, 'rdf/n3/algebra/math/sin' + autoload :SinH, 'rdf/n3/algebra/math/sinh' autoload :Sum, 'rdf/n3/algebra/math/sum' + autoload :Tan, 'rdf/n3/algebra/math/tan' + autoload :TanH, 'rdf/n3/algebra/math/tanh' end module Str @@ -79,7 +91,15 @@ def for(uri) RDF::N3::Log.supports => NotImplemented, RDF::N3::Math.absoluteValue => Math::AbsoluteValue, + RDF::N3::Math.acos => Math::ACos, + RDF::N3::Math.asin => Math::ASin, + RDF::N3::Math.atan => Math::ATan, + RDF::N3::Math.acosh => Math::ACosH, + RDF::N3::Math.asinh => Math::ASinH, + RDF::N3::Math.atanh => Math::ATanH, RDF::N3::Math.ceiling => Math::Ceiling, + RDF::N3::Math.cos => Math::Cos, + RDF::N3::Math.cosh => Math::CosH, RDF::N3::Math.difference => Math::Difference, RDF::N3::Math.equalTo => Math::EqualTo, RDF::N3::Math.exponentiation => Math::Exponentiation, @@ -95,6 +115,10 @@ def for(uri) RDF::N3::Math.quotient => Math::Quotient, RDF::N3::Math.remainder => Math::Remainder, RDF::N3::Math.rounded => Math::Rounded, + RDF::N3::Math.sin => Math::Sin, + RDF::N3::Math.sinh => Math::SinH, + RDF::N3::Math.tan => Math::Tan, + RDF::N3::Math.tanh => Math::TanH, RDF::N3::Math[:sum] => Math::Sum, RDF::N3::Str.concatenation => Str::Concatenation, diff --git a/lib/rdf/n3/algebra/math/acos.rb b/lib/rdf/n3/algebra/math/acos.rb new file mode 100644 index 0000000..f40c7f6 --- /dev/null +++ b/lib/rdf/n3/algebra/math/acos.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the arc cosine value of the subject. + class ACos < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathACos + + ## + # The math:acos operator takes string or number and calculates its arc cosine. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.acos(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/acosh.rb b/lib/rdf/n3/algebra/math/acosh.rb new file mode 100644 index 0000000..dcb1de0 --- /dev/null +++ b/lib/rdf/n3/algebra/math/acosh.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the inverse hyperbolic cosine value of the subject. + class ACosH < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathACosH + + ## + # The math:acosh operator takes string or number and calculates its inverse hyperbolic cosine. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.acosh(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/asin.rb b/lib/rdf/n3/algebra/math/asin.rb new file mode 100644 index 0000000..fc70dfa --- /dev/null +++ b/lib/rdf/n3/algebra/math/asin.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the arc sine value of the subject. + class ASin < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathASin + + ## + # The math:asin operator takes string or number and calculates its arc sine. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.asin(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/asinh.rb b/lib/rdf/n3/algebra/math/asinh.rb new file mode 100644 index 0000000..873adc2 --- /dev/null +++ b/lib/rdf/n3/algebra/math/asinh.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the inverse hyperbolic sine value of the subject. + class ASinH < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathASinH + + ## + # The math:asinh operator takes string or number and calculates its inverse hyperbolic sine. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.asinh(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/atan.rb b/lib/rdf/n3/algebra/math/atan.rb new file mode 100644 index 0000000..08d5934 --- /dev/null +++ b/lib/rdf/n3/algebra/math/atan.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the arc tangent value of the subject. + class ATan < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathATan + + ## + # The math:atan operator takes string or number and calculates its arc tangent. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.atan(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/atanh.rb b/lib/rdf/n3/algebra/math/atanh.rb new file mode 100644 index 0000000..d7c185f --- /dev/null +++ b/lib/rdf/n3/algebra/math/atanh.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the inverse hyperbolic tangent value of the subject. + class ATanH < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathATanH + + ## + # The math:atanh operator takes string or number and calculates its inverse hyperbolic tangent. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.atanh(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/cos.rb b/lib/rdf/n3/algebra/math/cos.rb new file mode 100644 index 0000000..7750291 --- /dev/null +++ b/lib/rdf/n3/algebra/math/cos.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The subject is an angle expressed in radians. The object is calulated as the cosine value of the subject. + class Cos < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathCos + + ## + # The math:cos operator takes string or number and calculates its cosine. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.cos(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/cosh.rb b/lib/rdf/n3/algebra/math/cosh.rb new file mode 100644 index 0000000..45a3da1 --- /dev/null +++ b/lib/rdf/n3/algebra/math/cosh.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The subject is an angle expressed in radians. The object is calulated as the hyperbolic cosine value of the subject. + class CosH < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathCosH + + ## + # The math:cosh operator takes string or number and calculates its hyperbolic cosine. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.cosh(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/sin.rb b/lib/rdf/n3/algebra/math/sin.rb new file mode 100644 index 0000000..cb58370 --- /dev/null +++ b/lib/rdf/n3/algebra/math/sin.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The subject is an angle expressed in radians. The object is calulated as the sine value of the subject. + class Sin < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathSin + + ## + # The math:sin operator takes string or number and calculates its sine. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.sin(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/sinh.rb b/lib/rdf/n3/algebra/math/sinh.rb new file mode 100644 index 0000000..3be3248 --- /dev/null +++ b/lib/rdf/n3/algebra/math/sinh.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The subject is an angle expressed in radians. The object is calulated as the hyperbolic sine value of the subject. + class SinH < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathSinH + + ## + # The math:sinh operator takes string or number and calculates its hyperbolic sine. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.sinh(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/tan.rb b/lib/rdf/n3/algebra/math/tan.rb new file mode 100644 index 0000000..02dfa57 --- /dev/null +++ b/lib/rdf/n3/algebra/math/tan.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The subject is an angle expressed in radians. The object is calulated as the tangent value of the subject. + class Tan < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathTan + + ## + # The math:tan operator takes string or number and calculates its tangent. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.tan(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/lib/rdf/n3/algebra/math/tanh.rb b/lib/rdf/n3/algebra/math/tanh.rb new file mode 100644 index 0000000..added14 --- /dev/null +++ b/lib/rdf/n3/algebra/math/tanh.rb @@ -0,0 +1,41 @@ +module RDF::N3::Algebra::Math + ## + # The subject is an angle expressed in radians. The object is calulated as the tangent value of the subject. + class TanH < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :mathTanH + + ## + # The math:tanh operator takes string or number and calculates its hyperbolic tangent. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + num = operand(0) + result = operand(1) + + @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} + unless num.literal? + log_error(NAME) {"num is not a literal: #{num.inspect}"} + next + end + + num = RDF::Literal(Math.tanh(num.as_number.object)) + + if result.variable? + solution.merge(result.to_sym => num) + elsif result != num + nil + else + solution + end + end.compact) + end + end +end diff --git a/script/tc b/script/tc index 0836d23..8f70c78 100755 --- a/script/tc +++ b/script/tc @@ -37,6 +37,13 @@ def earl_preamble(**options) end def run_tc(man, tc, **options) + case options[:type] + when :parser + return if tc.reason? + when :reasoner + return unless tc.reason? + end + STDERR.write "test #{tc.name} " if options[:verbose] @@ -172,7 +179,8 @@ options = { list_terms: true, output: STDOUT, results: {}, - slow: true # Run slow tests by default + slow: true, # Run slow tests by default + type: :all } opts = GetoptLong.new( @@ -183,6 +191,7 @@ opts = GetoptLong.new( ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT], ["--quiet", "-q", GetoptLong::NO_ARGUMENT], ["--skip-slow", "-s", GetoptLong::NO_ARGUMENT], + ["--type", GetoptLong::REQUIRED_ARGUMENT], ["--validate", GetoptLong::NO_ARGUMENT], ["--verbose", "-v", GetoptLong::NO_ARGUMENT], ) @@ -197,13 +206,13 @@ def help(**options) puts " --output: Output to specified file" puts " --quiet: Minimal output" puts " --skip-slow: Avoid files taking too much time" + puts " --type: Test type (`parser`, `reasoner`, or `all`)" puts " --validate: Validate input" puts " --verbose: Verbose processing" puts " --help,-?: This message" exit(0) end - opts.each do |opt, arg| case opt when '--help' then help(**options) @@ -217,6 +226,12 @@ opts.each do |opt, arg| options[:quiet] = true options[:level] = Logger::FATAL when '--skip-slow' then options[:slow] = false + when '--type' + unless %(parser reasoner all).include?(arg) + STDERR.puts "unknown test type: #{options[:type]}" + help(**options) + end + options[:type] = arg.to_sym when '--validate' then options[:validate] = true when '--verbose' then options[:verbose] = true end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 7181cd0..9dc7b70 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -19,6 +19,8 @@ next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do case t.id.split('#').last + when *%w{cwm_math_test} + pending "math numeric representation" when *%w{cwm_includes_conjunction} pending "log:conjunction" when *%w{cwm_includes_bnode} From 871018925ba2c26b5ad1f47025778f4e57de54eb Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 31 Aug 2020 17:01:18 -0700 Subject: [PATCH 091/193] Evaluate all operands, reverting to the original, if it evaluate to nil. --- lib/rdf/n3/algebra/formula.rb | 10 +++++----- lib/rdf/n3/algebra/list/in.rb | 2 +- lib/rdf/n3/algebra/list/member.rb | 10 +++++----- lib/rdf/n3/algebra/list_operator.rb | 14 +++++++------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index f0fc2c4..3fe41fc 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -27,8 +27,8 @@ class Formula < SPARQL::Algebra::Operator # optional initial solutions for chained queries # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options) - log_debug("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin} - log_debug("(formula bindings)") { solutions.bindings.map {|k,v| RDF::Query::Variable.new(k,v)}.to_sxp} + log_info("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin} + log_info("(formula bindings)") { solutions.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! @@ -46,7 +46,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new memo.merge(name => value) end) end - log_debug("(formula query solutions)") { these_solutions.to_sxp} + log_info("(formula query solutions)") { these_solutions.to_sxp} solutions.merge(these_solutions) end @@ -66,7 +66,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new end end end - log_debug("(formula sub-op solutions)") {@solutions.to_sxp} + log_info("(formula sub-op solutions)") {@solutions.to_sxp} # Only return solutions with universal variables variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?('$')} @@ -153,7 +153,7 @@ def statements # BNodes in statements are existential variables. @statements ||= begin # Operations/Builtins are not statements. - statements = operands.select {|op| op.is_a?(RDF::Statement)} + statements = operands.select {|op| op.is_a?(RDF::Statement) && !RDF::N3::Algebra.for(op.predicate)} statements.map do |pattern| if graph_name diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index 8434128..d4fca75 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -21,7 +21,7 @@ class In < SPARQL::Algebra::Operator::Binary # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| - subject = operand(0) + subject = operand(0).evaluate(solution.bindings) || operand(0) # Might be a variable or node evaluating to a list in queryable, or might be a list with variables list = operand(1).evaluate(solution.bindings) # If it evaluated to a BNode, re-expand as a list diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index b52b352..d02e3bc 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -20,9 +20,9 @@ def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| list = operand(0).evaluate(solution.bindings) list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - result = operand(1) + object = operand(1).evaluate(solution.bindings) || operand(1) - log_debug(NAME) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + log_debug(NAME) {"list: #{list.to_sxp}, object: #{object.to_sxp}"} unless list.list? && list.valid? log_error(NAME) {"operand is not a list: #{list.to_sxp}"} next @@ -32,12 +32,12 @@ def execute(queryable, solutions:, **options) # Can't bind list elements solution else - if result.variable? + if object.variable? # Bind all list entries to this solution, creates an array of solutions list.to_a.map do |term| - solution.merge(result.to_sym => term) + solution.merge(object.to_sym => term) end - elsif list.to_a.include?(result) + elsif list.to_a.include?(object) solution else nil diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index fd3e4d5..ed10df2 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -14,26 +14,26 @@ class ListOperator < SPARQL::Algebra::Operator::Binary # @param [RDF::Query::Solutions] solutions # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) - result = operand(1) - @solutions = RDF::Query::Solutions(solutions.map do |solution| # Might be a variable or node evaluating to a list in queryable, or might be a list with variables list = operand(0).evaluate(solution.bindings) # If it evaluated to a BNode, re-expand as a list list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + object = operand(1).evaluate(solution.bindings) || operand(1) + - log_debug(self.class.const_get(:NAME)) {"list: #{list.to_sxp}, result: #{result.to_sxp}"} + log_debug(self.class.const_get(:NAME)) {"list: #{list.to_sxp}, object: #{object.to_sxp}"} next unless validate(list) if list.to_a.any? {|op| op.variable? && op.unbound?} # Can't bind list elements solution else - rhs = evaluate(list) + lhs = evaluate(list) - if result.variable? - solution.merge(result.to_sym => rhs) - elsif result != rhs + if object.variable? + solution.merge(object.to_sym => lhs) + elsif object != lhs nil else solution From 5eef9ab6a24ab22f5ff3de1b0828ee0c246bea6e Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 31 Aug 2020 17:02:10 -0700 Subject: [PATCH 092/193] Adds LiteralOperator as generic class which is useful when operators take literal resources, and either subject or object can be evaluated. --- lib/rdf/n3/algebra.rb | 27 ++++++++++ lib/rdf/n3/algebra/literal_operator.rb | 69 ++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 lib/rdf/n3/algebra/literal_operator.rb diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 0e087f9..7416b40 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -9,6 +9,7 @@ module RDF::N3 module Algebra autoload :Formula, 'rdf/n3/algebra/formula' autoload :ListOperator, 'rdf/n3/algebra/list_operator' + autoload :LiteralOperator, 'rdf/n3/algebra/literal_operator' autoload :NotImplemented, 'rdf/n3/algebra/notImplemented' module List @@ -71,6 +72,20 @@ module Str autoload :Scrape, 'rdf/n3/algebra/str/scrape' end + module Time + autoload :DayOfWeek, 'rdf/n3/algebra/time/day_of_week' + autoload :Day, 'rdf/n3/algebra/time/day' + autoload :GmTime, 'rdf/n3/algebra/time/gm_time' + autoload :Hour, 'rdf/n3/algebra/time/hour' + autoload :InSeconds, 'rdf/n3/algebra/time/in_seconds' + autoload :LocalTime, 'rdf/n3/algebra/time/local_time' + autoload :Minute, 'rdf/n3/algebra/time/minute' + autoload :Month, 'rdf/n3/algebra/time/month' + autoload :Second, 'rdf/n3/algebra/time/second' + autoload :Timezone, 'rdf/n3/algebra/time/timezone' + autoload :Year, 'rdf/n3/algebra/time/year' + end + def for(uri) { RDF::N3::List.append => List::Append, @@ -138,6 +153,18 @@ def for(uri) RDF::N3::Str.replace => Str::Replace, RDF::N3::Str.scrape => Str::Scrape, RDF::N3::Str.startsWith => SPARQL::Algebra::Operator::StrStarts, + + RDF::N3::Time.dayOfWeek => Time::DayOfWeek, + RDF::N3::Time.day => Time::Day, + RDF::N3::Time.gmTime => Time::GmTime, + RDF::N3::Time.hour => Time::Hour, + RDF::N3::Time.inSeconds => Time::InSeconds, + RDF::N3::Time.localTime => Time::LocalTime, + RDF::N3::Time.minute => Time::Minute, + RDF::N3::Time.month => Time::Month, + RDF::N3::Time.second => Time::Second, + RDF::N3::Time.timeZone => Time::Timezone, + RDF::N3::Time.year => Time::Year, }[uri] end module_function :for diff --git a/lib/rdf/n3/algebra/literal_operator.rb b/lib/rdf/n3/algebra/literal_operator.rb new file mode 100644 index 0000000..a741597 --- /dev/null +++ b/lib/rdf/n3/algebra/literal_operator.rb @@ -0,0 +1,69 @@ +module RDF::N3::Algebra + ## + # This is a generic operator where the subject is a literal or binds to a literal and the object is either a constant that equals the evaluation of the subject, or a variable to which the result is bound in a solution + class LiteralOperator < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + ## + # The operator takes a literal and provides a mechanism for subclasses to operate over (and validate) that argument. + # + # @param [RDF::Queryable] queryable + # @param [RDF::Query::Solutions] solutions + # @return [RDF::Query::Solutions] + def execute(queryable, solutions:, **options) + @solutions = RDF::Query::Solutions(solutions.map do |solution| + subject = operand(0).evaluate(solution.bindings) || operand(0) + object = operand(1).evaluate(solution.bindings) || operand(1) + + log_debug(self.class.const_get(:NAME)) {"subject: #{subject.to_sxp}, object: #{object.to_sxp}"} + + lhs = evaluate(subject, position: :subject) + if lhs.nil? + log_error(self.class.const_get(:NAME)) {"subject is invalid: #{subject.inspect}"} + next + end + + rhs = evaluate(object, position: :object) + if lhs.nil? + log_error(self.class.const_get(:NAME)) {"object is invalid: #{object.inspect}"} + next + end + next unless valid?(subject, object) + + if object.variable? + solution.merge(object.to_sym => lhs) + elsif subject.variable? + solution.merge(subject.to_sym => rhs) + elsif rhs != lhs + nil + else + solution + end + end.compact) + end + + ## + # Subclasses implement `evaluate`. + # + # Returns nil if resource does not validate, given its position + # + # @param [RDF::N3::List] resource + # @return [RDF::Term] + def evaluate(resource, position: :subject) + raise NotImplemented + end + + ## + # Subclasses may override or supplement validate to perform validation on the list subject + # + # @param [RDF::Term] subject + # @param [RDF::Term] object + # @return [Boolean] + def valid?(subject, object) + true + end + end +end From cef18a552e0ae0d66325c8ceaf4c25d3db752650 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 31 Aug 2020 17:03:38 -0700 Subject: [PATCH 093/193] Add time built-in operators, evaluating to numbers, rather than strings. --- README.md | 14 ++++++++ lib/rdf/n3/algebra/time/day.rb | 31 +++++++++++++++++ lib/rdf/n3/algebra/time/day_of_week.rb | 25 ++++++++++++++ lib/rdf/n3/algebra/time/gm_time.rb | 25 ++++++++++++++ lib/rdf/n3/algebra/time/hour.rb | 31 +++++++++++++++++ lib/rdf/n3/algebra/time/in_seconds.rb | 47 ++++++++++++++++++++++++++ lib/rdf/n3/algebra/time/local_time.rb | 25 ++++++++++++++ lib/rdf/n3/algebra/time/minute.rb | 31 +++++++++++++++++ lib/rdf/n3/algebra/time/month.rb | 31 +++++++++++++++++ lib/rdf/n3/algebra/time/second.rb | 31 +++++++++++++++++ lib/rdf/n3/algebra/time/timezone.rb | 32 ++++++++++++++++++ lib/rdf/n3/algebra/time/year.rb | 25 ++++++++++++++ lib/rdf/n3/extensions.rb | 19 +++++++++++ spec/reasoner_spec.rb | 19 +++++++++++ 14 files changed, 386 insertions(+) create mode 100644 lib/rdf/n3/algebra/time/day.rb create mode 100644 lib/rdf/n3/algebra/time/day_of_week.rb create mode 100644 lib/rdf/n3/algebra/time/gm_time.rb create mode 100644 lib/rdf/n3/algebra/time/hour.rb create mode 100644 lib/rdf/n3/algebra/time/in_seconds.rb create mode 100644 lib/rdf/n3/algebra/time/local_time.rb create mode 100644 lib/rdf/n3/algebra/time/minute.rb create mode 100644 lib/rdf/n3/algebra/time/month.rb create mode 100644 lib/rdf/n3/algebra/time/second.rb create mode 100644 lib/rdf/n3/algebra/time/timezone.rb create mode 100644 lib/rdf/n3/algebra/time/year.rb diff --git a/README.md b/README.md index 42930ed..cb029b4 100755 --- a/README.md +++ b/README.md @@ -132,6 +132,20 @@ Reasoning is discussed in the [Design Issues][] document. * `string:scrape` (See {RDF::N3::Algebra::Str::Scrape}) * `string:startsWith` (See {SPARQL::Algebra::Operator::StrStarts}) +#### RDF Time vocabulary <> + + * `time:dayOfWeek` (See {RDF::N3::Algebra::Time::DayOfWeek}) + * `time:day` (See {RDF::N3::Algebra::Time::Day}) + * `time:gmTime` (See {RDF::N3::Algebra::Time::GmTime}) + * `time:hour` (See {RDF::N3::Algebra::Time::Hour}) + * `time:inSeconds` (See {RDF::N3::Algebra::Time::InSeconds}) + * `time:localTime` (See {RDF::N3::Algebra::Time::LocalTime}) + * `time:minute` (See {RDF::N3::Algebra::Time::Minute}) + * `time:month` (See {RDF::N3::Algebra::Time::Month}) + * `time:second` (See {RDF::N3::Algebra::Time::Second}) + * `time:timeZone` (See {RDF::N3::Algebra::Time::Timezone}) + * `time:year` (See {RDF::N3::Algebra::Time::Year}) + ### 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: diff --git a/lib/rdf/n3/algebra/time/day.rb b/lib/rdf/n3/algebra/time/day.rb new file mode 100644 index 0000000..435d111 --- /dev/null +++ b/lib/rdf/n3/algebra/time/day.rb @@ -0,0 +1,31 @@ +module RDF::N3::Algebra::Time + ## + # For a date-time, its time:day is the day of the month. + class Day < RDF::N3::Algebra::LiteralOperator + NAME = :timeDay + + ## + # The time:day operator takes string or dateTime and extracts the day component. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + resource = resource.as_datetime + RDF::Literal(resource.object.strftime("%d").to_i) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + + ## + # There is no day unless it was specified in the lexical form + def valid?(subject, object) + subject.value.match?(%r(^\d{4}-\d{2}-\d{2})) + end + end +end diff --git a/lib/rdf/n3/algebra/time/day_of_week.rb b/lib/rdf/n3/algebra/time/day_of_week.rb new file mode 100644 index 0000000..17fbf05 --- /dev/null +++ b/lib/rdf/n3/algebra/time/day_of_week.rb @@ -0,0 +1,25 @@ +module RDF::N3::Algebra::Time + ## + # For a date-time, its time:dayOfWeek is the the day number within the week, Sunday being 0. + class DayOfWeek < RDF::N3::Algebra::LiteralOperator + NAME = :timeDayOfWeek + + ## + # The time:dayOfWeek operator takes string or dateTime and returns the 0-based day of the week. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + resource = resource.as_datetime + RDF::Literal(resource.object.strftime("%w").to_i) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + end +end diff --git a/lib/rdf/n3/algebra/time/gm_time.rb b/lib/rdf/n3/algebra/time/gm_time.rb new file mode 100644 index 0000000..a6a4953 --- /dev/null +++ b/lib/rdf/n3/algebra/time/gm_time.rb @@ -0,0 +1,25 @@ +module RDF::N3::Algebra::Time + ## + # For a date-time format string, its time:gmtime is the result of formatting the Universal Time of processing in the format given. If the format string has zero length, then the ISOdate standard format is used. `[ is time:gmtime of ""]` the therefore the current date time. It will end with "Z" as a timezone code. + class GmTime < RDF::N3::Algebra::LiteralOperator + NAME = :timeGmTime + + ## + # The time:gmTime operator takes string or dateTime and returns current time formatted according to the subject. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + resource = "%FT%T%:z" if resource.to_s.empty? + RDF::Literal(DateTime.now.new_offset(0).strftime(resource.to_s)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + end +end diff --git a/lib/rdf/n3/algebra/time/hour.rb b/lib/rdf/n3/algebra/time/hour.rb new file mode 100644 index 0000000..d2a5291 --- /dev/null +++ b/lib/rdf/n3/algebra/time/hour.rb @@ -0,0 +1,31 @@ +module RDF::N3::Algebra::Time + ## + # For a date-time, its time:hour is the hour in the 24 hour clock. + class Hour < RDF::N3::Algebra::LiteralOperator + NAME = :timeHour + + ## + # The time:hour operator takes string or dateTime and extracts the hour component. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + resource = resource.as_datetime + RDF::Literal(resource.object.strftime("%H").to_i) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + + ## + # There is no hour unless it was specified in the lexical form + def valid?(subject, object) + subject.value.match?(%r(^\d{4}-\d{2}-\d{2}T\d{2})) + end + end +end diff --git a/lib/rdf/n3/algebra/time/in_seconds.rb b/lib/rdf/n3/algebra/time/in_seconds.rb new file mode 100644 index 0000000..6bb759d --- /dev/null +++ b/lib/rdf/n3/algebra/time/in_seconds.rb @@ -0,0 +1,47 @@ +module RDF::N3::Algebra::Time + ## + # For a date-time, its time:inSeconds is the (string represntation of) the floating point number of seconds since the beginning of the era on the given system. + class InSeconds < RDF::N3::Algebra::LiteralOperator + NAME = :timeInSeconds + + ## + # The time:inseconds operator takes may have either a bound subject or object. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + case resource + when RDF::Query::Variable + resource + when RDF::Literal + resource = resource.as_datetime + # Subject evaluates to seconds from the epoc + RDF::Literal::Double.new(resource.object.strftime("%s")) + else + nil + end + when :object + case resource + when RDF::Query::Variable + resource + when RDF::Literal + resource = resource.as_number + # Object evaluates to the DateTime representation of the seconds form the epoc + RDF::Literal(RDF::Literal::DateTime.new(::Time.at(resource).utc.to_datetime).to_s) + else + nil + end + end + end + + # Either subject or object must be a bound resource + def valid?(subject, object) + return true if subject.literal? || object.literal? + log_error(NAME) {"subject or object are not literals: #{subject.inspect}, #{object.inspect}"} + false + end + end +end diff --git a/lib/rdf/n3/algebra/time/local_time.rb b/lib/rdf/n3/algebra/time/local_time.rb new file mode 100644 index 0000000..80500f2 --- /dev/null +++ b/lib/rdf/n3/algebra/time/local_time.rb @@ -0,0 +1,25 @@ +module RDF::N3::Algebra::Time + ## + # For a date-time format string, its time:localTime is the result of formatting the current time of processing and local timezone in the format given. If the format string has zero length, then the ISOdate standrad format is used. [ is time:localTime of ""] the therefore the current date time. It will end with a numeric timezone code or "Z" for UTC (GMT). + class LocalTime < RDF::N3::Algebra::LiteralOperator + NAME = :timeLocalTime + + ## + # The time:localTime operator takes string or dateTime and returns current time formatted according to the subject. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + resource = "%FT%T%:z" if resource.to_s.empty? + RDF::Literal(DateTime.now.strftime(resource.to_s)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + end +end diff --git a/lib/rdf/n3/algebra/time/minute.rb b/lib/rdf/n3/algebra/time/minute.rb new file mode 100644 index 0000000..d478c22 --- /dev/null +++ b/lib/rdf/n3/algebra/time/minute.rb @@ -0,0 +1,31 @@ +module RDF::N3::Algebra::Time + ## + # For a date-time, its time:minute is the minutes component. + class Minute < RDF::N3::Algebra::LiteralOperator + NAME = :timeMinute + + ## + # The time:minute operator takes string or dateTime and extracts the minute component. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + resource = resource.as_datetime + RDF::Literal(resource.object.strftime("%M").to_i) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + + ## + # There is no minute unless it was specified in the lexical form + def valid?(subject, object) + subject.value.match?(%r(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2})) + end + end +end diff --git a/lib/rdf/n3/algebra/time/month.rb b/lib/rdf/n3/algebra/time/month.rb new file mode 100644 index 0000000..ed25050 --- /dev/null +++ b/lib/rdf/n3/algebra/time/month.rb @@ -0,0 +1,31 @@ +module RDF::N3::Algebra::Time + ## + # For a date-time, its time:month is the two-digit month. + class Month < RDF::N3::Algebra::LiteralOperator + NAME = :timeMonth + + ## + # The time:month operator takes string or dateTime and extracts the month component. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + resource = resource.as_datetime + RDF::Literal(resource.object.strftime("%m").to_i) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + + ## + # There is no month unless it was specified in the lexical form + def valid?(subject, object) + subject.value.match?(%r(^\d{4}-\d{2})) + end + end +end diff --git a/lib/rdf/n3/algebra/time/second.rb b/lib/rdf/n3/algebra/time/second.rb new file mode 100644 index 0000000..3484a8c --- /dev/null +++ b/lib/rdf/n3/algebra/time/second.rb @@ -0,0 +1,31 @@ +module RDF::N3::Algebra::Time + ## + # For a date-time, its time:second is the seconds component. + class Second < RDF::N3::Algebra::LiteralOperator + NAME = :timeSecond + + ## + # The time:second operator takes string or dateTime and extracts the seconds component. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + resource = resource.as_datetime + RDF::Literal(resource.object.strftime("%S").to_i) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + + ## + # There is no second unless it was specified in the lexical form + def valid?(subject, object) + subject.value.match?(%r(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})) + end + end +end diff --git a/lib/rdf/n3/algebra/time/timezone.rb b/lib/rdf/n3/algebra/time/timezone.rb new file mode 100644 index 0000000..7ef7cd7 --- /dev/null +++ b/lib/rdf/n3/algebra/time/timezone.rb @@ -0,0 +1,32 @@ +module RDF::N3::Algebra::Time + ## + # For a date-time, its time:timeZone is the trailing timezone offset part, e.g. "-05:00". + class Timezone < RDF::N3::Algebra::LiteralOperator + NAME = :timeTimezone + + ## + # The time:timeZone operator takes string or dateTime and extracts the timeZone component. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + resource = resource.as_datetime + RDF::Literal(resource.object.strftime("%Z")) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + + ## + # There is no timezone unless it was specified in the lexical form and is not "Z" + def valid?(subject, object) + md = subject.value.match(%r(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[\+-][\d-]+))) + md && md[1].to_s != 'Z' + end + end +end diff --git a/lib/rdf/n3/algebra/time/year.rb b/lib/rdf/n3/algebra/time/year.rb new file mode 100644 index 0000000..432756d --- /dev/null +++ b/lib/rdf/n3/algebra/time/year.rb @@ -0,0 +1,25 @@ +module RDF::N3::Algebra::Time + ## + # For a date-time, its time:year is the year component. + class Year < RDF::N3::Algebra::LiteralOperator + NAME = :timeYear + + ## + # The time:year operator takes string or dateTime and extracts the year component. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + resource = resource.as_datetime + RDF::Literal(resource.object.strftime("%Y").to_i) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + end +end diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index a2943c0..528c3d3 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -87,6 +87,14 @@ def sameTerm?(other) def as_number RDF::Literal(0) end + + ## + # Parse the value as a dateTime literal, or return now. + # + # @return [RDF::Literal::DateTime] + def as_datetime + RDF::Literal::DateTime.new(DateTime.now) + end end class Literal @@ -107,6 +115,17 @@ def as_number end end + ## + # Parse the value as a dateTime literal, or return now. + # + # @return [RDF::Literal::DateTime] + def as_datetime + mvalue = value.match?(%r(^\d{4}$)) ? "#{value}-" : value + RDF::Literal::DateTime.new(::DateTime.iso8601(mvalue), lexical: value) + rescue + RDF::Literal(0) + end + class Double ## # Returns the SXP representation of this object. diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 125b323..abfedcd 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -598,6 +598,25 @@ end end + context "n3:time" do + context "time:day" do + { + "2002-06-22T22:09:32-05:00" => { + input: %( + { "2002-06-22T22:09:32-05:00" time:day ?x } => { :test1 :is "22" }. + ), + expect: %(:test1 :is "22".) + }, + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end + end + # Parse N3 input into a repository def parse(input, **options) repo = options[:repo] || RDF::N3:: Repository.new From 97a273e191d1266b70fa531753382edd56d4c463 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 1 Sep 2020 11:22:41 -0700 Subject: [PATCH 094/193] Register vocabularies so that they can be used for creating prefixes. --- lib/rdf/n3/vocab.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/rdf/n3/vocab.rb b/lib/rdf/n3/vocab.rb index 64285c6..2917421 100644 --- a/lib/rdf/n3/vocab.rb +++ b/lib/rdf/n3/vocab.rb @@ -3,29 +3,35 @@ module RDF::N3 # # Crypto namespace # class Crypto < RDF::Vocabulary; end const_set("Crypto", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/crypto#"))) + RDF::Vocabulary.register(:crypto, Crypto) # @!parse # # Log namespace # class Log < RDF::Vocabulary; end const_set("Log", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/log#"))) + RDF::Vocabulary.register(:log, Log) # @!parse # # Math namespace # class Math < RDF::Vocabulary; end const_set("Math", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/math#"))) + RDF::Vocabulary.register(:math, Math) # @!parse # # Rei namespace # class Rei < RDF::Vocabulary; end const_set("Rei", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/reify#"))) + RDF::Vocabulary.register(:rei, Rei) # @!parse # # Str namespace # class Str < RDF::Vocabulary; end const_set("Str", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/string#"))) + RDF::Vocabulary.register(:string, Str) # @!parse # # Time namespace # class Time < RDF::Vocabulary; end const_set("Time", Class.new(RDF::Vocabulary("http://www.w3.org/2000/10/swap/time#"))) + RDF::Vocabulary.register(:time, Time) end From d9cdbe7e334120bb011fd9f215c9a941497b3507 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 1 Sep 2020 11:23:08 -0700 Subject: [PATCH 095/193] Simplify math and other builtins by using LiteralOperator. --- lib/rdf/n3/algebra/list/in.rb | 7 +--- lib/rdf/n3/algebra/list/member.rb | 7 +--- lib/rdf/n3/algebra/math/absoluteValue.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/acos.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/acosh.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/asin.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/asinh.rb | 38 +++++++-------------- lib/rdf/n3/algebra/math/atan.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/atanh.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/ceiling.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/cos.rb | 38 +++++++-------------- lib/rdf/n3/algebra/math/cosh.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/floor.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/negation.rb | 37 +++++++------------- lib/rdf/n3/algebra/math/rounded.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/sin.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/sinh.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/tan.rb | 43 +++++++----------------- lib/rdf/n3/algebra/math/tanh.rb | 43 +++++++----------------- script/tc | 6 ++-- spec/suite_reasoner_spec.rb | 4 ++- 21 files changed, 228 insertions(+), 511 deletions(-) diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index d4fca75..2be798f 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -4,12 +4,7 @@ module RDF::N3::Algebra::List # # @example # { 1 list:in ( 1 2 3 4 5 ) } => { :test4a a :SUCCESS }. - class In < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class In < RDF::N3::Algebra::ListOperator NAME = :listIn ## diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index d02e3bc..c96519b 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -1,12 +1,7 @@ module RDF::N3::Algebra::List ## # Iff the subject is a list and the object is in that list, then this is true. - class Member < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Member < RDF::N3::Algebra::ListOperator NAME = :listMember ## diff --git a/lib/rdf/n3/algebra/math/absoluteValue.rb b/lib/rdf/n3/algebra/math/absoluteValue.rb index a2b0248..5f07262 100644 --- a/lib/rdf/n3/algebra/math/absoluteValue.rb +++ b/lib/rdf/n3/algebra/math/absoluteValue.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the absolute value of the subject. - class AbsoluteValue < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class AbsoluteValue < RDF::N3::Algebra::LiteralOperator NAME = :mathAbsoluteValue ## # The math:sum operator takes string or number and calculates its absolute value. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = num.as_number.abs - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(resource.as_number.abs) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/acos.rb b/lib/rdf/n3/algebra/math/acos.rb index f40c7f6..f6a70c5 100644 --- a/lib/rdf/n3/algebra/math/acos.rb +++ b/lib/rdf/n3/algebra/math/acos.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the arc cosine value of the subject. - class ACos < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class ACos < RDF::N3::Algebra::LiteralOperator NAME = :mathACos ## # The math:acos operator takes string or number and calculates its arc cosine. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.acos(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.acos(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/acosh.rb b/lib/rdf/n3/algebra/math/acosh.rb index dcb1de0..b387b40 100644 --- a/lib/rdf/n3/algebra/math/acosh.rb +++ b/lib/rdf/n3/algebra/math/acosh.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the inverse hyperbolic cosine value of the subject. - class ACosH < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class ACosH < RDF::N3::Algebra::LiteralOperator NAME = :mathACosH ## # The math:acosh operator takes string or number and calculates its inverse hyperbolic cosine. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.acosh(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.acosh(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/asin.rb b/lib/rdf/n3/algebra/math/asin.rb index fc70dfa..e8a3852 100644 --- a/lib/rdf/n3/algebra/math/asin.rb +++ b/lib/rdf/n3/algebra/math/asin.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the arc sine value of the subject. - class ASin < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class ASin < RDF::N3::Algebra::LiteralOperator NAME = :mathASin ## # The math:asin operator takes string or number and calculates its arc sine. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.asin(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.asin(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/asinh.rb b/lib/rdf/n3/algebra/math/asinh.rb index 873adc2..c1c6f87 100644 --- a/lib/rdf/n3/algebra/math/asinh.rb +++ b/lib/rdf/n3/algebra/math/asinh.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the inverse hyperbolic sine value of the subject. - class ASinH < SPARQL::Algebra::Operator::Binary + class ASinH < RDF::N3::Algebra::LiteralOperator include SPARQL::Algebra::Query include SPARQL::Algebra::Update include RDF::Enumerable @@ -12,30 +12,18 @@ class ASinH < SPARQL::Algebra::Operator::Binary ## # The math:asinh operator takes string or number and calculates its inverse hyperbolic sine. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.asinh(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.asinh(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/atan.rb b/lib/rdf/n3/algebra/math/atan.rb index 08d5934..b489aca 100644 --- a/lib/rdf/n3/algebra/math/atan.rb +++ b/lib/rdf/n3/algebra/math/atan.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the arc tangent value of the subject. - class ATan < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class ATan < RDF::N3::Algebra::LiteralOperator NAME = :mathATan ## # The math:atan operator takes string or number and calculates its arc tangent. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.atan(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.atan(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/atanh.rb b/lib/rdf/n3/algebra/math/atanh.rb index d7c185f..ead639d 100644 --- a/lib/rdf/n3/algebra/math/atanh.rb +++ b/lib/rdf/n3/algebra/math/atanh.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the inverse hyperbolic tangent value of the subject. - class ATanH < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class ATanH < RDF::N3::Algebra::LiteralOperator NAME = :mathATanH ## # The math:atanh operator takes string or number and calculates its inverse hyperbolic tangent. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.atanh(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.atanh(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/ceiling.rb b/lib/rdf/n3/algebra/math/ceiling.rb index b29804e..a9d5713 100644 --- a/lib/rdf/n3/algebra/math/ceiling.rb +++ b/lib/rdf/n3/algebra/math/ceiling.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The object is calculated as the subject upwards to a whole number. - class Ceiling < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Ceiling < RDF::N3::Algebra::LiteralOperator NAME = :mathCeiling ## # The math:ceiling operator takes string or number and calculates its ceiling. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = num.as_number.ceil - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(resource.as_number.ceil) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/cos.rb b/lib/rdf/n3/algebra/math/cos.rb index 7750291..b5a916a 100644 --- a/lib/rdf/n3/algebra/math/cos.rb +++ b/lib/rdf/n3/algebra/math/cos.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the cosine value of the subject. - class Cos < SPARQL::Algebra::Operator::Binary + class Cos < RDF::N3::Algebra::LiteralOperator include SPARQL::Algebra::Query include SPARQL::Algebra::Update include RDF::Enumerable @@ -12,30 +12,18 @@ class Cos < SPARQL::Algebra::Operator::Binary ## # The math:cos operator takes string or number and calculates its cosine. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.cos(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.cos(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/cosh.rb b/lib/rdf/n3/algebra/math/cosh.rb index 45a3da1..8475674c 100644 --- a/lib/rdf/n3/algebra/math/cosh.rb +++ b/lib/rdf/n3/algebra/math/cosh.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the hyperbolic cosine value of the subject. - class CosH < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class CosH < RDF::N3::Algebra::LiteralOperator NAME = :mathCosH ## # The math:cosh operator takes string or number and calculates its hyperbolic cosine. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.cosh(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.cosh(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/floor.rb b/lib/rdf/n3/algebra/math/floor.rb index 8559d73..c3f3c26 100644 --- a/lib/rdf/n3/algebra/math/floor.rb +++ b/lib/rdf/n3/algebra/math/floor.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The object is calculated as the subject downwards to a whole number. - class Floor < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Floor < RDF::N3::Algebra::LiteralOperator NAME = :mathFloor ## # The math:floor operator takes string or number and calculates its floor. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = num.as_number.floor - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(resource.as_number.floor) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb index 2853e5a..139f32e 100644 --- a/lib/rdf/n3/algebra/math/negation.rb +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -12,31 +12,18 @@ class Negation < SPARQL::Algebra::Operator::Binary ## # The math:negation operator takes may have either a bound subject or object. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - @solutions = RDF::Query::Solutions(solutions.map do |solution| - subject, object = operand(0), operand(1) - - log_debug(NAME) {"subject: #{subject.to_sxp}, object: #{object.to_sxp}"} - unless subject.literal? || object.literal? - log_error(NAME) {"subject or object are not literals: #{subject.inspect}, #{object.inspect}"} - next - end - - subject = subject.as_number - object = object.as_number - if subject.variable? - solution.merge(subject.to_sym => -object) - elsif object.variable? - solution.merge(object.to_sym => -subject) - elsif subject == -object - solution - else - nil - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case resource + when RDF::Query::Variable + resource + when RDF::Literal + RDF::Literal(-resource.as_number) + else + nil + end end end end diff --git a/lib/rdf/n3/algebra/math/rounded.rb b/lib/rdf/n3/algebra/math/rounded.rb index 2e516fd..217ec24 100644 --- a/lib/rdf/n3/algebra/math/rounded.rb +++ b/lib/rdf/n3/algebra/math/rounded.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the subject rounded to the nearest integer. - class Rounded < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Rounded < RDF::N3::Algebra::LiteralOperator NAME = :mathRounded ## # The math:floor operator takes string or number and calculates its floor. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = num.as_number.round - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(resource.as_number.round) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/sin.rb b/lib/rdf/n3/algebra/math/sin.rb index cb58370..e2a9105 100644 --- a/lib/rdf/n3/algebra/math/sin.rb +++ b/lib/rdf/n3/algebra/math/sin.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the sine value of the subject. - class Sin < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Sin < RDF::N3::Algebra::LiteralOperator NAME = :mathSin ## # The math:sin operator takes string or number and calculates its sine. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.sin(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.sin(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/sinh.rb b/lib/rdf/n3/algebra/math/sinh.rb index 3be3248..26123a8 100644 --- a/lib/rdf/n3/algebra/math/sinh.rb +++ b/lib/rdf/n3/algebra/math/sinh.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the hyperbolic sine value of the subject. - class SinH < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class SinH < RDF::N3::Algebra::LiteralOperator NAME = :mathSinH ## # The math:sinh operator takes string or number and calculates its hyperbolic sine. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.sinh(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.sinh(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/tan.rb b/lib/rdf/n3/algebra/math/tan.rb index 02dfa57..c5a7488 100644 --- a/lib/rdf/n3/algebra/math/tan.rb +++ b/lib/rdf/n3/algebra/math/tan.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the tangent value of the subject. - class Tan < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class Tan < RDF::N3::Algebra::LiteralOperator NAME = :mathTan ## # The math:tan operator takes string or number and calculates its tangent. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.tan(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.tan(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/lib/rdf/n3/algebra/math/tanh.rb b/lib/rdf/n3/algebra/math/tanh.rb index added14..6794ecc 100644 --- a/lib/rdf/n3/algebra/math/tanh.rb +++ b/lib/rdf/n3/algebra/math/tanh.rb @@ -1,41 +1,24 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the tangent value of the subject. - class TanH < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - + class TanH < RDF::N3::Algebra::LiteralOperator NAME = :mathTanH ## # The math:tanh operator takes string or number and calculates its hyperbolic tangent. # - # @param [RDF::Queryable] queryable - # @param [RDF::Query::Solutions] solutions - # @return [RDF::Query::Solutions] - def execute(queryable, solutions:, **options) - num = operand(0) - result = operand(1) - - @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"num: #{num.to_sxp}, result: #{result.to_sxp}"} - unless num.literal? - log_error(NAME) {"num is not a literal: #{num.inspect}"} - next - end - - num = RDF::Literal(Math.tanh(num.as_number.object)) - - if result.variable? - solution.merge(result.to_sym => num) - elsif result != num - nil - else - solution - end - end.compact) + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + def evaluate(resource, position:) + case position + when :subject + return nil unless resource.literal? + RDF::Literal(Math.tanh(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end end end end diff --git a/script/tc b/script/tc index 8f70c78..10b5f25 100755 --- a/script/tc +++ b/script/tc @@ -126,9 +126,9 @@ def run_tc(man, tc, **options) end result = "failed" end - STDERR.puts "\nResult: #{repo.dump(:n3)}" if options[:verbose] + STDERR.puts "\nResult: #{repo.dump(:n3, base_uri: RDF::URI(tc.base).parent, standard_prefixes: true)}" if options[:verbose] - output_graph = RDF::N3::Repository.load(tc.result, base_uri: tc.base) + output_graph = RDF::N3::Repository.load(tc.result, base_uri: tc.base) # Check against expanded triples from repo expanded_repo = RDF::Repository.new do |r| @@ -227,7 +227,7 @@ opts.each do |opt, arg| options[:level] = Logger::FATAL when '--skip-slow' then options[:slow] = false when '--type' - unless %(parser reasoner all).include?(arg) + unless %w(parser reasoner all).include?(arg) STDERR.puts "unknown test type: #{options[:type]}" help(**options) end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 9dc7b70..89821b6 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -19,8 +19,10 @@ next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do case t.id.split('#').last - when *%w{cwm_math_test} + when *%w{cwm_math_test cwm_math_trigo} pending "math numeric representation" + when *%w{cwm_time_t1} + pending "time" when *%w{cwm_includes_conjunction} pending "log:conjunction" when *%w{cwm_includes_bnode} From b6b0c611ecefc69967cfdfd04eaa6c5088f59d3c Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 1 Sep 2020 13:26:37 -0700 Subject: [PATCH 096/193] Towards an N3 Abstract Syntax. --- etc/abstract_syntax.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 etc/abstract_syntax.md diff --git a/etc/abstract_syntax.md b/etc/abstract_syntax.md new file mode 100644 index 0000000..a7126df --- /dev/null +++ b/etc/abstract_syntax.md @@ -0,0 +1,26 @@ +# Notation-3 Abstract Syntax + +The [Notation-3][] Abstract Syntax generalizes the the [RDF Abstract Syntax](https://www.w3.org/TR/rdf11-concepts/) defined in [[RDF11-Concepts][]] further generalizing the concepts of [generalized RDF triple](https://www.w3.org/TR/rdf11-concepts/#dfn-generalized-rdf-triple) and [generalized RDF graph](https://www.w3.org/TR/rdf11-concepts/#dfn-generalized-rdf-graph). + +An [N3 triple](https://w3c.github.io/N3/spec/#N3-triple) is composed of three [N3 triple elements](https://w3c.github.io/N3/spec/#dfn-triple-elements), which each element can be an [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri), [blank node](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node), [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal), [list](https://w3c.github.io/N3/spec/#lists), [N3 graph](#n3-graph), or universal variable. + +An N3 graph abstracts a [generalized RDF graph](https://www.w3.org/TR/rdf11-concepts/#dfn-generalized-rdf-graph) is then a set of zero or more [N3 triples](https://w3c.github.io/N3/spec/#N3-triple) also having zero or more bound [universal variables](https://w3c.github.io/N3/spec/#dfn-universals). + +When appearing as the subject, predicate, or object of an [N3 triple](https://w3c.github.io/N3/spec/#N3-triple), an [N3 graph](#n3-graph) may also be quantified, unless given a separate interpretation by the semantics defined for the associated predicate (e.g., when it is a builtin). + +Note that in Notation-3, a [list](https://w3c.github.io/N3/spec/#lists) is a first-class resource, which may be quantified when appearing with a [quantified N3 formula](https://w3c.github.io/N3/spec/#quantified-formula). The notion of [RDF Collection](https://www.w3.org/TR/rdf11-mt/#rdf-collections) from [[RDF 1.1 Semantics](https://www.w3.org/TR/rdf11-mt/)] may be considered as a reification of an [N3 list](https://w3c.github.io/N3/spec/#lists). + +## Relationship to Datasets + +The description of the Abstract Syntax is based on the notion of resources, triples and graphs, where a graph may be a triple component, thus creating a recursive graph. This is similar to the notion of an [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset) where a blank node becomes a stand-in for the graph when used within a triple, and that same blank node names a named graph containing the triples from the referenced graph. The fact that both blank nodes and N3 graphs are existentially quantized leads to similar semantics, although in RDF, datasets have no defined semantics. + +## Notes + +[Blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node) in Notation-3 are unique across [N3 graphs](#n3-graph), unlike in other RDF syntaxes, however this is considered a concrete syntax-level concern, and does not affect the abstract syntax. + +Similarly, both [Blank Nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node) and [universal variables](https://w3c.github.io/N3/spec/#dfn-universals) act as quantifiers and are scoped to a particular [N3 graph](#n3-graph). This can also be considered a concrete syntax-level concern which can be addressed by appropriately renaming variables at the global scope. + +An [N3 graph](#n3-graph) is often referred to as a [formula](https://w3c.github.io/N3/spec/#N3-formula) (plural, formulae), however, the concept of formula in N3 also includes [N3 triples](https://w3c.github.io/N3/spec/#N3-triple). + +[Notation-3]: https://w3c.github.io/N3/spec/ +[RDF11-Concepts]: https://www.w3.org/TR/rdf11-concepts/ \ No newline at end of file From 54e4c0928a15a4c838d5a655b3b4a48063aab4ea Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 1 Sep 2020 16:03:53 -0700 Subject: [PATCH 097/193] Canonicalize trigonometric values. --- lib/rdf/n3/algebra/formula.rb | 1 + lib/rdf/n3/algebra/math/acos.rb | 2 +- lib/rdf/n3/algebra/math/acosh.rb | 2 +- lib/rdf/n3/algebra/math/asin.rb | 2 +- lib/rdf/n3/algebra/math/asinh.rb | 2 +- lib/rdf/n3/algebra/math/atan.rb | 2 +- lib/rdf/n3/algebra/math/atanh.rb | 2 +- lib/rdf/n3/algebra/math/cos.rb | 20 +++++++++------ lib/rdf/n3/algebra/math/cosh.rb | 20 +++++++++------ lib/rdf/n3/algebra/math/sin.rb | 20 +++++++++------ lib/rdf/n3/algebra/math/sinh.rb | 20 +++++++++------ lib/rdf/n3/algebra/math/tan.rb | 20 +++++++++------ lib/rdf/n3/algebra/math/tanh.rb | 20 +++++++++------ spec/reasoner_spec.rb | 43 +++++++++++++++++++++++++++++++- spec/suite_reasoner_spec.rb | 2 +- 15 files changed, 122 insertions(+), 56 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 3fe41fc..9e8082e 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -64,6 +64,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new @solutions : RDF::Query::Solutions.new end + log_debug("(formula intermediate solutions)") {"after #{op.to_sxp}: " + @solutions.to_sxp} end end log_info("(formula sub-op solutions)") {@solutions.to_sxp} diff --git a/lib/rdf/n3/algebra/math/acos.rb b/lib/rdf/n3/algebra/math/acos.rb index f6a70c5..436c29e 100644 --- a/lib/rdf/n3/algebra/math/acos.rb +++ b/lib/rdf/n3/algebra/math/acos.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.acos(resource.as_number.object)) + RDF::Literal(Math.acos(resource.as_number.object), canonicalize: true) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/acosh.rb b/lib/rdf/n3/algebra/math/acosh.rb index b387b40..24a2e39 100644 --- a/lib/rdf/n3/algebra/math/acosh.rb +++ b/lib/rdf/n3/algebra/math/acosh.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.acosh(resource.as_number.object)) + RDF::Literal(Math.acosh(resource.as_number.object), canonicalize: true) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/asin.rb b/lib/rdf/n3/algebra/math/asin.rb index e8a3852..f97e0b1 100644 --- a/lib/rdf/n3/algebra/math/asin.rb +++ b/lib/rdf/n3/algebra/math/asin.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.asin(resource.as_number.object)) + RDF::Literal(Math.asin(resource.as_number.object), canonicalize: true) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/asinh.rb b/lib/rdf/n3/algebra/math/asinh.rb index c1c6f87..961ccc0 100644 --- a/lib/rdf/n3/algebra/math/asinh.rb +++ b/lib/rdf/n3/algebra/math/asinh.rb @@ -19,7 +19,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.asinh(resource.as_number.object)) + RDF::Literal(Math.asinh(resource.as_number.object), canonicalize: true) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/atan.rb b/lib/rdf/n3/algebra/math/atan.rb index b489aca..4b8f14f 100644 --- a/lib/rdf/n3/algebra/math/atan.rb +++ b/lib/rdf/n3/algebra/math/atan.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.atan(resource.as_number.object)) + RDF::Literal(Math.atan(resource.as_number.object), canonicalize: true) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/atanh.rb b/lib/rdf/n3/algebra/math/atanh.rb index ead639d..4c2d96b 100644 --- a/lib/rdf/n3/algebra/math/atanh.rb +++ b/lib/rdf/n3/algebra/math/atanh.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.atanh(resource.as_number.object)) + RDF::Literal(Math.atanh(resource.as_number.object), canonicalize: true) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/cos.rb b/lib/rdf/n3/algebra/math/cos.rb index b5a916a..d8a9d7a 100644 --- a/lib/rdf/n3/algebra/math/cos.rb +++ b/lib/rdf/n3/algebra/math/cos.rb @@ -10,19 +10,23 @@ class Cos < RDF::N3::Algebra::LiteralOperator NAME = :mathCos ## - # The math:cos operator takes string or number and calculates its cosine. + # The math:cos operator takes string or number and calculates its cosine. The arc cosine of a concrete object can also calculate a variable subject. # # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - RDF::Literal(Math.cos(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource + case resource + when RDF::Query::Variable then resource + when RDF::Literal + case position + when :subject + RDF::Literal(Math.cos(resource.as_number.object), canonicalize: true) + when :object + RDF::Literal(Math.acos(resource.as_number.object), canonicalize: true) + end + else + nil end end end diff --git a/lib/rdf/n3/algebra/math/cosh.rb b/lib/rdf/n3/algebra/math/cosh.rb index 8475674c..217f54d 100644 --- a/lib/rdf/n3/algebra/math/cosh.rb +++ b/lib/rdf/n3/algebra/math/cosh.rb @@ -5,19 +5,23 @@ class CosH < RDF::N3::Algebra::LiteralOperator NAME = :mathCosH ## - # The math:cosh operator takes string or number and calculates its hyperbolic cosine. + # The math:cosh operator takes string or number and calculates its hyperbolic cosine. The inverse hyperbolic cosine of a concrete object can also calculate a variable subject. # # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - RDF::Literal(Math.cosh(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource + case resource + when RDF::Query::Variable then resource + when RDF::Literal + case position + when :subject + RDF::Literal(Math.cosh(resource.as_number.object), canonicalize: true) + when :object + RDF::Literal(Math.acosh(resource.as_number.object), canonicalize: true) + end + else + nil end end end diff --git a/lib/rdf/n3/algebra/math/sin.rb b/lib/rdf/n3/algebra/math/sin.rb index e2a9105..fd0b1ac 100644 --- a/lib/rdf/n3/algebra/math/sin.rb +++ b/lib/rdf/n3/algebra/math/sin.rb @@ -5,19 +5,23 @@ class Sin < RDF::N3::Algebra::LiteralOperator NAME = :mathSin ## - # The math:sin operator takes string or number and calculates its sine. + # The math:sin operator takes string or number and calculates its sine. The arc sine of a concrete object can also calculate a variable subject. # # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - RDF::Literal(Math.sin(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource + case resource + when RDF::Query::Variable then resource + when RDF::Literal + case position + when :subject + RDF::Literal(Math.sin(resource.as_number.object), canonicalize: true) + when :object + RDF::Literal(Math.asin(resource.as_number.object), canonicalize: true) + end + else + nil end end end diff --git a/lib/rdf/n3/algebra/math/sinh.rb b/lib/rdf/n3/algebra/math/sinh.rb index 26123a8..fc30df6 100644 --- a/lib/rdf/n3/algebra/math/sinh.rb +++ b/lib/rdf/n3/algebra/math/sinh.rb @@ -5,19 +5,23 @@ class SinH < RDF::N3::Algebra::LiteralOperator NAME = :mathSinH ## - # The math:sinh operator takes string or number and calculates its hyperbolic sine. + # The math:sinh operator takes string or number and calculates its hyperbolic sine. The inverse hyperbolic sine of a concrete object can also calculate a variable subject. # # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - RDF::Literal(Math.sinh(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource + case resource + when RDF::Query::Variable then resource + when RDF::Literal + case position + when :subject + RDF::Literal(Math.sinh(resource.as_number.object), canonicalize: true) + when :object + RDF::Literal(Math.asinh(resource.as_number.object), canonicalize: true) + end + else + nil end end end diff --git a/lib/rdf/n3/algebra/math/tan.rb b/lib/rdf/n3/algebra/math/tan.rb index c5a7488..483d05b 100644 --- a/lib/rdf/n3/algebra/math/tan.rb +++ b/lib/rdf/n3/algebra/math/tan.rb @@ -5,19 +5,23 @@ class Tan < RDF::N3::Algebra::LiteralOperator NAME = :mathTan ## - # The math:tan operator takes string or number and calculates its tangent. + # The math:tan operator takes string or number and calculates its tangent. The arc tangent of a concrete object can also calculate a variable subject. # # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - RDF::Literal(Math.tan(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource + case resource + when RDF::Query::Variable then resource + when RDF::Literal + case position + when :subject + RDF::Literal(Math.tan(resource.as_number.object), canonicalize: true) + when :object + RDF::Literal(Math.atan(resource.as_number.object), canonicalize: true) + end + else + nil end end end diff --git a/lib/rdf/n3/algebra/math/tanh.rb b/lib/rdf/n3/algebra/math/tanh.rb index 6794ecc..a6a74e0 100644 --- a/lib/rdf/n3/algebra/math/tanh.rb +++ b/lib/rdf/n3/algebra/math/tanh.rb @@ -5,19 +5,23 @@ class TanH < RDF::N3::Algebra::LiteralOperator NAME = :mathTanH ## - # The math:tanh operator takes string or number and calculates its hyperbolic tangent. + # The math:tanh operator takes string or number and calculates its hyperbolic tangent. The inverse hyperbolic tangent of a concrete object can also calculate a variable subject. # # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - RDF::Literal(Math.tanh(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource + case resource + when RDF::Query::Variable then resource + when RDF::Literal + case position + when :subject + RDF::Literal(Math.tanh(resource.as_number.object), canonicalize: true) + when :object + RDF::Literal(Math.atanh(resource.as_number.object), canonicalize: true) + end + else + nil end end end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index abfedcd..1266520 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -79,7 +79,7 @@ logger.info "input: #{options[:input]}" expected = parse(options[:expect]) result = reason(options[:input]) - expect(reason(options[:input])).to be_equivalent_graph(expected, logger: logger, format: :n3) + expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end end end @@ -450,6 +450,47 @@ expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) end end + + context "trig" do + { + "0": { + asin: "0.0e0", + sin: "0.0e0", + sinh: "0.0e0", + cos: "1.0e0", + cosh: "1.0e0", + atan: "0.0e0", + tan: "0.0e0", + tanh: "0.0e0", + }, + "3.14159265358979323846": { + cos: "-1.0e0" + }, + # pi/4 + "0.7853981633974483": { + tan: ["1.0e0", "0.9e0"], + }, + # pi/3 + "1.0471975511965976": { + cos: ["0.51e0", "0.49e0"], + }, + }.each do |subject, params| + params.each do |fun, object| + it "#{subject} math:#{fun} #{object}" do + if object.is_a?(Array) + input = %({ #{subject} math:#{fun} _:x . _:x math:lessThan #{object.first}; math:greaterThan #{object.last} } => { :#{fun} a :SUCCESS} .) + expect = %(:#{fun} a :SUCCESS .) + else + input = %({ #{subject} math:#{fun} ?x } => { #{subject} :#{fun} ?x} .) + expect = %(#{subject} :#{fun} #{object} .) + end + logger.info "input: #{input}" + expected = parse(expect) + expect(reason(input, filter: true)).to be_equivalent_graph(expected, logger: logger) + end + end + end + end end context "math-test" do diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 89821b6..1c38c05 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -19,7 +19,7 @@ next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do case t.id.split('#').last - when *%w{cwm_math_test cwm_math_trigo} + when *%w{cwm_math_test} pending "math numeric representation" when *%w{cwm_time_t1} pending "time" From 60a20fd983ec49c7f2e08ff128f7332ac4cc64b7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 7 Sep 2020 14:11:08 -0700 Subject: [PATCH 098/193] Use canonical representation for numeric literals in math results, with lower-case "e", rather than standard "E". --- lib/rdf/n3/algebra/literal_operator.rb | 13 +++++++++++++ lib/rdf/n3/algebra/math/absoluteValue.rb | 2 +- lib/rdf/n3/algebra/math/acos.rb | 2 +- lib/rdf/n3/algebra/math/acosh.rb | 2 +- lib/rdf/n3/algebra/math/asin.rb | 2 +- lib/rdf/n3/algebra/math/asinh.rb | 2 +- lib/rdf/n3/algebra/math/atan.rb | 2 +- lib/rdf/n3/algebra/math/atanh.rb | 2 +- lib/rdf/n3/algebra/math/ceiling.rb | 2 +- lib/rdf/n3/algebra/math/cos.rb | 4 ++-- lib/rdf/n3/algebra/math/cosh.rb | 4 ++-- lib/rdf/n3/algebra/math/negation.rb | 2 +- lib/rdf/n3/algebra/math/rounded.rb | 2 +- lib/rdf/n3/algebra/math/sin.rb | 4 ++-- lib/rdf/n3/algebra/math/sinh.rb | 4 ++-- lib/rdf/n3/algebra/math/tan.rb | 4 ++-- lib/rdf/n3/algebra/math/tanh.rb | 4 ++-- 17 files changed, 35 insertions(+), 22 deletions(-) diff --git a/lib/rdf/n3/algebra/literal_operator.rb b/lib/rdf/n3/algebra/literal_operator.rb index a741597..fbb9b21 100644 --- a/lib/rdf/n3/algebra/literal_operator.rb +++ b/lib/rdf/n3/algebra/literal_operator.rb @@ -65,5 +65,18 @@ def evaluate(resource, position: :subject) def valid?(subject, object) true end + + ## + # Returns a literal for the numeric argument, with doubles canonicalized using a lower-case 'e'. + def as_literal(object) + case object + when Float + literal = RDF::Literal(object, canonicalize: true) + literal.instance_variable_set(:@string, literal.to_s.downcase) + literal + else + RDF::Literal(object, canonicalize: true) + end + end end end diff --git a/lib/rdf/n3/algebra/math/absoluteValue.rb b/lib/rdf/n3/algebra/math/absoluteValue.rb index 5f07262..922c11c 100644 --- a/lib/rdf/n3/algebra/math/absoluteValue.rb +++ b/lib/rdf/n3/algebra/math/absoluteValue.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(resource.as_number.abs) + as_literal(resource.as_number.abs) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/acos.rb b/lib/rdf/n3/algebra/math/acos.rb index 436c29e..e33da34 100644 --- a/lib/rdf/n3/algebra/math/acos.rb +++ b/lib/rdf/n3/algebra/math/acos.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.acos(resource.as_number.object), canonicalize: true) + as_literal(Math.acos(resource.as_number.object)) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/acosh.rb b/lib/rdf/n3/algebra/math/acosh.rb index 24a2e39..ec77244 100644 --- a/lib/rdf/n3/algebra/math/acosh.rb +++ b/lib/rdf/n3/algebra/math/acosh.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.acosh(resource.as_number.object), canonicalize: true) + as_literal(Math.acosh(resource.as_number.object)) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/asin.rb b/lib/rdf/n3/algebra/math/asin.rb index f97e0b1..b25ed4d 100644 --- a/lib/rdf/n3/algebra/math/asin.rb +++ b/lib/rdf/n3/algebra/math/asin.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.asin(resource.as_number.object), canonicalize: true) + as_literal(Math.asin(resource.as_number.object)) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/asinh.rb b/lib/rdf/n3/algebra/math/asinh.rb index 961ccc0..182781a 100644 --- a/lib/rdf/n3/algebra/math/asinh.rb +++ b/lib/rdf/n3/algebra/math/asinh.rb @@ -19,7 +19,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.asinh(resource.as_number.object), canonicalize: true) + as_literal(Math.asinh(resource.as_number.object)) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/atan.rb b/lib/rdf/n3/algebra/math/atan.rb index 4b8f14f..d7efa62 100644 --- a/lib/rdf/n3/algebra/math/atan.rb +++ b/lib/rdf/n3/algebra/math/atan.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.atan(resource.as_number.object), canonicalize: true) + as_literal(Math.atan(resource.as_number.object)) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/atanh.rb b/lib/rdf/n3/algebra/math/atanh.rb index 4c2d96b..df0808e 100644 --- a/lib/rdf/n3/algebra/math/atanh.rb +++ b/lib/rdf/n3/algebra/math/atanh.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(Math.atanh(resource.as_number.object), canonicalize: true) + as_literal(Math.atanh(resource.as_number.object)) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/ceiling.rb b/lib/rdf/n3/algebra/math/ceiling.rb index a9d5713..8115371 100644 --- a/lib/rdf/n3/algebra/math/ceiling.rb +++ b/lib/rdf/n3/algebra/math/ceiling.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(resource.as_number.ceil) + as_literal(resource.as_number.ceil) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/cos.rb b/lib/rdf/n3/algebra/math/cos.rb index d8a9d7a..2a41e04 100644 --- a/lib/rdf/n3/algebra/math/cos.rb +++ b/lib/rdf/n3/algebra/math/cos.rb @@ -21,9 +21,9 @@ def evaluate(resource, position:) when RDF::Literal case position when :subject - RDF::Literal(Math.cos(resource.as_number.object), canonicalize: true) + as_literal(Math.cos(resource.as_number.object)) when :object - RDF::Literal(Math.acos(resource.as_number.object), canonicalize: true) + as_literal(Math.acos(resource.as_number.object)) end else nil diff --git a/lib/rdf/n3/algebra/math/cosh.rb b/lib/rdf/n3/algebra/math/cosh.rb index 217f54d..556c862 100644 --- a/lib/rdf/n3/algebra/math/cosh.rb +++ b/lib/rdf/n3/algebra/math/cosh.rb @@ -16,9 +16,9 @@ def evaluate(resource, position:) when RDF::Literal case position when :subject - RDF::Literal(Math.cosh(resource.as_number.object), canonicalize: true) + as_literal(Math.cosh(resource.as_number.object)) when :object - RDF::Literal(Math.acosh(resource.as_number.object), canonicalize: true) + as_literal(Math.acosh(resource.as_number.object)) end else nil diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb index 139f32e..bcd126c 100644 --- a/lib/rdf/n3/algebra/math/negation.rb +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -20,7 +20,7 @@ def evaluate(resource, position:) when RDF::Query::Variable resource when RDF::Literal - RDF::Literal(-resource.as_number) + as_literal(-resource.as_number) else nil end diff --git a/lib/rdf/n3/algebra/math/rounded.rb b/lib/rdf/n3/algebra/math/rounded.rb index 217ec24..3f6ddbd 100644 --- a/lib/rdf/n3/algebra/math/rounded.rb +++ b/lib/rdf/n3/algebra/math/rounded.rb @@ -14,7 +14,7 @@ def evaluate(resource, position:) case position when :subject return nil unless resource.literal? - RDF::Literal(resource.as_number.round) + as_literal(resource.as_number.round) when :object return nil unless resource.literal? || resource.variable? resource diff --git a/lib/rdf/n3/algebra/math/sin.rb b/lib/rdf/n3/algebra/math/sin.rb index fd0b1ac..0f343a2 100644 --- a/lib/rdf/n3/algebra/math/sin.rb +++ b/lib/rdf/n3/algebra/math/sin.rb @@ -16,9 +16,9 @@ def evaluate(resource, position:) when RDF::Literal case position when :subject - RDF::Literal(Math.sin(resource.as_number.object), canonicalize: true) + as_literal(Math.sin(resource.as_number.object)) when :object - RDF::Literal(Math.asin(resource.as_number.object), canonicalize: true) + as_literal(Math.sinh(resource.as_number.object)) end else nil diff --git a/lib/rdf/n3/algebra/math/sinh.rb b/lib/rdf/n3/algebra/math/sinh.rb index fc30df6..d7800eb 100644 --- a/lib/rdf/n3/algebra/math/sinh.rb +++ b/lib/rdf/n3/algebra/math/sinh.rb @@ -16,9 +16,9 @@ def evaluate(resource, position:) when RDF::Literal case position when :subject - RDF::Literal(Math.sinh(resource.as_number.object), canonicalize: true) + as_literal(Math.sinh(resource.as_number.object)) when :object - RDF::Literal(Math.asinh(resource.as_number.object), canonicalize: true) + as_literal(Math.asinh(resource.as_number.object)) end else nil diff --git a/lib/rdf/n3/algebra/math/tan.rb b/lib/rdf/n3/algebra/math/tan.rb index 483d05b..06207c8 100644 --- a/lib/rdf/n3/algebra/math/tan.rb +++ b/lib/rdf/n3/algebra/math/tan.rb @@ -16,9 +16,9 @@ def evaluate(resource, position:) when RDF::Literal case position when :subject - RDF::Literal(Math.tan(resource.as_number.object), canonicalize: true) + as_literal(Math.tan(resource.as_number.object)) when :object - RDF::Literal(Math.atan(resource.as_number.object), canonicalize: true) + as_literal(Math.atan(resource.as_number.object)) end else nil diff --git a/lib/rdf/n3/algebra/math/tanh.rb b/lib/rdf/n3/algebra/math/tanh.rb index a6a74e0..1601864 100644 --- a/lib/rdf/n3/algebra/math/tanh.rb +++ b/lib/rdf/n3/algebra/math/tanh.rb @@ -16,9 +16,9 @@ def evaluate(resource, position:) when RDF::Literal case position when :subject - RDF::Literal(Math.tanh(resource.as_number.object), canonicalize: true) + as_literal(Math.tanh(resource.as_number.object)) when :object - RDF::Literal(Math.atanh(resource.as_number.object), canonicalize: true) + as_literal(Math.atanh(resource.as_number.object)) end else nil From a97fc927e9a040969b8a51c9c17b24ffe0cb5cdb Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 7 Sep 2020 14:11:43 -0700 Subject: [PATCH 099/193] Don't allow white-space in IRIREF. --- lib/rdf/n3/terminals.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdf/n3/terminals.rb b/lib/rdf/n3/terminals.rb index 7eaf727..4dae60a 100644 --- a/lib/rdf/n3/terminals.rb +++ b/lib/rdf/n3/terminals.rb @@ -39,7 +39,7 @@ module Terminals # 159s ECHAR = /\\[tbnrf\\"']/u.freeze # 18 - IRIREF = /<(?:#{IRI_RANGE}|#{UCHAR}|\s)*>/mu.freeze + IRIREF = /<(?:#{IRI_RANGE}|#{UCHAR})*>/mu.freeze # 139s PNAME_NS = /#{PN_PREFIX}?:/u.freeze # 140s From b5ad26d3e328729713a31c475812490a9ba2f01a Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 7 Sep 2020 14:12:04 -0700 Subject: [PATCH 100/193] Don't canonicalize numeric literals when parsing. --- examples/gk-list-issue-12.n3 | 11 +++++++++++ examples/jos-list-issue-12.n3 | 11 +++++++++++ lib/rdf/n3/reader.rb | 8 ++++---- script/parse | 2 +- script/tc | 2 +- spec/suite_turtle_spec.rb | 5 ----- 6 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 examples/gk-list-issue-12.n3 create mode 100644 examples/jos-list-issue-12.n3 diff --git a/examples/gk-list-issue-12.n3 b/examples/gk-list-issue-12.n3 new file mode 100644 index 0000000..e587629 --- /dev/null +++ b/examples/gk-list-issue-12.n3 @@ -0,0 +1,11 @@ +@prefix rdf: . +@prefix list: . +@prefix : . + +{ (1 2 3) list:last ?x } => { :lastMember :is ?x } . + +{ + _:l3 rdf:first 3; rdf:rest rdf:nil . + _:l2 rdf:first 2; rdf:rest _:l3 . + _:l1 rdf:first 1; rdf:rest _:l2; list:last ?y +} => { :lastMember2 :is ?y } . diff --git a/examples/jos-list-issue-12.n3 b/examples/jos-list-issue-12.n3 new file mode 100644 index 0000000..8c7bed3 --- /dev/null +++ b/examples/jos-list-issue-12.n3 @@ -0,0 +1,11 @@ +@prefix rdf: . +@prefix list: . +@prefix : . + +_:list1 rdf:first :a; rdf:rest _:l2. +_:l2 rdf:first :b; rdf:rest _:l3. +_:l3 rdf:first :c; rdf:rest rdf:nil. +_:list1 :p :o. +(1 2 3) :p :o. + +{?LIST :p :o; rdf:first ?FIRST; list:last ?LAST} => {?LIST :first_element ?FIRST; :last_element ?LAST}. diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 2f00e79..9e8d56d 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -441,15 +441,15 @@ def read_path def read_literal error("Unexpected end of file", production: :literal) unless token = @lexer.first case token.type || token.value - when :INTEGER then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.integer, canonicalize: true)} + when :INTEGER then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.integer, canonicalize: canonicalize?)} when :DECIMAL prod(:literal) do value = @lexer.shift.value value = "0#{value}" if value.start_with?(".") - literal(value, datatype: RDF::XSD.decimal, canonicalize: true) + literal(value, datatype: RDF::XSD.decimal, canonicalize: canonicalize?) end - when :DOUBLE then prod(:literal) {literal(@lexer.shift.value.sub(/\.([eE])/, '.0\1'), datatype: RDF::XSD.double, canonicalize: true)} - when "true", "false" then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.boolean, canonicalize: true)} + when :DOUBLE then prod(:literal) {literal(@lexer.shift.value.sub(/\.([eE])/, '.0\1'), datatype: RDF::XSD.double, canonicalize: canonicalize?)} + when "true", "false" then prod(:literal) {literal(@lexer.shift.value, datatype: RDF::XSD.boolean, canonicalize: canonicalize?)} when :STRING_LITERAL_QUOTE, :STRING_LITERAL_SINGLE_QUOTE prod(:literal) do value = @lexer.shift.value[1..-2] diff --git a/script/parse b/script/parse index c8bcd40..4ccc10c 100755 --- a/script/parse +++ b/script/parse @@ -93,13 +93,13 @@ logger.formatter = lambda {|severity, datetime, progname, msg| "%5s %s\n" % [sev parser_options = { base_uri: "http://example.com", + list_terms: true, logger: logger, validate: false, } options = { input_format: :n3, - list_terms: true, logger: logger, output: STDOUT, output_format: :n3, diff --git a/script/tc b/script/tc index 10b5f25..ded3e7b 100755 --- a/script/tc +++ b/script/tc @@ -250,7 +250,7 @@ manifests.each do |man| Fixtures::SuiteTest::Manifest.open(man) do |m| m.entries.each do |tc| next unless ARGV.empty? || ARGV.any? {|n| tc.property('@id').match?(/#{n}/) || tc.property('action').match?(/#{n}/)} - run_tc(man, tc, **options) + run_tc(man, tc, **options.merge(list_terms: !man.include?("TurtleTests"))) end end end diff --git a/spec/suite_turtle_spec.rb b/spec/suite_turtle_spec.rb index be88555..c85431b 100644 --- a/spec/suite_turtle_spec.rb +++ b/spec/suite_turtle_spec.rb @@ -11,11 +11,6 @@ m.entries.each do |t| next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do - case t.name - when *%w(turtle-syntax-bad-struct-05 turtle-syntax-bad-kw-05 turtle-syntax-bad-struct-15) - pending("n3 allows odd predicates") - end - t.logger = RDF::Spec.logger t.logger.info t.inspect t.logger.info "source:\n#{t.input}" From 8da5ec0697ab81149e916200b0530826faed4b47 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 7 Sep 2020 14:41:31 -0700 Subject: [PATCH 101/193] Simplify some list operators which can a void a check for unbound variable checks. --- lib/rdf/n3/algebra/list/in.rb | 19 +++++++------------ lib/rdf/n3/algebra/list/member.rb | 19 +++++++------------ lib/rdf/n3/algebra/list_operator.rb | 19 +++++++------------ 3 files changed, 21 insertions(+), 36 deletions(-) diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index 2be798f..65838a0 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -28,20 +28,15 @@ def execute(queryable, solutions:, **options) next end - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements + if subject.variable? + # Bind all list entries to this solution, creates an array of solutions + list.to_a.map do |term| + solution.merge(subject.to_sym => term) + end + elsif list.to_a.include?(subject) solution else - if subject.variable? - # Bind all list entries to this solution, creates an array of solutions - list.to_a.map do |term| - solution.merge(subject.to_sym => term) - end - elsif list.to_a.include?(subject) - solution - else - nil - end + nil end end.flatten.compact) end diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index c96519b..cbfeeb4 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -23,20 +23,15 @@ def execute(queryable, solutions:, **options) next end - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements + if object.variable? + # Bind all list entries to this solution, creates an array of solutions + list.to_a.map do |term| + solution.merge(object.to_sym => term) + end + elsif list.to_a.include?(object) solution else - if object.variable? - # Bind all list entries to this solution, creates an array of solutions - list.to_a.map do |term| - solution.merge(object.to_sym => term) - end - elsif list.to_a.include?(object) - solution - else - nil - end + nil end end.flatten.compact) end diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index ed10df2..376b465 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -25,19 +25,14 @@ def execute(queryable, solutions:, **options) log_debug(self.class.const_get(:NAME)) {"list: #{list.to_sxp}, object: #{object.to_sxp}"} next unless validate(list) - if list.to_a.any? {|op| op.variable? && op.unbound?} - # Can't bind list elements - solution - else - lhs = evaluate(list) + lhs = evaluate(list) - if object.variable? - solution.merge(object.to_sym => lhs) - elsif object != lhs - nil - else - solution - end + if object.variable? + solution.merge(object.to_sym => lhs) + elsif object != lhs + nil + else + solution end end.compact) end From 984f08253057f42f69922f4fbf483ac7d1124de1 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 8 Sep 2020 17:06:58 -0700 Subject: [PATCH 102/193] Don't override evaluate on RDF::Node, as it's not necessary, and interferes with SPARQL. --- lib/rdf/n3/extensions.rb | 14 -------------- spec/extensions_spec.rb | 4 ---- spec/list_spec.rb | 4 ---- 3 files changed, 22 deletions(-) diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index 528c3d3..c2cb88a 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -141,20 +141,6 @@ def to_sxp end end - class Node - # Either binds to variable, or returns itself. - # - # @param [RDF::Query::Solution] bindings - # a query solution containing zero or more variable bindings - # @param [Hash{Symbol => Object}] options ({}) - # options passed from query - # @return [RDF::Term] - # def evaluate(bindings, **options); end - def evaluate(bindings, **options) - bindings.fetch(self.id.to_sym, self) - end - end - class Query::Pattern ## # Checks pattern equality against a statement, considering nesting an lists. diff --git a/spec/extensions_spec.rb b/spec/extensions_spec.rb index fe5531f..24966e3 100644 --- a/spec/extensions_spec.rb +++ b/spec/extensions_spec.rb @@ -103,10 +103,6 @@ it "returns itself if not bound" do expect(node.evaluate({})).to eq node end - - it "returns bound value if bound" do - expect(node.evaluate({a: RDF::URI("a")})).to eq RDF::URI("a") - end end end diff --git a/spec/list_spec.rb b/spec/list_spec.rb index 068f533..bfbdf42 100644 --- a/spec/list_spec.rb +++ b/spec/list_spec.rb @@ -629,10 +629,6 @@ expect(constant.evaluate(bindings)).to eq constant end - it "returns bound list if nodes" do - expect(nodes.evaluate(bindings)).to eq constant - end - it "returns bound list if variable" do expect(vars.evaluate(bindings)).to eq constant end From 310b15ed1e8cf7b1e741b77f82370301bdf67319 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 8 Sep 2020 17:15:47 -0700 Subject: [PATCH 103/193] Remove test for whitespace in IRIREF, which is no longer supported. --- spec/reader_spec.rb | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 51fd911..372f10a 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -280,13 +280,6 @@ end end - it "whitespace in uris" do - n3 = %{ <\nhttp://example.org/resource2\n> .} - nt = %{ .} - graph = parse(n3, logger: logger) - expect(graph).to be_equivalent_graph(nt, logger: logger) - end - it "should allow mixed-case language" do n3doc = %(:x2 :p "xyz"@en .) statement = parse(n3doc).statements.first @@ -690,27 +683,6 @@ end end - describe "IRIs" do - { - "with newlines": { - n3: %( - a :testcase . - ), - ttl: %( - a . - ) - }, - }.each do |name, params| - it name do - expected = parse(params[:ttl], base_uri: "http://a/b", logger: false) - expect(parse(params[:n3], base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) - end - end - end - describe "BNodes" do it "should create BNode for identifier with '_' prefix" do n3 = %(@prefix a: . _:a a:p a:v .) From e52dbc678bd880c423f6650bc59427b6fe5f13c0 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 9 Sep 2020 13:12:58 -0700 Subject: [PATCH 104/193] Don't format Nan or INF directly, use full literal form. --- lib/rdf/n3/writer.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 92daf79..834878b 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -283,7 +283,11 @@ def format_literal(literal, **options) when RDF::XSD.boolean, RDF::XSD.integer, RDF::XSD.decimal literal.canonicalize.to_s when RDF::XSD.double - literal.canonicalize.to_s.sub('E', 'e') # Favor lower case exponent + if literal.nan? || literal.infinite? + quoted(literal.value) + "^^#{format_uri(literal.datatype)}" + else + literal.canonicalize.to_s.sub('E', 'e') # Favor lower case exponent + end else text = quoted(literal.value) text << "@#{literal.language}" if literal.has_language? From 47b995819577462b50eaef8319bafb24749a531a Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 9 Sep 2020 13:14:39 -0700 Subject: [PATCH 105/193] Fix math:notEqualTo. --- lib/rdf/n3/algebra/math/notEqualTo.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdf/n3/algebra/math/notEqualTo.rb b/lib/rdf/n3/algebra/math/notEqualTo.rb index fd982a3..b7fc320 100644 --- a/lib/rdf/n3/algebra/math/notEqualTo.rb +++ b/lib/rdf/n3/algebra/math/notEqualTo.rb @@ -19,7 +19,7 @@ class NotEqualTo < SPARQL::Algebra::Operator::Compare # @see RDF::Term#== def apply(term1, term2) log_debug(NAME) { "term1: #{term1.to_sxp} != term2: #{term2.to_sxp} ? #{(term1 == term2).inspect}"} - RDF::Literal(term1 == term2) + RDF::Literal(term1 != term2) end end end From b408c2950be39a819b6721d69f09f5432e076134 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 9 Sep 2020 15:50:56 -0700 Subject: [PATCH 106/193] Provide a ranking of operators based on determining which operands are inputs and outputs and prefering those with fully evaluated variables, over those that have not been evaluated. This is an attempt to manage operator ordering to evaluate variables that are needed as inputs by other operators. --- Gemfile | 4 +- README.md | 36 ++++++------- lib/rdf/n3/algebra.rb | 37 +++++++++----- lib/rdf/n3/algebra/builtin.rb | 27 ++++++++++ lib/rdf/n3/algebra/formula.rb | 50 +++++++++++++++---- lib/rdf/n3/algebra/list_operator.rb | 10 +++- lib/rdf/n3/algebra/literal_operator.rb | 9 ++++ lib/rdf/n3/algebra/log/equalTo.rb | 3 +- lib/rdf/n3/algebra/log/implies.rb | 10 ++++ lib/rdf/n3/algebra/log/notEqualTo.rb | 17 ++++++- lib/rdf/n3/algebra/math/absoluteValue.rb | 8 +++ lib/rdf/n3/algebra/math/cos.rb | 8 +++ lib/rdf/n3/algebra/math/cosh.rb | 8 +++ lib/rdf/n3/algebra/math/equalTo.rb | 1 + lib/rdf/n3/algebra/math/greater_than.rb | 26 ++++++++++ lib/rdf/n3/algebra/math/less_than.rb | 26 ++++++++++ lib/rdf/n3/algebra/math/negation.rb | 9 ++++ lib/rdf/n3/algebra/math/notEqualTo.rb | 3 +- lib/rdf/n3/algebra/math/not_greater_than.rb | 26 ++++++++++ lib/rdf/n3/algebra/math/not_less_than.rb | 26 ++++++++++ lib/rdf/n3/algebra/math/sin.rb | 8 +++ lib/rdf/n3/algebra/math/sinh.rb | 8 +++ lib/rdf/n3/algebra/math/tan.rb | 8 +++ lib/rdf/n3/algebra/math/tanh.rb | 8 +++ lib/rdf/n3/algebra/notImplemented.rb | 2 + lib/rdf/n3/algebra/str/contains.rb | 8 +++ .../n3/algebra/str/containsIgnoringCase.rb | 4 +- lib/rdf/n3/algebra/str/ends_with.rb | 8 +++ lib/rdf/n3/algebra/str/equalIgnoringCase.rb | 1 + lib/rdf/n3/algebra/str/format.rb | 4 +- lib/rdf/n3/algebra/str/greater_than.rb | 25 ++++++++++ lib/rdf/n3/algebra/str/less_than.rb | 25 ++++++++++ lib/rdf/n3/algebra/str/matches.rb | 9 ++++ .../n3/algebra/str/notEqualIgnoringCase.rb | 4 +- lib/rdf/n3/algebra/str/notMatches.rb | 4 +- lib/rdf/n3/algebra/str/not_greater_than.rb | 25 ++++++++++ lib/rdf/n3/algebra/str/not_less_than.rb | 25 ++++++++++ lib/rdf/n3/algebra/str/replace.rb | 10 ++-- lib/rdf/n3/algebra/str/scrape.rb | 10 ++-- lib/rdf/n3/algebra/str/starts_with.rb | 8 +++ lib/rdf/n3/algebra/time/in_seconds.rb | 8 +++ lib/rdf/n3/reader.rb | 3 ++ lib/rdf/n3/reasoner.rb | 12 ++--- spec/suite_reasoner_spec.rb | 2 +- 44 files changed, 504 insertions(+), 69 deletions(-) create mode 100644 lib/rdf/n3/algebra/builtin.rb create mode 100644 lib/rdf/n3/algebra/math/greater_than.rb create mode 100644 lib/rdf/n3/algebra/math/less_than.rb create mode 100644 lib/rdf/n3/algebra/math/not_greater_than.rb create mode 100644 lib/rdf/n3/algebra/math/not_less_than.rb create mode 100644 lib/rdf/n3/algebra/str/contains.rb create mode 100644 lib/rdf/n3/algebra/str/ends_with.rb create mode 100644 lib/rdf/n3/algebra/str/greater_than.rb create mode 100644 lib/rdf/n3/algebra/str/less_than.rb create mode 100644 lib/rdf/n3/algebra/str/matches.rb create mode 100644 lib/rdf/n3/algebra/str/not_greater_than.rb create mode 100644 lib/rdf/n3/algebra/str/not_less_than.rb create mode 100644 lib/rdf/n3/algebra/str/starts_with.rb diff --git a/Gemfile b/Gemfile index 2e90f0b..425f827 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,8 @@ source "https://rubygems.org" gemspec gem "rdf", git: "https://github.com/ruby-rdf/rdf", branch: "develop" +gem 'sparql', git: "https://github.com/ruby-rdf/sparql", branch: "develop" +gem 'sxp', git: "https://github.com/dryruby/sxp.rb", branch: "develop" group :development do gem "ebnf", git: "https://github.com/dryruby/ebnf", branch: "develop" @@ -14,8 +16,6 @@ group :development do 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 :development, :test do diff --git a/README.md b/README.md index cb029b4..9058933 100755 --- a/README.md +++ b/README.md @@ -56,7 +56,9 @@ Experimental N3 reasoning is supported. Instantiate a reasoner from a dataset: 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: +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. + +When dispatching built-in operators, precedence is given to operators whos operands are fully evaluated, followed by those having only variable output operands, followed by those having the fewest operands. Operators are evaluated until either no solutions are derived, or all operators have been completed. Reasoning is discussed in the [Design Issues][] document. @@ -76,7 +78,7 @@ Reasoning is discussed in the [Design Issues][] document. * `log:equalTo` (See {RDF::N3::Algebra::Log::EqualTo}) * `log:implies` (See {RDF::N3::Algebra::Log::Implies}) * `log:includes` (See {RDF::N3::Algebra::Log::Includes}) - * `log:notEqualTo` (not implemented yet - See {RDF::N3::Algebra::Log::NotEqualTo}) + * `log:notEqualTo` (See {RDF::N3::Algebra::Log::NotEqualTo}) * `log:notIncludes` (not implemented yet - See {RDF::N3::Algebra::Log::NotIncludes}) * `log:outputString` (not implemented yet - See {RDF::N3::Algebra::Log::OutputString}) @@ -93,16 +95,16 @@ Reasoning is discussed in the [Design Issues][] document. * `math:cosh` (See {RDF::N3::Algebra::Math::CosH}) * `math:cos` (See {RDF::N3::Algebra::Math::Cos}) * `math:difference` (See {RDF::N3::Algebra::Math::Difference}) - * `math:equalTo` (See {SPARQL::Algebra::Operator::Equal}) + * `math:equalTo` (See {RDF::N3::Algebra::Math::Equal}) * `math:exponentiation` (See {RDF::N3::Algebra::Math::Exponentiation}) * `math:floor` (See {RDF::N3::Algebra::Math::Floor}) - * `math:greaterThan` (See {SPARQL::Algebra::Operator::GreaterThan}) + * `math:greaterThan` (See {RDF::N3::Algebra::Math::GreaterThan}) * `math:integerQuotient` (See {RDF::N3::Algebra::Math::IntegerQuotient}) - * `math:lessThan` (See {SPARQL::Algebra::Operator::LessThan}) - * `math:negation` (See {SPARQL::Algebra::Operator::Negate}) - * `math:notEqualTo` (See {SPARQL::Algebra::Operator::NotEqual}) - * `math:notGreaterThan` (See {SPARQL::Algebra::Operator::LessThanOrEqual}) - * `math:notLessThan` (See {SPARQL::Algebra::Operator::GreaterThanOrEqual}) + * `math:lessThan` (See {RDF::N3::Algebra::Math::LessThan}) + * `math:negation` (See {RDF::N3::Algebra::Math::Negate}) + * `math:notEqualTo` (See {RDF::N3::Algebra::Math::NotEqual}) + * `math:notGreaterThan` (See {RDF::N3::Algebra::Math::NotGreaterThan}) + * `math:notLessThan` (See {RDF::N3::Algebra::Math::NotLessThan}) * `math:product` (See {RDF::N3::Algebra::Math::Product}) * `math:quotient` (See {RDF::N3::Algebra::Math::Quotient}) * `math:remainder` (See {RDF::N3::Algebra::Math::Remainder}) @@ -116,21 +118,21 @@ Reasoning is discussed in the [Design Issues][] document. #### RDF String vocabulary * `string:concatenation` (See {RDF::N3::Algebra::Str::Concatenation}) - * `string:contains` (See {SPARQL::Algebra::Operator::Contains}) + * `string:contains` (See {RDF::N3::Algebra::Str::Contains}) * `string:containsIgnoringCase` (See {RDF::N3::Algebra::Str::ContainsIgnoringCase}) - * `string:endsWith` (See {SPARQL::Algebra::Operator::StrEnds}) + * `string:endsWith` (See {RDF::N3::Algebra::Str::EndsWith}) * `string:equalIgnoringCase` (See {RDF::N3::Algebra::Str::EqualIgnoringCase}) * `string:format` (See {RDF::N3::Algebra::Str::Format}) - * `string:greaterThan` (See {SPARQL::Algebra::Operator::GreaterThan}) - * `string:lessThan` (See {SPARQL::Algebra::Operator::LessThan}) - * `string:matches` (See {SPARQL::Algebra::Operator::Regex}) + * `string:greaterThan` (See {RDF::N3::Algebra::Str::GreaterThan}) + * `string:lessThan` (See {RDF::N3::Algebra::Str::LessThan}) + * `string:matches` (See {RDF::N3::Algebra::Str::Matches}) * `string:notEqualIgnoringCase` (See {RDF::N3::Algebra::Str::NotEqualIgnoringCase}) - * `string:notGreaterThan` (See {SPARQL::Algebra::Operator::LessThanOrEqual}) - * `string:notLessThan` (See {SPARQL::Algebra::Operator::GreaterThanOrEqual}) + * `string:notGreaterThan` (See {RDF::N3::Algebra::Str::NotGreaterThan}) + * `string:notLessThan` (See {RDF::N3::Algebra::Str::NotLessThan}) * `string:notMatches` (See {RDF::N3::Algebra::Str::NotMatches}) * `string:replace` (See {RDF::N3::Algebra::Str::Replace}) * `string:scrape` (See {RDF::N3::Algebra::Str::Scrape}) - * `string:startsWith` (See {SPARQL::Algebra::Operator::StrStarts}) + * `string:startsWith` (See {RDF::N3::Algebra::Str::StartsWith}) #### RDF Time vocabulary <> diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 7416b40..7a84072 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -7,6 +7,7 @@ module RDF::N3 # # @author [Gregg Kellogg](http://greggkellogg.net/) module Algebra + autoload :Builtin, 'rdf/n3/algebra/builtin' autoload :Formula, 'rdf/n3/algebra/formula' autoload :ListOperator, 'rdf/n3/algebra/list_operator' autoload :LiteralOperator, 'rdf/n3/algebra/literal_operator' @@ -47,9 +48,13 @@ module Math autoload :EqualTo, 'rdf/n3/algebra/math/equalTo' autoload :Exponentiation, 'rdf/n3/algebra/math/exponentiation' autoload :Floor, 'rdf/n3/algebra/math/floor' + autoload :GreaterThan, 'rdf/n3/algebra/math/greater_than' autoload :IntegerQuotient, 'rdf/n3/algebra/math/integerQuotient' + autoload :LessThan, 'rdf/n3/algebra/math/less_than' autoload :Negation, 'rdf/n3/algebra/math/negation' autoload :NotEqualTo, 'rdf/n3/algebra/math/notEqualTo' + autoload :NotGreaterThan, 'rdf/n3/algebra/math/not_greater_than' + autoload :NotLessThan, 'rdf/n3/algebra/math/not_less_than' autoload :Product, 'rdf/n3/algebra/math/product' autoload :Quotient, 'rdf/n3/algebra/math/quotient' autoload :Remainder, 'rdf/n3/algebra/math/remainder' @@ -63,13 +68,21 @@ module Math 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/ends_with' autoload :EqualIgnoringCase, 'rdf/n3/algebra/str/equalIgnoringCase' autoload :Format, 'rdf/n3/algebra/str/format' + autoload :GreaterThan, 'rdf/n3/algebra/str/greater_than' + autoload :LessThan, 'rdf/n3/algebra/str/less_than' + autoload :Matches, 'rdf/n3/algebra/str/matches' autoload :NotEqualIgnoringCase, 'rdf/n3/algebra/str/notEqualIgnoringCase' + autoload :NotGreaterThan, 'rdf/n3/algebra/str/not_greater_than' + autoload :NotLessThan, 'rdf/n3/algebra/str/not_less_than' 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/starts_with' end module Time @@ -119,13 +132,13 @@ def for(uri) RDF::N3::Math.equalTo => Math::EqualTo, RDF::N3::Math.exponentiation => Math::Exponentiation, RDF::N3::Math.floor => Math::Floor, - RDF::N3::Math.greaterThan => SPARQL::Algebra::Operator::GreaterThan, + RDF::N3::Math.greaterThan => Math::GreaterThan, RDF::N3::Math.integerQuotient => Math::IntegerQuotient, - RDF::N3::Math.lessThan => SPARQL::Algebra::Operator::LessThan, + RDF::N3::Math.lessThan => Math::LessThan, RDF::N3::Math.negation => Math::Negation, RDF::N3::Math.notEqualTo => Math::NotEqualTo, - RDF::N3::Math.notGreaterThan => SPARQL::Algebra::Operator::LessThanOrEqual, - RDF::N3::Math.notLessThan => SPARQL::Algebra::Operator::GreaterThanOrEqual, + 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, @@ -137,22 +150,22 @@ def for(uri) RDF::N3::Math[:sum] => Math::Sum, RDF::N3::Str.concatenation => Str::Concatenation, - RDF::N3::Str.contains => SPARQL::Algebra::Operator::Contains, + RDF::N3::Str.contains => Str::Contains, RDF::N3::Str.containsIgnoringCase => Str::ContainsIgnoringCase, RDF::N3::Str.containsRoughly => NotImplemented, - RDF::N3::Str.endsWith => SPARQL::Algebra::Operator::StrEnds, + RDF::N3::Str.endsWith => Str::EndsWith, RDF::N3::Str.equalIgnoringCase => Str::EqualIgnoringCase, RDF::N3::Str.format => Str::Format, - RDF::N3::Str.greaterThan => SPARQL::Algebra::Operator::GreaterThan, - RDF::N3::Str.lessThan => SPARQL::Algebra::Operator::LessThan, - RDF::N3::Str.matches => SPARQL::Algebra::Operator::Regex, + 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 => SPARQL::Algebra::Operator::LessThanOrEqual, - RDF::N3::Str.notLessThan => SPARQL::Algebra::Operator::GreaterThanOrEqual, + 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 => SPARQL::Algebra::Operator::StrStarts, + RDF::N3::Str.startsWith => Str::StartsWith, RDF::N3::Time.dayOfWeek => Time::DayOfWeek, RDF::N3::Time.day => Time::Day, diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb new file mode 100644 index 0000000..6e92e36 --- /dev/null +++ b/lib/rdf/n3/algebra/builtin.rb @@ -0,0 +1,27 @@ +require 'rdf/n3' + +module RDF::N3::Algebra + ## + # Behavior for N3 builtin operators + module Builtin + ## + # Determine ordering for running built-in operator considering if subject or object is varaible and considered an input or an output. Accepts a solution set to determine if variable inputs are bound. + # + # @param [RDF::Query::Solutions] solutions + # @return [Integer] rake for ordering, lower numbers have fewer unbound output variables. + def rank(solutions) + vars = input_operand.vars - solutions.variable_names + # The rank is the remaining unbound variables + vars.count + end + + ## + # Return subject or object operand, or both, depending on which is considered an input. + # + # @return [RDF::Term] + def input_operand + # By default, return the merger of input and output operands + RDF::N3::List.new(values: operands) + end + end +end diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 9e8082e..71f4efc 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -1,4 +1,4 @@ -require 'rdf' +require 'rdf/n3' module RDF::N3::Algebra # @@ -9,6 +9,7 @@ class Formula < SPARQL::Algebra::Operator include SPARQL::Algebra::Update include RDF::Enumerable include RDF::Util::Logger + include RDF::N3::Algebra::Builtin attr_accessor :query @@ -55,16 +56,40 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new # Use our solutions for sub-ops # Join solutions from other operands + # + # * Order operands by those having inputs which are constant or bound. + # * Run built-ins with indeterminant inputs (two-way) until any produces non-empty solutions, and then run remaining built-ins until exhasted or finished. + # * Re-calculate inputs with bound inputs after each built-in is run. log_depth do - sub_ops.each do |op| - @solutions = if op.executable? - op.execute(queryable, solutions: @solutions) - else # Evaluatable - @solutions.all? {|s| op.evaluate(s.bindings) == RDF::Literal::TRUE} ? - @solutions : - RDF::Query::Solutions.new + # Iterate over sub_ops using evaluation heuristic + ops = sub_ops.sort_by {|op| op.rank(@solutions)} + while !ops.empty? + last_op = nil + ops.each do |op| + log_debug("(formula built-in)") {op.to_sxp} + solutions = if op.executable? + op.execute(queryable, solutions: @solutions) + else # Evaluatable + @solutions.all? {|s| op.evaluate(s.bindings) == RDF::Literal::TRUE} ? + @solutions : + RDF::Query::Solutions.new + end + log_debug("(formula intermediate solutions)") {"after #{op.to_sxp}: " + @solutions.to_sxp} + # If there are no solutions, try the next one, until we either run out of operations, or we have solutions + next if solutions.empty? + last_op = op + @solutions = solutions + break + end + + # If there is no last_op, there are no solutions. + unless last_op + @solutions = RDF::Query::Solutions.new + break end - log_debug("(formula intermediate solutions)") {"after #{op.to_sxp}: " + @solutions.to_sxp} + + # Remove op from list, and re-order remaining ops. + ops = (ops - [last_op]).sort_by {|op| op.rank(@solutions)} end end log_info("(formula sub-op solutions)") {@solutions.to_sxp} @@ -82,6 +107,13 @@ def formula? true end + ## + # Return the variables contained within this formula + # @return [Array] + def vars + operands.map(&:vars).flatten.compact + end + ## # Yields each statement from this formula bound to previously determined solutions. # diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index 376b465..16c9d0b 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -6,6 +6,7 @@ class ListOperator < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Update include RDF::Enumerable include RDF::Util::Logger + include RDF::N3::Algebra::Builtin ## # The operator takes a list and provides a mechanism for subclasses to operate over (and validate) that list argument. @@ -21,7 +22,6 @@ def execute(queryable, solutions:, **options) list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) object = operand(1).evaluate(solution.bindings) || operand(1) - log_debug(self.class.const_get(:NAME)) {"list: #{list.to_sxp}, object: #{object.to_sxp}"} next unless validate(list) @@ -37,6 +37,14 @@ def execute(queryable, solutions:, **options) end.compact) end + ## + # Input is generically the subject + # + # @return [RDF::Term] + def input_operand + operand(0) + end + ## # Subclasses implement `evaluate`. # diff --git a/lib/rdf/n3/algebra/literal_operator.rb b/lib/rdf/n3/algebra/literal_operator.rb index fbb9b21..8fd5735 100644 --- a/lib/rdf/n3/algebra/literal_operator.rb +++ b/lib/rdf/n3/algebra/literal_operator.rb @@ -6,6 +6,7 @@ class LiteralOperator < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Update include RDF::Enumerable include RDF::Util::Logger + include RDF::N3::Algebra::Builtin ## # The operator takes a literal and provides a mechanism for subclasses to operate over (and validate) that argument. @@ -45,6 +46,14 @@ def execute(queryable, solutions:, **options) end.compact) end + ## + # Input is generically the subject + # + # @return [RDF::Term] + def input_operand + operand(0) + end + ## # Subclasses implement `evaluate`. # diff --git a/lib/rdf/n3/algebra/log/equalTo.rb b/lib/rdf/n3/algebra/log/equalTo.rb index e93a7e5..d07e9d1 100644 --- a/lib/rdf/n3/algebra/log/equalTo.rb +++ b/lib/rdf/n3/algebra/log/equalTo.rb @@ -1,7 +1,8 @@ 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 EqualTo < SPARQL::Algebra::Operator::Binary + class EqualTo < SPARQL::Algebra::Operator::SameTerm + include RDF::N3::Algebra::Builtin NAME = :logEqualTo end end diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 2ef772c..386e058 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -10,6 +10,7 @@ class Implies < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Update include RDF::Enumerable include RDF::Util::Logger + include RDF::N3::Algebra::Builtin NAME = :logImplies @@ -39,6 +40,15 @@ def execute(queryable, solutions:, **options) solutions end + ## + # Input is the subject + # + # @return [RDF::Term] + def input_operand + # By default, return the merger of input and output operands + operand(0) + 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. # diff --git a/lib/rdf/n3/algebra/log/notEqualTo.rb b/lib/rdf/n3/algebra/log/notEqualTo.rb index 8346b6c..eaa1aff 100644 --- a/lib/rdf/n3/algebra/log/notEqualTo.rb +++ b/lib/rdf/n3/algebra/log/notEqualTo.rb @@ -1,7 +1,22 @@ module RDF::N3::Algebra::Log ## # Equality in this sense is actually the same URI. A cwm built-in logical operator. - class NotEqualTo < SPARQL::Algebra::Operator::Binary + class NotEqualTo < SPARQL::Algebra::Operator::SameTerm + include RDF::N3::Algebra::Builtin NAME = :logNotEqualTo + + ## + # Returns `true` if the operands are not the same RDF term; returns + # `false` otherwise. + # + # @param [RDF::Term] term1 + # an RDF term + # @param [RDF::Term] term2 + # an RDF term + # @return [RDF::Literal::Boolean] `true` or `false` + # @raise [TypeError] if either operand is unbound + def apply(term1, term2) + RDF::Literal(!term1.eql?(term2)) + end end end diff --git a/lib/rdf/n3/algebra/math/absoluteValue.rb b/lib/rdf/n3/algebra/math/absoluteValue.rb index 922c11c..d7734ba 100644 --- a/lib/rdf/n3/algebra/math/absoluteValue.rb +++ b/lib/rdf/n3/algebra/math/absoluteValue.rb @@ -20,5 +20,13 @@ def evaluate(resource, position:) resource end end + + ## + # Input is either the subject or object + # + # @return [RDF::Term] + def input_operand + RDF::N3::List.new(values: operands) + end end end diff --git a/lib/rdf/n3/algebra/math/cos.rb b/lib/rdf/n3/algebra/math/cos.rb index 2a41e04..ae74096 100644 --- a/lib/rdf/n3/algebra/math/cos.rb +++ b/lib/rdf/n3/algebra/math/cos.rb @@ -29,5 +29,13 @@ def evaluate(resource, position:) nil end end + + ## + # Input is either the subject or object + # + # @return [RDF::Term] + def input_operand + RDF::N3::List.new(values: operands) + end end end diff --git a/lib/rdf/n3/algebra/math/cosh.rb b/lib/rdf/n3/algebra/math/cosh.rb index 556c862..7edfc06 100644 --- a/lib/rdf/n3/algebra/math/cosh.rb +++ b/lib/rdf/n3/algebra/math/cosh.rb @@ -24,5 +24,13 @@ def evaluate(resource, position:) nil end end + + ## + # Input is either the subject or object + # + # @return [RDF::Term] + def input_operand + RDF::N3::List.new(values: operands) + end end end diff --git a/lib/rdf/n3/algebra/math/equalTo.rb b/lib/rdf/n3/algebra/math/equalTo.rb index beb6d54..ffa1bd1 100644 --- a/lib/rdf/n3/algebra/math/equalTo.rb +++ b/lib/rdf/n3/algebra/math/equalTo.rb @@ -3,6 +3,7 @@ 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::Compare include RDF::Util::Logger + include RDF::N3::Algebra::Builtin NAME = :'=' diff --git a/lib/rdf/n3/algebra/math/greater_than.rb b/lib/rdf/n3/algebra/math/greater_than.rb new file mode 100644 index 0000000..697aaf8 --- /dev/null +++ b/lib/rdf/n3/algebra/math/greater_than.rb @@ -0,0 +1,26 @@ +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::Compare + include RDF::Util::Logger + include RDF::N3::Algebra::Builtin + + NAME = :'>' + + ## + # Returns TRUE if `term1` is greater than `term2`. + # + # @param [RDF::Term] term1 + # an RDF term + # @param [RDF::Term] term2 + # an RDF term + # @return [RDF::Literal::Boolean] `true` or `false` + # @raise [TypeError] if either operand is not an RDF term or operands are not comperable + # + # @see RDF::Term#== + def apply(term1, term2) + log_debug(NAME) { "term1: #{term1.to_sxp} > term2: #{term2.to_sxp} ? #{(term1 > term2).inspect}"} + RDF::Literal(term1 > term2) + end + end +end diff --git a/lib/rdf/n3/algebra/math/less_than.rb b/lib/rdf/n3/algebra/math/less_than.rb new file mode 100644 index 0000000..040d41e --- /dev/null +++ b/lib/rdf/n3/algebra/math/less_than.rb @@ -0,0 +1,26 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is less than the number of which the object is a string representation. + class LessThan < SPARQL::Algebra::Operator::Compare + include RDF::Util::Logger + include RDF::N3::Algebra::Builtin + + NAME = :'<' + + ## + # Returns TRUE if `term1` is less than `term2`. + # + # @param [RDF::Term] term1 + # an RDF term + # @param [RDF::Term] term2 + # an RDF term + # @return [RDF::Literal::Boolean] `true` or `false` + # @raise [TypeError] if either operand is not an RDF term or operands are not comperable + # + # @see RDF::Term#== + def apply(term1, term2) + log_debug(NAME) { "term1: #{term1.to_sxp} < term2: #{term2.to_sxp} ? #{(term1 < term2).inspect}"} + RDF::Literal(term1 < term2) + end + end +end diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb index bcd126c..435b4f9 100644 --- a/lib/rdf/n3/algebra/math/negation.rb +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -6,6 +6,7 @@ class Negation < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Update include RDF::Enumerable include RDF::Util::Logger + include RDF::N3::Algebra::Builtin NAME = :mathNegation @@ -25,5 +26,13 @@ def evaluate(resource, position:) nil end end + + ## + # Input is either the subject or object + # + # @return [RDF::Term] + def input_operand + RDF::N3::List.new(values: operands) + end end end diff --git a/lib/rdf/n3/algebra/math/notEqualTo.rb b/lib/rdf/n3/algebra/math/notEqualTo.rb index b7fc320..f6f0beb 100644 --- a/lib/rdf/n3/algebra/math/notEqualTo.rb +++ b/lib/rdf/n3/algebra/math/notEqualTo.rb @@ -3,6 +3,7 @@ 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::Compare include RDF::Util::Logger + include RDF::N3::Algebra::Builtin NAME = :'!=' @@ -18,7 +19,7 @@ class NotEqualTo < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) - log_debug(NAME) { "term1: #{term1.to_sxp} != term2: #{term2.to_sxp} ? #{(term1 == term2).inspect}"} + log_debug(NAME) { "term1: #{term1.to_sxp} != term2: #{term2.to_sxp} ? #{(term1 != term2).inspect}"} RDF::Literal(term1 != term2) end end diff --git a/lib/rdf/n3/algebra/math/not_greater_than.rb b/lib/rdf/n3/algebra/math/not_greater_than.rb new file mode 100644 index 0000000..ca325d4 --- /dev/null +++ b/lib/rdf/n3/algebra/math/not_greater_than.rb @@ -0,0 +1,26 @@ +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::Compare + include RDF::Util::Logger + include RDF::N3::Algebra::Builtin + + NAME = :'<=' + + ## + # Returns TRUE if `term1` is less than or equal to `term2`. + # + # @param [RDF::Term] term1 + # an RDF term + # @param [RDF::Term] term2 + # an RDF term + # @return [RDF::Literal::Boolean] `true` or `false` + # @raise [TypeError] if either operand is not an RDF term or operands are not comperable + # + # @see RDF::Term#== + def apply(term1, term2) + log_debug(NAME) { "term1: #{term1.to_sxp} <= term2: #{term2.to_sxp} ? #{(term1 <= term2).inspect}"} + RDF::Literal(term1 <= term2) + end + end +end diff --git a/lib/rdf/n3/algebra/math/not_less_than.rb b/lib/rdf/n3/algebra/math/not_less_than.rb new file mode 100644 index 0000000..bb527aa --- /dev/null +++ b/lib/rdf/n3/algebra/math/not_less_than.rb @@ -0,0 +1,26 @@ +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::Compare + include RDF::Util::Logger + include RDF::N3::Algebra::Builtin + + NAME = :'>=' + + ## + # Returns TRUE if `term1` is greater than or equal to `term2`. + # + # @param [RDF::Term] term1 + # an RDF term + # @param [RDF::Term] term2 + # an RDF term + # @return [RDF::Literal::Boolean] `true` or `false` + # @raise [TypeError] if either operand is not an RDF term or operands are not comperable + # + # @see RDF::Term#== + def apply(term1, term2) + log_debug(NAME) { "term1: #{term1.to_sxp} >= term2: #{term2.to_sxp} ? #{(term1 >= term2).inspect}"} + RDF::Literal(term1 >= term2) + end + end +end diff --git a/lib/rdf/n3/algebra/math/sin.rb b/lib/rdf/n3/algebra/math/sin.rb index 0f343a2..08f70f9 100644 --- a/lib/rdf/n3/algebra/math/sin.rb +++ b/lib/rdf/n3/algebra/math/sin.rb @@ -24,5 +24,13 @@ def evaluate(resource, position:) nil end end + + ## + # Input is either the subject or object + # + # @return [RDF::Term] + def input_operand + RDF::N3::List.new(values: operands) + end end end diff --git a/lib/rdf/n3/algebra/math/sinh.rb b/lib/rdf/n3/algebra/math/sinh.rb index d7800eb..5990aaf 100644 --- a/lib/rdf/n3/algebra/math/sinh.rb +++ b/lib/rdf/n3/algebra/math/sinh.rb @@ -24,5 +24,13 @@ def evaluate(resource, position:) nil end end + + ## + # Input is either the subject or object + # + # @return [RDF::Term] + def input_operand + RDF::N3::List.new(values: operands) + end end end diff --git a/lib/rdf/n3/algebra/math/tan.rb b/lib/rdf/n3/algebra/math/tan.rb index 06207c8..8f122c9 100644 --- a/lib/rdf/n3/algebra/math/tan.rb +++ b/lib/rdf/n3/algebra/math/tan.rb @@ -24,5 +24,13 @@ def evaluate(resource, position:) nil end end + + ## + # Input is either the subject or object + # + # @return [RDF::Term] + def input_operand + RDF::N3::List.new(values: operands) + end end end diff --git a/lib/rdf/n3/algebra/math/tanh.rb b/lib/rdf/n3/algebra/math/tanh.rb index 1601864..dbbdd21 100644 --- a/lib/rdf/n3/algebra/math/tanh.rb +++ b/lib/rdf/n3/algebra/math/tanh.rb @@ -24,5 +24,13 @@ def evaluate(resource, position:) nil end end + + ## + # Input is either the subject or object + # + # @return [RDF::Term] + def input_operand + RDF::N3::List.new(values: operands) + end end end diff --git a/lib/rdf/n3/algebra/notImplemented.rb b/lib/rdf/n3/algebra/notImplemented.rb index d37bbf8..38bff76 100644 --- a/lib/rdf/n3/algebra/notImplemented.rb +++ b/lib/rdf/n3/algebra/notImplemented.rb @@ -4,6 +4,8 @@ module RDF::N3::Algebra # # A Notation3 Formula combines a graph with a BGP query. class NotImplemented < SPARQL::Algebra::Operator + include RDF::N3::Algebra::Builtin + def initialize(*args, predicate:, **options) raise NotImplementedError, "The #{predicate} operator is not implemented" 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..d8d89be --- /dev/null +++ b/lib/rdf/n3/algebra/str/contains.rb @@ -0,0 +1,8 @@ +module RDF::N3::Algebra::Str + # True iff the subject string contains the object string. + class Contains < SPARQL::Algebra::Operator::Contains + include RDF::N3::Algebra::Builtin + + NAME = :strContains + end +end diff --git a/lib/rdf/n3/algebra/str/containsIgnoringCase.rb b/lib/rdf/n3/algebra/str/containsIgnoringCase.rb index e66de41..076a51f 100644 --- a/lib/rdf/n3/algebra/str/containsIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/containsIgnoringCase.rb @@ -1,13 +1,13 @@ 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 SPARQL::Algebra::Evaluatable include RDF::Util::Logger + include RDF::N3::Algebra::Builtin NAME = :strContainsIgnoringCase ## - # True iff the subject string contains the object string, with the comparison done ignoring the difference between upper case and lower case characters. - # # @param [RDF::Literal] left # a literal # @param [RDF::Literal] right diff --git a/lib/rdf/n3/algebra/str/ends_with.rb b/lib/rdf/n3/algebra/str/ends_with.rb new file mode 100644 index 0000000..1954e8e --- /dev/null +++ b/lib/rdf/n3/algebra/str/ends_with.rb @@ -0,0 +1,8 @@ +module RDF::N3::Algebra::Str + # True iff the subject string ends with the object string. + class EndsWith < SPARQL::Algebra::Operator::StrEnds + include RDF::N3::Algebra::Builtin + + NAME = :strEndsWith + end +end diff --git a/lib/rdf/n3/algebra/str/equalIgnoringCase.rb b/lib/rdf/n3/algebra/str/equalIgnoringCase.rb index 22eb7aa..3e605cb 100644 --- a/lib/rdf/n3/algebra/str/equalIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/equalIgnoringCase.rb @@ -2,6 +2,7 @@ module RDF::N3::Algebra::Str class EqualIgnoringCase < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Evaluatable include RDF::Util::Logger + include RDF::N3::Algebra::Builtin NAME = :strEqualIgnoringCase diff --git a/lib/rdf/n3/algebra/str/format.rb b/lib/rdf/n3/algebra/str/format.rb index 5bc81e5..a0e83bc 100644 --- a/lib/rdf/n3/algebra/str/format.rb +++ b/lib/rdf/n3/algebra/str/format.rb @@ -1,10 +1,10 @@ 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 < RDF::N3::Algebra::ListOperator + include RDF::N3::Algebra::Builtin NAME = :strFormat ## - # 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. - # # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate diff --git a/lib/rdf/n3/algebra/str/greater_than.rb b/lib/rdf/n3/algebra/str/greater_than.rb new file mode 100644 index 0000000..809e5a2 --- /dev/null +++ b/lib/rdf/n3/algebra/str/greater_than.rb @@ -0,0 +1,25 @@ +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 SPARQL::Algebra::Evaluatable + include RDF::Util::Logger + include RDF::N3::Algebra::Builtin + + NAME = :strGreaterThan + + ## + # @param [RDF::Literal] left + # a literal + # @param [RDF::Literal] right + # a literal + # @return [RDF::Literal::Boolean] + def apply(left, right) + case + when !left.compatible?(right) + log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} + when left > right then RDF::Literal::TRUE + else RDF::Literal::FALSE + end + end + end +end diff --git a/lib/rdf/n3/algebra/str/less_than.rb b/lib/rdf/n3/algebra/str/less_than.rb new file mode 100644 index 0000000..87c8032 --- /dev/null +++ b/lib/rdf/n3/algebra/str/less_than.rb @@ -0,0 +1,25 @@ +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 SPARQL::Algebra::Evaluatable + include RDF::Util::Logger + include RDF::N3::Algebra::Builtin + + NAME = :strLessThan + + ## + # @param [RDF::Literal] left + # a literal + # @param [RDF::Literal] right + # a literal + # @return [RDF::Literal::Boolean] + def apply(left, right) + case + when !left.compatible?(right) + log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} + when left < right then RDF::Literal::TRUE + else RDF::Literal::FALSE + end + end + 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..14d1711 --- /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::Regex + include RDF::N3::Algebra::Builtin + + NAME = :strMatches + end +end diff --git a/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb b/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb index 5f4a857..e228a1a 100644 --- a/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb @@ -1,13 +1,13 @@ 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 SPARQL::Algebra::Evaluatable include RDF::Util::Logger + include RDF::N3::Algebra::Builtin NAME = :strNotEqualIgnoringCase ## - # True iff the subject string is the NOT same as object string ignoring differences between upper and lower case. - # # @param [RDF::Literal] left # a literal # @param [RDF::Literal] right diff --git a/lib/rdf/n3/algebra/str/notMatches.rb b/lib/rdf/n3/algebra/str/notMatches.rb index 88f7f9d..e84ae2b 100644 --- a/lib/rdf/n3/algebra/str/notMatches.rb +++ b/lib/rdf/n3/algebra/str/notMatches.rb @@ -1,13 +1,13 @@ module RDF::N3::Algebra::Str + # The subject string; the object 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 SPARQL::Algebra::Evaluatable include RDF::Util::Logger + include RDF::N3::Algebra::Builtin NAME = :strNotMatches ## - # The subject string; the object is a regular expression in the perl, python style. It is true iff the string does NOT match the regexp. - # # @param [RDF::Literal] text # a simple literal # @param [RDF::Literal] pattern diff --git a/lib/rdf/n3/algebra/str/not_greater_than.rb b/lib/rdf/n3/algebra/str/not_greater_than.rb new file mode 100644 index 0000000..aa7bfd5 --- /dev/null +++ b/lib/rdf/n3/algebra/str/not_greater_than.rb @@ -0,0 +1,25 @@ +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 SPARQL::Algebra::Evaluatable + include RDF::Util::Logger + include RDF::N3::Algebra::Builtin + + NAME = :strNotGreaterThan + + ## + # @param [RDF::Literal] left + # a literal + # @param [RDF::Literal] right + # a literal + # @return [RDF::Literal::Boolean] + def apply(left, right) + case + when !left.compatible?(right) + log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} + when left <= right then RDF::Literal::TRUE + else RDF::Literal::FALSE + end + end + end +end diff --git a/lib/rdf/n3/algebra/str/not_less_than.rb b/lib/rdf/n3/algebra/str/not_less_than.rb new file mode 100644 index 0000000..2da3364 --- /dev/null +++ b/lib/rdf/n3/algebra/str/not_less_than.rb @@ -0,0 +1,25 @@ +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 SPARQL::Algebra::Evaluatable + include RDF::Util::Logger + include RDF::N3::Algebra::Builtin + + NAME = :strNotLessThan + + ## + # @param [RDF::Literal] left + # a literal + # @param [RDF::Literal] right + # a literal + # @return [RDF::Literal::Boolean] + def apply(left, right) + case + when !left.compatible?(right) + log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} + when left >= right then RDF::Literal::TRUE + else RDF::Literal::FALSE + end + end + end +end diff --git a/lib/rdf/n3/algebra/str/replace.rb b/lib/rdf/n3/algebra/str/replace.rb index 347177b..58e8144 100644 --- a/lib/rdf/n3/algebra/str/replace.rb +++ b/lib/rdf/n3/algebra/str/replace.rb @@ -1,13 +1,13 @@ 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 replaced string. + # + # @example + # ("fofof bar", "of", "baz") string:replace "fbazbaz bar" class Replace < RDF::N3::Algebra::ListOperator + include RDF::N3::Algebra::Builtin NAME = :strReplace ## - # 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 replaced string. - # - # @example - # ("fofof bar", "of", "baz") string:replace "fbazbaz bar" - # # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate diff --git a/lib/rdf/n3/algebra/str/scrape.rb b/lib/rdf/n3/algebra/str/scrape.rb index 27533d8..692fe86 100644 --- a/lib/rdf/n3/algebra/str/scrape.rb +++ b/lib/rdf/n3/algebra/str/scrape.rb @@ -1,13 +1,13 @@ 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 the part of the first string which matches the group. + # + # @example + # ("abcdef" "ab(..)ef") string:scrape "cd" class Scrape < RDF::N3::Algebra::ListOperator + include RDF::N3::Algebra::Builtin NAME = :strScrape ## - # 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 the part of the first string which matches the group. - # - # @example - # ("abcdef" "ab(..)ef") string:scrape "cd" - # # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate diff --git a/lib/rdf/n3/algebra/str/starts_with.rb b/lib/rdf/n3/algebra/str/starts_with.rb new file mode 100644 index 0000000..4bc1291 --- /dev/null +++ b/lib/rdf/n3/algebra/str/starts_with.rb @@ -0,0 +1,8 @@ +module RDF::N3::Algebra::Str + # True iff the subject string starts with the object string. + class StartsWith < SPARQL::Algebra::Operator::StrStarts + include RDF::N3::Algebra::Builtin + + NAME = :strStartsWith + end +end diff --git a/lib/rdf/n3/algebra/time/in_seconds.rb b/lib/rdf/n3/algebra/time/in_seconds.rb index 6bb759d..9d4e3fc 100644 --- a/lib/rdf/n3/algebra/time/in_seconds.rb +++ b/lib/rdf/n3/algebra/time/in_seconds.rb @@ -43,5 +43,13 @@ def valid?(subject, object) log_error(NAME) {"subject or object are not literals: #{subject.inspect}, #{object.inspect}"} false end + + ## + # Return both subject and object operands. + # + # @return [RDF::Term] + def input_operand + RDF::N3::List.new(values: operands) + end end end diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 9e8d56d..cbf7874 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -85,6 +85,9 @@ def initialize(input = $stdin, **options, &block) # Prepopulate operator namespaces unless validating unless validate? + namespace(:rdf, RDF.to_uri) + namespace(:rdfs, RDF::RDFS.to_uri) + namespace(:xsd, RDF::XSD.to_uri) namespace(:crypto, RDF::N3::Crypto.to_uri) namespace(:list, RDF::N3::List.to_uri) namespace(:log, RDF::N3::Log.to_uri) diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index fdcf7f0..c894a25 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -287,12 +287,12 @@ def formula # Create a graph for each formula, containing statements and built-in operands formulae.each do |gn, form| - form_graph = RDF::N3::Repository.new(with_graph_name: false) do |g| - # Graph initialized with non-built-in statements - form.operands.each do |op| - g << op if op.is_a?(RDF::Statement) - end - end + #form_graph = RDF::N3::Repository.new(with_graph_name: false) do |g| + # # Graph initialized with non-built-in statements + # form.operands.each do |op| + # g << op if op.is_a?(RDF::Statement) + # end + #end form.operands.each do |op| next unless op.is_a?(SPARQL::Algebra::Operator) # Bind built-in operands which are constant lists to RDF::N3::List diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 1c38c05..4807168 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -27,7 +27,7 @@ pending "log:conjunction" when *%w{cwm_includes_bnode} pending "log:includes" - when *%w{cwm_includes_conclusion_simple cwm_unify_unify1} + when *%w{cwm_includes_conclusion_simple cwm_unify_unify1 cwm_unify_unify2} pending "reason over formulae" when *%w{cwm_supports_simple cwm_string_roughly cwm_string_uriEncode} pending "Uses unsupported builtin" From 9251fce18645674a34a516a64daf53ddc5528e7e Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 10 Sep 2020 16:36:11 -0700 Subject: [PATCH 107/193] Improve #inspect on formula. --- lib/rdf/n3/algebra/formula.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 71f4efc..bfebb87 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -257,9 +257,16 @@ def to_s end def to_sxp_bin - raise "a formula can't contain itself" if operands.include?(self) [:formula, graph_name].compact + operands.map(&:to_sxp_bin) end + + def to_base + inspect + end + + def inspect + sprintf("#<%s:%s(%d)>", self.class.name, self.graph_name, self.operands.count) + end end end From 9ebf5dcf64539f43903863e81da35e30381b5073 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 10 Sep 2020 16:37:37 -0700 Subject: [PATCH 108/193] Simplify bnode and formula naming in reader to quash intermittent errors in reasoning. --- lib/rdf/n3/reader.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index cbf7874..cc8fc5e 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -74,7 +74,7 @@ def initialize(input = $stdin, **options, &block) @formulae = [] @formula_nodes = {} - @label_uniquifier = "#{Random.new_seed}_000000" + @label_uniquifier = "0" @bnodes = {} # allocated bnodes by formula @variables = {} @@ -753,17 +753,15 @@ def process_pname(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) + fl = @formulae.last ? "#{label}_#{formulae.last.id}" : label + @bnodes[fl] ||= RDF::Node.new(fl) else RDF::Node.new end end 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, existential: existential) + RDF::Query::Variable.new(label, existential: existential) end # add a pattern or statement From c6bb79c1f5b6fb83df38afe9d6f0e1ac073b3c03 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 10 Sep 2020 16:39:09 -0700 Subject: [PATCH 109/193] When indexing formulae, only index if the position is a bnode. Otherwise, variables can compare oddly. --- lib/rdf/n3/extensions.rb | 2 +- lib/rdf/n3/reasoner.rb | 35 +++++-------------------- spec/reasoner_spec.rb | 51 +++++++++++++++++++------------------ spec/suite_reasoner_spec.rb | 1 + 4 files changed, 35 insertions(+), 54 deletions(-) diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index c2cb88a..ad95677 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -51,7 +51,7 @@ class Statement # Transform Statement into an SXP # @return [Array] def to_sxp_bin - [(variable? ? :pattern : :triple), subject, predicate, object, graph_name].compact + [(variable? ? :pattern : (has_graph? ? :quad : :triple)), subject, predicate, object, graph_name].compact end ## diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index c894a25..91239e1 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -269,43 +269,22 @@ def formula # 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) + fs = pattern.subject.node? ? formulae.fetch(pattern.subject, pattern.subject) : pattern.subject + fo = pattern.object.node? ? formulae.fetch(pattern.object, pattern.object) : pattern.object form.operands << klass.new(fs, fo, parent: form, predicate: pattern.predicate, **@options) else - # Add formulae as direct operators - if formulae.has_key?(pattern.subject) - form.operands << formulae[pattern.subject] + # Use formulae instead of referencing bnodes + if pattern.subject.node? && formulae.has_key?(pattern.subject) + pattern.subject = formulae[pattern.subject] end - if formulae.has_key?(pattern.object) - form.operands << formulae[pattern.object] + if pattern.object.node? && formulae.has_key?(pattern.object) + pattern.object = formulae[pattern.object] end pattern.graph_name = nil form.operands << pattern end end - # Create a graph for each formula, containing statements and built-in operands - formulae.each do |gn, form| - #form_graph = RDF::N3::Repository.new(with_graph_name: false) do |g| - # # Graph initialized with non-built-in statements - # form.operands.each do |op| - # g << op if op.is_a?(RDF::Statement) - # end - #end - form.operands.each do |op| - next unless op.is_a?(SPARQL::Algebra::Operator) - # Bind built-in operands which are constant lists to RDF::N3::List - op.operands.map! do |operand| - # Use a List object, if it is constant - ln = RDF::N3::List.try_list(operand, queryable) unless !operand.is_a?(RDF::Term) || operand.list? - ln || operand - end - end - end - - log_info("reasoner formula") {SXP::Generator.string formulae[nil].to_sxp_bin} - # Formula is that without a graph name formulae[nil] end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 1266520..1ac4b23 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -4,6 +4,7 @@ describe "RDF::N3::Reasoner" do let(:logger) {RDF::Spec.logger} + before {logger.level = Logger::INFO} context "n3:log" do context "log:implies" do @@ -516,31 +517,31 @@ 9.5 :valueOf "(7 / 2) + ((7 % 2)^10000000) + 5 [should be 9.5]" . ) }, - "Combinatorial test - worksWith": { - input: %( - "3.1415926" a :testValue. - 3.1415926 a :testValue. - "1729" a :testValue. - 1729 a :testValue. - "0" a :testValue. - 0 a :testValue. - "1.0e7" a :testValue. - 1.0e7 a :testValue. - { ?x a :testValue. ?y a :testValue. - (?x [ is math:difference of (?y ?x)]) math:sum ?y } => {?x :worksWith ?y}. - ), - expect: %( - "1.0e7" :worksWith 1.0e7 . - "1729" :worksWith 3.1415926, 1.0e7, 0, 1729 . - 1729 :worksWith 3.1415926, 1.0e7, 0, 1729 . - "3.1415926" :worksWith 3.1415926, 1.0e7 . - 3.1415926 :worksWith 3.1415926, 1.0e7 . - 1.0e7 :worksWith 1.0e7 . - "0" :worksWith 3.1415926, 1.0e7, 0, 1729 . - 0 :worksWith 3.1415926, 1.0e7, 0, 1729 . - ), - pending: true - }, + #"Combinatorial test - worksWith": { + # input: %( + # "3.1415926" a :testValue. + # 3.1415926 a :testValue. + # "1729" a :testValue. + # 1729 a :testValue. + # "0" a :testValue. + # 0 a :testValue. + # "1.0e7" a :testValue. + # 1.0e7 a :testValue. + # { ?x a :testValue. ?y a :testValue. + # (?x [ is math:difference of (?y ?x)]) math:sum ?y } => {?x :worksWith ?y}. + # ), + # expect: %( + # "1.0e7" :worksWith 1.0e7 . + # "1729" :worksWith 3.1415926, 1.0e7, 0, 1729 . + # 1729 :worksWith 3.1415926, 1.0e7, 0, 1729 . + # "3.1415926" :worksWith 3.1415926, 1.0e7 . + # 3.1415926 :worksWith 3.1415926, 1.0e7 . + # 1.0e7 :worksWith 1.0e7 . + # "0" :worksWith 3.1415926, 1.0e7, 0, 1729 . + # 0 :worksWith 3.1415926, 1.0e7, 0, 1729 . + # ), + # pending: true + #}, "Combinatorial test - SumDifferenceFAILS": { input: %( "3.1415926" a :testValue. diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 4807168..7c37ccb 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -6,6 +6,7 @@ describe "w3c n3 tests" do require_relative 'suite_helper' let(:logger) {RDF::Spec.logger} + before {logger.level = Logger::INFO} #after(:each) do |example| # puts logger.to_s if From 0e3142666de46f224e440b8bdb86f391cac59d56 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 15 Sep 2020 12:22:38 -0700 Subject: [PATCH 110/193] * Make Builtin include Logger and Enumerable, and simplify the operators using it. * Default implementation of `#each` in Builtin does not yield statements. This overrides `#each` in SPARQL::Algebra::Operator whichi is an alias for `#each_descendant`. --- lib/rdf/n3/algebra/builtin.rb | 8 ++++++++ lib/rdf/n3/algebra/formula.rb | 2 -- lib/rdf/n3/algebra/list/last.rb | 5 ----- lib/rdf/n3/algebra/list/length.rb | 5 ----- lib/rdf/n3/algebra/list_operator.rb | 2 -- lib/rdf/n3/algebra/literal_operator.rb | 2 -- lib/rdf/n3/algebra/log/conclusion.rb | 4 ++++ lib/rdf/n3/algebra/log/conjunction.rb | 4 ++++ lib/rdf/n3/algebra/log/implies.rb | 2 -- lib/rdf/n3/algebra/log/includes.rb | 4 ++++ lib/rdf/n3/algebra/log/notIncludes.rb | 4 ++++ lib/rdf/n3/algebra/log/outputString.rb | 4 ++++ lib/rdf/n3/algebra/math/asinh.rb | 5 ----- lib/rdf/n3/algebra/math/cos.rb | 5 ----- lib/rdf/n3/algebra/math/equalTo.rb | 1 - lib/rdf/n3/algebra/math/greater_than.rb | 1 - lib/rdf/n3/algebra/math/less_than.rb | 1 - lib/rdf/n3/algebra/math/negation.rb | 6 +----- lib/rdf/n3/algebra/math/notEqualTo.rb | 1 - lib/rdf/n3/algebra/math/not_greater_than.rb | 1 - lib/rdf/n3/algebra/math/not_less_than.rb | 1 - lib/rdf/n3/algebra/str/containsIgnoringCase.rb | 1 - lib/rdf/n3/algebra/str/equalIgnoringCase.rb | 1 - lib/rdf/n3/algebra/str/greater_than.rb | 1 - lib/rdf/n3/algebra/str/less_than.rb | 1 - lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb | 1 - lib/rdf/n3/algebra/str/notMatches.rb | 1 - lib/rdf/n3/algebra/str/not_greater_than.rb | 1 - lib/rdf/n3/algebra/str/not_less_than.rb | 1 - 29 files changed, 29 insertions(+), 47 deletions(-) diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb index 6e92e36..f0a527c 100644 --- a/lib/rdf/n3/algebra/builtin.rb +++ b/lib/rdf/n3/algebra/builtin.rb @@ -4,6 +4,9 @@ module RDF::N3::Algebra ## # Behavior for N3 builtin operators module Builtin + include RDF::Enumerable + include RDF::Util::Logger + ## # Determine ordering for running built-in operator considering if subject or object is varaible and considered an input or an output. Accepts a solution set to determine if variable inputs are bound. # @@ -23,5 +26,10 @@ def input_operand # By default, return the merger of input and output operands RDF::N3::List.new(values: operands) end + + ## + # By default, operators do not yield statements + def each(&block) + end end end diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index bfebb87..fd70beb 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -7,8 +7,6 @@ class Formula < SPARQL::Algebra::Operator include RDF::Term include SPARQL::Algebra::Query include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin attr_accessor :query diff --git a/lib/rdf/n3/algebra/list/last.rb b/lib/rdf/n3/algebra/list/last.rb index 620232b..2d7eaec 100644 --- a/lib/rdf/n3/algebra/list/last.rb +++ b/lib/rdf/n3/algebra/list/last.rb @@ -7,11 +7,6 @@ module RDF::N3::Algebra::List # # The object can be calculated as a function of the list. class Last < RDF::N3::Algebra::ListOperator - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - NAME = :listLast ## diff --git a/lib/rdf/n3/algebra/list/length.rb b/lib/rdf/n3/algebra/list/length.rb index c415d1b..d88ec42 100644 --- a/lib/rdf/n3/algebra/list/length.rb +++ b/lib/rdf/n3/algebra/list/length.rb @@ -7,11 +7,6 @@ module RDF::N3::Algebra::List # # The object can be calculated as a function of the list. class Length < RDF::N3::Algebra::ListOperator - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - NAME = :listLength ## diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index 16c9d0b..b86aacb 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -4,8 +4,6 @@ module RDF::N3::Algebra class ListOperator < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Query include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin ## diff --git a/lib/rdf/n3/algebra/literal_operator.rb b/lib/rdf/n3/algebra/literal_operator.rb index 8fd5735..2c5f73e 100644 --- a/lib/rdf/n3/algebra/literal_operator.rb +++ b/lib/rdf/n3/algebra/literal_operator.rb @@ -4,8 +4,6 @@ module RDF::N3::Algebra class LiteralOperator < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Query include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin ## diff --git a/lib/rdf/n3/algebra/log/conclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb index 569fe4e..0087d6b 100644 --- a/lib/rdf/n3/algebra/log/conclusion.rb +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -4,6 +4,10 @@ module RDF::N3::Algebra::Log # # 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 Conclusion < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::N3::Algebra::Builtin + NAME = :logConclusion end end diff --git a/lib/rdf/n3/algebra/log/conjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb index 55bc59c..0d9eb6a 100644 --- a/lib/rdf/n3/algebra/log/conjunction.rb +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -4,6 +4,10 @@ module RDF::N3::Algebra::Log # # 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 Conjunction < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::N3::Algebra::Builtin + NAME = :logConjunction end end diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 386e058..d617ff2 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -8,8 +8,6 @@ module RDF::N3::Algebra::Log class Implies < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Query include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :logImplies diff --git a/lib/rdf/n3/algebra/log/includes.rb b/lib/rdf/n3/algebra/log/includes.rb index ee3aa3d..30d60bb 100644 --- a/lib/rdf/n3/algebra/log/includes.rb +++ b/lib/rdf/n3/algebra/log/includes.rb @@ -8,6 +8,10 @@ module RDF::N3::Algebra::Log # # (Understood natively by cwm when in in the antecedent of a rule. You can use this to peer inside nested formulae.) class Includes < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::N3::Algebra::Builtin + NAME = :logIncludes end end diff --git a/lib/rdf/n3/algebra/log/notIncludes.rb b/lib/rdf/n3/algebra/log/notIncludes.rb index d73513d..22f3347 100644 --- a/lib/rdf/n3/algebra/log/notIncludes.rb +++ b/lib/rdf/n3/algebra/log/notIncludes.rb @@ -7,6 +7,10 @@ module RDF::N3::Algebra::Log # # Related: See includes class NotIncludes < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::N3::Algebra::Builtin + NAME = :logNotIncludes end end diff --git a/lib/rdf/n3/algebra/log/outputString.rb b/lib/rdf/n3/algebra/log/outputString.rb index 0b525e5..04f282c 100644 --- a/lib/rdf/n3/algebra/log/outputString.rb +++ b/lib/rdf/n3/algebra/log/outputString.rb @@ -2,6 +2,10 @@ 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 OutputString < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::N3::Algebra::Builtin + NAME = :logOutputString end end diff --git a/lib/rdf/n3/algebra/math/asinh.rb b/lib/rdf/n3/algebra/math/asinh.rb index 182781a..d3a19f4 100644 --- a/lib/rdf/n3/algebra/math/asinh.rb +++ b/lib/rdf/n3/algebra/math/asinh.rb @@ -2,11 +2,6 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the inverse hyperbolic sine value of the subject. class ASinH < RDF::N3::Algebra::LiteralOperator - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - NAME = :mathASinH ## diff --git a/lib/rdf/n3/algebra/math/cos.rb b/lib/rdf/n3/algebra/math/cos.rb index ae74096..c9c7390 100644 --- a/lib/rdf/n3/algebra/math/cos.rb +++ b/lib/rdf/n3/algebra/math/cos.rb @@ -2,11 +2,6 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the cosine value of the subject. class Cos < RDF::N3::Algebra::LiteralOperator - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger - NAME = :mathCos ## diff --git a/lib/rdf/n3/algebra/math/equalTo.rb b/lib/rdf/n3/algebra/math/equalTo.rb index ffa1bd1..c89cc50 100644 --- a/lib/rdf/n3/algebra/math/equalTo.rb +++ b/lib/rdf/n3/algebra/math/equalTo.rb @@ -2,7 +2,6 @@ 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::Compare - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :'=' diff --git a/lib/rdf/n3/algebra/math/greater_than.rb b/lib/rdf/n3/algebra/math/greater_than.rb index 697aaf8..1415a21 100644 --- a/lib/rdf/n3/algebra/math/greater_than.rb +++ b/lib/rdf/n3/algebra/math/greater_than.rb @@ -2,7 +2,6 @@ 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::Compare - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :'>' diff --git a/lib/rdf/n3/algebra/math/less_than.rb b/lib/rdf/n3/algebra/math/less_than.rb index 040d41e..622c16e 100644 --- a/lib/rdf/n3/algebra/math/less_than.rb +++ b/lib/rdf/n3/algebra/math/less_than.rb @@ -2,7 +2,6 @@ module RDF::N3::Algebra::Math ## # True iff the subject is a string representation of a number which is less than the number of which the object is a string representation. class LessThan < SPARQL::Algebra::Operator::Compare - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :'<' diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb index 435b4f9..b0ef212 100644 --- a/lib/rdf/n3/algebra/math/negation.rb +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -1,11 +1,7 @@ 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 SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::Enumerable - include RDF::Util::Logger + class Negation < RDF::N3::Algebra::LiteralOperator include RDF::N3::Algebra::Builtin NAME = :mathNegation diff --git a/lib/rdf/n3/algebra/math/notEqualTo.rb b/lib/rdf/n3/algebra/math/notEqualTo.rb index f6f0beb..ac50713 100644 --- a/lib/rdf/n3/algebra/math/notEqualTo.rb +++ b/lib/rdf/n3/algebra/math/notEqualTo.rb @@ -2,7 +2,6 @@ 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::Compare - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :'!=' diff --git a/lib/rdf/n3/algebra/math/not_greater_than.rb b/lib/rdf/n3/algebra/math/not_greater_than.rb index ca325d4..6996e7b 100644 --- a/lib/rdf/n3/algebra/math/not_greater_than.rb +++ b/lib/rdf/n3/algebra/math/not_greater_than.rb @@ -2,7 +2,6 @@ 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::Compare - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :'<=' diff --git a/lib/rdf/n3/algebra/math/not_less_than.rb b/lib/rdf/n3/algebra/math/not_less_than.rb index bb527aa..c5f2456 100644 --- a/lib/rdf/n3/algebra/math/not_less_than.rb +++ b/lib/rdf/n3/algebra/math/not_less_than.rb @@ -2,7 +2,6 @@ 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::Compare - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :'>=' diff --git a/lib/rdf/n3/algebra/str/containsIgnoringCase.rb b/lib/rdf/n3/algebra/str/containsIgnoringCase.rb index 076a51f..02df949 100644 --- a/lib/rdf/n3/algebra/str/containsIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/containsIgnoringCase.rb @@ -2,7 +2,6 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :strContainsIgnoringCase diff --git a/lib/rdf/n3/algebra/str/equalIgnoringCase.rb b/lib/rdf/n3/algebra/str/equalIgnoringCase.rb index 3e605cb..e2ea636 100644 --- a/lib/rdf/n3/algebra/str/equalIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/equalIgnoringCase.rb @@ -1,7 +1,6 @@ module RDF::N3::Algebra::Str class EqualIgnoringCase < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Evaluatable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :strEqualIgnoringCase diff --git a/lib/rdf/n3/algebra/str/greater_than.rb b/lib/rdf/n3/algebra/str/greater_than.rb index 809e5a2..8ada469 100644 --- a/lib/rdf/n3/algebra/str/greater_than.rb +++ b/lib/rdf/n3/algebra/str/greater_than.rb @@ -2,7 +2,6 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :strGreaterThan diff --git a/lib/rdf/n3/algebra/str/less_than.rb b/lib/rdf/n3/algebra/str/less_than.rb index 87c8032..cdfee62 100644 --- a/lib/rdf/n3/algebra/str/less_than.rb +++ b/lib/rdf/n3/algebra/str/less_than.rb @@ -2,7 +2,6 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :strLessThan diff --git a/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb b/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb index e228a1a..0eaa5c9 100644 --- a/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb +++ b/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb @@ -2,7 +2,6 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :strNotEqualIgnoringCase diff --git a/lib/rdf/n3/algebra/str/notMatches.rb b/lib/rdf/n3/algebra/str/notMatches.rb index e84ae2b..36580da 100644 --- a/lib/rdf/n3/algebra/str/notMatches.rb +++ b/lib/rdf/n3/algebra/str/notMatches.rb @@ -2,7 +2,6 @@ module RDF::N3::Algebra::Str # The subject string; the object 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 SPARQL::Algebra::Evaluatable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :strNotMatches diff --git a/lib/rdf/n3/algebra/str/not_greater_than.rb b/lib/rdf/n3/algebra/str/not_greater_than.rb index aa7bfd5..36af673 100644 --- a/lib/rdf/n3/algebra/str/not_greater_than.rb +++ b/lib/rdf/n3/algebra/str/not_greater_than.rb @@ -2,7 +2,6 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :strNotGreaterThan diff --git a/lib/rdf/n3/algebra/str/not_less_than.rb b/lib/rdf/n3/algebra/str/not_less_than.rb index 2da3364..396a36d 100644 --- a/lib/rdf/n3/algebra/str/not_less_than.rb +++ b/lib/rdf/n3/algebra/str/not_less_than.rb @@ -2,7 +2,6 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::Util::Logger include RDF::N3::Algebra::Builtin NAME = :strNotLessThan From 9422b98103d9ed639c6a1c4ca77eed106ba1d263 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 15 Sep 2020 12:41:13 -0700 Subject: [PATCH 111/193] Rename camalCase files to snake_case. Add pchampin example. --- examples/pchampin-issue-56.n3 | 3 +++ lib/rdf/n3/algebra.rb | 26 +++++++++---------- .../algebra/log/{equalTo.rb => equal_to.rb} | 0 .../log/{notEqualTo.rb => not_equal_to.rb} | 0 .../log/{notIncludes.rb => not_includes.rb} | 0 .../log/{outputString.rb => output_string.rb} | 0 .../{absoluteValue.rb => absolute_value.rb} | 0 .../algebra/math/{equalTo.rb => equal_to.rb} | 0 ...integerQuotient.rb => integer_quotient.rb} | 0 .../math/{notEqualTo.rb => not_equal_to.rb} | 0 .../{notImplemented.rb => not_implemented.rb} | 0 ...oringCase.rb => contains_ignoring_case.rb} | 0 ...IgnoringCase.rb => equal_ignoring_case.rb} | 0 ...ringCase.rb => not_equal_ignoring_case.rb} | 0 .../str/{notMatches.rb => not_matches.rb} | 0 15 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 examples/pchampin-issue-56.n3 rename lib/rdf/n3/algebra/log/{equalTo.rb => equal_to.rb} (100%) rename lib/rdf/n3/algebra/log/{notEqualTo.rb => not_equal_to.rb} (100%) rename lib/rdf/n3/algebra/log/{notIncludes.rb => not_includes.rb} (100%) rename lib/rdf/n3/algebra/log/{outputString.rb => output_string.rb} (100%) rename lib/rdf/n3/algebra/math/{absoluteValue.rb => absolute_value.rb} (100%) rename lib/rdf/n3/algebra/math/{equalTo.rb => equal_to.rb} (100%) rename lib/rdf/n3/algebra/math/{integerQuotient.rb => integer_quotient.rb} (100%) rename lib/rdf/n3/algebra/math/{notEqualTo.rb => not_equal_to.rb} (100%) rename lib/rdf/n3/algebra/{notImplemented.rb => not_implemented.rb} (100%) rename lib/rdf/n3/algebra/str/{containsIgnoringCase.rb => contains_ignoring_case.rb} (100%) rename lib/rdf/n3/algebra/str/{equalIgnoringCase.rb => equal_ignoring_case.rb} (100%) rename lib/rdf/n3/algebra/str/{notEqualIgnoringCase.rb => not_equal_ignoring_case.rb} (100%) rename lib/rdf/n3/algebra/str/{notMatches.rb => not_matches.rb} (100%) diff --git a/examples/pchampin-issue-56.n3 b/examples/pchampin-issue-56.n3 new file mode 100644 index 0000000..0329869 --- /dev/null +++ b/examples/pchampin-issue-56.n3 @@ -0,0 +1,3 @@ +:captain :age _:a. +(39 3) math:sum _:a. +{ :captain :age 42 } => { :test1 a :Success }. diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 7a84072..01d7ba1 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -11,7 +11,7 @@ module Algebra autoload :Formula, 'rdf/n3/algebra/formula' autoload :ListOperator, 'rdf/n3/algebra/list_operator' autoload :LiteralOperator, 'rdf/n3/algebra/literal_operator' - autoload :NotImplemented, 'rdf/n3/algebra/notImplemented' + autoload :NotImplemented, 'rdf/n3/algebra/not_implemented' module List autoload :Append, 'rdf/n3/algebra/list/append' @@ -25,16 +25,16 @@ module List module Log autoload :Conclusion, 'rdf/n3/algebra/log/conclusion' autoload :Conjunction, 'rdf/n3/algebra/log/conjunction' - autoload :EqualTo, 'rdf/n3/algebra/log/equalTo' + autoload :EqualTo, 'rdf/n3/algebra/log/equal_to' 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' + autoload :NotEqualTo, 'rdf/n3/algebra/log/not_equal_to' + autoload :NotIncludes, 'rdf/n3/algebra/log/not_includes' + autoload :OutputString, 'rdf/n3/algebra/log/output_string' end module Math - autoload :AbsoluteValue, 'rdf/n3/algebra/math/absoluteValue' + autoload :AbsoluteValue, 'rdf/n3/algebra/math/absolute_value' autoload :ACos, 'rdf/n3/algebra/math/acos' autoload :ASin, 'rdf/n3/algebra/math/asin' autoload :ATan, 'rdf/n3/algebra/math/atan' @@ -45,14 +45,14 @@ module Math autoload :Cos, 'rdf/n3/algebra/math/cos' autoload :CosH, 'rdf/n3/algebra/math/cosh' autoload :Difference, 'rdf/n3/algebra/math/difference' - autoload :EqualTo, 'rdf/n3/algebra/math/equalTo' + autoload :EqualTo, 'rdf/n3/algebra/math/equal_to' autoload :Exponentiation, 'rdf/n3/algebra/math/exponentiation' autoload :Floor, 'rdf/n3/algebra/math/floor' autoload :GreaterThan, 'rdf/n3/algebra/math/greater_than' - autoload :IntegerQuotient, 'rdf/n3/algebra/math/integerQuotient' + autoload :IntegerQuotient, 'rdf/n3/algebra/math/integer_quotient' autoload :LessThan, 'rdf/n3/algebra/math/less_than' autoload :Negation, 'rdf/n3/algebra/math/negation' - autoload :NotEqualTo, 'rdf/n3/algebra/math/notEqualTo' + autoload :NotEqualTo, 'rdf/n3/algebra/math/not_equal_to' autoload :NotGreaterThan, 'rdf/n3/algebra/math/not_greater_than' autoload :NotLessThan, 'rdf/n3/algebra/math/not_less_than' autoload :Product, 'rdf/n3/algebra/math/product' @@ -69,17 +69,17 @@ module Math module Str autoload :Concatenation, 'rdf/n3/algebra/str/concatenation' autoload :Contains, 'rdf/n3/algebra/str/contains' - autoload :ContainsIgnoringCase, 'rdf/n3/algebra/str/containsIgnoringCase' + autoload :ContainsIgnoringCase, 'rdf/n3/algebra/str/contains_ignoring_case' autoload :EndsWith, 'rdf/n3/algebra/str/ends_with' - autoload :EqualIgnoringCase, 'rdf/n3/algebra/str/equalIgnoringCase' + autoload :EqualIgnoringCase, 'rdf/n3/algebra/str/equal_ignoring_case' autoload :Format, 'rdf/n3/algebra/str/format' autoload :GreaterThan, 'rdf/n3/algebra/str/greater_than' autoload :LessThan, 'rdf/n3/algebra/str/less_than' autoload :Matches, 'rdf/n3/algebra/str/matches' - autoload :NotEqualIgnoringCase, 'rdf/n3/algebra/str/notEqualIgnoringCase' + autoload :NotEqualIgnoringCase, 'rdf/n3/algebra/str/not_equal_ignoring_case' autoload :NotGreaterThan, 'rdf/n3/algebra/str/not_greater_than' autoload :NotLessThan, 'rdf/n3/algebra/str/not_less_than' - autoload :NotMatches, 'rdf/n3/algebra/str/notMatches' + autoload :NotMatches, 'rdf/n3/algebra/str/not_matches' autoload :Replace, 'rdf/n3/algebra/str/replace' autoload :Scrape, 'rdf/n3/algebra/str/scrape' autoload :StartsWith, 'rdf/n3/algebra/str/starts_with' diff --git a/lib/rdf/n3/algebra/log/equalTo.rb b/lib/rdf/n3/algebra/log/equal_to.rb similarity index 100% rename from lib/rdf/n3/algebra/log/equalTo.rb rename to lib/rdf/n3/algebra/log/equal_to.rb diff --git a/lib/rdf/n3/algebra/log/notEqualTo.rb b/lib/rdf/n3/algebra/log/not_equal_to.rb similarity index 100% rename from lib/rdf/n3/algebra/log/notEqualTo.rb rename to lib/rdf/n3/algebra/log/not_equal_to.rb diff --git a/lib/rdf/n3/algebra/log/notIncludes.rb b/lib/rdf/n3/algebra/log/not_includes.rb similarity index 100% rename from lib/rdf/n3/algebra/log/notIncludes.rb rename to lib/rdf/n3/algebra/log/not_includes.rb diff --git a/lib/rdf/n3/algebra/log/outputString.rb b/lib/rdf/n3/algebra/log/output_string.rb similarity index 100% rename from lib/rdf/n3/algebra/log/outputString.rb rename to lib/rdf/n3/algebra/log/output_string.rb diff --git a/lib/rdf/n3/algebra/math/absoluteValue.rb b/lib/rdf/n3/algebra/math/absolute_value.rb similarity index 100% rename from lib/rdf/n3/algebra/math/absoluteValue.rb rename to lib/rdf/n3/algebra/math/absolute_value.rb diff --git a/lib/rdf/n3/algebra/math/equalTo.rb b/lib/rdf/n3/algebra/math/equal_to.rb similarity index 100% rename from lib/rdf/n3/algebra/math/equalTo.rb rename to lib/rdf/n3/algebra/math/equal_to.rb diff --git a/lib/rdf/n3/algebra/math/integerQuotient.rb b/lib/rdf/n3/algebra/math/integer_quotient.rb similarity index 100% rename from lib/rdf/n3/algebra/math/integerQuotient.rb rename to lib/rdf/n3/algebra/math/integer_quotient.rb diff --git a/lib/rdf/n3/algebra/math/notEqualTo.rb b/lib/rdf/n3/algebra/math/not_equal_to.rb similarity index 100% rename from lib/rdf/n3/algebra/math/notEqualTo.rb rename to lib/rdf/n3/algebra/math/not_equal_to.rb diff --git a/lib/rdf/n3/algebra/notImplemented.rb b/lib/rdf/n3/algebra/not_implemented.rb similarity index 100% rename from lib/rdf/n3/algebra/notImplemented.rb rename to lib/rdf/n3/algebra/not_implemented.rb diff --git a/lib/rdf/n3/algebra/str/containsIgnoringCase.rb b/lib/rdf/n3/algebra/str/contains_ignoring_case.rb similarity index 100% rename from lib/rdf/n3/algebra/str/containsIgnoringCase.rb rename to lib/rdf/n3/algebra/str/contains_ignoring_case.rb diff --git a/lib/rdf/n3/algebra/str/equalIgnoringCase.rb b/lib/rdf/n3/algebra/str/equal_ignoring_case.rb similarity index 100% rename from lib/rdf/n3/algebra/str/equalIgnoringCase.rb rename to lib/rdf/n3/algebra/str/equal_ignoring_case.rb diff --git a/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb b/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb similarity index 100% rename from lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb rename to lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb diff --git a/lib/rdf/n3/algebra/str/notMatches.rb b/lib/rdf/n3/algebra/str/not_matches.rb similarity index 100% rename from lib/rdf/n3/algebra/str/notMatches.rb rename to lib/rdf/n3/algebra/str/not_matches.rb From 20618aaf422bf46a1e0097b2aece411b879da30c Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 15 Sep 2020 15:07:15 -0700 Subject: [PATCH 112/193] log:conjunction creates a formula by combining the operands of the constituent formulae of the subject list. --- lib/rdf/n3/algebra/log/conjunction.rb | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/rdf/n3/algebra/log/conjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb index 0d9eb6a..c9c122e 100644 --- a/lib/rdf/n3/algebra/log/conjunction.rb +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -3,11 +3,32 @@ 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 Conjunction < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update + class Conjunction < RDF::N3::Algebra::ListOperator include RDF::N3::Algebra::Builtin NAME = :logConjunction + + ## + # Evaluates this operator by creating a new formula containing the triples from each of the formulae in the list. + # + # @param [RDF::N3::List] list + # @return [RDF::N3::Algebra::Formula] + # @see RDF::N3::ListOperator#evaluate + def evaluate(list) + form = RDF::N3::Algebra::Formula.new() + + list.each do |f| + form.operands.append(*f.operands) + end + form + end + + ## + # Return subject operand. + # + # @return [RDF::Term] + def input_operand + operands.first + end end end From 53df07a234d8dd937f6f95dbf37b4eaec82f2ed7 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 15 Sep 2020 15:07:49 -0700 Subject: [PATCH 113/193] * Add List#to_sxp_bin and List#transform, used to transform bnode elements to formulae. * Do this when constructing the top-level formulae in the reasoner. * Add List#hash so that equivalent hashes will be eliminated in the repository. --- lib/rdf/n3/list.rb | 21 +++++++++++++++++++++ lib/rdf/n3/reasoner.rb | 36 ++++++++++++++++++++---------------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index e3f2689..4fd6a3e 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -103,6 +103,12 @@ def ==(other) end end + ## + # @see RDF::Value#hash + def hash + to_a.hash + end + ## # Element Assignment — Sets the element at `index`, or replaces a subarray from the `start` index for `length` elements, or replaces a subarray specified by the `range` of indices. # @@ -569,6 +575,21 @@ def to_base "(#{@values.map(&:to_base).join(' ')})" end + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + to_a.to_sxp_bin + end + + ## + # Creates a new list by recusively mapping the values of the list + # + # @return [RDF::N3::list] + def transform(&block) + values = self.to_a.map {|v| v.list? ? v.map(&block) : block.call(v)} + RDF::N3::List.new(values: values) + end + private ## diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 91239e1..8d1836e 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -237,7 +237,9 @@ def enum_conclusions end ## - # Returns the top-level formula for this file + # Returns the top-level formula for this file. + # + # Transforms an RDF dataset into a recursive formula structure. # # @return [RDF::N3::Algebra::Formula] def formula @@ -257,29 +259,31 @@ def formula # and replace subject and object bnodes which identify # named graphs with those formula @mutable.each_statement do |statement| + # A graph name indicates a formula. + graph_name = statement.graph_name + form = formulae[graph_name] + + # Map statement components to formulae, if necessary. + statement = RDF::Statement.from(statement.to_a.map do |term| + case term + when RDF::Node + formulae.fetch(term, term) + when RDF::N3::List + term.transform {|t| t.node? ? formulae.fetch(t, t) : t} + else + term + end + end) + pattern = statement.variable? ? RDF::Query::Pattern.from(statement) : statement if statement.subject.constant? && statement.predicate.constant? queryable << statement end - - # A graph name indicates a formula. - graph_name = pattern.graph_name - form = formulae[graph_name] - # Formulae may be the subject or object of a known operator if klass = Algebra.for(pattern.predicate) - fs = pattern.subject.node? ? formulae.fetch(pattern.subject, pattern.subject) : pattern.subject - fo = pattern.object.node? ? formulae.fetch(pattern.object, pattern.object) : pattern.object - form.operands << klass.new(fs, fo, parent: form, predicate: pattern.predicate, **@options) + form.operands << klass.new(pattern.subject, pattern.object, parent: form, predicate: pattern.predicate, **@options) else - # Use formulae instead of referencing bnodes - if pattern.subject.node? && formulae.has_key?(pattern.subject) - pattern.subject = formulae[pattern.subject] - end - if pattern.object.node? && formulae.has_key?(pattern.object) - pattern.object = formulae[pattern.object] - end pattern.graph_name = nil form.operands << pattern end From bdf5a5665f4cdadce82ac28605b7c1d5a272b22f Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 16 Sep 2020 13:23:23 -0700 Subject: [PATCH 114/193] In Formula#each, also yield statements from sub-formulae. --- lib/rdf/n3/algebra/formula.rb | 24 +++++++++++++++++++++--- lib/rdf/n3/algebra/log/implies.rb | 2 +- spec/suite_reasoner_spec.rb | 2 +- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index fd70beb..48255c1 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -5,10 +5,15 @@ module RDF::N3::Algebra # A Notation3 Formula combines a graph with a BGP query. class Formula < SPARQL::Algebra::Operator include RDF::Term + include RDF::Enumerable include SPARQL::Algebra::Query include SPARQL::Algebra::Update include RDF::N3::Algebra::Builtin + ## + # Query to run against a queryable to determine if the formula matches the queryable. + # + # @return [RDF::Query] attr_accessor :query NAME = [:formula] @@ -147,11 +152,16 @@ def each(&block) else solution[o] end - else o + when RDF::N3::Algebra::Formula + # uses the graph_name of the formula, and yields statements from the formula + o.each(&block) + o.graph_name + else + o end end - statement = RDF::Statement.from(terms) + statement = RDF::Statement.from(terms, graph_name: graph_name) # Sanity checking on statement if statement.variable? @@ -179,7 +189,12 @@ def solutions=(solutions) def graph_name; @options[:graph_name]; end ## - # Statements memoizer + # Statements memoizer. + # + # * Statements exclude builtins. + # * Blank nodes are replaced with existential variables. + # + # @return [Array] def statements # BNodes in statements are existential variables. @statements ||= begin @@ -196,6 +211,9 @@ def statements o.has_nodes? ? o.to_existential : o when RDF::Node RDF::Query::Variable.new(o.id, existential: true) + when RDF::N3::Algebra::Formula + # FIXME: is this also existential? + o else o end diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index d617ff2..929e26f 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -70,7 +70,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, graph_name: graph_name)) + block.call(RDF::Statement.from(statement.to_quad, inferred: true)) end end end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 7c37ccb..2c3e658 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -28,7 +28,7 @@ pending "log:conjunction" when *%w{cwm_includes_bnode} pending "log:includes" - when *%w{cwm_includes_conclusion_simple cwm_unify_unify1 cwm_unify_unify2} + when *%w{cwm_includes_conclusion_simple cwm_unify_unify1} pending "reason over formulae" when *%w{cwm_supports_simple cwm_string_roughly cwm_string_uriEncode} pending "Uses unsupported builtin" From 0e8ff533a38046d8ae0063398e788a23af5bdf26 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 16 Sep 2020 13:47:50 -0700 Subject: [PATCH 115/193] Add Formula.from_enumerable, which replaces the body of Reasoner#formula. This allows a generic method to turn a dataset into a recursive set of formulae (currently excludes list transformation). --- lib/rdf/n3/algebra/formula.rb | 51 +++++++++++++++++++++++++++++++++++ lib/rdf/n3/reasoner.rb | 50 +--------------------------------- 2 files changed, 52 insertions(+), 49 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 48255c1..04bda10 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -18,6 +18,57 @@ class Formula < SPARQL::Algebra::Operator NAME = [:formula] + ## + # Create a formula from an RDF::Enumerable (such as RDF::N3::Repository) + # + # @param [RDF::Enumerable] enumerable + # @param [Hash{Symbol => Object}] options + # any additional keyword options + # @return [RDF::N3::Algebra::Formula] + def self.from_enumerable(enumerable, **options) + # SPARQL used for SSE and algebra functionality + require 'sparql' unless defined?(:SPARQL) + + # Create formulae from statement graph_names + formulae = (enumerable.graph_names.unshift(nil)).inject({}) do |memo, graph_name| + memo.merge(graph_name => 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 + enumerable.each_statement do |statement| + # A graph name indicates a formula. + graph_name = statement.graph_name + form = formulae[graph_name] + + # Map statement components to formulae, if necessary. + statement = RDF::Statement.from(statement.to_a.map do |term| + case term + when RDF::Node + formulae.fetch(term, term) + when RDF::N3::List + term.transform {|t| t.node? ? formulae.fetch(t, t) : t} + else + term + end + end) + + pattern = statement.variable? ? RDF::Query::Pattern.from(statement) : statement + + # Formulae may be the subject or object of a known operator + if klass = RDF::N3::Algebra.for(pattern.predicate) + form.operands << klass.new(pattern.subject, pattern.object, parent: form, predicate: pattern.predicate, **options) + else + pattern.graph_name = nil + form.operands << pattern + end + end + + # Formula is that without a graph name + formulae[nil] + end + ## # Yields solutions from patterns and other operands. Solutions are created by evaluating each pattern and other sub-operand against `queryable`. # diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 8d1836e..6003f13 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -243,55 +243,7 @@ def enum_conclusions # # @return [RDF::N3::Algebra::Formula] def formula - # SPARQL used for SSE and algebra functionality - 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 - - # Create `queryable` as a repo subset of `mutable` excluding built-ins and statements with a variable subject or predicate. This is useful for extracting lists. - queryable = RDF::N3::Repository.new - - # 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| - # A graph name indicates a formula. - graph_name = statement.graph_name - form = formulae[graph_name] - - # Map statement components to formulae, if necessary. - statement = RDF::Statement.from(statement.to_a.map do |term| - case term - when RDF::Node - formulae.fetch(term, term) - when RDF::N3::List - term.transform {|t| t.node? ? formulae.fetch(t, t) : t} - else - term - end - end) - - pattern = statement.variable? ? RDF::Query::Pattern.from(statement) : statement - - if statement.subject.constant? && statement.predicate.constant? - queryable << statement - end - # Formulae may be the subject or object of a known operator - if klass = Algebra.for(pattern.predicate) - form.operands << klass.new(pattern.subject, pattern.object, parent: form, predicate: pattern.predicate, **@options) - else - pattern.graph_name = nil - form.operands << pattern - end - end - - # Formula is that without a graph name - formulae[nil] - end + @formula ||= RDF::N3::Algebra::Formula.from_enumerable(@mutable, **@options) end ## From de59c9e23a2a56c16912ace82f3d2d5a6e516c09 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 16 Sep 2020 14:34:34 -0700 Subject: [PATCH 116/193] Fix List#hash to not affect operands; progress towards log:conjunction. --- lib/rdf/n3/algebra/formula.rb | 20 +++++++++++++++++--- lib/rdf/n3/algebra/list_operator.rb | 2 +- lib/rdf/n3/algebra/log/conjunction.rb | 4 ++-- lib/rdf/n3/algebra/log/implies.rb | 8 ++++---- lib/rdf/n3/list.rb | 2 ++ 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 04bda10..0222236 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -128,7 +128,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new @solutions : RDF::Query::Solutions.new end - log_debug("(formula intermediate solutions)") {"after #{op.to_sxp}: " + @solutions.to_sxp} + log_debug("(formula intermediate solutions)") {"after #{op.to_sxp}: " + SXP::Generator.string(@solutions.to_sxp_bin)} # If there are no solutions, try the next one, until we either run out of operations, or we have solutions next if solutions.empty? last_op = op @@ -146,7 +146,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new ops = (ops - [last_op]).sort_by {|op| op.rank(@solutions)} end end - log_info("(formula sub-op solutions)") {@solutions.to_sxp} + log_info("(formula sub-op solutions)") {SXP::Generator.string @solutions.to_sxp_bin} # Only return solutions with universal variables variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?('$')} @@ -168,6 +168,14 @@ def vars operands.map(&:vars).flatten.compact end + ## + # The formula hash is the hash of it's operands and graph_name. + # + # @see RDF::Value#hash + def hash + ([graph_name] + operands).hash + end + ## # Yields each statement from this formula bound to previously determined solutions. # @@ -180,7 +188,7 @@ def each(&block) # If there are no solutions, create a single solution RDF::Query::Solutions(RDF::Query::Solution.new) end - log_debug("formula #{graph_name} each") {@solutions.to_sxp} + log_debug("formula #{graph_name} each") {SXP::Generator.string @solutions.to_sxp_bin} # Yield patterns by binding variables @solutions.each do |solution| @@ -200,6 +208,12 @@ def each(&block) solution[o].each_statement(&block) # Bind the list subject, and emit list statements solution[o].subject + elsif solution[o] && solution[o].formula? + form = solution[o] + # uses the graph_name of the formula, and yields statements from the formula + form.solutions = RDF::Query::Solutions(solution) + form.each(&block) + form.graph_name else solution[o] end diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index b86aacb..8549ed8 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -20,7 +20,7 @@ def execute(queryable, solutions:, **options) list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) object = operand(1).evaluate(solution.bindings) || operand(1) - log_debug(self.class.const_get(:NAME)) {"list: #{list.to_sxp}, object: #{object.to_sxp}"} + log_debug(self.class.const_get(:NAME)) {"list: #{SXP::Generator.string list.to_sxp_bin}, object: #{SXP::Generator.string object.to_sxp_bin}"} next unless validate(list) lhs = evaluate(list) diff --git a/lib/rdf/n3/algebra/log/conjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb index c9c122e..444af3f 100644 --- a/lib/rdf/n3/algebra/log/conjunction.rb +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -15,8 +15,8 @@ class Conjunction < RDF::N3::Algebra::ListOperator # @return [RDF::N3::Algebra::Formula] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - form = RDF::N3::Algebra::Formula.new() - + form = RDF::N3::Algebra::Formula.new(graph_name: RDF::Node.new(list.hash)) + list.each do |f| form.operands.append(*f.operands) end diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 929e26f..c20451c 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -25,14 +25,14 @@ def execute(queryable, solutions:, **options) @queryable = queryable log_debug {"logImplies"} @solutions = log_depth {operands.first.execute(queryable, solutions: solutions, **options)} - log_debug("(logImplies solutions pre-filter)") {@solutions.to_sxp} + log_debug("(logImplies solutions pre-filter)") {SXP::Generator.string @solutions.to_sxp_bin} # filter solutions where not all variables in antecedant are bound. vars = operands.first.universal_vars @solutions = @solutions.filter do |solution| vars.all? {|v| solution.bound?(v)} end - log_debug("(logImplies solutions)") {@solutions.to_sxp} + log_debug("(logImplies solutions)") {SXP::Generator.string @solutions.to_sxp_bin} # Return original solutions, without bindings solutions @@ -60,13 +60,13 @@ def each(&block) return if @solutions.empty? - log_debug {"logImplies each #{@solutions.to_sxp}"} + log_debug {"logImplies each #{SXP::Generator.string @solutions.to_sxp_bin}"} # Use solutions from subject for object object.solutions = @solutions # Nothing emitted if @solutions is not complete. Solutions are complete when all variables are bound. - log_debug("(logImplies implication true; solutions: #{solutions.to_sxp})") + log_debug("(logImplies implication true; solutions: #{SXP::Generator.string @solutions.to_sxp_bin})") # Yield statements into the default graph log_depth do object.each do |statement| diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index 4fd6a3e..3e8c776 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -104,6 +104,8 @@ def ==(other) end ## + # The list hash is the hash of it's members. + # # @see RDF::Value#hash def hash to_a.hash From ccd20a4dab5eb8d1dc4b9a4f7e210ff8730b7099 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 19 Sep 2020 12:25:10 -0700 Subject: [PATCH 117/193] Reintroduce compile-time variable scope through naming, and when creating a formulae, trasform variable lists and blank nodes to nondistinguiished variables. --- lib/rdf/n3/algebra/formula.rb | 123 ++++++++++++++++-------------- lib/rdf/n3/algebra/log/implies.rb | 2 +- lib/rdf/n3/extensions.rb | 21 ++++- lib/rdf/n3/list.rb | 7 +- lib/rdf/n3/reader.rb | 37 +++++---- lib/rdf/n3/writer.rb | 9 +-- spec/list_spec.rb | 6 +- spec/reasoner_spec.rb | 12 ++- spec/suite_reasoner_spec.rb | 2 +- spec/writer_spec.rb | 10 +-- 10 files changed, 136 insertions(+), 93 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 0222236..7af2ea7 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -46,12 +46,25 @@ def self.from_enumerable(enumerable, **options) statement = RDF::Statement.from(statement.to_a.map do |term| case term when RDF::Node - formulae.fetch(term, term) + term = if formulae[term] + # Transform blank nodes denoting formulae into those formulae + formulae[term] + elsif graph_name + # If we're in a quoted graph, transform blank nodes into undistinguished existential variables. + term.to_ndvar(graph_name) + else + term + end when RDF::N3::List - term.transform {|t| t.node? ? formulae.fetch(t, t) : t} - else - term + # Transform blank nodes denoting formulae into those formulae + term = term.transform {|t| t.node? ? formulae.fetch(t, t) : t} + + # If we're in a quoted graph, transform blank node components into existential variables + if graph_name && term.has_nodes? + term = term.to_existential(graph_name) + end end + term end) pattern = statement.variable? ? RDF::Query::Pattern.from(statement) : statement @@ -148,9 +161,17 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new end log_info("(formula sub-op solutions)") {SXP::Generator.string @solutions.to_sxp_bin} - # Only return solutions with universal variables - variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?('$')} - variable_names.empty? ? @solutions : @solutions.dup.project(*variable_names) + # Only return solutions with universal variables and distinguished existential variables. + # FIXME: also filter in-coming existential variables. + #unless undistinguished_vars.empty? + # @solutions = if distinguished_vars.empty? + # # No remaining variables, this only an empty solution + # RDF::Query::Solutions(RDF::Query::Solution.new) + # else + # @solutions.dup.project(*distinguished_vars) + # end + #end + @solutions end ## @@ -161,13 +182,6 @@ def formula? true end - ## - # Return the variables contained within this formula - # @return [Array] - def vars - operands.map(&:vars).flatten.compact - end - ## # The formula hash is the hash of it's operands and graph_name. # @@ -254,50 +268,24 @@ def solutions=(solutions) def graph_name; @options[:graph_name]; end ## - # Statements memoizer. - # - # * Statements exclude builtins. - # * Blank nodes are replaced with existential variables. + # Statements are the operands # # @return [Array] - def statements - # BNodes in statements are existential variables. - @statements ||= begin - # Operations/Builtins are not statements. - statements = operands.select {|op| op.is_a?(RDF::Statement) && !RDF::N3::Algebra.for(op.predicate)} - - statements.map do |pattern| - if graph_name - terms = {} - [:subject, :predicate, :object].each do |r| - terms[r] = case o = pattern.send(r) - when RDF::N3::List - # Substitute blank node members with existential variables, recusively. - o.has_nodes? ? o.to_existential : o - when RDF::Node - RDF::Query::Variable.new(o.id, existential: true) - when RDF::N3::Algebra::Formula - # FIXME: is this also existential? - o - else - o - end - end - - # A pattern with a non-destinguished variable becomes optional, so that it will bind to itself, if not matched in queryable. - RDF::Query::Pattern.from(terms) - else - RDF::Query::Pattern.from(pattern) - end - end - end - end + alias_method :statements, :operands ## # Patterns memoizer + # + # * Patterns exclude builtins. + # * Blank nodes are replaced with existential variables. def patterns - # BNodes in statements are existential variables - @patterns ||= statements + # BNodes in statements are existential variables. + @patterns ||= begin + # Operations/Builtins are not statements. + operands. + select {|op| op.is_a?(RDF::Statement) && !RDF::N3::Algebra.for(op.predicate)}. + map {|st| RDF::Query::Pattern.from(st)} + end end ## @@ -310,9 +298,9 @@ def sub_ops case o when RDF::N3::List # Substitute blank node members with existential variables, recusively. - o.has_nodes? ? o.to_existential : o + graph_name && o.has_nodes? ? o.to_existential(graph_name) : o when RDF::Node - RDF::Query::Variable.new(o.id, existential: true) + graph_name ? o.to_ndvar(graph_name) : o else o end @@ -321,16 +309,39 @@ def sub_ops end end + ## + # Return the variables contained within this formula + # @return [Array] + def vars + (statements.vars + sub_ops.vars).flatten.compact + end + ## # Universal vars in this formula and sub-formulae + # @return [Array\) a :TestCase \.}, + %r{ a :RESULT \.}, ] }, }.each do |name, params| @@ -622,8 +622,8 @@ "?o": { input: %(:s :p ?o .), regexp: [ - %r(@forAll :o \.), - %r(:s :p :o \.), + %r(@forAll \.), + %r(:s :p \.), ] }, }.each do |name, params| @@ -665,7 +665,7 @@ specify "#{t.name}: #{t.comment} (action)" do case t.name when *%w(cwm_syntax_path2.n3 - cwm_includes_quantifiers.n3 cwm_includes_quantifiers_limited.n3 + cwm_includes_quantifiers_limited.n3 cwm_includes_builtins.n3 cwm_list_unify5-ref.n3 cwm_other_lists-simple.n3 cwm_syntax_qual-after-user.n3 From 7bdbfab11e1490323d75de7a4a0b75e37e557dd1 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 19 Sep 2020 17:44:07 -0700 Subject: [PATCH 118/193] In formula#each, only add graph_name to statementes for included formula, not for outer level. satisfies cwm_includes_conjunction. --- lib/rdf/n3/algebra/formula.rb | 12 +++++++++--- spec/suite_reasoner_spec.rb | 2 -- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 7af2ea7..26fc7c6 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -226,21 +226,27 @@ def each(&block) form = solution[o] # uses the graph_name of the formula, and yields statements from the formula form.solutions = RDF::Query::Solutions(solution) - form.each(&block) + form.each do |stmt| + stmt.graph_name = form.graph_name + block.call(stmt) + end form.graph_name else solution[o] end when RDF::N3::Algebra::Formula # uses the graph_name of the formula, and yields statements from the formula - o.each(&block) + o.each do |stmt| + stmt.graph_name = o.graph_name + block.call(stmt) + end o.graph_name else o end end - statement = RDF::Statement.from(terms, graph_name: graph_name) + statement = RDF::Statement.from(terms) # Sanity checking on statement if statement.variable? diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index f978ccb..9d7c12e 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -24,8 +24,6 @@ pending "math numeric representation" when *%w{cwm_time_t1} pending "time" - when *%w{cwm_includes_conjunction} - pending "log:conjunction" when *%w{cwm_includes_bnode} pending "log:includes" when *%w{cwm_includes_conclusion_simple cwm_unify_unify1} From af0045a033263453c81ae7a13bda13fe8548c799 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 20 Sep 2020 12:50:20 -0700 Subject: [PATCH 119/193] Account for lists containing formulae when serializing. --- lib/rdf/n3/list.rb | 16 +++++++++++++++- lib/rdf/n3/writer.rb | 15 ++++++++++++--- spec/writer_spec.rb | 33 ++++++++++++++++++++++++--------- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index 86113ec..023399b 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -426,6 +426,21 @@ def each_subject(&block) each_statement {|st| block.call(st.subject) if st.predicate == RDF.rest} end + ## + # Enumerate via depth-first recursive descent over list members, yielding each member + # @yield term + # @yieldparam [RDF::Term] term + # @return [Enumerator] + def each_descendant(&block) + if block_given? + each do |term| + term.each_descendant(&block) if term.list? + block.call(term) + end + end + enum_for(:each_descendant) + end + ## # Does this list, or any recusive list have any blank node members? # @@ -550,7 +565,6 @@ def evaluate(bindings, **options) RDF::N3::List.new(subject: RDF::Node.intern(subj), values: to_a.map {|o| o.evaluate(bindings, **options)}) end - ## # Returns a query solution constructed by binding any variables in this list with the corresponding terms in the given `list`. # diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 78c4579..7d297a2 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -724,10 +724,19 @@ def with_graph(graph_name) lists = {} graph.each do |statement| preprocess_graph_statement(statement) - [statement.subject, statement.object].select(&:node?).each do |resource| + [statement.subject, statement.object].each do |resource| @formulae[resource] = true if - formula_names.include?(resource) || - resource.id.start_with?('.form_') + resource.node? && + (formula_names.include?(resource) || resource.id.start_with?('.form_')) + + # First-class list may have members which are formulae + if resource.list? + resource.each_descendant do |term| + @formulae[term] = true if + term.node? && + (formula_names.include?(term) || term.id.start_with?('.form_')) + end + end end # Collect list elements diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index d1f618e..a50d299 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -303,21 +303,36 @@ }, "list with multiple lists": { input: %( - _:l1 . - _:a "a" . - _:a . - _:b "b" . - _:b . - _:l1 _:a . - _:l1 _:l2 . - _:l2 _:b . - _:l2 . + _:l1 . + _:a "a" . + _:a . + _:b "b" . + _:b . + _:l1 _:a . + _:l1 _:l2 . + _:l2 _:b . + _:l2 . ), regexp: [ %r( \(\s*\(\s*"a"\) \(\s*"b"\)\) .) ], standard_prefixes: true }, + "list with formula": { + input: %( + @prefix log: . + { + ({:sky :color :blue} {:sky :color :green}) log:conjunction ?F + } => { ?F a :result} . + ), + regexp: [ + %r(@forAll \.), + %r[{\s+\(\s*{\s*:sky :color :blue \.\s+}\s+{]m, + %r[{\s+:sky :color :green \.\s+}\s*\)]m, + %r[}\)\s+log:conjunction\s+\s+\.\s+} =>]m, + %r[=>\s+{\s+ a :result \.\s*}]m + ] + }, }.each do |name, params| it name do serialize(params[:input], params[:regexp], **params) From c2d12283348a59fa81c13510c1d9425b1b971af8 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 20 Sep 2020 13:46:40 -0700 Subject: [PATCH 120/193] Implements log:content. --- README.md | 3 ++- lib/rdf/n3/algebra.rb | 2 ++ lib/rdf/n3/algebra/literal_operator.rb | 2 +- lib/rdf/n3/algebra/log/content.rb | 33 ++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 lib/rdf/n3/algebra/log/content.rb diff --git a/README.md b/README.md index 9058933..4232208 100755 --- a/README.md +++ b/README.md @@ -74,7 +74,8 @@ Reasoning is discussed in the [Design Issues][] document. #### RDF Log vocabulary * `log:conclusion` (not implemented yet - See {RDF::N3::Algebra::Log::Conclusion}) - * `log:conjunction` (not implemented yet - See {RDF::N3::Algebra::Log::Conjunction}) + * `log:conjunction` (See {RDF::N3::Algebra::Log::Conjunction}) + * `log:content` (See {RDF::N3::Algebra::Log::Content}) * `log:equalTo` (See {RDF::N3::Algebra::Log::EqualTo}) * `log:implies` (See {RDF::N3::Algebra::Log::Implies}) * `log:includes` (See {RDF::N3::Algebra::Log::Includes}) diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 01d7ba1..404b79c 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -25,6 +25,7 @@ module List module Log autoload :Conclusion, 'rdf/n3/algebra/log/conclusion' autoload :Conjunction, 'rdf/n3/algebra/log/conjunction' + autoload :Content, 'rdf/n3/algebra/log/content' autoload :EqualTo, 'rdf/n3/algebra/log/equal_to' autoload :Implies, 'rdf/n3/algebra/log/implies' autoload :Includes, 'rdf/n3/algebra/log/includes' @@ -110,6 +111,7 @@ def for(uri) RDF::N3::Log.conclusion => Log::Conclusion, RDF::N3::Log.conjunction => Log::Conjunction, + RDF::N3::Log.content => Log::Content, RDF::N3::Log.equalTo => Log::EqualTo, RDF::N3::Log.implies => Log::Implies, RDF::N3::Log.includes => Log::Includes, diff --git a/lib/rdf/n3/algebra/literal_operator.rb b/lib/rdf/n3/algebra/literal_operator.rb index 2c5f73e..f81742c 100644 --- a/lib/rdf/n3/algebra/literal_operator.rb +++ b/lib/rdf/n3/algebra/literal_operator.rb @@ -57,7 +57,7 @@ def input_operand # # Returns nil if resource does not validate, given its position # - # @param [RDF::N3::List] resource + # @param [RDF::Term] resource # @return [RDF::Term] def evaluate(resource, position: :subject) raise NotImplemented diff --git a/lib/rdf/n3/algebra/log/content.rb b/lib/rdf/n3/algebra/log/content.rb new file mode 100644 index 0000000..7642780 --- /dev/null +++ b/lib/rdf/n3/algebra/log/content.rb @@ -0,0 +1,33 @@ +module RDF::N3::Algebra::Log + ## + # This connects a document and a string that represents it. + # + # (Cwm knows how to go get a document in order to evaluate this.) + # + # Note that the content-type of the information is not given and so must be known or guessed. + class Content < RDF::N3::Algebra::LiteralOperator + NAME = :logContent + + ## + # Reads the subject into the object. + # + # Returns nil if resource does not validate, given its position + # + # @param [RDF::N3::List] resource + # @return [RDF::Term] + def evaluate(resource, position: :subject) + case position + when :subject + return nil unless resource.literal? || resource.uri? + content = begin + as_literal(RDF::Util::File.open_file(resource) {|f| f.read}) + rescue IOError + nil + end + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + end +end From ac6895cc867e50591c5e042e0cd42a9431977aa2 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 20 Sep 2020 14:08:55 -0700 Subject: [PATCH 121/193] log:n3String. --- README.md | 1 + lib/rdf/n3/algebra.rb | 2 ++ lib/rdf/n3/algebra/log/n3_string.rb | 33 +++++++++++++++++++++++++++++ spec/reasoner_spec.rb | 25 ++++++++++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 lib/rdf/n3/algebra/log/n3_string.rb diff --git a/README.md b/README.md index 4232208..d7b2013 100755 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Reasoning is discussed in the [Design Issues][] document. * `log:equalTo` (See {RDF::N3::Algebra::Log::EqualTo}) * `log:implies` (See {RDF::N3::Algebra::Log::Implies}) * `log:includes` (See {RDF::N3::Algebra::Log::Includes}) + * `log:n3String` (See {RDF::N3::Algebra::Log::N3String}) * `log:notEqualTo` (See {RDF::N3::Algebra::Log::NotEqualTo}) * `log:notIncludes` (not implemented yet - See {RDF::N3::Algebra::Log::NotIncludes}) * `log:outputString` (not implemented yet - See {RDF::N3::Algebra::Log::OutputString}) diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 404b79c..8fdd695 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -29,6 +29,7 @@ module Log autoload :EqualTo, 'rdf/n3/algebra/log/equal_to' autoload :Implies, 'rdf/n3/algebra/log/implies' autoload :Includes, 'rdf/n3/algebra/log/includes' + autoload :N3String, 'rdf/n3/algebra/log/n3_string' autoload :NotEqualTo, 'rdf/n3/algebra/log/not_equal_to' autoload :NotIncludes, 'rdf/n3/algebra/log/not_includes' autoload :OutputString, 'rdf/n3/algebra/log/output_string' @@ -115,6 +116,7 @@ def for(uri) RDF::N3::Log.equalTo => Log::EqualTo, RDF::N3::Log.implies => Log::Implies, RDF::N3::Log.includes => Log::Includes, + RDF::N3::Log.n3String => Log::N3String, RDF::N3::Log.notEqualTo => Log::NotEqualTo, RDF::N3::Log.notIncludes => Log::NotIncludes, RDF::N3::Log.outputString => Log::OutputString, diff --git a/lib/rdf/n3/algebra/log/n3_string.rb b/lib/rdf/n3/algebra/log/n3_string.rb new file mode 100644 index 0000000..a2cdb17 --- /dev/null +++ b/lib/rdf/n3/algebra/log/n3_string.rb @@ -0,0 +1,33 @@ +module RDF::N3::Algebra::Log + ## + # The subject formula, expressed as N3, gives this string. + class N3String < RDF::N3::Algebra::LiteralOperator + NAME = :logN3String + + ## + # Serializes the subject formula into an N3 string representation. + # + # @param [RDF::N3::List] resource + # @return [RDF::Term] + def evaluate(resource, position: :subject) + case position + when :subject + return nil unless resource.formula? + as_literal(RDF::N3::Writer.buffer {|w| resource.each {|st| w << st}}) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + + ## + # Subject must evaluate to a formula and object to a literal. + # + # @param [RDF::Term] subject + # @param [RDF::Term] object + # @return [Boolean] + def valid?(subject, object) + subject.formula? && (object.variable? || object.literal?) + end + end +end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 2094de0..2edd528 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -94,6 +94,31 @@ end end end + + context "log:n3String" do + { + "i18n" => { + input: %( + {{:㐭 :b :c} log:n3String ?x} => {?x a :interesting}. + ), + regexp: [ + %r("""\s*<#㐭> <#b> <#c> \.\s*""" a <#interesting> \.)m + ] + }, + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + result = reason(options[:input]) + n3str = RDF::N3::Writer.buffer {|writer| writer << result} + + logger.info "result: #{n3str}" + Array(options[:regexp]).each do |re| + logger.info "match: #{re.inspect}" + expect(n3str).to match_re(re, logger: logger, input: n3str), logger.to_s + end + end + end + end end context "n3:list" do From c86c836a4e071fbcdbc37b56c9977abf1a964713 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 2 Oct 2020 13:28:27 -0700 Subject: [PATCH 122/193] Intern calculated bnode used for graph name in log:conjunction. --- lib/rdf/n3/algebra/log/conjunction.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdf/n3/algebra/log/conjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb index 444af3f..1bba364 100644 --- a/lib/rdf/n3/algebra/log/conjunction.rb +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -15,7 +15,7 @@ class Conjunction < RDF::N3::Algebra::ListOperator # @return [RDF::N3::Algebra::Formula] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - form = RDF::N3::Algebra::Formula.new(graph_name: RDF::Node.new(list.hash)) + form = RDF::N3::Algebra::Formula.new(graph_name: RDF::Node.intern(list.hash)) list.each do |f| form.operands.append(*f.operands) From 3b5c78dd68456b97ac82d37a14cbb4bc1a7bceb3 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 2 Oct 2020 13:31:27 -0700 Subject: [PATCH 123/193] Define `#to_ndvar` instead of `#to_existential` in List for general compatibility. --- lib/rdf/n3/algebra/formula.rb | 4 ++-- lib/rdf/n3/extensions.rb | 33 ++++++++++++++++++++++----------- lib/rdf/n3/list.rb | 4 ++-- spec/list_spec.rb | 8 ++++---- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 26fc7c6..a4ebcf4 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -61,7 +61,7 @@ def self.from_enumerable(enumerable, **options) # If we're in a quoted graph, transform blank node components into existential variables if graph_name && term.has_nodes? - term = term.to_existential(graph_name) + term = term.to_ndvar(graph_name) end end term @@ -304,7 +304,7 @@ def sub_ops case o when RDF::N3::List # Substitute blank node members with existential variables, recusively. - graph_name && o.has_nodes? ? o.to_existential(graph_name) : o + graph_name && o.has_nodes? ? o.to_ndvar(graph_name) : o when RDF::Node graph_name ? o.to_ndvar(graph_name) : o else diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index 42197f3..74d7db8 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -12,15 +12,6 @@ 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 List @@ -65,12 +56,20 @@ def to_sxp module Value ## - # Returns `true` if `self` is a {RDF::N3::Formula}. + # Returns `true` if `self` is a {RDF::N3::Algebra::Formula}. # # @return [Boolean] def formula? false end + + # By default, returns itself. Can be used for terms such as blank nodes to be turned into non-disinguished variables. + # + # @param [RDF::Node] scope + # return [RDF::Query::Variable] + def to_ndvar(scope) + self + end end module Term @@ -147,12 +146,24 @@ class Node # @param [RDF::Node] scope # return [RDF::Query::Variable] def to_ndvar(scope) - label = "#{id}_#{scope.id}_undext" + label = "#{id}_#{scope ? scope.id : 'base'}_undext" RDF::Query::Variable.new(label, existential: true, distinguished: false) end end class Query::Pattern + ## + # Overrides `#initialize!` to turn blank nodes into non-distinguished variables, if the `:ndvars` option is set. + def initialize! + if @options[:ndvars] + @graph_name = @graph_name.to_ndvar(nil) if @graph_name + @subject = @subject.to_ndvar(@graph_name) + @predicate = @predicate.to_ndvar(@graph_name) + @object = @object.to_ndvar(@graph_name) + end + super + end + ## # Checks pattern equality against a statement, considering nesting an lists. # diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index 023399b..311ed37 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -454,11 +454,11 @@ def has_nodes? # # @param [RDF::Node] scope # @return [RDF::N3::List] - def to_existential(scope) + def to_ndvar(scope) values = @values.map do |e| case e when RDF::Node then e.to_ndvar(scope) - when RDF::N3::List then e.to_existential(scope) + when RDF::N3::List then e.to_ndvar(scope) else e end end diff --git a/spec/list_spec.rb b/spec/list_spec.rb index a52cef2..280f9dd 100644 --- a/spec/list_spec.rb +++ b/spec/list_spec.rb @@ -550,9 +550,9 @@ end end - describe "#to_existential" do + describe "#to_ndvar" do it "creates existential vars for list having nodes" do - expect(nodes.to_existential(RDF::Node.new)).to all(be_variable) + expect(nodes.to_ndvar(RDF::Node.new)).to all(be_variable) end end @@ -566,7 +566,7 @@ end it "finds list with existentials" do - expect(nodes.to_existential(RDF::Node.new)).to be_variable + expect(nodes.to_ndvar(RDF::Node.new)).to be_variable end end @@ -580,7 +580,7 @@ end it "finds variables in existential list" do - expect(nodes.to_existential(RDF::Node.new).variables).to all(be_variable) + expect(nodes.to_ndvar(RDF::Node.new).variables).to all(be_variable) end end From 6258a25313ec20841c263488464527e54149d8d2 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 2 Oct 2020 13:38:49 -0700 Subject: [PATCH 124/193] Adds `Repository#to_query` and some debugging for log:implies. --- lib/rdf/n3/algebra/formula.rb | 15 +++++++++++++-- lib/rdf/n3/algebra/list/first.rb | 2 +- lib/rdf/n3/algebra/list/last.rb | 2 +- lib/rdf/n3/algebra/list/length.rb | 2 +- lib/rdf/n3/algebra/log/implies.rb | 10 ++++++---- lib/rdf/n3/refinements.rb | 2 +- lib/rdf/n3/repository.rb | 12 ++++++++++++ 7 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index a4ebcf4..6a763f7 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -79,7 +79,11 @@ def self.from_enumerable(enumerable, **options) end # Formula is that without a graph name - formulae[nil] + this = formulae[nil] + + # If assigned a graph name, add it here + this.graph_name = options[:graph_name] if options[:graph_name] + this end ## @@ -175,7 +179,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new end ## - # Returns `true` if `self` is a {RDF::N3::Formula}. + # Returns `true` if `self` is a {RDF::N3::Algebra::Formula}. # # @return [Boolean] def formula? @@ -273,6 +277,13 @@ def solutions=(solutions) # @return [RDF::Resource] def graph_name; @options[:graph_name]; end + # Assign a graph name to this formula + # @param [RDF::Resource] name + # @return [RDF::Resource] + def graph_name=(name) + @options[:graph_name] = name + end + ## # Statements are the operands # diff --git a/lib/rdf/n3/algebra/list/first.rb b/lib/rdf/n3/algebra/list/first.rb index 72286f1..8251f1e 100644 --- a/lib/rdf/n3/algebra/list/first.rb +++ b/lib/rdf/n3/algebra/list/first.rb @@ -1,6 +1,6 @@ module RDF::N3::Algebra::List ## - # Iff the suject is a list and the object is the first thing that list, then this is true. The object can be calculated as a function of the list. + # Iff the subject is a list and the object is the first thing that list, then this is true. The object can be calculated as a function of the list. # # @example # { ( 1 2 3 4 5 6 ) list:first 1 } => { :test1 a :SUCCESS }. diff --git a/lib/rdf/n3/algebra/list/last.rb b/lib/rdf/n3/algebra/list/last.rb index 2d7eaec..6ea0ae1 100644 --- a/lib/rdf/n3/algebra/list/last.rb +++ b/lib/rdf/n3/algebra/list/last.rb @@ -1,6 +1,6 @@ module RDF::N3::Algebra::List ## - # Iff the suject is a list and the object is the last thing that list, then this is true. The object can be calculated as a function of the list. + # Iff the subject is a list and the object is the last thing that list, then this is true. The object can be calculated as a function of the list. # # @example # { ( 1 2 3 4 5 6 ) list:last 6 } => { :test1 a :SUCCESS }. diff --git a/lib/rdf/n3/algebra/list/length.rb b/lib/rdf/n3/algebra/list/length.rb index d88ec42..3c9ce23 100644 --- a/lib/rdf/n3/algebra/list/length.rb +++ b/lib/rdf/n3/algebra/list/length.rb @@ -1,6 +1,6 @@ module RDF::N3::Algebra::List ## - # Iff the suject is a list and the object is the last thing that list, then this is true. The object can be calculated as a function of the list. + # Iff the subject is a list and the object is the last thing that list, then this is true. The object can be calculated as a function of the list. # # @example # { ( 1 2 3 4 5 6 ) list:length 6 } => { :test1 a :SUCCESS }. diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 8c74bd3..f59181e 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -12,7 +12,8 @@ class Implies < SPARQL::Algebra::Operator::Binary NAME = :logImplies - # Yields solutions from subject. Solutions are created by evaluating subject against `queryable`. + ## + # Returns solutions from subject. Solutions are created by evaluating subject against `queryable`. # # @param [RDF::Queryable] queryable # the graph or repository to query @@ -23,12 +24,13 @@ class Implies < SPARQL::Algebra::Operator::Binary # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions:, **options) @queryable = queryable - log_debug {"logImplies"} - @solutions = log_depth {operands.first.execute(queryable, solutions: solutions, **options)} + log_debug(NAME) {"subject: #{SXP::Generator.string operand(0).to_sxp_bin}"} + log_debug(NAME) {"object: #{SXP::Generator.string operand(1).to_sxp_bin}"} + @solutions = log_depth {operand(0).execute(queryable, solutions: solutions, **options)} log_debug("(logImplies solutions pre-filter)") {SXP::Generator.string @solutions.to_sxp_bin} # filter solutions where not all variables in antecedant are bound. - vars = operands.first.universal_vars + vars = operand(0).universal_vars @solutions = @solutions.filter do |solution| vars.all? {|v| solution.bound?(v)} end diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb index da253c4..199682c 100644 --- a/lib/rdf/n3/refinements.rb +++ b/lib/rdf/n3/refinements.rb @@ -52,7 +52,7 @@ def validate! # @!parse # # Refinements on RDF::Query::Pattern # class ::RDF::Query::Pattern - # # Refines `valid?` to allow literal subjects and BNode predicates. + # # Refines `#valid?` to allow literal subjects and BNode predicates. # # @return [Boolean] # def valid?; end # end diff --git a/lib/rdf/n3/repository.rb b/lib/rdf/n3/repository.rb index 21c45d3..3f2953c 100644 --- a/lib/rdf/n3/repository.rb +++ b/lib/rdf/n3/repository.rb @@ -44,6 +44,18 @@ def supports?(feature) end end + ## + # Creates a query from the statements in this repository, turning blank nodes into non-distinguished variables. This can be used to determine if this repository is logically a subset of another repository. + # + # @return [RDF::Query] + def to_query + RDF::Query.new do |query| + each do |statement| + query.pattern RDF::Query::Pattern.from(statement, ndvars: true) + end + end + end + ## # @private # @see RDF::Countable#count From 745aa625e2aabc951a4a7e7f0fd0bf8bad1c6a54 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 2 Oct 2020 14:29:19 -0700 Subject: [PATCH 125/193] log:semantics and log:parsedAsN3. --- README.md | 2 ++ lib/rdf/n3/algebra.rb | 4 +++ lib/rdf/n3/algebra/log/parsed_as_n3.rb | 33 ++++++++++++++++++ lib/rdf/n3/algebra/log/semantics.rb | 36 +++++++++++++++++++ spec/reasoner_spec.rb | 48 ++++++++++++++++++++++++++ 5 files changed, 123 insertions(+) create mode 100644 lib/rdf/n3/algebra/log/parsed_as_n3.rb create mode 100644 lib/rdf/n3/algebra/log/semantics.rb diff --git a/README.md b/README.md index d7b2013..372ceaa 100755 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ Reasoning is discussed in the [Design Issues][] document. * `log:notEqualTo` (See {RDF::N3::Algebra::Log::NotEqualTo}) * `log:notIncludes` (not implemented yet - See {RDF::N3::Algebra::Log::NotIncludes}) * `log:outputString` (not implemented yet - See {RDF::N3::Algebra::Log::OutputString}) + * `log:parsedAsN3` (See {RDF::N3::Algebra::Log::ParsedAsN3}) + * `log:semantics` (See {RDF::N3::Algebra::Log::Semantics}) #### RDF Math vocabulary diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 8fdd695..e317200 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -33,6 +33,8 @@ module Log autoload :NotEqualTo, 'rdf/n3/algebra/log/not_equal_to' autoload :NotIncludes, 'rdf/n3/algebra/log/not_includes' autoload :OutputString, 'rdf/n3/algebra/log/output_string' + autoload :ParsedAsN3, 'rdf/n3/algebra/log/parsed_as_n3' + autoload :Semantics, 'rdf/n3/algebra/log/semantics' end module Math @@ -120,6 +122,8 @@ def for(uri) RDF::N3::Log.notEqualTo => Log::NotEqualTo, RDF::N3::Log.notIncludes => Log::NotIncludes, RDF::N3::Log.outputString => Log::OutputString, + RDF::N3::Log.parsedAsN3 => Log::ParsedAsN3, + RDF::N3::Log.semantics => Log::Semantics, RDF::N3::Log.supports => NotImplemented, RDF::N3::Math.absoluteValue => Math::AbsoluteValue, diff --git a/lib/rdf/n3/algebra/log/parsed_as_n3.rb b/lib/rdf/n3/algebra/log/parsed_as_n3.rb new file mode 100644 index 0000000..7d93977 --- /dev/null +++ b/lib/rdf/n3/algebra/log/parsed_as_n3.rb @@ -0,0 +1,33 @@ +module RDF::N3::Algebra::Log + ## + # The subject string, parsed as N3, gives this formula. + class ParsedAsN3 < RDF::N3::Algebra::LiteralOperator + NAME = :logParsedAsN3 + + ## + # Parses the subject into a new formula. + # + # Returns nil if resource does not validate, given its position + # + # @param [RDF::N3::List] resource + # @return [RDF::Term] + def evaluate(resource, position: :subject) + case position + when :subject + return nil unless resource.literal? + begin + repo = RDF::N3::Repository.new + repo << RDF::N3::Reader.new(resource.to_s, **@options) + log_debug("logParsedAsN3") {SXP::Generator.string repo.statements.to_sxp_bin} + content_hash = resource.hash # used as name of resulting formula + RDF::N3::Algebra::Formula.from_enumerable(repo, graph_name: RDF::Node.intern(content_hash)) + rescue RDF::ReaderError + nil + end + when :object + return nil unless resource.literal? + resource + end + end + end +end diff --git a/lib/rdf/n3/algebra/log/semantics.rb b/lib/rdf/n3/algebra/log/semantics.rb new file mode 100644 index 0000000..0f65a9d --- /dev/null +++ b/lib/rdf/n3/algebra/log/semantics.rb @@ -0,0 +1,36 @@ +module RDF::N3::Algebra::Log + ## + # The log:semantics of a document is the formula. achieved by parsing representation of the document. For a document in Notation3, log:semantics is the log:parsedAsN3 of the log:contents of the document. For a document in RDF/XML, it is parsed according to the RDF/XML specification to yield an RDF formula (a subclass of N3 log:Formula). + # + # [Aside: Philosophers will be distracted here into worrying about the meaning of meaning. At least we didn't call this function "meaning"! In as much as N3 is used as an interlingua for interoperability for different systems, this for an N3 based system is the meaning expressed by a document.] + # + # (Cwm knows how to go get a document and parse N3 and RDF/XML it in order to evaluate this. Other languages for web documents may be defined whose N3 semantics are therefore also calculable, and so they could be added in due course. See for example GRDDL, RDFa, etc) + class Semantics < RDF::N3::Algebra::LiteralOperator + NAME = :logSemantics + + ## + # Parses the subject into a new formula. + # + # Returns nil if resource does not validate, given its position + # + # @param [RDF::N3::List] resource + # @return [RDF::Term] + def evaluate(resource, position: :subject) + case position + when :subject + return nil unless resource.literal? || resource.uri? + begin + repo = RDF::N3::Repository.new + repo << RDF::Reader.open(resource) + content_hash = repo.hash # used as name of resulting formula + RDF::N3::Formula.from_enumerable(repo, graph_name: RDF::Node.new(content_hash)) + rescue IOError, RDF::ReaderError + nil + end + when :object + return nil unless resource.literal? + resource + end + end + end +end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 2edd528..c11857b 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -94,6 +94,54 @@ end end end + context "log:parsedAsN3" do + { + "i18n" => { + input: %( + {":㐭 :b :c." log:parsedAsN3 ?x} => {?x a log:Formula} . + ), + expect: %( + { } a log:Formula . + ) + }, + "log_parsedAsN3" => { + input: %( + @prefix log: . + @prefix : <#>. + + @forAll :F. + + {""" @prefix : . + @prefix crypto: . + @prefix log: . + @prefix os: . + @prefix string: . + + :foo :credential ; + :forDocument ; + :junk "32746213462187364732164732164321" . + """ log:parsedAsN3 :F} log:implies { :F a :result }. + ), + expect: %( + @prefix rdf: . + @prefix xsd: . + + { + ; + ; + "32746213462187364732164732164321" . + } a <#result> . + ) + } + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + result = reason(options[:input], data: false, filter: true) + expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) + end + end + end context "log:n3String" do { From cd4ff62dac27ab86209de3ed4aa8803905a8c122 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 2 Oct 2020 15:48:00 -0700 Subject: [PATCH 126/193] log:includes, log:notIncludes, log:conjunction, and fixes to bind inuts to log:implices. --- README.md | 2 +- lib/rdf/n3/algebra/log/implies.rb | 12 ++++-- lib/rdf/n3/algebra/log/includes.rb | 54 ++++++++++++++++++++++++++ lib/rdf/n3/algebra/log/not_includes.rb | 16 +++++++- lib/rdf/n3/algebra/log/semantics.rb | 2 +- spec/reasoner_spec.rb | 53 +++++++++++++++++++++++++ spec/suite_reasoner_spec.rb | 11 +++--- 7 files changed, 138 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 372ceaa..727cce3 100755 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Reasoning is discussed in the [Design Issues][] document. * `log:includes` (See {RDF::N3::Algebra::Log::Includes}) * `log:n3String` (See {RDF::N3::Algebra::Log::N3String}) * `log:notEqualTo` (See {RDF::N3::Algebra::Log::NotEqualTo}) - * `log:notIncludes` (not implemented yet - See {RDF::N3::Algebra::Log::NotIncludes}) + * `log:notIncludes` (See {RDF::N3::Algebra::Log::NotIncludes}) * `log:outputString` (not implemented yet - See {RDF::N3::Algebra::Log::OutputString}) * `log:parsedAsN3` (See {RDF::N3::Algebra::Log::ParsedAsN3}) * `log:semantics` (See {RDF::N3::Algebra::Log::Semantics}) diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index f59181e..1b8a346 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -24,13 +24,19 @@ class Implies < SPARQL::Algebra::Operator::Binary # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions:, **options) @queryable = queryable - log_debug(NAME) {"subject: #{SXP::Generator.string operand(0).to_sxp_bin}"} + subject = subject.evaluate(solutions.first.bindings) || operand(0) + object = object.evaluate(solutions.first.bindings) || operand(1) + log_debug(NAME) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} log_debug(NAME) {"object: #{SXP::Generator.string operand(1).to_sxp_bin}"} - @solutions = log_depth {operand(0).execute(queryable, solutions: solutions, **options)} + + # Nothing to do if variables aren't resolved. + return @solutions = solutions if subject.is_a?(RDF::Query::Variable) || object.is_a?(RDF::Query::Variable) + + @solutions = log_depth {subject.execute(queryable, solutions: solutions, **options)} log_debug("(logImplies solutions pre-filter)") {SXP::Generator.string @solutions.to_sxp_bin} # filter solutions where not all variables in antecedant are bound. - vars = operand(0).universal_vars + vars = subject.universal_vars @solutions = @solutions.filter do |solution| vars.all? {|v| solution.bound?(v)} end diff --git a/lib/rdf/n3/algebra/log/includes.rb b/lib/rdf/n3/algebra/log/includes.rb index 30d60bb..5e9bfb3 100644 --- a/lib/rdf/n3/algebra/log/includes.rb +++ b/lib/rdf/n3/algebra/log/includes.rb @@ -13,5 +13,59 @@ class Includes < SPARQL::Algebra::Operator::Binary include RDF::N3::Algebra::Builtin NAME = :logIncludes + + ## + # Creates a repository constructed by evaluating the subject against queryable and queries object against that repository. Either retuns a single solution, or no solutions + # + # @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 + # @return [RDF::Solutions] distinct solutions + def execute(queryable, solutions:, **options) + @queryable = queryable + subject = subject.evaluate(solutions.first.bindings) || operand(0) + object = object.evaluate(solutions.first.bindings) || operand(1) + log_debug(self.class.const_get(:NAME)) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} + log_debug(self.class.const_get(:NAME)) {"object: #{SXP::Generator.string object.to_sxp_bin}"} + + # Nothing to do if variables aren't resolved. + return @solutions = solutions if subject.is_a?(RDF::Query::Variable) || object.is_a?(RDF::Query::Variable) + + solutions = log_depth {subject.execute(queryable, solutions: solutions, **options)} + log_debug("(logImplies solutions pre-filter)") {SXP::Generator.string solutions.to_sxp_bin} + + # filter solutions where not all variables in antecedant are bound. + vars = subject.universal_vars + solutions = solutions.filter do |solution| + vars.all? {|v| solution.bound?(v)} + end + log_debug("(#{self.class.const_get(:NAME)} solutions(0))") {SXP::Generator.string solutions.to_sxp_bin} + return @solutions = solutions if solutions.empty? + + repo = RDF::N3::Repository.new << subject + + # Query object against repo + solutions = log_depth {object.execute(repo, solutions: solutions, **options)} + + # filter solutions where not all variables in antecedant are bound. + vars = object.universal_vars + @solutions = solutions.filter do |solution| + vars.all? {|v| solution.bound?(v)} + end + log_debug("(#{self.class.const_get(:NAME)} solutions(1))") {SXP::Generator.string @solutions.to_sxp_bin} + # Return original solutions, without bindings + @solutions + end + + ## + # Both subject and object are inputs + # + # @return [RDF::Term] + def input_operand + RDF::N3::List.new(values: operands) + end end end diff --git a/lib/rdf/n3/algebra/log/not_includes.rb b/lib/rdf/n3/algebra/log/not_includes.rb index 22f3347..8f2c97e 100644 --- a/lib/rdf/n3/algebra/log/not_includes.rb +++ b/lib/rdf/n3/algebra/log/not_includes.rb @@ -6,11 +6,25 @@ module RDF::N3::Algebra::Log # (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 NotIncludes < SPARQL::Algebra::Operator::Binary + class NotIncludes < Includes include SPARQL::Algebra::Query include SPARQL::Algebra::Update include RDF::N3::Algebra::Builtin NAME = :logNotIncludes + ## + # Uses log:includes and returns a solution if log:includes fails + # + # @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 + # @return [RDF::Solutions] distinct solutions + def execute(queryable, solutions:, **options) + super + @solutions = solutions.empty? ? RDF::Query::Solutions(RDF::Query::Solution.new) : RDF::Query::Solutions.new + end end end diff --git a/lib/rdf/n3/algebra/log/semantics.rb b/lib/rdf/n3/algebra/log/semantics.rb index 0f65a9d..46c60a5 100644 --- a/lib/rdf/n3/algebra/log/semantics.rb +++ b/lib/rdf/n3/algebra/log/semantics.rb @@ -23,7 +23,7 @@ def evaluate(resource, position: :subject) repo = RDF::N3::Repository.new repo << RDF::Reader.open(resource) content_hash = repo.hash # used as name of resulting formula - RDF::N3::Formula.from_enumerable(repo, graph_name: RDF::Node.new(content_hash)) + RDF::N3::Algebra::Formula.from_enumerable(repo, graph_name: RDF::Node.new(content_hash)) rescue IOError, RDF::ReaderError nil end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index c11857b..b217958 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -17,6 +17,29 @@ end context "n3:log" do + context "log:conjunction" do + { + "conjunction" => { + input: %( + { + ({:sky :color :blue} {:sky :color :green}) + log:conjunction ?F + } => { ?F a :result} . + ), + expect: %( + {:sky :color :blue, :green } a :result . + ) + }, + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + result = reason(options[:input], data: false, filter: true) + expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) + end + end + end + context "log:implies" do { "r1" => { @@ -94,6 +117,36 @@ end end end + + context "log:includes" do + { + "t1" => { + input: %( + {{ :a :b :c } log:includes { :a :b :c }} log:implies { :test1 a :success } . + ), + expect: %( + :test1 a :success . + ) + }, + "t2" => { + input: %( + { { <#theSky> <#is> <#blue> } log:includes {<#theSky> <#is> <#blue>} } log:implies { :test3 a :success } . + { { <#theSky> <#is> <#blue> } log:notIncludes {<#theSky> <#is> <#blue>} } log:implies { :test3_bis a :FAILURE } . + ), + expect: %( + :test3 a :success . + ) + }, + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + result = reason(options[:input]) + expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) + end + end + end + context "log:parsedAsN3" do { "i18n" => { diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 9d7c12e..8b401c7 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -20,14 +20,13 @@ next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do case t.id.split('#').last - when *%w{cwm_math_test} - pending "math numeric representation" + #when *%w{cwm_math_test} + # pending "math numeric representation" when *%w{cwm_time_t1} pending "time" - when *%w{cwm_includes_bnode} - pending "log:includes" - when *%w{cwm_includes_conclusion_simple cwm_unify_unify1} - pending "reason over formulae" + when *%w{cwm_includes_conclusion_simple cwm_unify_unify1 cwm_includes_builtins + cwm_includes_t10 cwm_includes_t11 cwm_includes_quantifiers_limited} + pending "log:includes etc." when *%w{cwm_supports_simple cwm_string_roughly cwm_string_uriEncode} pending "Uses unsupported builtin" when *%w{cwm_list_builtin_generated_match} From c3dc57cf19482d260a0d10570696667b5f8f8d5a Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 3 Oct 2020 11:48:38 -0700 Subject: [PATCH 127/193] Iterate over solutions in log:implies and log:includes to make sure subject and object are bound. --- lib/rdf/n3/algebra/log/implies.rb | 62 +++++++++++++----------------- lib/rdf/n3/algebra/log/includes.rb | 61 +++++++++++++---------------- 2 files changed, 54 insertions(+), 69 deletions(-) diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 1b8a346..b3399a0 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -24,37 +24,31 @@ class Implies < SPARQL::Algebra::Operator::Binary # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions:, **options) @queryable = queryable - subject = subject.evaluate(solutions.first.bindings) || operand(0) - object = object.evaluate(solutions.first.bindings) || operand(1) - log_debug(NAME) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} - log_debug(NAME) {"object: #{SXP::Generator.string operand(1).to_sxp_bin}"} + @solutions = RDF::Query::Solutions(solutions.map do |solution| + subject = operand(0).evaluate(solution.bindings) + object = operand(1).evaluate(solution.bindings) + log_debug(NAME) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} + log_debug(NAME) {"object: #{SXP::Generator.string operand(1).to_sxp_bin}"} - # Nothing to do if variables aren't resolved. - return @solutions = solutions if subject.is_a?(RDF::Query::Variable) || object.is_a?(RDF::Query::Variable) + # Nothing to do if variables aren't resolved. + next unless subject && object - @solutions = log_depth {subject.execute(queryable, solutions: solutions, **options)} - log_debug("(logImplies solutions pre-filter)") {SXP::Generator.string @solutions.to_sxp_bin} + solns = log_depth {subject.execute(queryable, solutions: RDF::Query::Solutions(solution), **options)} + log_debug("(logImplies solutions pre-filter)") {SXP::Generator.string solns.to_sxp_bin} - # filter solutions where not all variables in antecedant are bound. - vars = subject.universal_vars - @solutions = @solutions.filter do |solution| - vars.all? {|v| solution.bound?(v)} - end - log_debug("(logImplies solutions)") {SXP::Generator.string @solutions.to_sxp_bin} + # filter solutions where not all variables in antecedant are bound. + vars = subject.universal_vars + solns = solns.filter do |soln| + vars.all? {|v| soln.bound?(v)} + end + log_debug("(logImplies solutions)") {SXP::Generator.string solns.to_sxp_bin} + solns + end.flatten.compact) # Return original solutions, without bindings solutions end - ## - # Input is the subject - # - # @return [RDF::Term] - def input_operand - # By default, return the merger of input and output operands - operand(0) - 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. # @@ -64,21 +58,19 @@ def input_operand # @yieldreturn [void] ignored def each(&block) @solutions ||= RDF::Query::Solutions.new - subject, object = operands - - return if @solutions.empty? - log_debug {"logImplies each #{SXP::Generator.string @solutions.to_sxp_bin}"} - # Use solutions from subject for object - object.solutions = @solutions - - # Nothing emitted if @solutions is not complete. Solutions are complete when all variables are bound. - log_info("(logImplies implication true; solutions: #{SXP::Generator.string @solutions.to_sxp_bin})") - # Yield statements into the default graph log_depth do - object.each do |statement| - block.call(RDF::Statement.from(statement.to_quad, inferred: true)) + @solutions.each do |solution| + object = operand(1).evaluate(solution.bindings) + next unless object # shouldn't happen + + object.solutions = RDF::Query::Solutions(solution) + + # Yield statements into the default graph + object.each do |statement| + block.call(RDF::Statement.from(statement.to_quad, inferred: true)) + end end end end diff --git a/lib/rdf/n3/algebra/log/includes.rb b/lib/rdf/n3/algebra/log/includes.rb index 5e9bfb3..9fa5c95 100644 --- a/lib/rdf/n3/algebra/log/includes.rb +++ b/lib/rdf/n3/algebra/log/includes.rb @@ -26,46 +26,39 @@ class Includes < SPARQL::Algebra::Operator::Binary # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions:, **options) @queryable = queryable - subject = subject.evaluate(solutions.first.bindings) || operand(0) - object = object.evaluate(solutions.first.bindings) || operand(1) - log_debug(self.class.const_get(:NAME)) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} - log_debug(self.class.const_get(:NAME)) {"object: #{SXP::Generator.string object.to_sxp_bin}"} + @solutions = RDF::Query::Solutions(solutions.map do |solution| + subject = operand(0).evaluate(solution.bindings) + object = operand(1).evaluate(solution.bindings) + log_debug(NAME) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} + log_debug(NAME) {"object: #{SXP::Generator.string operand(1).to_sxp_bin}"} - # Nothing to do if variables aren't resolved. - return @solutions = solutions if subject.is_a?(RDF::Query::Variable) || object.is_a?(RDF::Query::Variable) + # Nothing to do if variables aren't resolved. + next unless subject && object - solutions = log_depth {subject.execute(queryable, solutions: solutions, **options)} - log_debug("(logImplies solutions pre-filter)") {SXP::Generator.string solutions.to_sxp_bin} + solns = log_depth {subject.execute(queryable, solutions: RDF::Query::Solutions(solution), **options)} + log_debug("(logIncludes solutions pre-filter)") {SXP::Generator.string solns.to_sxp_bin} - # filter solutions where not all variables in antecedant are bound. - vars = subject.universal_vars - solutions = solutions.filter do |solution| - vars.all? {|v| solution.bound?(v)} - end - log_debug("(#{self.class.const_get(:NAME)} solutions(0))") {SXP::Generator.string solutions.to_sxp_bin} - return @solutions = solutions if solutions.empty? + # filter solutions where not all variables in antecedant are bound. + vars = subject.universal_vars + solns = solns.filter do |solution| + vars.all? {|v| solution.bound?(v)} + end + log_debug("(logIncludes subject)") {SXP::Generator.string solns.to_sxp_bin} + next if solns.empty? - repo = RDF::N3::Repository.new << subject + repo = RDF::N3::Repository.new << subject - # Query object against repo - solutions = log_depth {object.execute(repo, solutions: solutions, **options)} + # Query object against repo + solns = log_depth {object.execute(repo, solutions: solns, **options)} - # filter solutions where not all variables in antecedant are bound. - vars = object.universal_vars - @solutions = solutions.filter do |solution| - vars.all? {|v| solution.bound?(v)} - end - log_debug("(#{self.class.const_get(:NAME)} solutions(1))") {SXP::Generator.string @solutions.to_sxp_bin} - # Return original solutions, without bindings - @solutions - end - - ## - # Both subject and object are inputs - # - # @return [RDF::Term] - def input_operand - RDF::N3::List.new(values: operands) + # filter solutions where not all variables in antecedant are bound. + vars = object.universal_vars + solns = solns.filter do |soln| + vars.all? {|v| soln.bound?(v)} + end + log_debug("(logIncludes object)") {SXP::Generator.string solns.to_sxp_bin} + solns + end.flatten.compact) end end end From 3bbf87f5c8b4b564ad3c7d27a738ca0bb1895823 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 3 Oct 2020 14:16:37 -0700 Subject: [PATCH 128/193] Use regular double canonicalization. --- lib/rdf/n3/algebra/str/concatenation.rb | 2 +- lib/rdf/n3/refinements.rb | 8 -------- lib/rdf/n3/writer.rb | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb index 93a777c..dd35710 100644 --- a/lib/rdf/n3/algebra/str/concatenation.rb +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -14,7 +14,7 @@ class Concatenation < RDF::N3::Algebra::ListOperator # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - RDF::Literal(list.to_a.map{|li| li.is_a?(RDF::Literal::Double) ? li.canonicalize.to_s.downcase : li.canonicalize}.join("")) + RDF::Literal(list.to_a.map(&:canonicalize).join("")) end end end diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb index 199682c..743f0f9 100644 --- a/lib/rdf/n3/refinements.rb +++ b/lib/rdf/n3/refinements.rb @@ -71,14 +71,6 @@ def valid? end end - refine ::RDF::Literal::Double do - ## - # Canonicalizes to lower-case 'e' - def to_s - super.downcase - end - end - refine ::RDF::Graph do # Allow a graph to be treated as a term in a statement. include ::RDF::Term diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 7d297a2..da44da9 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -285,7 +285,7 @@ def format_literal(literal, **options) if literal.nan? || literal.infinite? quoted(literal.value) + "^^#{format_uri(literal.datatype)}" else - literal.canonicalize.to_s.sub('E', 'e') # Favor lower case exponent + literal.canonicalize.to_s end else text = quoted(literal.value) From f3a7d3119d150bf17abf48f47e81d7f115bb11ac Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 3 Oct 2020 14:18:40 -0700 Subject: [PATCH 129/193] math equal/notEqual compares literals converted to numbers. --- lib/rdf/n3/algebra/formula.rb | 12 +++++------- lib/rdf/n3/algebra/list_operator.rb | 2 +- lib/rdf/n3/algebra/log/implies.rb | 2 +- lib/rdf/n3/algebra/math/equal_to.rb | 2 +- lib/rdf/n3/algebra/math/not_equal_to.rb | 2 +- spec/suite_helper.rb | 3 --- 6 files changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 6a763f7..f3dc50f 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -118,7 +118,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new memo.merge(name => value) end) end - log_info("(formula query solutions)") { these_solutions.to_sxp} + log_info("(formula query solutions)") { SXP::Generator.string(these_solutions.to_sxp_bin)} solutions.merge(these_solutions) end @@ -137,19 +137,17 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new while !ops.empty? last_op = nil ops.each do |op| - log_debug("(formula built-in)") {op.to_sxp} + log_debug("(formula built-in)") {SXP::Generator.string op.to_sxp_bin} solutions = if op.executable? op.execute(queryable, solutions: @solutions) else # Evaluatable - @solutions.all? {|s| op.evaluate(s.bindings) == RDF::Literal::TRUE} ? - @solutions : - RDF::Query::Solutions.new + @solutions.filter {|s| op.evaluate(s.bindings) == RDF::Literal::TRUE} end - log_debug("(formula intermediate solutions)") {"after #{op.to_sxp}: " + SXP::Generator.string(@solutions.to_sxp_bin)} + log_debug("(formula intermediate solutions)") {"after #{op.class.const_get(:NAME)}: " + SXP::Generator.string(@solutions.to_sxp_bin)} # If there are no solutions, try the next one, until we either run out of operations, or we have solutions next if solutions.empty? last_op = op - @solutions = solutions + @solutions = RDF::Query::Solutions(solutions) break end diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index 8549ed8..754397d 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -20,7 +20,7 @@ def execute(queryable, solutions:, **options) list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) object = operand(1).evaluate(solution.bindings) || operand(1) - log_debug(self.class.const_get(:NAME)) {"list: #{SXP::Generator.string list.to_sxp_bin}, object: #{SXP::Generator.string object.to_sxp_bin}"} + log_debug(self.class.const_get(:NAME)) {"list: #{SXP::Generator.string(list.to_sxp_bin).gsub(/\s+/m, ' ')}, object: #{SXP::Generator.string(object.to_sxp_bin).gsub(/\s+/m, ' ')}"} next unless validate(list) lhs = evaluate(list) diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index b3399a0..45ee591 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -41,7 +41,7 @@ def execute(queryable, solutions:, **options) solns = solns.filter do |soln| vars.all? {|v| soln.bound?(v)} end - log_debug("(logImplies solutions)") {SXP::Generator.string solns.to_sxp_bin} + log_info("(logImplies solutions)") {SXP::Generator.string solns.to_sxp_bin} solns end.flatten.compact) diff --git a/lib/rdf/n3/algebra/math/equal_to.rb b/lib/rdf/n3/algebra/math/equal_to.rb index c89cc50..39260ae 100644 --- a/lib/rdf/n3/algebra/math/equal_to.rb +++ b/lib/rdf/n3/algebra/math/equal_to.rb @@ -19,7 +19,7 @@ class EqualTo < SPARQL::Algebra::Operator::Compare # @see RDF::Term#== def apply(term1, term2) log_debug(NAME) { "term1: #{term1.to_sxp} == term2: #{term2.to_sxp} ? #{(term1 == term2).inspect}"} - RDF::Literal(term1 == term2) + RDF::Literal(term1.as_number == term2.as_number) end end end diff --git a/lib/rdf/n3/algebra/math/not_equal_to.rb b/lib/rdf/n3/algebra/math/not_equal_to.rb index ac50713..a061b13 100644 --- a/lib/rdf/n3/algebra/math/not_equal_to.rb +++ b/lib/rdf/n3/algebra/math/not_equal_to.rb @@ -19,7 +19,7 @@ class NotEqualTo < SPARQL::Algebra::Operator::Compare # @see RDF::Term#== def apply(term1, term2) log_debug(NAME) { "term1: #{term1.to_sxp} != term2: #{term2.to_sxp} ? #{(term1 != term2).inspect}"} - RDF::Literal(term1 != term2) + RDF::Literal(term1.as_number != term2.as_number) end end end diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index ee3bcfc..e3f80dc 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -178,9 +178,6 @@ class Entry < JSON::LD::Resource # For debug output formatting def format; :n3; end - # For debug output formatting - def format; :ttl; end - def base action end From bc2f9eaefbdf4f8ff1f82cfa1c6e86992f1836ab Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 3 Oct 2020 14:25:16 -0700 Subject: [PATCH 130/193] Evaluate list terms that are variable in Formula#each. --- lib/rdf/n3/algebra/formula.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index f3dc50f..cfb14c8 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -236,6 +236,8 @@ def each(&block) else solution[o] end + when RDF::N3::List + o.variable? ? o.evaluate(solution.bindings) : o when RDF::N3::Algebra::Formula # uses the graph_name of the formula, and yields statements from the formula o.each do |stmt| From 1e6953ac5061c319d0892ddd54ffd02a3049af91 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 3 Oct 2020 15:15:58 -0700 Subject: [PATCH 131/193] Fix Pattern#initialize! monkey patch. --- lib/rdf/n3/extensions.rb | 3 ++- spec/extensions_spec.rb | 18 --------------- spec/reasoner_spec.rb | 49 +++++++++++----------------------------- spec/writer_spec.rb | 32 +++++++++++++------------- 4 files changed, 31 insertions(+), 71 deletions(-) diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index 74d7db8..a1c68d7 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -154,6 +154,7 @@ def to_ndvar(scope) class Query::Pattern ## # Overrides `#initialize!` to turn blank nodes into non-distinguished variables, if the `:ndvars` option is set. + alias_method :orig_initialize!, :initialize! def initialize! if @options[:ndvars] @graph_name = @graph_name.to_ndvar(nil) if @graph_name @@ -161,7 +162,7 @@ def initialize! @predicate = @predicate.to_ndvar(@graph_name) @object = @object.to_ndvar(@graph_name) end - super + orig_initialize! end ## diff --git a/spec/extensions_spec.rb b/spec/extensions_spec.rb index 24966e3..d71a2d4 100644 --- a/spec/extensions_spec.rb +++ b/spec/extensions_spec.rb @@ -1,24 +1,6 @@ # coding: utf-8 require_relative 'spec_helper' -describe "RDF::Enmerable" do - let(:g1) {RDF::Graph.new {|g| g.insert(*RDF::Spec.triples)}} - let(:g2) {RDF::Graph.new {|g| g.insert(*RDF::Spec.triples[0..(RDF::Spec.triples.length/2)])}} - describe "#contain?" do - it "contains itself" do - expect(g1).to be_contain(g1) - end - - it "contains a subset" do - expect(g1).to be_contain(g2) - end - - it "does not contain a superset" do - expect(g2).not_to be_contain(g1) - end - end -end - describe RDF::List do let(:constant) {RDF::List[RDF::URI("A"), RDF::URI("B")]} let(:nodes) {RDF::List[RDF::Node.new("a"), RDF::Node.new("b")]} diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index b217958..0a9ecab 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -653,31 +653,6 @@ 9.5 :valueOf "(7 / 2) + ((7 % 2)^10000000) + 5 [should be 9.5]" . ) }, - #"Combinatorial test - worksWith": { - # input: %( - # "3.1415926" a :testValue. - # 3.1415926 a :testValue. - # "1729" a :testValue. - # 1729 a :testValue. - # "0" a :testValue. - # 0 a :testValue. - # "1.0e7" a :testValue. - # 1.0e7 a :testValue. - # { ?x a :testValue. ?y a :testValue. - # (?x [ is math:difference of (?y ?x)]) math:sum ?y } => {?x :worksWith ?y}. - # ), - # expect: %( - # "1.0e7" :worksWith 1.0e7 . - # "1729" :worksWith 3.1415926, 1.0e7, 0, 1729 . - # 1729 :worksWith 3.1415926, 1.0e7, 0, 1729 . - # "3.1415926" :worksWith 3.1415926, 1.0e7 . - # 3.1415926 :worksWith 3.1415926, 1.0e7 . - # 1.0e7 :worksWith 1.0e7 . - # "0" :worksWith 3.1415926, 1.0e7, 0, 1729 . - # 0 :worksWith 3.1415926, 1.0e7, 0, 1729 . - # ), - # pending: true - #}, "Combinatorial test - SumDifferenceFAILS": { input: %( "3.1415926" a :testValue. @@ -690,9 +665,20 @@ 1.0e7 a :testValue. { ?x a :testValue. ?y a :testValue. ?z is math:sum of (?x (?y ?x)!math:difference). - ?z math:notEqualTo ?y } => {(?x ?y) :SumDifferenceFAILS ?z}. + ?z math:notEqualTo ?y } => {(?x ?y) :is :SumDifferenceFAILS}. ), - expect: %() + expect: %( + @prefix rdf: . + @prefix xsd: . + + ("1.0e7" "3.1415926"^^xsd:decimal) <#is> <#SumDifferenceFAILS> . + + ("1.0e7"^^xsd:double "3.1415926"^^xsd:decimal) <#is> <#SumDifferenceFAILS> . + + ("1.0e7" "3.1415926") <#is> <#SumDifferenceFAILS> . + + ("1.0e7"^^xsd:double "3.1415926") <#is> <#SumDifferenceFAILS> . + ) }, "Combinatorial test - concatenation": { input: %( @@ -703,8 +689,6 @@ 1729 a :testValue. "0" a :testValue. 0 a :testValue. - "1.0e7" a :testValue. - 1.0e7 a :testValue. { ?x a :testValue. ?y a :testValue. (?x ?y) math:sum ?z. (?x " + " ?y " = " ?z ) string:concatenation ?s @@ -712,19 +696,12 @@ ), expect: %( "0 + 0 = 0" a :RESULT . - "0 + 1.0e7 = 1.0e7" a :RESULT . "0 + 1729 = 1729" a :RESULT . "0 + 3.1415926 = 3.1415926" a :RESULT . - "1.0e7 + 0 = 1.0e7" a :RESULT . - "1.0e7 + 1.0e7 = 2.0e7" a :RESULT . - "1.0e7 + 1729 = 1.0001729e7" a :RESULT . - "1.0e7 + 3.1415926 = 1.00000031415926e7" a :RESULT . "1729 + 0 = 1729" a :RESULT . - "1729 + 1.0e7 = 1.0001729e7" a :RESULT . "1729 + 1729 = 3458" a :RESULT . "1729 + 3.1415926 = 1732.1415926" a :RESULT . "3.1415926 + 0 = 3.1415926" a :RESULT . - "3.1415926 + 1.0e7 = 1.00000031415926e7" a :RESULT . "3.1415926 + 1729 = 1732.1415926" a :RESULT . "3.1415926 + 3.1415926 = 6.2831852" a :RESULT . ) diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index a50d299..8300fca 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -477,13 +477,13 @@ describe "xsd:double" do [ - [%q("1.0e1"^^xsd:double), /1.0e1 ./], - [%q(1.0e1), /1.0e1 ./], - [%q("0.1e1"^^xsd:double), /1.0e0 ./], - [%q(0.1e1), /1.0e0 ./], - [%q("10.02e1"^^xsd:double), /1.002e2 ./], - [%q(10.02e1), /1.002e2 ./], - [%q("14"^^xsd:double), /1.4e1 ./], + [%q("1.0e1"^^xsd:double), /1.0E1 ./], + [%q(1.0e1), /1.0E1 ./], + [%q("0.1e1"^^xsd:double), /1.0E0 ./], + [%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 xsd: . #{l} .) @@ -495,15 +495,15 @@ 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"], + [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"^^}], From bc990d666adc6fa751f54b0adc5581b122a76234 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 3 Oct 2020 16:46:00 -0700 Subject: [PATCH 132/193] Formula only yields statements that aren't builtins. The reasoner creates a knowledge base from the statements yielded by formula merged back into the fuller dataset including the builtins. --- lib/rdf/n3/algebra/formula.rb | 3 +++ lib/rdf/n3/reasoner.rb | 27 ++++++++++++++++----------- spec/suite_reasoner_spec.rb | 2 -- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index cfb14c8..eb43971 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -216,6 +216,9 @@ def each(&block) log_debug("(formula apply)") {solution.to_sxp} # Yield each variable statement which is constant after applying solution patterns.each do |pattern| + # Skip builtins + next if RDF::N3::Algebra.for(pattern.predicate) + terms = {} [:subject, :predicate, :object].each do |part| terms[part] = case o = pattern.send(part) diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 6003f13..9263fd0 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -11,7 +11,7 @@ class Reasoner include RDF::Mutable include RDF::Util::Logger - # The top-level parsed formula + # The top-level parsed formula, including builtins and variables. # @return [RDF::N3::Algebra::Formula] attr_reader :formula @@ -109,28 +109,33 @@ def insert_statement(statement) def execute(**options, &block) @options[:logger] = options[:logger] if options.has_key?(:logger) + # The knowledge base is the non-variable portions of formula + knowledge_base = RDF::N3::Repository.new {|r| r << formula} + log_debug("reasoner: knowledge_base") {SXP::Generator.string(knowledge_base.statements.to_sxp_bin)} + # If thinking, continuously execute until results stop growing if options[:think] - count = 0 + count = -1 log_info("reasoner: think start") { "count: #{count}"} - while @mutable.count > count + while knowledge_base.count > count log_info("reasoner: think do") { "count: #{count}"} - count = @mutable.count - dataset = @mutable.project_graph(nil) - log_depth {formula.execute(dataset, **options)} - @mutable << formula + count = knowledge_base.count + log_depth {formula.execute(knowledge_base, **options)} + knowledge_base << formula end log_info("reasoner: think end") { "count: #{count}"} else # Run one iteration log_info("reasoner: rules start") { "count: #{count}"} - dataset = @mutable.project_graph(nil) - log_depth {formula.execute(dataset, **options)} - @mutable << formula + log_depth {formula.execute(knowledge_base, **options)} + knowledge_base << formula log_info("reasoner: rules end") { "count: #{count}"} end - log_debug("reasoner: datastore") {SXP::Generator.string @mutable.statements.to_sxp_bin} + log_debug("reasoner: datastore") {SXP::Generator.string knowledge_base.statements.to_sxp_bin} + + # Add updates back to mutable, containg builtins and variables. + @mutable << knowledge_base conclusions(&block) if block_given? self diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 8b401c7..7f4ad66 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -20,8 +20,6 @@ next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do case t.id.split('#').last - #when *%w{cwm_math_test} - # pending "math numeric representation" when *%w{cwm_time_t1} pending "time" when *%w{cwm_includes_conclusion_simple cwm_unify_unify1 cwm_includes_builtins From 000ef2f3a18240f19eb486b1272a081392d96391 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 3 Oct 2020 16:57:02 -0700 Subject: [PATCH 133/193] Rename LiteralOperator to ResourceOperator, as it's not just for literals. --- lib/rdf/n3/algebra.rb | 2 +- lib/rdf/n3/algebra/log/content.rb | 2 +- lib/rdf/n3/algebra/log/n3_string.rb | 2 +- lib/rdf/n3/algebra/log/parsed_as_n3.rb | 2 +- lib/rdf/n3/algebra/log/semantics.rb | 2 +- lib/rdf/n3/algebra/math/absolute_value.rb | 3 ++- lib/rdf/n3/algebra/math/acos.rb | 3 ++- lib/rdf/n3/algebra/math/acosh.rb | 3 ++- lib/rdf/n3/algebra/math/asin.rb | 3 ++- lib/rdf/n3/algebra/math/asinh.rb | 3 ++- lib/rdf/n3/algebra/math/atan.rb | 3 ++- lib/rdf/n3/algebra/math/atanh.rb | 3 ++- lib/rdf/n3/algebra/math/ceiling.rb | 3 ++- lib/rdf/n3/algebra/math/cos.rb | 3 ++- lib/rdf/n3/algebra/math/cosh.rb | 3 ++- lib/rdf/n3/algebra/math/floor.rb | 3 ++- lib/rdf/n3/algebra/math/negation.rb | 3 ++- lib/rdf/n3/algebra/math/rounded.rb | 3 ++- lib/rdf/n3/algebra/math/sin.rb | 3 ++- lib/rdf/n3/algebra/math/sinh.rb | 3 ++- lib/rdf/n3/algebra/math/tan.rb | 3 ++- lib/rdf/n3/algebra/math/tanh.rb | 3 ++- .../n3/algebra/{literal_operator.rb => resource_operator.rb} | 2 +- lib/rdf/n3/algebra/time/day.rb | 3 ++- lib/rdf/n3/algebra/time/day_of_week.rb | 3 ++- lib/rdf/n3/algebra/time/gm_time.rb | 3 ++- lib/rdf/n3/algebra/time/hour.rb | 3 ++- lib/rdf/n3/algebra/time/in_seconds.rb | 3 ++- lib/rdf/n3/algebra/time/local_time.rb | 3 ++- lib/rdf/n3/algebra/time/minute.rb | 3 ++- lib/rdf/n3/algebra/time/month.rb | 3 ++- lib/rdf/n3/algebra/time/second.rb | 3 ++- lib/rdf/n3/algebra/time/timezone.rb | 3 ++- lib/rdf/n3/algebra/time/year.rb | 3 ++- 34 files changed, 62 insertions(+), 34 deletions(-) rename lib/rdf/n3/algebra/{literal_operator.rb => resource_operator.rb} (97%) diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index e317200..56b57f9 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -10,8 +10,8 @@ module Algebra autoload :Builtin, 'rdf/n3/algebra/builtin' autoload :Formula, 'rdf/n3/algebra/formula' autoload :ListOperator, 'rdf/n3/algebra/list_operator' - autoload :LiteralOperator, 'rdf/n3/algebra/literal_operator' autoload :NotImplemented, 'rdf/n3/algebra/not_implemented' + autoload :ResourceOperator, 'rdf/n3/algebra/resource_operator' module List autoload :Append, 'rdf/n3/algebra/list/append' diff --git a/lib/rdf/n3/algebra/log/content.rb b/lib/rdf/n3/algebra/log/content.rb index 7642780..1928c19 100644 --- a/lib/rdf/n3/algebra/log/content.rb +++ b/lib/rdf/n3/algebra/log/content.rb @@ -5,7 +5,7 @@ module RDF::N3::Algebra::Log # (Cwm knows how to go get a document in order to evaluate this.) # # Note that the content-type of the information is not given and so must be known or guessed. - class Content < RDF::N3::Algebra::LiteralOperator + class Content < RDF::N3::Algebra::ResourceOperator NAME = :logContent ## diff --git a/lib/rdf/n3/algebra/log/n3_string.rb b/lib/rdf/n3/algebra/log/n3_string.rb index a2cdb17..83be33d 100644 --- a/lib/rdf/n3/algebra/log/n3_string.rb +++ b/lib/rdf/n3/algebra/log/n3_string.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Log ## # The subject formula, expressed as N3, gives this string. - class N3String < RDF::N3::Algebra::LiteralOperator + class N3String < RDF::N3::Algebra::ResourceOperator NAME = :logN3String ## diff --git a/lib/rdf/n3/algebra/log/parsed_as_n3.rb b/lib/rdf/n3/algebra/log/parsed_as_n3.rb index 7d93977..f5afcf7 100644 --- a/lib/rdf/n3/algebra/log/parsed_as_n3.rb +++ b/lib/rdf/n3/algebra/log/parsed_as_n3.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Log ## # The subject string, parsed as N3, gives this formula. - class ParsedAsN3 < RDF::N3::Algebra::LiteralOperator + class ParsedAsN3 < RDF::N3::Algebra::ResourceOperator NAME = :logParsedAsN3 ## diff --git a/lib/rdf/n3/algebra/log/semantics.rb b/lib/rdf/n3/algebra/log/semantics.rb index 46c60a5..ffbae77 100644 --- a/lib/rdf/n3/algebra/log/semantics.rb +++ b/lib/rdf/n3/algebra/log/semantics.rb @@ -5,7 +5,7 @@ module RDF::N3::Algebra::Log # [Aside: Philosophers will be distracted here into worrying about the meaning of meaning. At least we didn't call this function "meaning"! In as much as N3 is used as an interlingua for interoperability for different systems, this for an N3 based system is the meaning expressed by a document.] # # (Cwm knows how to go get a document and parse N3 and RDF/XML it in order to evaluate this. Other languages for web documents may be defined whose N3 semantics are therefore also calculable, and so they could be added in due course. See for example GRDDL, RDFa, etc) - class Semantics < RDF::N3::Algebra::LiteralOperator + class Semantics < RDF::N3::Algebra::ResourceOperator NAME = :logSemantics ## diff --git a/lib/rdf/n3/algebra/math/absolute_value.rb b/lib/rdf/n3/algebra/math/absolute_value.rb index d7734ba..05253be 100644 --- a/lib/rdf/n3/algebra/math/absolute_value.rb +++ b/lib/rdf/n3/algebra/math/absolute_value.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the absolute value of the subject. - class AbsoluteValue < RDF::N3::Algebra::LiteralOperator + class AbsoluteValue < RDF::N3::Algebra::ResourceOperator NAME = :mathAbsoluteValue ## @@ -10,6 +10,7 @@ class AbsoluteValue < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/math/acos.rb b/lib/rdf/n3/algebra/math/acos.rb index e33da34..505e5f7 100644 --- a/lib/rdf/n3/algebra/math/acos.rb +++ b/lib/rdf/n3/algebra/math/acos.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the arc cosine value of the subject. - class ACos < RDF::N3::Algebra::LiteralOperator + class ACos < RDF::N3::Algebra::ResourceOperator NAME = :mathACos ## @@ -10,6 +10,7 @@ class ACos < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/math/acosh.rb b/lib/rdf/n3/algebra/math/acosh.rb index ec77244..c62cb40 100644 --- a/lib/rdf/n3/algebra/math/acosh.rb +++ b/lib/rdf/n3/algebra/math/acosh.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the inverse hyperbolic cosine value of the subject. - class ACosH < RDF::N3::Algebra::LiteralOperator + class ACosH < RDF::N3::Algebra::ResourceOperator NAME = :mathACosH ## @@ -10,6 +10,7 @@ class ACosH < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/math/asin.rb b/lib/rdf/n3/algebra/math/asin.rb index b25ed4d..9133f52 100644 --- a/lib/rdf/n3/algebra/math/asin.rb +++ b/lib/rdf/n3/algebra/math/asin.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the arc sine value of the subject. - class ASin < RDF::N3::Algebra::LiteralOperator + class ASin < RDF::N3::Algebra::ResourceOperator NAME = :mathASin ## @@ -10,6 +10,7 @@ class ASin < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/math/asinh.rb b/lib/rdf/n3/algebra/math/asinh.rb index d3a19f4..ddfe04e 100644 --- a/lib/rdf/n3/algebra/math/asinh.rb +++ b/lib/rdf/n3/algebra/math/asinh.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the inverse hyperbolic sine value of the subject. - class ASinH < RDF::N3::Algebra::LiteralOperator + class ASinH < RDF::N3::Algebra::ResourceOperator NAME = :mathASinH ## @@ -10,6 +10,7 @@ class ASinH < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/math/atan.rb b/lib/rdf/n3/algebra/math/atan.rb index d7efa62..efa7c31 100644 --- a/lib/rdf/n3/algebra/math/atan.rb +++ b/lib/rdf/n3/algebra/math/atan.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the arc tangent value of the subject. - class ATan < RDF::N3::Algebra::LiteralOperator + class ATan < RDF::N3::Algebra::ResourceOperator NAME = :mathATan ## @@ -10,6 +10,7 @@ class ATan < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/math/atanh.rb b/lib/rdf/n3/algebra/math/atanh.rb index df0808e..ebd7ebb 100644 --- a/lib/rdf/n3/algebra/math/atanh.rb +++ b/lib/rdf/n3/algebra/math/atanh.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the inverse hyperbolic tangent value of the subject. - class ATanH < RDF::N3::Algebra::LiteralOperator + class ATanH < RDF::N3::Algebra::ResourceOperator NAME = :mathATanH ## @@ -10,6 +10,7 @@ class ATanH < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/math/ceiling.rb b/lib/rdf/n3/algebra/math/ceiling.rb index 8115371..063f160 100644 --- a/lib/rdf/n3/algebra/math/ceiling.rb +++ b/lib/rdf/n3/algebra/math/ceiling.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The object is calculated as the subject upwards to a whole number. - class Ceiling < RDF::N3::Algebra::LiteralOperator + class Ceiling < RDF::N3::Algebra::ResourceOperator NAME = :mathCeiling ## @@ -10,6 +10,7 @@ class Ceiling < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/math/cos.rb b/lib/rdf/n3/algebra/math/cos.rb index c9c7390..e6f6aed 100644 --- a/lib/rdf/n3/algebra/math/cos.rb +++ b/lib/rdf/n3/algebra/math/cos.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the cosine value of the subject. - class Cos < RDF::N3::Algebra::LiteralOperator + class Cos < RDF::N3::Algebra::ResourceOperator NAME = :mathCos ## @@ -10,6 +10,7 @@ class Cos < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case resource when RDF::Query::Variable then resource diff --git a/lib/rdf/n3/algebra/math/cosh.rb b/lib/rdf/n3/algebra/math/cosh.rb index 7edfc06..1569178 100644 --- a/lib/rdf/n3/algebra/math/cosh.rb +++ b/lib/rdf/n3/algebra/math/cosh.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the hyperbolic cosine value of the subject. - class CosH < RDF::N3::Algebra::LiteralOperator + class CosH < RDF::N3::Algebra::ResourceOperator NAME = :mathCosH ## @@ -10,6 +10,7 @@ class CosH < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case resource when RDF::Query::Variable then resource diff --git a/lib/rdf/n3/algebra/math/floor.rb b/lib/rdf/n3/algebra/math/floor.rb index c3f3c26..2509222 100644 --- a/lib/rdf/n3/algebra/math/floor.rb +++ b/lib/rdf/n3/algebra/math/floor.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The object is calculated as the subject downwards to a whole number. - class Floor < RDF::N3::Algebra::LiteralOperator + class Floor < RDF::N3::Algebra::ResourceOperator NAME = :mathFloor ## @@ -10,6 +10,7 @@ class Floor < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb index b0ef212..e9e9ac9 100644 --- a/lib/rdf/n3/algebra/math/negation.rb +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The subject or object is calculated to be the negation of the other. - class Negation < RDF::N3::Algebra::LiteralOperator + class Negation < RDF::N3::Algebra::ResourceOperator include RDF::N3::Algebra::Builtin NAME = :mathNegation @@ -12,6 +12,7 @@ class Negation < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case resource when RDF::Query::Variable diff --git a/lib/rdf/n3/algebra/math/rounded.rb b/lib/rdf/n3/algebra/math/rounded.rb index 3f6ddbd..aecb888 100644 --- a/lib/rdf/n3/algebra/math/rounded.rb +++ b/lib/rdf/n3/algebra/math/rounded.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the subject rounded to the nearest integer. - class Rounded < RDF::N3::Algebra::LiteralOperator + class Rounded < RDF::N3::Algebra::ResourceOperator NAME = :mathRounded ## @@ -10,6 +10,7 @@ class Rounded < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/math/sin.rb b/lib/rdf/n3/algebra/math/sin.rb index 08f70f9..4ec2f5d 100644 --- a/lib/rdf/n3/algebra/math/sin.rb +++ b/lib/rdf/n3/algebra/math/sin.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the sine value of the subject. - class Sin < RDF::N3::Algebra::LiteralOperator + class Sin < RDF::N3::Algebra::ResourceOperator NAME = :mathSin ## @@ -10,6 +10,7 @@ class Sin < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case resource when RDF::Query::Variable then resource diff --git a/lib/rdf/n3/algebra/math/sinh.rb b/lib/rdf/n3/algebra/math/sinh.rb index 5990aaf..117de9d 100644 --- a/lib/rdf/n3/algebra/math/sinh.rb +++ b/lib/rdf/n3/algebra/math/sinh.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the hyperbolic sine value of the subject. - class SinH < RDF::N3::Algebra::LiteralOperator + class SinH < RDF::N3::Algebra::ResourceOperator NAME = :mathSinH ## @@ -10,6 +10,7 @@ class SinH < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case resource when RDF::Query::Variable then resource diff --git a/lib/rdf/n3/algebra/math/tan.rb b/lib/rdf/n3/algebra/math/tan.rb index 8f122c9..3cd8f9f 100644 --- a/lib/rdf/n3/algebra/math/tan.rb +++ b/lib/rdf/n3/algebra/math/tan.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the tangent value of the subject. - class Tan < RDF::N3::Algebra::LiteralOperator + class Tan < RDF::N3::Algebra::ResourceOperator NAME = :mathTan ## @@ -10,6 +10,7 @@ class Tan < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case resource when RDF::Query::Variable then resource diff --git a/lib/rdf/n3/algebra/math/tanh.rb b/lib/rdf/n3/algebra/math/tanh.rb index dbbdd21..4200f16 100644 --- a/lib/rdf/n3/algebra/math/tanh.rb +++ b/lib/rdf/n3/algebra/math/tanh.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the tangent value of the subject. - class TanH < RDF::N3::Algebra::LiteralOperator + class TanH < RDF::N3::Algebra::ResourceOperator NAME = :mathTanH ## @@ -10,6 +10,7 @@ class TanH < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case resource when RDF::Query::Variable then resource diff --git a/lib/rdf/n3/algebra/literal_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb similarity index 97% rename from lib/rdf/n3/algebra/literal_operator.rb rename to lib/rdf/n3/algebra/resource_operator.rb index f81742c..37ff6ec 100644 --- a/lib/rdf/n3/algebra/literal_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra ## # This is a generic operator where the subject is a literal or binds to a literal and the object is either a constant that equals the evaluation of the subject, or a variable to which the result is bound in a solution - class LiteralOperator < SPARQL::Algebra::Operator::Binary + class ResourceOperator < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Query include SPARQL::Algebra::Update include RDF::N3::Algebra::Builtin diff --git a/lib/rdf/n3/algebra/time/day.rb b/lib/rdf/n3/algebra/time/day.rb index 435d111..1514e97 100644 --- a/lib/rdf/n3/algebra/time/day.rb +++ b/lib/rdf/n3/algebra/time/day.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:day is the day of the month. - class Day < RDF::N3::Algebra::LiteralOperator + class Day < RDF::N3::Algebra::ResourceOperator NAME = :timeDay ## @@ -10,6 +10,7 @@ class Day < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/time/day_of_week.rb b/lib/rdf/n3/algebra/time/day_of_week.rb index 17fbf05..987378e 100644 --- a/lib/rdf/n3/algebra/time/day_of_week.rb +++ b/lib/rdf/n3/algebra/time/day_of_week.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:dayOfWeek is the the day number within the week, Sunday being 0. - class DayOfWeek < RDF::N3::Algebra::LiteralOperator + class DayOfWeek < RDF::N3::Algebra::ResourceOperator NAME = :timeDayOfWeek ## @@ -10,6 +10,7 @@ class DayOfWeek < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/time/gm_time.rb b/lib/rdf/n3/algebra/time/gm_time.rb index a6a4953..b61c96d 100644 --- a/lib/rdf/n3/algebra/time/gm_time.rb +++ b/lib/rdf/n3/algebra/time/gm_time.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Time ## # For a date-time format string, its time:gmtime is the result of formatting the Universal Time of processing in the format given. If the format string has zero length, then the ISOdate standard format is used. `[ is time:gmtime of ""]` the therefore the current date time. It will end with "Z" as a timezone code. - class GmTime < RDF::N3::Algebra::LiteralOperator + class GmTime < RDF::N3::Algebra::ResourceOperator NAME = :timeGmTime ## @@ -10,6 +10,7 @@ class GmTime < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/time/hour.rb b/lib/rdf/n3/algebra/time/hour.rb index d2a5291..db533bc 100644 --- a/lib/rdf/n3/algebra/time/hour.rb +++ b/lib/rdf/n3/algebra/time/hour.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:hour is the hour in the 24 hour clock. - class Hour < RDF::N3::Algebra::LiteralOperator + class Hour < RDF::N3::Algebra::ResourceOperator NAME = :timeHour ## @@ -10,6 +10,7 @@ class Hour < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/time/in_seconds.rb b/lib/rdf/n3/algebra/time/in_seconds.rb index 9d4e3fc..f070fab 100644 --- a/lib/rdf/n3/algebra/time/in_seconds.rb +++ b/lib/rdf/n3/algebra/time/in_seconds.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:inSeconds is the (string represntation of) the floating point number of seconds since the beginning of the era on the given system. - class InSeconds < RDF::N3::Algebra::LiteralOperator + class InSeconds < RDF::N3::Algebra::ResourceOperator NAME = :timeInSeconds ## @@ -10,6 +10,7 @@ class InSeconds < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/time/local_time.rb b/lib/rdf/n3/algebra/time/local_time.rb index 80500f2..3718d9f 100644 --- a/lib/rdf/n3/algebra/time/local_time.rb +++ b/lib/rdf/n3/algebra/time/local_time.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Time ## # For a date-time format string, its time:localTime is the result of formatting the current time of processing and local timezone in the format given. If the format string has zero length, then the ISOdate standrad format is used. [ is time:localTime of ""] the therefore the current date time. It will end with a numeric timezone code or "Z" for UTC (GMT). - class LocalTime < RDF::N3::Algebra::LiteralOperator + class LocalTime < RDF::N3::Algebra::ResourceOperator NAME = :timeLocalTime ## @@ -10,6 +10,7 @@ class LocalTime < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/time/minute.rb b/lib/rdf/n3/algebra/time/minute.rb index d478c22..3ef1090 100644 --- a/lib/rdf/n3/algebra/time/minute.rb +++ b/lib/rdf/n3/algebra/time/minute.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:minute is the minutes component. - class Minute < RDF::N3::Algebra::LiteralOperator + class Minute < RDF::N3::Algebra::ResourceOperator NAME = :timeMinute ## @@ -10,6 +10,7 @@ class Minute < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/time/month.rb b/lib/rdf/n3/algebra/time/month.rb index ed25050..bb07833 100644 --- a/lib/rdf/n3/algebra/time/month.rb +++ b/lib/rdf/n3/algebra/time/month.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:month is the two-digit month. - class Month < RDF::N3::Algebra::LiteralOperator + class Month < RDF::N3::Algebra::ResourceOperator NAME = :timeMonth ## @@ -10,6 +10,7 @@ class Month < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/time/second.rb b/lib/rdf/n3/algebra/time/second.rb index 3484a8c..23a25d9 100644 --- a/lib/rdf/n3/algebra/time/second.rb +++ b/lib/rdf/n3/algebra/time/second.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:second is the seconds component. - class Second < RDF::N3::Algebra::LiteralOperator + class Second < RDF::N3::Algebra::ResourceOperator NAME = :timeSecond ## @@ -10,6 +10,7 @@ class Second < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/time/timezone.rb b/lib/rdf/n3/algebra/time/timezone.rb index 7ef7cd7..c3ab8f0 100644 --- a/lib/rdf/n3/algebra/time/timezone.rb +++ b/lib/rdf/n3/algebra/time/timezone.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:timeZone is the trailing timezone offset part, e.g. "-05:00". - class Timezone < RDF::N3::Algebra::LiteralOperator + class Timezone < RDF::N3::Algebra::ResourceOperator NAME = :timeTimezone ## @@ -10,6 +10,7 @@ class Timezone < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject diff --git a/lib/rdf/n3/algebra/time/year.rb b/lib/rdf/n3/algebra/time/year.rb index 432756d..d3594d1 100644 --- a/lib/rdf/n3/algebra/time/year.rb +++ b/lib/rdf/n3/algebra/time/year.rb @@ -1,7 +1,7 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:year is the year component. - class Year < RDF::N3::Algebra::LiteralOperator + class Year < RDF::N3::Algebra::ResourceOperator NAME = :timeYear ## @@ -10,6 +10,7 @@ class Year < RDF::N3::Algebra::LiteralOperator # @param [RDF::Term] resource # @param [:subject, :object] position # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate def evaluate(resource, position:) case position when :subject From 962e0a5181c7c3a4428f78016eae31f7a253dc03 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 8 Oct 2020 14:23:28 -0700 Subject: [PATCH 134/193] * Keep index of bnode=>formula in formula across all operators. * When evaluating, transform bnodes identifying formulae to those formulae. --- README.md | 2 +- lib/rdf/n3/algebra/formula.rb | 34 ++++++++++++++++++++----- lib/rdf/n3/algebra/list/in.rb | 6 ++--- lib/rdf/n3/algebra/list/member.rb | 7 ++--- lib/rdf/n3/algebra/list_operator.rb | 7 ++--- lib/rdf/n3/algebra/log/parsed_as_n3.rb | 2 +- lib/rdf/n3/algebra/resource_operator.rb | 10 +++++--- lib/rdf/n3/extensions.rb | 10 ++++++++ lib/rdf/n3/list.rb | 9 +++++-- lib/rdf/n3/writer.rb | 2 +- 10 files changed, 65 insertions(+), 24 deletions(-) mode change 100755 => 100644 README.md diff --git a/README.md b/README.md old mode 100755 new mode 100644 index 727cce3..725401f --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Reasoning is discussed in the [Design Issues][] document. #### RDF Log vocabulary - * `log:conclusion` (not implemented yet - See {RDF::N3::Algebra::Log::Conclusion}) + * `log:conclusion` (See {RDF::N3::Algebra::Log::Conclusion}) * `log:conjunction` (See {RDF::N3::Algebra::Log::Conjunction}) * `log:content` (See {RDF::N3::Algebra::Log::Content}) * `log:equalTo` (See {RDF::N3::Algebra::Log::EqualTo}) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index eb43971..7029eac 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -30,8 +30,9 @@ def self.from_enumerable(enumerable, **options) require 'sparql' unless defined?(:SPARQL) # Create formulae from statement graph_names - formulae = (enumerable.graph_names.unshift(nil)).inject({}) do |memo, graph_name| - memo.merge(graph_name => Formula.new(graph_name: graph_name, **options)) + formulae = {} + enumerable.graph_names.unshift(nil).each do |graph_name| + formulae[graph_name] = Formula.new(graph_name: graph_name, formulae: formulae, **options) end # Add patterns to appropiate formula based on graph_name, @@ -71,7 +72,12 @@ def self.from_enumerable(enumerable, **options) # Formulae may be the subject or object of a known operator if klass = RDF::N3::Algebra.for(pattern.predicate) - form.operands << klass.new(pattern.subject, pattern.object, parent: form, predicate: pattern.predicate, **options) + form.operands << klass.new(pattern.subject, + pattern.object, + formulae: formulae, + parent: form, + predicate: pattern.predicate, + **options) else pattern.graph_name = nil form.operands << pattern @@ -112,7 +118,8 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new these_solutions = queryable.query(@query, solutions: solutions, **options) these_solutions.map! do |solution| RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)| - # Replace blank node bindings with lists, where those blank nodes are associated with lists. + # Replace blank node bindings with lists and formula references with formula, where those blank nodes are associated with lists. + value = formulae.fetch(value, value) if value.node? l = RDF::N3::List.try_list(value, queryable) value = l if l.constant? memo.merge(name => value) @@ -143,7 +150,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new else # Evaluatable @solutions.filter {|s| op.evaluate(s.bindings) == RDF::Literal::TRUE} end - log_debug("(formula intermediate solutions)") {"after #{op.class.const_get(:NAME)}: " + SXP::Generator.string(@solutions.to_sxp_bin)} + log_debug("(formula intermediate solutions)") {"after #{op.class.const_get(:NAME)}: " + SXP::Generator.string(solutions.to_sxp_bin)} # If there are no solutions, try the next one, until we either run out of operations, or we have solutions next if solutions.empty? last_op = op @@ -176,6 +183,21 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new @solutions end + ## + # Evaluates the formula using the given variable `bindings` by cloning the formula and setting the solutions to `bindings` so that `#each` will generate statements from those bindings. + # + # @param [Hash{Symbol => RDF::Term}] bindings + # a query solution containing zero or more variable bindings + # @param [Hash{Symbol => Object}] options ({}) + # options passed from query + # @return [RDF::N3::List] + # @see SPARQL::Algebra::Expression.evaluate + def evaluate(bindings, **options) + this = dup + this.solutions = RDF::Query::Solutions(bindings) + this + end + ## # Returns `true` if `self` is a {RDF::N3::Algebra::Formula}. # @@ -240,7 +262,7 @@ def each(&block) solution[o] end when RDF::N3::List - o.variable? ? o.evaluate(solution.bindings) : o + o.variable? ? o.evaluate(solution.bindings, formulae: formulae) : o when RDF::N3::Algebra::Formula # uses the graph_name of the formula, and yields statements from the formula o.each do |stmt| diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index 65838a0..9c86d31 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -16,11 +16,11 @@ class In < RDF::N3::Algebra::ListOperator # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| - subject = operand(0).evaluate(solution.bindings) || operand(0) + subject = operand(0).evaluate(solution.bindings, formulae: formulae) || operand(0) # Might be a variable or node evaluating to a list in queryable, or might be a list with variables - list = operand(1).evaluate(solution.bindings) + list = operand(1).evaluate(solution.bindings, formulae: formulae) # If it evaluated to a BNode, re-expand as a list - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) log_debug(NAME) {"subject: #{subject.to_sxp}, list: #{list.to_sxp}"} unless list.list? && list.valid? diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index cbfeeb4..646a414 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -13,9 +13,10 @@ class Member < RDF::N3::Algebra::ListOperator # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| - list = operand(0).evaluate(solution.bindings) - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - object = operand(1).evaluate(solution.bindings) || operand(1) + list = operand(0).evaluate(solution.bindings, formulae: formulae) + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) + object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) + object = formulae.fetch(object, object) if object.node? log_debug(NAME) {"list: #{list.to_sxp}, object: #{object.to_sxp}"} unless list.list? && list.valid? diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index 754397d..1ac22b0 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -15,10 +15,11 @@ class ListOperator < SPARQL::Algebra::Operator::Binary def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| # Might be a variable or node evaluating to a list in queryable, or might be a list with variables - list = operand(0).evaluate(solution.bindings) + list = operand(0).evaluate(solution.bindings, formulae: formulae) # If it evaluated to a BNode, re-expand as a list - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings) - object = operand(1).evaluate(solution.bindings) || operand(1) + list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) + object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) + object = formulae.fetch(object, object) if object.node? log_debug(self.class.const_get(:NAME)) {"list: #{SXP::Generator.string(list.to_sxp_bin).gsub(/\s+/m, ' ')}, object: #{SXP::Generator.string(object.to_sxp_bin).gsub(/\s+/m, ' ')}"} next unless validate(list) diff --git a/lib/rdf/n3/algebra/log/parsed_as_n3.rb b/lib/rdf/n3/algebra/log/parsed_as_n3.rb index f5afcf7..ec1e8bb 100644 --- a/lib/rdf/n3/algebra/log/parsed_as_n3.rb +++ b/lib/rdf/n3/algebra/log/parsed_as_n3.rb @@ -25,7 +25,7 @@ def evaluate(resource, position: :subject) nil end when :object - return nil unless resource.literal? + return nil unless resource.literal? || resource.is_a?(RDF::Query::Variable) resource end end diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 37ff6ec..346a631 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -14,10 +14,13 @@ class ResourceOperator < SPARQL::Algebra::Operator::Binary # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| - subject = operand(0).evaluate(solution.bindings) || operand(0) - object = operand(1).evaluate(solution.bindings) || operand(1) + subject = operand(0).evaluate(solution.bindings, formulae: formulae) || operand(0) + object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) + subject = formulae.fetch(subject, subject) if subject.node? + object = formulae.fetch(object, object) if object.node? log_debug(self.class.const_get(:NAME)) {"subject: #{subject.to_sxp}, object: #{object.to_sxp}"} + next unless valid?(subject, object) lhs = evaluate(subject, position: :subject) if lhs.nil? @@ -26,11 +29,10 @@ def execute(queryable, solutions:, **options) end rhs = evaluate(object, position: :object) - if lhs.nil? + if rhs.nil? log_error(self.class.const_get(:NAME)) {"object is invalid: #{object.inspect}"} next end - next unless valid?(subject, object) if object.variable? solution.merge(object.to_sym => lhs) diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index a1c68d7..4a9106d 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -233,4 +233,14 @@ def to_sxp to_s end end + + class SPARQL::Algebra::Operator + ## + # Map of related formulae, indexed by graph name. + # + # @return [Hash{RDF::Resource => RDF::N3::Algebra::Formula}] + def formulae + @options.fetch(:formulae, {}) + end + end end diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index 311ed37..adb6bf6 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -556,13 +556,18 @@ def var_values(var, list) # options passed from query # @return [RDF::N3::List] # @see SPARQL::Algebra::Expression.evaluate - def evaluate(bindings, **options) + def evaluate(bindings, formulae: {}, **options) # if values are constant, simply return ourselves return self if to_a.none? {|li| li.node? || li.variable?} bindings = bindings.to_h unless bindings.is_a?(Hash) # Create a new list subject using a combination of the current subject and a hash of the binding values subj = "#{subject.id}_#{bindings.values.sort.hash}" - RDF::N3::List.new(subject: RDF::Node.intern(subj), values: to_a.map {|o| o.evaluate(bindings, **options)}) + values = to_a.map do |o| + o = o.evaluate(bindings, formulae: formulae, **options) + # Map graph names to graphs + o.node? ? formulae.fetch(o, o) : o + end + RDF::N3::List.new(subject: RDF::Node.intern(subj), values: values) end ## diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index da44da9..0ae3e4b 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -663,7 +663,7 @@ def triples(subject) path(subject, :subject) @output.write(" ") num_props = predicateObjectList(subject) - @output.write("#{num_props > 0 ? ' ' : ''}.") + @output.puts("#{num_props > 0 ? ' ' : ''}.") true end From 663ff84415f4e45bcdd5f8bb558fe4f82dd1afbf Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 8 Oct 2020 14:24:55 -0700 Subject: [PATCH 135/193] Implement log:conclusion, allowing reasoner to be invoked with formula. --- lib/rdf/n3/algebra/log/conclusion.rb | 45 ++++++++++++++++++++++++---- lib/rdf/n3/reasoner.rb | 7 +++-- spec/reasoner_spec.rb | 29 ++++++++++++++++-- spec/suite_reasoner_spec.rb | 2 +- 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/lib/rdf/n3/algebra/log/conclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb index 0087d6b..c713a70 100644 --- a/lib/rdf/n3/algebra/log/conclusion.rb +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -3,11 +3,46 @@ 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 Conclusion < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::N3::Algebra::Builtin - + class Conclusion < RDF::N3::Algebra::ResourceOperator NAME = :logConclusion + + ## + # Evaluates this operator by creating a new formula containing the triples generated by reasoning over the input formula using think.. + # + # @param [RDF::N3::Algebra:Formula] resource + # @return [RDF::N3::Algebra::Formula] + # @see RDF::N3::ListOperator#evaluate + def evaluate(resource, position:) + return resource unless position == :subject + + log_depth do + reasoner = RDF::N3::Reasoner.new(resource, @options) + conclusions = [].extend(RDF::Enumerable) + reasoner.execute(think: true) {|stmt| conclusions << stmt} + + # The result is a formula containing the conclusions + form = RDF::N3::Algebra::Formula.from_enumerable(conclusions, graph_name: RDF::Node.intern(conclusions.hash)) + log_info('(logConclusion result)') {SXP::Generator.string(form.to_sxp_bin).gsub(/\s+/m, ' ')} + form + end + end + + ## + # To be valid, subject must be a formula, and object a formula or variable. + # + # @param [RDF::Term] subject + # @param [RDF::Term] object + # @return [Boolean] + def valid?(subject, object) + subject.formula? && (object.formula? || object.is_a?(RDF::Query::Variable)) + end + + ## + # Return subject operand. + # + # @return [RDF::Term] + def input_operand + operands.first + end end end diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 9263fd0..32036ce 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -67,6 +67,8 @@ def initialize(input, **options, &block) else RDF::N3::Repository.new end + @formula = input if input.is_a?(RDF::N3::Algebra::Formula) + log_debug("reasoner: expression") {SXP::Generator.string(formula.to_sxp_bin)} if block_given? @@ -93,6 +95,7 @@ def dup # @param [RDF::Statement] statement # @return [void] def insert_statement(statement) + @formula = nil @mutable.insert_statement(statement) end @@ -122,6 +125,7 @@ def execute(**options, &block) count = knowledge_base.count log_depth {formula.execute(knowledge_base, **options)} knowledge_base << formula + log_debug("reasoner: datastore") {SXP::Generator.string knowledge_base.statements.to_sxp_bin} end log_info("reasoner: think end") { "count: #{count}"} else @@ -137,8 +141,7 @@ def execute(**options, &block) # Add updates back to mutable, containg builtins and variables. @mutable << knowledge_base - conclusions(&block) if block_given? - self + block_given? ? conclusions(&block) : self end alias_method :reason!, :execute diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 0a9ecab..e217443 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -17,6 +17,31 @@ end context "n3:log" do + context "log:conclusion" do + { + "conclusion-simple" => { + input: %( + { + { } => { a } . + . + } a :TestRule. + + { ?x a :TestRule; log:conclusion ?y } => { ?y a :TestResult }. + ), + expect: %( + { a } a :TestResult . + ) + }, + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + expected = parse(options[:expect]) + result = reason(options[:input], data: false, filter: true) + expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) + end + end + end + context "log:conjunction" do { "conjunction" => { @@ -784,10 +809,10 @@ def parse(input, **options) # Reason over input, returning a repo def reason(input, base_uri: 'http://example.com/', filter: false, data: true, think: true, **options) input = parse(input, list_terms: true, **options) if input.is_a?(String) - reasoner = RDF::N3::Reasoner.new(input, base_uri: base_uri) + reasoner = RDF::N3::Reasoner.new(input, base_uri: base_uri, logger: logger) repo = RDF::N3:: Repository.new - reasoner.execute(logger: logger, think: think) + reasoner.execute(think: think) if filter repo << reasoner.conclusions elsif data diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 7f4ad66..367de6c 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -22,7 +22,7 @@ case t.id.split('#').last when *%w{cwm_time_t1} pending "time" - when *%w{cwm_includes_conclusion_simple cwm_unify_unify1 cwm_includes_builtins + when *%w{cwm_unify_unify1 cwm_includes_builtins cwm_includes_t10 cwm_includes_t11 cwm_includes_quantifiers_limited} pending "log:includes etc." when *%w{cwm_supports_simple cwm_string_roughly cwm_string_uriEncode} From 7f0ebf241f80530591ec66858a758a6a0f3f4595 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 8 Oct 2020 14:44:38 -0700 Subject: [PATCH 136/193] Remove explicit arc- versions of trig builtins, as the normal operations work in both directions. --- README.md | 6 ------ lib/rdf/n3/algebra.rb | 12 ------------ lib/rdf/n3/algebra/math/acos.rb | 25 ------------------------- lib/rdf/n3/algebra/math/acosh.rb | 25 ------------------------- lib/rdf/n3/algebra/math/asin.rb | 25 ------------------------- lib/rdf/n3/algebra/math/asinh.rb | 25 ------------------------- lib/rdf/n3/algebra/math/atan.rb | 25 ------------------------- lib/rdf/n3/algebra/math/atanh.rb | 25 ------------------------- spec/reasoner_spec.rb | 2 -- 9 files changed, 170 deletions(-) delete mode 100644 lib/rdf/n3/algebra/math/acos.rb delete mode 100644 lib/rdf/n3/algebra/math/acosh.rb delete mode 100644 lib/rdf/n3/algebra/math/asin.rb delete mode 100644 lib/rdf/n3/algebra/math/asinh.rb delete mode 100644 lib/rdf/n3/algebra/math/atan.rb delete mode 100644 lib/rdf/n3/algebra/math/atanh.rb diff --git a/README.md b/README.md index 725401f..07ef4e6 100644 --- a/README.md +++ b/README.md @@ -89,12 +89,6 @@ Reasoning is discussed in the [Design Issues][] document. #### RDF Math vocabulary * `math:absoluteValue` (See {RDF::N3::Algebra::Math::AbsoluteValue}) - * `math:acos` (See {RDF::N3::Algebra::Math::ACos}) - * `math:asin` (See {RDF::N3::Algebra::Math::ASin}) - * `math:atan` (See {RDF::N3::Algebra::Math::ATan}) - * `math:acosh` (See {RDF::N3::Algebra::Math::ACosH}) - * `math:asinh` (See {RDF::N3::Algebra::Math::ASinH}) - * `math:atanh` (See {RDF::N3::Algebra::Math::ATanH}) * `math:ceiling` (See {RDF::N3::Algebra::Math::Ceiling}) * `math:cosh` (See {RDF::N3::Algebra::Math::CosH}) * `math:cos` (See {RDF::N3::Algebra::Math::Cos}) diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 56b57f9..767268e 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -39,12 +39,6 @@ module Log module Math autoload :AbsoluteValue, 'rdf/n3/algebra/math/absolute_value' - autoload :ACos, 'rdf/n3/algebra/math/acos' - autoload :ASin, 'rdf/n3/algebra/math/asin' - autoload :ATan, 'rdf/n3/algebra/math/atan' - autoload :ACosH, 'rdf/n3/algebra/math/acosh' - autoload :ASinH, 'rdf/n3/algebra/math/asinh' - autoload :ATanH, 'rdf/n3/algebra/math/atanh' autoload :Ceiling, 'rdf/n3/algebra/math/ceiling' autoload :Cos, 'rdf/n3/algebra/math/cos' autoload :CosH, 'rdf/n3/algebra/math/cosh' @@ -127,12 +121,6 @@ def for(uri) RDF::N3::Log.supports => NotImplemented, RDF::N3::Math.absoluteValue => Math::AbsoluteValue, - RDF::N3::Math.acos => Math::ACos, - RDF::N3::Math.asin => Math::ASin, - RDF::N3::Math.atan => Math::ATan, - RDF::N3::Math.acosh => Math::ACosH, - RDF::N3::Math.asinh => Math::ASinH, - RDF::N3::Math.atanh => Math::ATanH, RDF::N3::Math.ceiling => Math::Ceiling, RDF::N3::Math.cos => Math::Cos, RDF::N3::Math.cosh => Math::CosH, diff --git a/lib/rdf/n3/algebra/math/acos.rb b/lib/rdf/n3/algebra/math/acos.rb deleted file mode 100644 index 505e5f7..0000000 --- a/lib/rdf/n3/algebra/math/acos.rb +++ /dev/null @@ -1,25 +0,0 @@ -module RDF::N3::Algebra::Math - ## - # The object is calulated as the arc cosine value of the subject. - class ACos < RDF::N3::Algebra::ResourceOperator - NAME = :mathACos - - ## - # The math:acos operator takes string or number and calculates its arc cosine. - # - # @param [RDF::Term] resource - # @param [:subject, :object] position - # @return [RDF::Term] - # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - as_literal(Math.acos(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource - end - end - end -end diff --git a/lib/rdf/n3/algebra/math/acosh.rb b/lib/rdf/n3/algebra/math/acosh.rb deleted file mode 100644 index c62cb40..0000000 --- a/lib/rdf/n3/algebra/math/acosh.rb +++ /dev/null @@ -1,25 +0,0 @@ -module RDF::N3::Algebra::Math - ## - # The object is calulated as the inverse hyperbolic cosine value of the subject. - class ACosH < RDF::N3::Algebra::ResourceOperator - NAME = :mathACosH - - ## - # The math:acosh operator takes string or number and calculates its inverse hyperbolic cosine. - # - # @param [RDF::Term] resource - # @param [:subject, :object] position - # @return [RDF::Term] - # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - as_literal(Math.acosh(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource - end - end - end -end diff --git a/lib/rdf/n3/algebra/math/asin.rb b/lib/rdf/n3/algebra/math/asin.rb deleted file mode 100644 index 9133f52..0000000 --- a/lib/rdf/n3/algebra/math/asin.rb +++ /dev/null @@ -1,25 +0,0 @@ -module RDF::N3::Algebra::Math - ## - # The object is calulated as the arc sine value of the subject. - class ASin < RDF::N3::Algebra::ResourceOperator - NAME = :mathASin - - ## - # The math:asin operator takes string or number and calculates its arc sine. - # - # @param [RDF::Term] resource - # @param [:subject, :object] position - # @return [RDF::Term] - # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - as_literal(Math.asin(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource - end - end - end -end diff --git a/lib/rdf/n3/algebra/math/asinh.rb b/lib/rdf/n3/algebra/math/asinh.rb deleted file mode 100644 index ddfe04e..0000000 --- a/lib/rdf/n3/algebra/math/asinh.rb +++ /dev/null @@ -1,25 +0,0 @@ -module RDF::N3::Algebra::Math - ## - # The object is calulated as the inverse hyperbolic sine value of the subject. - class ASinH < RDF::N3::Algebra::ResourceOperator - NAME = :mathASinH - - ## - # The math:asinh operator takes string or number and calculates its inverse hyperbolic sine. - # - # @param [RDF::Term] resource - # @param [:subject, :object] position - # @return [RDF::Term] - # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - as_literal(Math.asinh(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource - end - end - end -end diff --git a/lib/rdf/n3/algebra/math/atan.rb b/lib/rdf/n3/algebra/math/atan.rb deleted file mode 100644 index efa7c31..0000000 --- a/lib/rdf/n3/algebra/math/atan.rb +++ /dev/null @@ -1,25 +0,0 @@ -module RDF::N3::Algebra::Math - ## - # The object is calulated as the arc tangent value of the subject. - class ATan < RDF::N3::Algebra::ResourceOperator - NAME = :mathATan - - ## - # The math:atan operator takes string or number and calculates its arc tangent. - # - # @param [RDF::Term] resource - # @param [:subject, :object] position - # @return [RDF::Term] - # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - as_literal(Math.atan(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource - end - end - end -end diff --git a/lib/rdf/n3/algebra/math/atanh.rb b/lib/rdf/n3/algebra/math/atanh.rb deleted file mode 100644 index ebd7ebb..0000000 --- a/lib/rdf/n3/algebra/math/atanh.rb +++ /dev/null @@ -1,25 +0,0 @@ -module RDF::N3::Algebra::Math - ## - # The object is calulated as the inverse hyperbolic tangent value of the subject. - class ATanH < RDF::N3::Algebra::ResourceOperator - NAME = :mathATanH - - ## - # The math:atanh operator takes string or number and calculates its inverse hyperbolic tangent. - # - # @param [RDF::Term] resource - # @param [:subject, :object] position - # @return [RDF::Term] - # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) - case position - when :subject - return nil unless resource.literal? - as_literal(Math.atanh(resource.as_number.object)) - when :object - return nil unless resource.literal? || resource.variable? - resource - end - end - end -end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index e217443..7603f77 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -616,12 +616,10 @@ context "trig" do { "0": { - asin: "0.0e0", sin: "0.0e0", sinh: "0.0e0", cos: "1.0e0", cosh: "1.0e0", - atan: "0.0e0", tan: "0.0e0", tanh: "0.0e0", }, From e67211c1425d616215e57becef5e267568648ce3 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 8 Oct 2020 15:27:41 -0700 Subject: [PATCH 137/193] Update time:inSeconds to have an integer range, rather than double. --- lib/rdf/n3/algebra/time/in_seconds.rb | 4 ++-- spec/suite_reasoner_spec.rb | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/rdf/n3/algebra/time/in_seconds.rb b/lib/rdf/n3/algebra/time/in_seconds.rb index f070fab..4b3ef62 100644 --- a/lib/rdf/n3/algebra/time/in_seconds.rb +++ b/lib/rdf/n3/algebra/time/in_seconds.rb @@ -1,6 +1,6 @@ module RDF::N3::Algebra::Time ## - # For a date-time, its time:inSeconds is the (string represntation of) the floating point number of seconds since the beginning of the era on the given system. + # Iff the _subject_ is a `xsd:dateTime` and the _object_ is the integer number of seconds since the beginning of the era on a given system. Don't assume a particular value, always test for it. The _object_ can be calculated as a function of the _subject_. class InSeconds < RDF::N3::Algebra::ResourceOperator NAME = :timeInSeconds @@ -20,7 +20,7 @@ def evaluate(resource, position:) when RDF::Literal resource = resource.as_datetime # Subject evaluates to seconds from the epoc - RDF::Literal::Double.new(resource.object.strftime("%s")) + RDF::Literal::Integer.new(resource.object.strftime("%s")) else nil end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 367de6c..ac4c3ef 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -20,8 +20,6 @@ next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do case t.id.split('#').last - when *%w{cwm_time_t1} - pending "time" when *%w{cwm_unify_unify1 cwm_includes_builtins cwm_includes_t10 cwm_includes_t11 cwm_includes_quantifiers_limited} pending "log:includes etc." From bed9b838f648697b1b5f728da9888e190ac7f7e2 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 8 Oct 2020 16:29:09 -0700 Subject: [PATCH 138/193] Update some builtins with references to XPath function definitions. --- lib/rdf/n3/algebra/math/absolute_value.rb | 4 +++- lib/rdf/n3/algebra/math/ceiling.rb | 2 ++ lib/rdf/n3/algebra/math/cos.rb | 2 ++ lib/rdf/n3/algebra/math/difference.rb | 2 ++ lib/rdf/n3/algebra/math/equal_to.rb | 19 +++++++++++++++++-- lib/rdf/n3/algebra/math/exponentiation.rb | 2 ++ lib/rdf/n3/algebra/math/floor.rb | 2 ++ lib/rdf/n3/algebra/math/greater_than.rb | 2 ++ lib/rdf/n3/algebra/math/integer_quotient.rb | 4 +++- lib/rdf/n3/algebra/math/less_than.rb | 2 ++ lib/rdf/n3/algebra/math/negation.rb | 2 ++ lib/rdf/n3/algebra/math/not_equal_to.rb | 4 +++- lib/rdf/n3/algebra/math/not_greater_than.rb | 2 ++ lib/rdf/n3/algebra/math/not_less_than.rb | 2 ++ lib/rdf/n3/algebra/math/product.rb | 2 ++ lib/rdf/n3/algebra/math/quotient.rb | 2 ++ lib/rdf/n3/algebra/math/remainder.rb | 4 +++- lib/rdf/n3/algebra/math/rounded.rb | 2 +- lib/rdf/n3/algebra/math/sin.rb | 2 ++ lib/rdf/n3/algebra/math/sum.rb | 20 +++++++++++++++++++- lib/rdf/n3/algebra/math/tan.rb | 2 ++ lib/rdf/n3/algebra/str/not_matches.rb | 2 +- lib/rdf/n3/algebra/time/day.rb | 2 ++ lib/rdf/n3/algebra/time/gm_time.rb | 2 ++ lib/rdf/n3/algebra/time/hour.rb | 2 ++ lib/rdf/n3/algebra/time/in_seconds.rb | 2 ++ lib/rdf/n3/algebra/time/local_time.rb | 2 ++ lib/rdf/n3/algebra/time/minute.rb | 2 ++ lib/rdf/n3/algebra/time/month.rb | 2 ++ lib/rdf/n3/algebra/time/second.rb | 2 ++ lib/rdf/n3/algebra/time/timezone.rb | 2 ++ lib/rdf/n3/algebra/time/year.rb | 2 ++ 32 files changed, 98 insertions(+), 9 deletions(-) diff --git a/lib/rdf/n3/algebra/math/absolute_value.rb b/lib/rdf/n3/algebra/math/absolute_value.rb index 05253be..c1c04f9 100644 --- a/lib/rdf/n3/algebra/math/absolute_value.rb +++ b/lib/rdf/n3/algebra/math/absolute_value.rb @@ -1,11 +1,13 @@ module RDF::N3::Algebra::Math ## # The object is calulated as the absolute value of the subject. + # + # @see https://www.w3.org/TR/xpath-functions/#func-abs class AbsoluteValue < RDF::N3::Algebra::ResourceOperator NAME = :mathAbsoluteValue ## - # The math:sum operator takes string or number and calculates its absolute value. + # The math:absoluteValue operator takes string or number and calculates its absolute value. # # @param [RDF::Term] resource # @param [:subject, :object] position diff --git a/lib/rdf/n3/algebra/math/ceiling.rb b/lib/rdf/n3/algebra/math/ceiling.rb index 063f160..e0b7a0e 100644 --- a/lib/rdf/n3/algebra/math/ceiling.rb +++ b/lib/rdf/n3/algebra/math/ceiling.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Math ## # The object is calculated as the subject upwards to a whole number. + # + # @see https://www.w3.org/TR/xpath-functions/#func-ceiling class Ceiling < RDF::N3::Algebra::ResourceOperator NAME = :mathCeiling diff --git a/lib/rdf/n3/algebra/math/cos.rb b/lib/rdf/n3/algebra/math/cos.rb index e6f6aed..fd83851 100644 --- a/lib/rdf/n3/algebra/math/cos.rb +++ b/lib/rdf/n3/algebra/math/cos.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the cosine value of the subject. + # + # @see https://www.w3.org/TR/xpath-functions/#func-math-cos class Cos < RDF::N3::Algebra::ResourceOperator NAME = :mathCos diff --git a/lib/rdf/n3/algebra/math/difference.rb b/lib/rdf/n3/algebra/math/difference.rb index 8870fcd..e0c7678 100644 --- a/lib/rdf/n3/algebra/math/difference.rb +++ b/lib/rdf/n3/algebra/math/difference.rb @@ -6,6 +6,8 @@ module RDF::N3::Algebra::Math # { ("8" "3") math:difference ?x} => { ?x :valueOf "8 - 3" } . # { ("8") math:difference ?x } => { ?x :valueOf "8 - (error?)" } . # { (8 3) math:difference ?x} => { ?x :valueOf "8 - 3" } . + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-subtract class Difference < RDF::N3::Algebra::ListOperator NAME = :mathDifference diff --git a/lib/rdf/n3/algebra/math/equal_to.rb b/lib/rdf/n3/algebra/math/equal_to.rb index 39260ae..227bb4f 100644 --- a/lib/rdf/n3/algebra/math/equal_to.rb +++ b/lib/rdf/n3/algebra/math/equal_to.rb @@ -1,13 +1,28 @@ 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. + # **schema**: + # `$a1 math:equalTo $a2` + # + # **summary**: + # checks equality of numbers + # + # **definition**: + # `true` if and only if `$a1` is equal to `$a2`. + # Requires both arguments to be either concrete numerals, or variables bound to a numeral. + # + # **literal domains**: + # + # * `$a1`: `xs:decimal` (or its derived types), `xs:float`, or `xs:double` (see note on type promotion, and casting from string) + # * `$a2`: `xs:decimal` (or its derived types), `xs:float`, or `xs:double` (see note on type promotion, and casting from string) + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-equal class EqualTo < SPARQL::Algebra::Operator::Compare include RDF::N3::Algebra::Builtin NAME = :'=' ## - # Returns TRUE if `term1` and `term2` are the same RDF term as defined in Resource Description Framework (RDF): Concepts and Abstract Syntax [CONCEPTS]; produces a type error if the arguments are both literal but are not the same RDF term *; returns FALSE otherwise. `term1` and `term2` are the same if any of the following is true: + # The math:equalTo operator takes a pair of strings or numbers and determines if they are the same numeric value. # # @param [RDF::Term] term1 # an RDF term diff --git a/lib/rdf/n3/algebra/math/exponentiation.rb b/lib/rdf/n3/algebra/math/exponentiation.rb index 1f361cf..fa8766a 100644 --- a/lib/rdf/n3/algebra/math/exponentiation.rb +++ b/lib/rdf/n3/algebra/math/exponentiation.rb @@ -1,6 +1,8 @@ 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. + # + # @see https://www.w3.org/TR/xpath-functions/#func-math-exp class Exponentiation < RDF::N3::Algebra::ListOperator NAME = :mathExponentiation diff --git a/lib/rdf/n3/algebra/math/floor.rb b/lib/rdf/n3/algebra/math/floor.rb index 2509222..1b1e181 100644 --- a/lib/rdf/n3/algebra/math/floor.rb +++ b/lib/rdf/n3/algebra/math/floor.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Math ## # The object is calculated as the subject downwards to a whole number. + # + # @see https://www.w3.org/TR/xpath-functions/#func-floor class Floor < RDF::N3::Algebra::ResourceOperator NAME = :mathFloor diff --git a/lib/rdf/n3/algebra/math/greater_than.rb b/lib/rdf/n3/algebra/math/greater_than.rb index 1415a21..3ada1fd 100644 --- a/lib/rdf/n3/algebra/math/greater_than.rb +++ b/lib/rdf/n3/algebra/math/greater_than.rb @@ -1,6 +1,8 @@ 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. + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-greater-than class GreaterThan < SPARQL::Algebra::Operator::Compare include RDF::N3::Algebra::Builtin diff --git a/lib/rdf/n3/algebra/math/integer_quotient.rb b/lib/rdf/n3/algebra/math/integer_quotient.rb index a19b2e2..9879c76 100644 --- a/lib/rdf/n3/algebra/math/integer_quotient.rb +++ b/lib/rdf/n3/algebra/math/integer_quotient.rb @@ -1,7 +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 < RDF::N3::Algebra::ListOperator + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-integer-divide + class IntegerQuotient < RDF::N3::Algebra::Math::Quotient NAME = :mathIntegerQuotient ## diff --git a/lib/rdf/n3/algebra/math/less_than.rb b/lib/rdf/n3/algebra/math/less_than.rb index 622c16e..36a2466 100644 --- a/lib/rdf/n3/algebra/math/less_than.rb +++ b/lib/rdf/n3/algebra/math/less_than.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Math ## # True iff the subject is a string representation of a number which is less than the number of which the object is a string representation. + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-less-than class LessThan < SPARQL::Algebra::Operator::Compare include RDF::N3::Algebra::Builtin diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb index e9e9ac9..4a52038 100644 --- a/lib/rdf/n3/algebra/math/negation.rb +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Math ## # The subject or object is calculated to be the negation of the other. + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-unary-minus class Negation < RDF::N3::Algebra::ResourceOperator include RDF::N3::Algebra::Builtin diff --git a/lib/rdf/n3/algebra/math/not_equal_to.rb b/lib/rdf/n3/algebra/math/not_equal_to.rb index a061b13..deaa4ca 100644 --- a/lib/rdf/n3/algebra/math/not_equal_to.rb +++ b/lib/rdf/n3/algebra/math/not_equal_to.rb @@ -1,13 +1,15 @@ 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. + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-equal class NotEqualTo < SPARQL::Algebra::Operator::Compare include RDF::N3::Algebra::Builtin NAME = :'!=' ## - # Returns TRUE if `term1` and `term2` are the same RDF term as defined in Resource Description Framework (RDF): Concepts and Abstract Syntax [CONCEPTS]; produces a type error if the arguments are both literal but are not the same RDF term *; returns FALSE otherwise. `term1` and `term2` are the same if any of the following is true: + # The math:notEqualTo operator takes a pair of strings or numbers and determines if they are not the same numeric value. # # @param [RDF::Term] term1 # an RDF term diff --git a/lib/rdf/n3/algebra/math/not_greater_than.rb b/lib/rdf/n3/algebra/math/not_greater_than.rb index 6996e7b..f2e45e0 100644 --- a/lib/rdf/n3/algebra/math/not_greater_than.rb +++ b/lib/rdf/n3/algebra/math/not_greater_than.rb @@ -1,6 +1,8 @@ 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. + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-greater-than class NotGreaterThan < SPARQL::Algebra::Operator::Compare include RDF::N3::Algebra::Builtin diff --git a/lib/rdf/n3/algebra/math/not_less_than.rb b/lib/rdf/n3/algebra/math/not_less_than.rb index c5f2456..7899abe 100644 --- a/lib/rdf/n3/algebra/math/not_less_than.rb +++ b/lib/rdf/n3/algebra/math/not_less_than.rb @@ -1,6 +1,8 @@ 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. + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-less-than class NotLessThan < SPARQL::Algebra::Operator::Compare include RDF::N3::Algebra::Builtin diff --git a/lib/rdf/n3/algebra/math/product.rb b/lib/rdf/n3/algebra/math/product.rb index 4d371c7..84c5714 100644 --- a/lib/rdf/n3/algebra/math/product.rb +++ b/lib/rdf/n3/algebra/math/product.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Math ## # The subject is a list of numbers. The object is calculated as the arithmentic product of those numbers. + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-multiply class Product < RDF::N3::Algebra::ListOperator NAME = :mathProduct diff --git a/lib/rdf/n3/algebra/math/quotient.rb b/lib/rdf/n3/algebra/math/quotient.rb index 6d25e6d..2b27692 100644 --- a/lib/rdf/n3/algebra/math/quotient.rb +++ b/lib/rdf/n3/algebra/math/quotient.rb @@ -1,6 +1,8 @@ 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. + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-divide class Quotient < RDF::N3::Algebra::ListOperator NAME = :mathQuotient diff --git a/lib/rdf/n3/algebra/math/remainder.rb b/lib/rdf/n3/algebra/math/remainder.rb index 6536313..9003d32 100644 --- a/lib/rdf/n3/algebra/math/remainder.rb +++ b/lib/rdf/n3/algebra/math/remainder.rb @@ -1,11 +1,13 @@ 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. + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-mod class Remainder < RDF::N3::Algebra::ListOperator NAME = :mathRemainder ## - # The math:quotient operator takes a pair of strings or numbers and calculates their quotient. + # The math:remainder operator takes a pair of strings or numbers and calculates their remainder. # # @param [RDF::N3::List] list # @return [RDF::Term] diff --git a/lib/rdf/n3/algebra/math/rounded.rb b/lib/rdf/n3/algebra/math/rounded.rb index aecb888..3e3f065 100644 --- a/lib/rdf/n3/algebra/math/rounded.rb +++ b/lib/rdf/n3/algebra/math/rounded.rb @@ -5,7 +5,7 @@ class Rounded < RDF::N3::Algebra::ResourceOperator NAME = :mathRounded ## - # The math:floor operator takes string or number and calculates its floor. + # The math:rounded operator takes string or number rounds it to the next integer. # # @param [RDF::Term] resource # @param [:subject, :object] position diff --git a/lib/rdf/n3/algebra/math/sin.rb b/lib/rdf/n3/algebra/math/sin.rb index 4ec2f5d..c0ef617 100644 --- a/lib/rdf/n3/algebra/math/sin.rb +++ b/lib/rdf/n3/algebra/math/sin.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the sine value of the subject. + # + # @see https://www.w3.org/TR/xpath-functions/#func-math-sin class Sin < RDF::N3::Algebra::ResourceOperator NAME = :mathSin diff --git a/lib/rdf/n3/algebra/math/sum.rb b/lib/rdf/n3/algebra/math/sum.rb index 9e266cd..65a097a 100644 --- a/lib/rdf/n3/algebra/math/sum.rb +++ b/lib/rdf/n3/algebra/math/sum.rb @@ -1,10 +1,28 @@ module RDF::N3::Algebra::Math ## - # The subject is a list of numbers. The object is calculated as the arithmentic sum of those numbers. + # **schema**: + # `($a_1 .. $a_n) math:sum $a_s` + # + # **summary**: + # performs addition of numbers + # + # **definition**: + # `true` if and only if the arithmetic sum of `$a_1, .. $a_n` equals `$a_s`. + # Requires either: + # + # 1. all `$a_1, .., $a_n` to be bound; or + # 2. all but one `$a_i` (subject list) to be bound, and `$a_s` to be bound. + # + # **literal domains**: + # + # * `$a_1 .. $a_n` : `xs:decimal` (or its derived types), `xs:float`, or `xs:double` (see note on type promotion, and casting from string) + # * `$a_s`: `xs:decimal` (or its derived types), `xs:float`, or `xs:double` (see note on type promotion, and casting from string) # # @example # { ("3" "5") math:sum ?x } => { ?x :valueOf "3 + 5" } . # { (3 5) math:sum ?x } => { ?x :valueOf "3 + 5 = 8" } . + # + # @see https://www.w3.org/TR/xpath-functions/#func-numeric-add class Sum < RDF::N3::Algebra::ListOperator NAME = :mathSum diff --git a/lib/rdf/n3/algebra/math/tan.rb b/lib/rdf/n3/algebra/math/tan.rb index 3cd8f9f..5d94f0a 100644 --- a/lib/rdf/n3/algebra/math/tan.rb +++ b/lib/rdf/n3/algebra/math/tan.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Math ## # The subject is an angle expressed in radians. The object is calulated as the tangent value of the subject. + # + # @see https://www.w3.org/TR/xpath-functions/#func-math-tan class Tan < RDF::N3::Algebra::ResourceOperator NAME = :mathTan diff --git a/lib/rdf/n3/algebra/str/not_matches.rb b/lib/rdf/n3/algebra/str/not_matches.rb index 36580da..1193518 100644 --- a/lib/rdf/n3/algebra/str/not_matches.rb +++ b/lib/rdf/n3/algebra/str/not_matches.rb @@ -12,8 +12,8 @@ class NotMatches < SPARQL::Algebra::Operator::Binary # @param [RDF::Literal] pattern # a simple literal # @return [RDF::Literal::Boolean] `true` or `false` + # @see https://www.w3.org/TR/xpath-functions/#regex-syntax def apply(text, pattern) - # @see https://www.w3.org/TR/xpath-functions/#regex-syntax log_error(NAME) {"expected a plain RDF::Literal, but got #{text.inspect}"} unless text.is_a?(RDF::Literal) && text.plain? text = text.to_s # TODO: validate text syntax diff --git a/lib/rdf/n3/algebra/time/day.rb b/lib/rdf/n3/algebra/time/day.rb index 1514e97..2eae593 100644 --- a/lib/rdf/n3/algebra/time/day.rb +++ b/lib/rdf/n3/algebra/time/day.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:day is the day of the month. + # + # @see https://www.w3.org/TR/xpath-functions/#func-day-from-dateTime class Day < RDF::N3::Algebra::ResourceOperator NAME = :timeDay diff --git a/lib/rdf/n3/algebra/time/gm_time.rb b/lib/rdf/n3/algebra/time/gm_time.rb index b61c96d..050b437 100644 --- a/lib/rdf/n3/algebra/time/gm_time.rb +++ b/lib/rdf/n3/algebra/time/gm_time.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Time ## # For a date-time format string, its time:gmtime is the result of formatting the Universal Time of processing in the format given. If the format string has zero length, then the ISOdate standard format is used. `[ is time:gmtime of ""]` the therefore the current date time. It will end with "Z" as a timezone code. + # + # @see https://www.w3.org/TR/xpath-functions/#func-current-dateTime class GmTime < RDF::N3::Algebra::ResourceOperator NAME = :timeGmTime diff --git a/lib/rdf/n3/algebra/time/hour.rb b/lib/rdf/n3/algebra/time/hour.rb index db533bc..4355b58 100644 --- a/lib/rdf/n3/algebra/time/hour.rb +++ b/lib/rdf/n3/algebra/time/hour.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:hour is the hour in the 24 hour clock. + # + # @see https://www.w3.org/TR/xpath-functions/#func-hours-from-dateTime class Hour < RDF::N3::Algebra::ResourceOperator NAME = :timeHour diff --git a/lib/rdf/n3/algebra/time/in_seconds.rb b/lib/rdf/n3/algebra/time/in_seconds.rb index 4b3ef62..d205b29 100644 --- a/lib/rdf/n3/algebra/time/in_seconds.rb +++ b/lib/rdf/n3/algebra/time/in_seconds.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Time ## # Iff the _subject_ is a `xsd:dateTime` and the _object_ is the integer number of seconds since the beginning of the era on a given system. Don't assume a particular value, always test for it. The _object_ can be calculated as a function of the _subject_. + # + # @see https://www.w3.org/TR/xpath-functions/#func-timezone-from-dateTime class InSeconds < RDF::N3::Algebra::ResourceOperator NAME = :timeInSeconds diff --git a/lib/rdf/n3/algebra/time/local_time.rb b/lib/rdf/n3/algebra/time/local_time.rb index 3718d9f..c3714d1 100644 --- a/lib/rdf/n3/algebra/time/local_time.rb +++ b/lib/rdf/n3/algebra/time/local_time.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Time ## # For a date-time format string, its time:localTime is the result of formatting the current time of processing and local timezone in the format given. If the format string has zero length, then the ISOdate standrad format is used. [ is time:localTime of ""] the therefore the current date time. It will end with a numeric timezone code or "Z" for UTC (GMT). + # + # @see https://www.w3.org/TR/xpath-functions/#func-current-dateTime class LocalTime < RDF::N3::Algebra::ResourceOperator NAME = :timeLocalTime diff --git a/lib/rdf/n3/algebra/time/minute.rb b/lib/rdf/n3/algebra/time/minute.rb index 3ef1090..9d83e7f 100644 --- a/lib/rdf/n3/algebra/time/minute.rb +++ b/lib/rdf/n3/algebra/time/minute.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:minute is the minutes component. + # + # @see https://www.w3.org/TR/xpath-functions/#func-minutes-from-dateTime class Minute < RDF::N3::Algebra::ResourceOperator NAME = :timeMinute diff --git a/lib/rdf/n3/algebra/time/month.rb b/lib/rdf/n3/algebra/time/month.rb index bb07833..10a8312 100644 --- a/lib/rdf/n3/algebra/time/month.rb +++ b/lib/rdf/n3/algebra/time/month.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:month is the two-digit month. + # + # @see https://www.w3.org/TR/xpath-functions/#func-month-from-dateTime class Month < RDF::N3::Algebra::ResourceOperator NAME = :timeMonth diff --git a/lib/rdf/n3/algebra/time/second.rb b/lib/rdf/n3/algebra/time/second.rb index 23a25d9..1d254e8 100644 --- a/lib/rdf/n3/algebra/time/second.rb +++ b/lib/rdf/n3/algebra/time/second.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:second is the seconds component. + # + # @see https://www.w3.org/TR/xpath-functions/#func-seconds-from-dateTime class Second < RDF::N3::Algebra::ResourceOperator NAME = :timeSecond diff --git a/lib/rdf/n3/algebra/time/timezone.rb b/lib/rdf/n3/algebra/time/timezone.rb index c3ab8f0..c218d26 100644 --- a/lib/rdf/n3/algebra/time/timezone.rb +++ b/lib/rdf/n3/algebra/time/timezone.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:timeZone is the trailing timezone offset part, e.g. "-05:00". + # + # @see https://www.w3.org/TR/xpath-functions/#func-timezone-from-dateTime class Timezone < RDF::N3::Algebra::ResourceOperator NAME = :timeTimezone diff --git a/lib/rdf/n3/algebra/time/year.rb b/lib/rdf/n3/algebra/time/year.rb index d3594d1..44adc1a 100644 --- a/lib/rdf/n3/algebra/time/year.rb +++ b/lib/rdf/n3/algebra/time/year.rb @@ -1,6 +1,8 @@ module RDF::N3::Algebra::Time ## # For a date-time, its time:year is the year component. + # + # @see https://www.w3.org/TR/xpath-functions/#func-year-from-dateTime class Year < RDF::N3::Algebra::ResourceOperator NAME = :timeYear From f0120438a64b80c2a147c7d8de71737cce873491 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 8 Oct 2020 16:29:43 -0700 Subject: [PATCH 139/193] Integer quotient is actually just quotient cast to an intgeger. --- lib/rdf/n3/algebra/math/integer_quotient.rb | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/lib/rdf/n3/algebra/math/integer_quotient.rb b/lib/rdf/n3/algebra/math/integer_quotient.rb index 9879c76..8b3c6e9 100644 --- a/lib/rdf/n3/algebra/math/integer_quotient.rb +++ b/lib/rdf/n3/algebra/math/integer_quotient.rb @@ -14,26 +14,7 @@ class IntegerQuotient < RDF::N3::Algebra::Math::Quotient # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate def evaluate(list) - RDF::Literal::Integer.new(list.to_a.map do |li| - li.is_a?(RDF::Literal::Integer) ? - li : - RDF::Literal::Integer.new(li.value) - end.reduce(&:/)) - end - - ## - # The list argument must be a pair of literals. - # - # @param [RDF::N3::List] list - # @return [Boolean] - # @see RDF::N3::ListOperator#validate - def validate(list) - if super && list.all?(&:literal?) && list.length == 2 - true - else - log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} - false - end + RDF::Literal::Integer.new(super) end end end From dc17784a035bdbdbc7ed0cd50353b4256ea307b9 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 10 Oct 2020 17:13:56 -0700 Subject: [PATCH 140/193] Update string builtins to not simply subclass SPARQL operators. --- lib/rdf/n3/algebra/math/not_equal_to.rb | 2 +- lib/rdf/n3/algebra/str/contains.rb | 19 +++++++++++++++- .../n3/algebra/str/contains_ignoring_case.rb | 2 +- lib/rdf/n3/algebra/str/ends_with.rb | 19 +++++++++++++++- lib/rdf/n3/algebra/str/equal_ignoring_case.rb | 2 +- lib/rdf/n3/algebra/str/greater_than.rb | 2 +- lib/rdf/n3/algebra/str/less_than.rb | 2 +- lib/rdf/n3/algebra/str/matches.rb | 22 ++++++++++++++++++- .../n3/algebra/str/not_equal_ignoring_case.rb | 12 ++-------- lib/rdf/n3/algebra/str/not_greater_than.rb | 12 ++-------- lib/rdf/n3/algebra/str/not_less_than.rb | 12 ++-------- lib/rdf/n3/algebra/str/not_matches.rb | 15 ++----------- lib/rdf/n3/algebra/str/starts_with.rb | 19 +++++++++++++++- 13 files changed, 88 insertions(+), 52 deletions(-) diff --git a/lib/rdf/n3/algebra/math/not_equal_to.rb b/lib/rdf/n3/algebra/math/not_equal_to.rb index deaa4ca..a53177f 100644 --- a/lib/rdf/n3/algebra/math/not_equal_to.rb +++ b/lib/rdf/n3/algebra/math/not_equal_to.rb @@ -21,7 +21,7 @@ class NotEqualTo < SPARQL::Algebra::Operator::Compare # @see RDF::Term#== def apply(term1, term2) log_debug(NAME) { "term1: #{term1.to_sxp} != term2: #{term2.to_sxp} ? #{(term1 != term2).inspect}"} - RDF::Literal(term1.as_number != term2.as_number) + RDF::Literal(term1.is_a?(RDF::Term) && term2.is_a?(RDF::Term) && term1.as_number != term2.as_number) end end end diff --git a/lib/rdf/n3/algebra/str/contains.rb b/lib/rdf/n3/algebra/str/contains.rb index d8d89be..9377223 100644 --- a/lib/rdf/n3/algebra/str/contains.rb +++ b/lib/rdf/n3/algebra/str/contains.rb @@ -1,8 +1,25 @@ module RDF::N3::Algebra::Str # True iff the subject string contains the object string. - class Contains < SPARQL::Algebra::Operator::Contains + class Contains < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Evaluatable include RDF::N3::Algebra::Builtin NAME = :strContains + + ## + # @param [RDF::Literal] left + # a literal + # @param [RDF::Literal] right + # a literal + # @return [RDF::Literal::Boolean] + def apply(left, right) + case + when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) + log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} + RDF::Literal::FALSE + when left.to_s.include?(right.to_s) then RDF::Literal::TRUE + else RDF::Literal::FALSE + end + end end end diff --git a/lib/rdf/n3/algebra/str/contains_ignoring_case.rb b/lib/rdf/n3/algebra/str/contains_ignoring_case.rb index 02df949..3378a08 100644 --- a/lib/rdf/n3/algebra/str/contains_ignoring_case.rb +++ b/lib/rdf/n3/algebra/str/contains_ignoring_case.rb @@ -14,7 +14,7 @@ class ContainsIgnoringCase < SPARQL::Algebra::Operator::Binary # @return [RDF::Literal::Boolean] def apply(left, right) case - when !left.compatible?(right) + when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} RDF::Literal::FALSE when left.to_s.downcase.include?(right.to_s.downcase) then RDF::Literal::TRUE diff --git a/lib/rdf/n3/algebra/str/ends_with.rb b/lib/rdf/n3/algebra/str/ends_with.rb index 1954e8e..a21a4ca 100644 --- a/lib/rdf/n3/algebra/str/ends_with.rb +++ b/lib/rdf/n3/algebra/str/ends_with.rb @@ -1,8 +1,25 @@ module RDF::N3::Algebra::Str # True iff the subject string ends with the object string. - class EndsWith < SPARQL::Algebra::Operator::StrEnds + class EndsWith < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Evaluatable include RDF::N3::Algebra::Builtin NAME = :strEndsWith + + ## + # @param [RDF::Literal] left + # a literal + # @param [RDF::Literal] right + # a literal + # @return [RDF::Literal::Boolean] + def apply(left, right) + case + when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) + log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} + RDF::Literal::FALSE + when left.to_s.end_with?(right.to_s) then RDF::Literal::TRUE + else RDF::Literal::FALSE + end + end end end diff --git a/lib/rdf/n3/algebra/str/equal_ignoring_case.rb b/lib/rdf/n3/algebra/str/equal_ignoring_case.rb index e2ea636..55cc6cb 100644 --- a/lib/rdf/n3/algebra/str/equal_ignoring_case.rb +++ b/lib/rdf/n3/algebra/str/equal_ignoring_case.rb @@ -15,7 +15,7 @@ class EqualIgnoringCase < SPARQL::Algebra::Operator::Binary # @return [RDF::Literal::Boolean] def apply(left, right) case - when !left.compatible?(right) + when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} when left.to_s.downcase == right.to_s.downcase then RDF::Literal::TRUE else RDF::Literal::FALSE diff --git a/lib/rdf/n3/algebra/str/greater_than.rb b/lib/rdf/n3/algebra/str/greater_than.rb index 8ada469..fa8bbaa 100644 --- a/lib/rdf/n3/algebra/str/greater_than.rb +++ b/lib/rdf/n3/algebra/str/greater_than.rb @@ -14,7 +14,7 @@ class GreaterThan < SPARQL::Algebra::Operator::Binary # @return [RDF::Literal::Boolean] def apply(left, right) case - when !left.compatible?(right) + when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} when left > right then RDF::Literal::TRUE else RDF::Literal::FALSE diff --git a/lib/rdf/n3/algebra/str/less_than.rb b/lib/rdf/n3/algebra/str/less_than.rb index cdfee62..5b74c6b 100644 --- a/lib/rdf/n3/algebra/str/less_than.rb +++ b/lib/rdf/n3/algebra/str/less_than.rb @@ -14,7 +14,7 @@ class LessThan < SPARQL::Algebra::Operator::Binary # @return [RDF::Literal::Boolean] def apply(left, right) case - when !left.compatible?(right) + when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} when left < right then RDF::Literal::TRUE else RDF::Literal::FALSE diff --git a/lib/rdf/n3/algebra/str/matches.rb b/lib/rdf/n3/algebra/str/matches.rb index 14d1711..6024daa 100644 --- a/lib/rdf/n3/algebra/str/matches.rb +++ b/lib/rdf/n3/algebra/str/matches.rb @@ -1,9 +1,29 @@ 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::Regex + class Matches < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Evaluatable include RDF::N3::Algebra::Builtin NAME = :strMatches + + ## + # @param [RDF::Literal] text + # a simple literal + # @param [RDF::Literal] pattern + # a simple literal + # @return [RDF::Literal::Boolean] `true` or `false` + # @see https://www.w3.org/TR/xpath-functions/#regex-syntax + def apply(text, pattern) + log_error(NAME) {"expected a plain RDF::Literal, but got #{text.inspect}"} unless text.is_a?(RDF::Literal) && text.plain? + text = text.to_s + # TODO: validate text syntax + + # @see https://www.w3.org/TR/xpath-functions/#regex-syntax + log_error(NAME) {"expected a plain RDF::Literal, but got #{pattern.inspect}"} unless pattern.is_a?(RDF::Literal) && pattern.plain? + pattern = pattern.to_s + + RDF::Literal(Regexp.new(pattern).match?(text)) + end end end diff --git a/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb b/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb index 0eaa5c9..2fa4498 100644 --- a/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb +++ b/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb @@ -1,9 +1,6 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::N3::Algebra::Builtin - + class NotEqualIgnoringCase < EqualIgnoringCase NAME = :strNotEqualIgnoringCase ## @@ -13,12 +10,7 @@ class NotEqualIgnoringCase < SPARQL::Algebra::Operator::Binary # a literal # @return [RDF::Literal::Boolean] def apply(left, right) - case - when !left.compatible?(right) - log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} - when left.to_s.downcase != right.to_s.downcase then RDF::Literal::TRUE - else RDF::Literal::FALSE - end + RDF::Literal(super != RDF::Literal::TRUE) end end end diff --git a/lib/rdf/n3/algebra/str/not_greater_than.rb b/lib/rdf/n3/algebra/str/not_greater_than.rb index 36af673..001c197 100644 --- a/lib/rdf/n3/algebra/str/not_greater_than.rb +++ b/lib/rdf/n3/algebra/str/not_greater_than.rb @@ -1,9 +1,6 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::N3::Algebra::Builtin - + class NotGreaterThan < GreaterThan NAME = :strNotGreaterThan ## @@ -13,12 +10,7 @@ class NotGreaterThan < SPARQL::Algebra::Operator::Binary # a literal # @return [RDF::Literal::Boolean] def apply(left, right) - case - when !left.compatible?(right) - log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} - when left <= right then RDF::Literal::TRUE - else RDF::Literal::FALSE - end + RDF::Literal(super != RDF::Literal::TRUE) end end end diff --git a/lib/rdf/n3/algebra/str/not_less_than.rb b/lib/rdf/n3/algebra/str/not_less_than.rb index 396a36d..1b5a83f 100644 --- a/lib/rdf/n3/algebra/str/not_less_than.rb +++ b/lib/rdf/n3/algebra/str/not_less_than.rb @@ -1,9 +1,6 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::N3::Algebra::Builtin - + class NotLessThan < LessThan NAME = :strNotLessThan ## @@ -13,12 +10,7 @@ class NotLessThan < SPARQL::Algebra::Operator::Binary # a literal # @return [RDF::Literal::Boolean] def apply(left, right) - case - when !left.compatible?(right) - log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} - when left >= right then RDF::Literal::TRUE - else RDF::Literal::FALSE - end + RDF::Literal(super != RDF::Literal::TRUE) end end end diff --git a/lib/rdf/n3/algebra/str/not_matches.rb b/lib/rdf/n3/algebra/str/not_matches.rb index 1193518..728392b 100644 --- a/lib/rdf/n3/algebra/str/not_matches.rb +++ b/lib/rdf/n3/algebra/str/not_matches.rb @@ -1,9 +1,6 @@ module RDF::N3::Algebra::Str # The subject string; the object 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 SPARQL::Algebra::Evaluatable - include RDF::N3::Algebra::Builtin - + class NotMatches < Matches NAME = :strNotMatches ## @@ -14,15 +11,7 @@ class NotMatches < SPARQL::Algebra::Operator::Binary # @return [RDF::Literal::Boolean] `true` or `false` # @see https://www.w3.org/TR/xpath-functions/#regex-syntax def apply(text, pattern) - log_error(NAME) {"expected a plain RDF::Literal, but got #{text.inspect}"} unless text.is_a?(RDF::Literal) && text.plain? - text = text.to_s - # TODO: validate text syntax - - # @see https://www.w3.org/TR/xpath-functions/#regex-syntax - log_error(NAME) {"expected a plain RDF::Literal, but got #{pattern.inspect}"} unless pattern.is_a?(RDF::Literal) && pattern.plain? - pattern = pattern.to_s - - RDF::Literal(!Regexp.new(pattern).match?(text)) + RDF::Literal(super != RDF::Literal::TRUE) end end end diff --git a/lib/rdf/n3/algebra/str/starts_with.rb b/lib/rdf/n3/algebra/str/starts_with.rb index 4bc1291..c36d35b 100644 --- a/lib/rdf/n3/algebra/str/starts_with.rb +++ b/lib/rdf/n3/algebra/str/starts_with.rb @@ -1,8 +1,25 @@ module RDF::N3::Algebra::Str # True iff the subject string starts with the object string. - class StartsWith < SPARQL::Algebra::Operator::StrStarts + class StartsWith < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Evaluatable include RDF::N3::Algebra::Builtin NAME = :strStartsWith + + ## + # @param [RDF::Literal] left + # a literal + # @param [RDF::Literal] right + # a literal + # @return [RDF::Literal::Boolean] + def apply(left, right) + case + when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) + log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} + RDF::Literal::FALSE + when left.to_s.start_with?(right.to_s) then RDF::Literal::TRUE + else RDF::Literal::FALSE + end + end end end From ce4ae568b44fb285c6c71d16907b99b4f69bb695 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 10 Oct 2020 17:45:40 -0700 Subject: [PATCH 141/193] More checking of inputs. --- lib/rdf/n3/algebra/formula.rb | 6 ++---- lib/rdf/n3/algebra/list/in.rb | 1 + lib/rdf/n3/algebra/list/member.rb | 1 + lib/rdf/n3/algebra/list_operator.rb | 1 + lib/rdf/n3/algebra/math/equal_to.rb | 3 ++- lib/rdf/n3/algebra/math/greater_than.rb | 5 +++-- lib/rdf/n3/algebra/math/less_than.rb | 5 +++-- lib/rdf/n3/algebra/math/not_equal_to.rb | 7 ++----- lib/rdf/n3/algebra/math/not_greater_than.rb | 7 ++----- lib/rdf/n3/algebra/math/not_less_than.rb | 7 ++----- lib/rdf/n3/algebra/math/quotient.rb | 2 +- lib/rdf/n3/algebra/math/remainder.rb | 2 +- lib/rdf/n3/algebra/resource_operator.rb | 2 +- lib/rdf/n3/list.rb | 4 ++-- 14 files changed, 24 insertions(+), 29 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 7029eac..e230fa7 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -222,10 +222,8 @@ def hash # @yieldparam [RDF::Statement] solution # @yieldreturn [void] ignored def each(&block) - @solutions ||= begin - # If there are no solutions, create a single solution - RDF::Query::Solutions(RDF::Query::Solution.new) - end + # If there are no solutions, create a single solution + @solutions ||= RDF::Query::Solutions(RDF::Query::Solution.new) log_debug("formula #{graph_name} each") {SXP::Generator.string @solutions.to_sxp_bin} # Yield patterns by binding variables diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index 9c86d31..c0d1fb6 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -19,6 +19,7 @@ def execute(queryable, solutions:, **options) subject = operand(0).evaluate(solution.bindings, formulae: formulae) || operand(0) # Might be a variable or node evaluating to a list in queryable, or might be a list with variables list = operand(1).evaluate(solution.bindings, formulae: formulae) + next unless list # If it evaluated to a BNode, re-expand as a list list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index 646a414..b6d7e95 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -14,6 +14,7 @@ class Member < RDF::N3::Algebra::ListOperator def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| list = operand(0).evaluate(solution.bindings, formulae: formulae) + next unless list list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) object = formulae.fetch(object, object) if object.node? diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index 1ac22b0..91b60fd 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -16,6 +16,7 @@ def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| # Might be a variable or node evaluating to a list in queryable, or might be a list with variables list = operand(0).evaluate(solution.bindings, formulae: formulae) + next unless list # If it evaluated to a BNode, re-expand as a list list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) diff --git a/lib/rdf/n3/algebra/math/equal_to.rb b/lib/rdf/n3/algebra/math/equal_to.rb index 227bb4f..de945fd 100644 --- a/lib/rdf/n3/algebra/math/equal_to.rb +++ b/lib/rdf/n3/algebra/math/equal_to.rb @@ -33,7 +33,8 @@ class EqualTo < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) - log_debug(NAME) { "term1: #{term1.to_sxp} == term2: #{term2.to_sxp} ? #{(term1 == term2).inspect}"} + return RDF::Literal::FALSE unless term1.is_a?(RDF::Term) && term2.is_a?(RDF::Term) + log_debug(NAME) { "term1: #{term1.to_sxp} == term2: #{term2.to_sxp} ? #{(term1.as_number == term2.as_number).inspect}"} RDF::Literal(term1.as_number == term2.as_number) end end diff --git a/lib/rdf/n3/algebra/math/greater_than.rb b/lib/rdf/n3/algebra/math/greater_than.rb index 3ada1fd..d2e267f 100644 --- a/lib/rdf/n3/algebra/math/greater_than.rb +++ b/lib/rdf/n3/algebra/math/greater_than.rb @@ -20,8 +20,9 @@ class GreaterThan < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) - log_debug(NAME) { "term1: #{term1.to_sxp} > term2: #{term2.to_sxp} ? #{(term1 > term2).inspect}"} - RDF::Literal(term1 > term2) + return RDF::Literal::FALSE unless term1.is_a?(RDF::Term) && term2.is_a?(RDF::Term) + log_debug(NAME) { "term1: #{term1.to_sxp} > term2: #{term2.to_sxp} ? #{(term1.as_number > term2.as_number).inspect}"} + RDF::Literal(term1.as_number > term2.as_number) end end end diff --git a/lib/rdf/n3/algebra/math/less_than.rb b/lib/rdf/n3/algebra/math/less_than.rb index 36a2466..9b9bf09 100644 --- a/lib/rdf/n3/algebra/math/less_than.rb +++ b/lib/rdf/n3/algebra/math/less_than.rb @@ -20,8 +20,9 @@ class LessThan < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) - log_debug(NAME) { "term1: #{term1.to_sxp} < term2: #{term2.to_sxp} ? #{(term1 < term2).inspect}"} - RDF::Literal(term1 < term2) + return RDF::Literal::FALSE unless term1.is_a?(RDF::Term) && term2.is_a?(RDF::Term) + log_debug(NAME) { "term1: #{term1.to_sxp} < term2: #{term2.to_sxp} ? #{(term1.as_number < term2.as_number).inspect}"} + RDF::Literal(term1.as_number < term2.as_number) end end end diff --git a/lib/rdf/n3/algebra/math/not_equal_to.rb b/lib/rdf/n3/algebra/math/not_equal_to.rb index a53177f..ba6c150 100644 --- a/lib/rdf/n3/algebra/math/not_equal_to.rb +++ b/lib/rdf/n3/algebra/math/not_equal_to.rb @@ -3,9 +3,7 @@ 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. # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-equal - class NotEqualTo < SPARQL::Algebra::Operator::Compare - include RDF::N3::Algebra::Builtin - + class NotEqualTo < EqualTo NAME = :'!=' ## @@ -20,8 +18,7 @@ class NotEqualTo < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) - log_debug(NAME) { "term1: #{term1.to_sxp} != term2: #{term2.to_sxp} ? #{(term1 != term2).inspect}"} - RDF::Literal(term1.is_a?(RDF::Term) && term2.is_a?(RDF::Term) && term1.as_number != term2.as_number) + RDF::Literal(super != RDF::Literal::TRUE) end end end diff --git a/lib/rdf/n3/algebra/math/not_greater_than.rb b/lib/rdf/n3/algebra/math/not_greater_than.rb index f2e45e0..80a826f 100644 --- a/lib/rdf/n3/algebra/math/not_greater_than.rb +++ b/lib/rdf/n3/algebra/math/not_greater_than.rb @@ -3,9 +3,7 @@ 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. # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-greater-than - class NotGreaterThan < SPARQL::Algebra::Operator::Compare - include RDF::N3::Algebra::Builtin - + class NotGreaterThan < GreaterThan NAME = :'<=' ## @@ -20,8 +18,7 @@ class NotGreaterThan < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) - log_debug(NAME) { "term1: #{term1.to_sxp} <= term2: #{term2.to_sxp} ? #{(term1 <= term2).inspect}"} - RDF::Literal(term1 <= term2) + RDF::Literal(super != RDF::Literal::TRUE) end end end diff --git a/lib/rdf/n3/algebra/math/not_less_than.rb b/lib/rdf/n3/algebra/math/not_less_than.rb index 7899abe..bda837a 100644 --- a/lib/rdf/n3/algebra/math/not_less_than.rb +++ b/lib/rdf/n3/algebra/math/not_less_than.rb @@ -3,9 +3,7 @@ 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. # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-less-than - class NotLessThan < SPARQL::Algebra::Operator::Compare - include RDF::N3::Algebra::Builtin - + class NotLessThan < LessThan NAME = :'>=' ## @@ -20,8 +18,7 @@ class NotLessThan < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) - log_debug(NAME) { "term1: #{term1.to_sxp} >= term2: #{term2.to_sxp} ? #{(term1 >= term2).inspect}"} - RDF::Literal(term1 >= term2) + RDF::Literal(super != RDF::Literal::TRUE) end end end diff --git a/lib/rdf/n3/algebra/math/quotient.rb b/lib/rdf/n3/algebra/math/quotient.rb index 2b27692..ac606d1 100644 --- a/lib/rdf/n3/algebra/math/quotient.rb +++ b/lib/rdf/n3/algebra/math/quotient.rb @@ -24,7 +24,7 @@ def evaluate(list) # @return [Boolean] # @see RDF::N3::ListOperator#validate def validate(list) - if super && list.all?(&:literal?) && list.length == 2 + if super && list.all? {|le| le.is_a?(RDF::Literal)} && list.length == 2 true else log_error(NAME) {"list is not a pair of literals: #{list.to_sxp}"} diff --git a/lib/rdf/n3/algebra/math/remainder.rb b/lib/rdf/n3/algebra/math/remainder.rb index 9003d32..3ac6334 100644 --- a/lib/rdf/n3/algebra/math/remainder.rb +++ b/lib/rdf/n3/algebra/math/remainder.rb @@ -23,7 +23,7 @@ def evaluate(list) # @return [Boolean] # @see RDF::N3::ListOperator#validate def validate(list) - if super && list.all? {|li| li.as_number.is_a?(RDF::Literal::Integer)} && list.length == 2 + if super && list.all? {|li| li.is_a?(RDF::Literal) && li.as_number.is_a?(RDF::Literal::Integer)} && list.length == 2 true else log_error(NAME) {"list is not a pair of integers: #{list.to_sxp}"} diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 346a631..392330e 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -72,7 +72,7 @@ def evaluate(resource, position: :subject) # @param [RDF::Term] object # @return [Boolean] def valid?(subject, object) - true + subject.is_a?(RDF::Term) && object.is_a?(RDF::Term) end ## diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index adb6bf6..2ee0750 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -33,7 +33,7 @@ def self.to_uri # @param [RDF::Resource] subject # @return [RDF::List, RDF::Resource] returns either the original resource, or a list based on that resource def self.try_list(subject, graph) - return subject unless subject.node? || subject.uri? && subject == RDF.nil + return subject unless subject && (subject.node? || subject.uri? && subject == RDF.nil) ln = RDF::List.new(subject: subject, graph: graph) return subject unless ln.valid? @@ -563,7 +563,7 @@ def evaluate(bindings, formulae: {}, **options) # Create a new list subject using a combination of the current subject and a hash of the binding values subj = "#{subject.id}_#{bindings.values.sort.hash}" values = to_a.map do |o| - o = o.evaluate(bindings, formulae: formulae, **options) + o = o.evaluate(bindings, formulae: formulae, **options) || o # Map graph names to graphs o.node? ? formulae.fetch(o, o) : o end From cbb13fc17079b32a336a67815611d6901fe12b03 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 12 Oct 2020 13:00:43 -0700 Subject: [PATCH 142/193] * Transform bnode references to formulae with the associated formula. * Duplicate formulae when binding, and otherwise. * Requires that the map of formulae be passed to each operator for lookup. This satisfies remaining log:conclusion issues (some, anyway). --- lib/rdf/n3/algebra/formula.rb | 52 ++++++++++++++++++------- lib/rdf/n3/algebra/list/in.rb | 4 +- lib/rdf/n3/algebra/list/member.rb | 6 ++- lib/rdf/n3/algebra/list_operator.rb | 6 ++- lib/rdf/n3/algebra/log/conclusion.rb | 6 ++- lib/rdf/n3/algebra/log/conjunction.rb | 2 +- lib/rdf/n3/algebra/log/implies.rb | 6 +-- lib/rdf/n3/algebra/log/includes.rb | 4 +- lib/rdf/n3/algebra/resource_operator.rb | 8 ++-- lib/rdf/n3/list.rb | 2 +- lib/rdf/n3/reasoner.rb | 2 + spec/reasoner_spec.rb | 15 ++++++- 12 files changed, 80 insertions(+), 33 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index e230fa7..fd6ad78 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -58,7 +58,7 @@ def self.from_enumerable(enumerable, **options) end when RDF::N3::List # Transform blank nodes denoting formulae into those formulae - term = term.transform {|t| t.node? ? formulae.fetch(t, t) : t} + term = term.transform {|t| t.node? ? formulae.fetch(t, t).dup : t} # If we're in a quoted graph, transform blank node components into existential variables if graph_name && term.has_nodes? @@ -92,6 +92,18 @@ def self.from_enumerable(enumerable, **options) this end + ## + # Duplicate this formula, recursively, renaming graph names using hash function. + # + # @return [RDF::N3::Algebra::Formula] + def dup + new_ops = operands.map(&:dup) + graph_name = RDF::Node.intern(new_ops.hash) + that = self.class.new(*new_ops, graph_name: graph_name, formulae: formulae) + that.formulae[graph_name] = that + that + end + ## # Yields solutions from patterns and other operands. Solutions are created by evaluating each pattern and other sub-operand against `queryable`. # @@ -99,10 +111,10 @@ def self.from_enumerable(enumerable, **options) # # @param [RDF::Queryable] queryable # the graph or repository to query + # @param [RDF::Query::Solutions] solutions + # initial solutions for chained queries (RDF::Query::Solutions(RDF::Query::Solution.new)) # @param [Hash{Symbol => Object}] options # any additional keyword options - # @option options [RDF::Query::Solutions] solutions - # optional initial solutions for chained queries # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options) log_info("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin} @@ -119,7 +131,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new these_solutions.map! do |solution| RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)| # Replace blank node bindings with lists and formula references with formula, where those blank nodes are associated with lists. - value = formulae.fetch(value, value) if value.node? + value = formulae.fetch(value, value).dup if value.node? l = RDF::N3::List.try_list(value, queryable) value = l if l.constant? memo.merge(name => value) @@ -192,9 +204,11 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new # options passed from query # @return [RDF::N3::List] # @see SPARQL::Algebra::Expression.evaluate - def evaluate(bindings, **options) + def evaluate(bindings, formulae:, **options) this = dup - this.solutions = RDF::Query::Solutions(bindings) + # Maintain formula relationships + formulae {|k, v| this.formulae[k] ||= v} + this.solutions = RDF::Query::Solutions(bindings) this end @@ -250,10 +264,13 @@ def each(&block) elsif solution[o] && solution[o].formula? form = solution[o] # uses the graph_name of the formula, and yields statements from the formula - form.solutions = RDF::Query::Solutions(solution) - form.each do |stmt| - stmt.graph_name = form.graph_name - block.call(stmt) + log_depth do + form.solutions = RDF::Query::Solutions(solution) + form.each do |stmt| + stmt.graph_name = form.graph_name + log_debug("(formula add from var form)") {stmt.to_sxp} + block.call(stmt) + end end form.graph_name else @@ -262,10 +279,15 @@ def each(&block) when RDF::N3::List o.variable? ? o.evaluate(solution.bindings, formulae: formulae) : o when RDF::N3::Algebra::Formula - # uses the graph_name of the formula, and yields statements from the formula - o.each do |stmt| - stmt.graph_name = o.graph_name - block.call(stmt) + # uses the graph_name of the formula, and yields statements from the formula. No solutions are passed in. + log_depth do + o.solutions = RDF::Query::Solutions(solution) + o.each do |stmt| + stmt.graph_name = o.graph_name + log_debug("(formula add from form)") {stmt.to_sxp} + #require 'byebug'; byebug if o.graph_name.to_s == '_:.form_0' + block.call(stmt) + end end o.graph_name else @@ -281,7 +303,7 @@ def each(&block) next end - #log_debug("(formula add)") {statement.to_sxp} + log_debug("(formula add)") {statement.to_sxp} block.call(statement) end end diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index c0d1fb6..8ac268c 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -11,8 +11,10 @@ class In < RDF::N3::Algebra::ListOperator # Evaluates this operator using the given variable `bindings`. # If the first operand is a variable, it creates a solution for each element in the list. # - # @param [RDF::Queryable] queryable + # @param [RDF::Queryable] queryable + # the graph or repository to query # @param [RDF::Query::Solutions] solutions + # solutions for chained queries # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index b6d7e95..eaa10a7 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -8,8 +8,10 @@ class Member < RDF::N3::Algebra::ListOperator # Evaluates this operator using the given variable `bindings`. # If the last operand is a variable, it creates a solution for each element in the list. # - # @param [RDF::Queryable] queryable + # @param [RDF::Queryable] queryable + # the graph or repository to query # @param [RDF::Query::Solutions] solutions + # solutions for chained queries # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| @@ -17,7 +19,7 @@ def execute(queryable, solutions:, **options) next unless list list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) - object = formulae.fetch(object, object) if object.node? + object = formulae.fetch(object, object).dup if object.node? log_debug(NAME) {"list: #{list.to_sxp}, object: #{object.to_sxp}"} unless list.list? && list.valid? diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index 91b60fd..4a6316a 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -9,8 +9,10 @@ class ListOperator < SPARQL::Algebra::Operator::Binary ## # The operator takes a list and provides a mechanism for subclasses to operate over (and validate) that list argument. # - # @param [RDF::Queryable] queryable + # @param [RDF::Queryable] queryable + # the graph or repository to query # @param [RDF::Query::Solutions] solutions + # solutions for chained queries # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| @@ -20,7 +22,7 @@ def execute(queryable, solutions:, **options) # If it evaluated to a BNode, re-expand as a list list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) - object = formulae.fetch(object, object) if object.node? + object = formulae.fetch(object, object).dup if object.node? log_debug(self.class.const_get(:NAME)) {"list: #{SXP::Generator.string(list.to_sxp_bin).gsub(/\s+/m, ' ')}, object: #{SXP::Generator.string(object.to_sxp_bin).gsub(/\s+/m, ' ')}"} next unless validate(list) diff --git a/lib/rdf/n3/algebra/log/conclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb index c713a70..a4ad73d 100644 --- a/lib/rdf/n3/algebra/log/conclusion.rb +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -7,7 +7,9 @@ class Conclusion < RDF::N3::Algebra::ResourceOperator NAME = :logConclusion ## - # Evaluates this operator by creating a new formula containing the triples generated by reasoning over the input formula using think.. + # Evaluates this operator by creating a new formula containing the triples generated by reasoning over the input formula using think. + # + # The subject is evaluated into an isolated repository so that conclusions evaluated when evaluating the subject are not necessarily conclusions resulting from evaluating this operator. # # @param [RDF::N3::Algebra:Formula] resource # @return [RDF::N3::Algebra::Formula] @@ -21,7 +23,7 @@ def evaluate(resource, position:) reasoner.execute(think: true) {|stmt| conclusions << stmt} # The result is a formula containing the conclusions - form = RDF::N3::Algebra::Formula.from_enumerable(conclusions, graph_name: RDF::Node.intern(conclusions.hash)) + form = RDF::N3::Algebra::Formula.from_enumerable(conclusions).dup log_info('(logConclusion result)') {SXP::Generator.string(form.to_sxp_bin).gsub(/\s+/m, ' ')} form end diff --git a/lib/rdf/n3/algebra/log/conjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb index 1bba364..0a681e9 100644 --- a/lib/rdf/n3/algebra/log/conjunction.rb +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -20,7 +20,7 @@ def evaluate(list) list.each do |f| form.operands.append(*f.operands) end - form + form.dup end ## diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 45ee591..a12dac1 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -25,8 +25,8 @@ class Implies < SPARQL::Algebra::Operator::Binary def execute(queryable, solutions:, **options) @queryable = queryable @solutions = RDF::Query::Solutions(solutions.map do |solution| - subject = operand(0).evaluate(solution.bindings) - object = operand(1).evaluate(solution.bindings) + subject = operand(0).evaluate(solution.bindings, formulae: formulae) + object = operand(1).evaluate(solution.bindings, formulae: formulae) log_debug(NAME) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} log_debug(NAME) {"object: #{SXP::Generator.string operand(1).to_sxp_bin}"} @@ -62,7 +62,7 @@ def each(&block) log_depth do @solutions.each do |solution| - object = operand(1).evaluate(solution.bindings) + object = operand(1).evaluate(solution.bindings, formulae: formulae) next unless object # shouldn't happen object.solutions = RDF::Query::Solutions(solution) diff --git a/lib/rdf/n3/algebra/log/includes.rb b/lib/rdf/n3/algebra/log/includes.rb index 9fa5c95..83a3c1f 100644 --- a/lib/rdf/n3/algebra/log/includes.rb +++ b/lib/rdf/n3/algebra/log/includes.rb @@ -27,8 +27,8 @@ class Includes < SPARQL::Algebra::Operator::Binary def execute(queryable, solutions:, **options) @queryable = queryable @solutions = RDF::Query::Solutions(solutions.map do |solution| - subject = operand(0).evaluate(solution.bindings) - object = operand(1).evaluate(solution.bindings) + subject = operand(0).evaluate(solution.bindings, formulae: formulae) + object = operand(1).evaluate(solution.bindings, formulae: formulae) log_debug(NAME) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} log_debug(NAME) {"object: #{SXP::Generator.string operand(1).to_sxp_bin}"} diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 392330e..9a24262 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -9,15 +9,17 @@ class ResourceOperator < SPARQL::Algebra::Operator::Binary ## # The operator takes a literal and provides a mechanism for subclasses to operate over (and validate) that argument. # - # @param [RDF::Queryable] queryable + # @param [RDF::Queryable] queryable + # the graph or repository to query # @param [RDF::Query::Solutions] solutions + # solutions for chained queries # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| subject = operand(0).evaluate(solution.bindings, formulae: formulae) || operand(0) object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) - subject = formulae.fetch(subject, subject) if subject.node? - object = formulae.fetch(object, object) if object.node? + subject = formulae.fetch(subject, subject).dup if subject.node? + object = formulae.fetch(object, object).dup if object.node? log_debug(self.class.const_get(:NAME)) {"subject: #{subject.to_sxp}, object: #{object.to_sxp}"} next unless valid?(subject, object) diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index 2ee0750..ae6babb 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -565,7 +565,7 @@ def evaluate(bindings, formulae: {}, **options) values = to_a.map do |o| o = o.evaluate(bindings, formulae: formulae, **options) || o # Map graph names to graphs - o.node? ? formulae.fetch(o, o) : o + o.node? ? formulae.fetch(o, o).dup : o end RDF::N3::List.new(subject: RDF::Node.intern(subj), values: values) end diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 32036ce..d0c3ce9 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -126,6 +126,7 @@ def execute(**options, &block) log_depth {formula.execute(knowledge_base, **options)} knowledge_base << formula log_debug("reasoner: datastore") {SXP::Generator.string knowledge_base.statements.to_sxp_bin} + log_debug("reasoner: inferred") {SXP::Generator.string knowledge_base.statements.select(&:inferred?).to_sxp_bin} end log_info("reasoner: think end") { "count: #{count}"} else @@ -137,6 +138,7 @@ def execute(**options, &block) end log_debug("reasoner: datastore") {SXP::Generator.string knowledge_base.statements.to_sxp_bin} + log_debug("reasoner: inferred") {SXP::Generator.string knowledge_base.statements.select(&:inferred?).to_sxp_bin} # Add updates back to mutable, containg builtins and variables. @mutable << knowledge_base diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 7603f77..aff2f0d 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -19,6 +19,19 @@ context "n3:log" do context "log:conclusion" do { + "conclusion-super-simple" => { + input: %( + { + { + { } => { a } . + . + } log:conclusion ?y + } => { ?y a :TestResult }. + ), + expect: %( + { a } a :TestResult . + ) + }, "conclusion-simple" => { input: %( { @@ -26,7 +39,7 @@ . } a :TestRule. - { ?x a :TestRule; log:conclusion ?y } => { ?y a :TestResult }. + { ?x a :TestRule; log:conclusion ?y } => { ?y a :TestResult }. ), expect: %( { a } a :TestResult . From 2866676c44b0979b20159c8e08da7ef3aea11d55 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 12 Oct 2020 15:27:18 -0700 Subject: [PATCH 143/193] Improve validation in ResourceOperator, and show load errors in log:semantics. --- lib/rdf/n3/algebra/log/semantics.rb | 5 +++-- lib/rdf/n3/algebra/resource_operator.rb | 9 ++++++++- script/parse | 19 +++++++++++-------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/lib/rdf/n3/algebra/log/semantics.rb b/lib/rdf/n3/algebra/log/semantics.rb index ffbae77..dc8c0f5 100644 --- a/lib/rdf/n3/algebra/log/semantics.rb +++ b/lib/rdf/n3/algebra/log/semantics.rb @@ -24,11 +24,12 @@ def evaluate(resource, position: :subject) repo << RDF::Reader.open(resource) content_hash = repo.hash # used as name of resulting formula RDF::N3::Algebra::Formula.from_enumerable(repo, graph_name: RDF::Node.new(content_hash)) - rescue IOError, RDF::ReaderError + rescue IOError, RDF::ReaderError => e + log_error(NAME) {"error loading #{resource}: #{e}"} nil end when :object - return nil unless resource.literal? + return nil unless resource.literal? || resource.variable? resource end end diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 9a24262..d69ce7e 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -74,7 +74,14 @@ def evaluate(resource, position: :subject) # @param [RDF::Term] object # @return [Boolean] def valid?(subject, object) - subject.is_a?(RDF::Term) && object.is_a?(RDF::Term) + case subject + when RDF::Query::Variable + object.term? + when RDF::Term + object.term? || object.variable? + else + false + end end ## diff --git a/script/parse b/script/parse index 4ccc10c..5db6cc6 100755 --- a/script/parse +++ b/script/parse @@ -11,6 +11,8 @@ require 'open-uri' def run(input, **options) require 'profiler' if options[:profile] + parser_options = options[:parser_options] + parser_options[:base_uri] ||= File.expand_path(input.path) if input.respond_to?(:path) reader_class = RDF::Reader.for(options[:input_format].to_sym) raise "Reader not found for #{options[:input_format]}" unless reader_class @@ -21,8 +23,8 @@ def run(input, **options) if options[:think] || options[:rules] STDERR.puts "Reason" if $verbose # Parse into a new reasoner and evaluate - reader_class.new(input, **options[:parser_options].merge(logger: nil)) do |reader| - reasoner = RDF::N3::Reasoner.new(reader, **options[:parser_options]) + reader_class.new(input, **parser_options.merge(logger: nil)) do |reader| + reasoner = RDF::N3::Reasoner.new(reader, **parser_options) reasoner.reason!(**options) repo = RDF::N3::Repository.new if options[:conclusions] @@ -37,7 +39,7 @@ def run(input, **options) end elsif options[:output_format] == :ntriples || options[:quiet] STDERR.puts "Parse nt/quiet" if $verbose - reader_class.new(input, **options[:parser_options]).each do |statement| + reader_class.new(input, **parser_options).each do |statement| num += 1 if options[:errors] && statement.invalid? $stderr.puts "Invalid statement at #{r.lineno}: #{statement.inspect}" @@ -49,19 +51,19 @@ def run(input, **options) end elsif options[:output_format] == :sxp STDERR.puts "Parse to SXP" if $verbose - reader_class.new(input, **options[:parser_options]) do |reader| + reader_class.new(input, **parser_options) do |reader| reasoner = RDF::N3::Reasoner.new(reader) SXP::Generator.print(reasoner.to_sxp_bin) end elsif options[:output_format] == :inspect STDERR.puts "Parse to inspect" if $verbose - reader_class.new(input, **options[:parser_options]).each do |statement| + reader_class.new(input, **parser_options).each do |statement| num += 1 options[:output].puts statement.inspect end else STDERR.puts "Parse to #{options[:output_format]}" if $verbose - reader = reader_class.new(input, **options[:parser_options]) + reader = reader_class.new(input, **parser_options) repo = [].extend(RDF::Enumerable, RDF::Queryable) reader.each_statement {|st| repo << st} num = repo.count @@ -92,7 +94,6 @@ logger.level = Logger::WARN logger.formatter = lambda {|severity, datetime, progname, msg| "%5s %s\n" % [severity, msg]} parser_options = { - base_uri: "http://example.com", list_terms: true, logger: logger, validate: false, @@ -159,7 +160,9 @@ opts.each do |opt, arg| when "--data" then options[:data] = true when '--debug' then logger.level = Logger::DEBUG when '--errors' then options[:errors] = true - when '--execute' then input = arg + when '--execute' + options[:base_uri] = "http://example.com/", + input = arg when '--format' then options[:output_format] = arg.to_sym when "--help" then usage() when '--info' then logger.level = Logger::INFO From 24c1e6e08246b6f4c9848e281e91bb5030dd7b36 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 13 Oct 2020 14:36:37 -0700 Subject: [PATCH 144/193] Use bnode labeler in reader to make reproducible blank node identifiers. This is necessary for log:semantics. --- lib/rdf/n3/reader.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 4f60dc7..4be668f 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -78,6 +78,7 @@ def initialize(input = $stdin, **options, &block) @formula_nodes = {} @label_uniquifier = "0" @bnodes = {} # allocated bnodes by formula + @bn_labler = ".anon0" @variables = {} if options[:base_uri] @@ -719,7 +720,7 @@ def process_path(path) debug("process_path", depth: @options[:depth]) {path.inspect} while pathtail - bnode = RDF::Node.new + bnode = bnode() pred = pathtail.is_a?(RDF::Term) ? pathtail : pathtail[:pathitem] if direction == :reverse add_statement("process_path(reverse)", bnode, pred, pathitem) @@ -756,13 +757,14 @@ def process_pname(value) end # Keep track of allocated BNodes. Blank nodes are allocated to the formula. + # Unnnamed bnodes are created using an incrementing labeler for repeatability. def bnode(label = nil) - if label - fl = "#{label}_#{formulae.last ? formulae.last.id : 'bn_ground'}" - @bnodes[fl] ||= RDF::Node.new(fl) - else - RDF::Node.new + if label.nil? + label = @bn_labler + @bn_labler.succ! end + fl = "#{label}_#{formulae.last ? formulae.last.id : 'bn_ground'}" + @bnodes[fl] ||= RDF::Node.new(fl) end # If not in ground formula, note scope, and if existential From febe76272f49f3af52a9f2c6354dafcadd727a97 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 13 Oct 2020 14:54:26 -0700 Subject: [PATCH 145/193] Define `Builtin#hash` and intern all bnodes when parsing. --- lib/rdf/n3/algebra/builtin.rb | 8 ++++++++ lib/rdf/n3/algebra/formula.rb | 2 +- lib/rdf/n3/algebra/log/conjunction.rb | 1 + lib/rdf/n3/algebra/log/parsed_as_n3.rb | 6 ++++-- lib/rdf/n3/algebra/log/semantics.rb | 8 +++++--- lib/rdf/n3/reader.rb | 4 ++-- 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb index f0a527c..88bccf4 100644 --- a/lib/rdf/n3/algebra/builtin.rb +++ b/lib/rdf/n3/algebra/builtin.rb @@ -31,5 +31,13 @@ def input_operand # By default, operators do not yield statements def each(&block) end + + ## + # The builtin hash is the hash of it's operands and NAME. + # + # @see RDF::Value#hash + def hash + ([self.class.const_get(:NAME)] + operands).hash + end end end diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index fd6ad78..c2fdf0e 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -16,7 +16,7 @@ class Formula < SPARQL::Algebra::Operator # @return [RDF::Query] attr_accessor :query - NAME = [:formula] + NAME = :formula ## # Create a formula from an RDF::Enumerable (such as RDF::N3::Repository) diff --git a/lib/rdf/n3/algebra/log/conjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb index 0a681e9..63863e8 100644 --- a/lib/rdf/n3/algebra/log/conjunction.rb +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -16,6 +16,7 @@ class Conjunction < RDF::N3::Algebra::ListOperator # @see RDF::N3::ListOperator#evaluate def evaluate(list) form = RDF::N3::Algebra::Formula.new(graph_name: RDF::Node.intern(list.hash)) + log_info(NAME) {"list hash: #{form.graph_name}"} list.each do |f| form.operands.append(*f.operands) diff --git a/lib/rdf/n3/algebra/log/parsed_as_n3.rb b/lib/rdf/n3/algebra/log/parsed_as_n3.rb index ec1e8bb..6e104f3 100644 --- a/lib/rdf/n3/algebra/log/parsed_as_n3.rb +++ b/lib/rdf/n3/algebra/log/parsed_as_n3.rb @@ -17,10 +17,12 @@ def evaluate(resource, position: :subject) return nil unless resource.literal? begin repo = RDF::N3::Repository.new - repo << RDF::N3::Reader.new(resource.to_s, **@options) + repo << RDF::N3::Reader.new(resource.to_s, list_terms: true, **@options) log_debug("logParsedAsN3") {SXP::Generator.string repo.statements.to_sxp_bin} content_hash = resource.hash # used as name of resulting formula - RDF::N3::Algebra::Formula.from_enumerable(repo, graph_name: RDF::Node.intern(content_hash)) + form = RDF::N3::Algebra::Formula.from_enumerable(repo, graph_name: RDF::Node.intern(content_hash)) + log_info(NAME) {"form hash (#{resource}): #{form.hash}"} + form rescue RDF::ReaderError nil end diff --git a/lib/rdf/n3/algebra/log/semantics.rb b/lib/rdf/n3/algebra/log/semantics.rb index dc8c0f5..5851162 100644 --- a/lib/rdf/n3/algebra/log/semantics.rb +++ b/lib/rdf/n3/algebra/log/semantics.rb @@ -21,9 +21,11 @@ def evaluate(resource, position: :subject) return nil unless resource.literal? || resource.uri? begin repo = RDF::N3::Repository.new - repo << RDF::Reader.open(resource) - content_hash = repo.hash # used as name of resulting formula - RDF::N3::Algebra::Formula.from_enumerable(repo, graph_name: RDF::Node.new(content_hash)) + repo << RDF::Reader.open(resource, list_terms: true, **@options) + content_hash = repo.statements.hash # used as name of resulting formula + form = RDF::N3::Algebra::Formula.from_enumerable(repo, graph_name: RDF::Node.intern(content_hash)) + log_info(NAME) {"form hash (#{resource}): #{form.hash}"} + form rescue IOError, RDF::ReaderError => e log_error(NAME) {"error loading #{resource}: #{e}"} nil diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 4be668f..b3fd477 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -551,7 +551,7 @@ def read_formula if @lexer.first === '{' prod(:formula, %(})) do @lexer.shift - node = RDF::Node.new(".form_#{unique_label}") + node = RDF::Node.intern(".form_#{unique_label}") formulae.push(node) formula_nodes[node] = true debug(:formula, depth: @options[:depth]) {"id: #{node}, depth: #{formulae.length}"} @@ -764,7 +764,7 @@ def bnode(label = nil) @bn_labler.succ! end fl = "#{label}_#{formulae.last ? formulae.last.id : 'bn_ground'}" - @bnodes[fl] ||= RDF::Node.new(fl) + @bnodes[fl] ||= RDF::Node.intern(fl) end # If not in ground formula, note scope, and if existential From b679796778a6a2496b78b0579c97a7bc1bee4298 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 14 Oct 2020 15:01:59 -0700 Subject: [PATCH 146/193] Create `Builtin#to_uri` and lazy-evaluate builtin classes in `Algebra.for`. --- lib/rdf/n3/algebra.rb | 148 +++++++++++++------------ lib/rdf/n3/algebra/builtin.rb | 5 + lib/rdf/n3/algebra/formula.rb | 5 + lib/rdf/n3/algebra/log/parsed_as_n3.rb | 2 +- lib/rdf/n3/algebra/log/semantics.rb | 2 +- 5 files changed, 91 insertions(+), 71 deletions(-) diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 767268e..49462cb 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -14,6 +14,8 @@ module Algebra autoload :ResourceOperator, 'rdf/n3/algebra/resource_operator' module List + def vocab; RDF::N3::List.to_uri; end + module_function :vocab autoload :Append, 'rdf/n3/algebra/list/append' autoload :First, 'rdf/n3/algebra/list/first' autoload :In, 'rdf/n3/algebra/list/in' @@ -23,6 +25,8 @@ module List end module Log + def vocab; RDF::N3::Log.to_uri; end + module_function :vocab autoload :Conclusion, 'rdf/n3/algebra/log/conclusion' autoload :Conjunction, 'rdf/n3/algebra/log/conjunction' autoload :Content, 'rdf/n3/algebra/log/content' @@ -38,6 +42,8 @@ module Log end module Math + def vocab; RDF::N3::Math.to_uri; end + module_function :vocab autoload :AbsoluteValue, 'rdf/n3/algebra/math/absolute_value' autoload :Ceiling, 'rdf/n3/algebra/math/ceiling' autoload :Cos, 'rdf/n3/algebra/math/cos' @@ -65,6 +71,8 @@ module Math end module Str + def vocab; RDF::N3::Str.to_uri; end + module_function :vocab autoload :Concatenation, 'rdf/n3/algebra/str/concatenation' autoload :Contains, 'rdf/n3/algebra/str/contains' autoload :ContainsIgnoringCase, 'rdf/n3/algebra/str/contains_ignoring_case' @@ -84,6 +92,8 @@ module Str end module Time + def vocab; RDF::N3::Time.to_uri; end + module_function :vocab autoload :DayOfWeek, 'rdf/n3/algebra/time/day_of_week' autoload :Day, 'rdf/n3/algebra/time/day' autoload :GmTime, 'rdf/n3/algebra/time/gm_time' @@ -99,81 +109,81 @@ module Time def for(uri) { - RDF::N3::List.append => List::Append, - RDF::N3::List.first => List::First, - RDF::N3::List.in => List::In, - RDF::N3::List.last => List::Last, - RDF::N3::List.length => List::Length, - RDF::N3::List.member => List::Member, + RDF::N3::List.append => List.const_get(:Append), + RDF::N3::List.first => List.const_get(:First), + RDF::N3::List.in => List.const_get(:In), + RDF::N3::List.last => List.const_get(:Last), + RDF::N3::List.length => List.const_get(:Length), + RDF::N3::List.member => List.const_get(:Member), - RDF::N3::Log.conclusion => Log::Conclusion, - RDF::N3::Log.conjunction => Log::Conjunction, - RDF::N3::Log.content => Log::Content, - RDF::N3::Log.equalTo => Log::EqualTo, - RDF::N3::Log.implies => Log::Implies, - RDF::N3::Log.includes => Log::Includes, - RDF::N3::Log.n3String => Log::N3String, - RDF::N3::Log.notEqualTo => Log::NotEqualTo, - RDF::N3::Log.notIncludes => Log::NotIncludes, - RDF::N3::Log.outputString => Log::OutputString, - RDF::N3::Log.parsedAsN3 => Log::ParsedAsN3, - RDF::N3::Log.semantics => Log::Semantics, + RDF::N3::Log.conclusion => Log.const_get(:Conclusion), + RDF::N3::Log.conjunction => Log.const_get(:Conjunction), + RDF::N3::Log.content => Log.const_get(:Content), + RDF::N3::Log.equalTo => Log.const_get(:EqualTo), + RDF::N3::Log.implies => Log.const_get(:Implies), + RDF::N3::Log.includes => Log.const_get(:Includes), + RDF::N3::Log.n3String => Log.const_get(:N3String), + RDF::N3::Log.notEqualTo => Log.const_get(:NotEqualTo), + RDF::N3::Log.notIncludes => Log.const_get(:NotIncludes), + RDF::N3::Log.outputString => Log.const_get(:OutputString), + RDF::N3::Log.parsedAsN3 => Log.const_get(:ParsedAsN3), + RDF::N3::Log.semantics => Log.const_get(:Semantics), RDF::N3::Log.supports => NotImplemented, - RDF::N3::Math.absoluteValue => Math::AbsoluteValue, - RDF::N3::Math.ceiling => Math::Ceiling, - RDF::N3::Math.cos => Math::Cos, - RDF::N3::Math.cosh => Math::CosH, - RDF::N3::Math.difference => Math::Difference, - RDF::N3::Math.equalTo => Math::EqualTo, - RDF::N3::Math.exponentiation => Math::Exponentiation, - RDF::N3::Math.floor => Math::Floor, - RDF::N3::Math.greaterThan => Math::GreaterThan, - RDF::N3::Math.integerQuotient => Math::IntegerQuotient, - RDF::N3::Math.lessThan => Math::LessThan, - 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.sin => Math::Sin, - RDF::N3::Math.sinh => Math::SinH, - RDF::N3::Math.tan => Math::Tan, - RDF::N3::Math.tanh => Math::TanH, - RDF::N3::Math[:sum] => Math::Sum, + RDF::N3::Math.absoluteValue => Math.const_get(:AbsoluteValue), + RDF::N3::Math.ceiling => Math.const_get(:Ceiling), + RDF::N3::Math.cos => Math.const_get(:Cos), + RDF::N3::Math.cosh => Math.const_get(:CosH), + RDF::N3::Math.difference => Math.const_get(:Difference), + RDF::N3::Math.equalTo => Math.const_get(:EqualTo), + RDF::N3::Math.exponentiation => Math.const_get(:Exponentiation), + RDF::N3::Math.floor => Math.const_get(:Floor), + RDF::N3::Math.greaterThan => Math.const_get(:GreaterThan), + RDF::N3::Math.integerQuotient => Math.const_get(:IntegerQuotient), + RDF::N3::Math.lessThan => Math.const_get(:LessThan), + RDF::N3::Math.negation => Math.const_get(:Negation), + RDF::N3::Math.notEqualTo => Math.const_get(:NotEqualTo), + RDF::N3::Math.notGreaterThan => Math.const_get(:NotGreaterThan), + RDF::N3::Math.notLessThan => Math.const_get(:NotLessThan), + RDF::N3::Math.product => Math.const_get(:Product), + RDF::N3::Math.quotient => Math.const_get(:Quotient), + RDF::N3::Math.remainder => Math.const_get(:Remainder), + RDF::N3::Math.rounded => Math.const_get(:Rounded), + RDF::N3::Math.sin => Math.const_get(:Sin), + RDF::N3::Math.sinh => Math.const_get(:SinH), + RDF::N3::Math.tan => Math.const_get(:Tan), + RDF::N3::Math.tanh => Math.const_get(:TanH), + RDF::N3::Math[:sum] => Math.const_get(:Sum), - RDF::N3::Str.concatenation => Str::Concatenation, - RDF::N3::Str.contains => Str::Contains, - RDF::N3::Str.containsIgnoringCase => Str::ContainsIgnoringCase, + RDF::N3::Str.concatenation => Str.const_get(:Concatenation), + RDF::N3::Str.contains => Str.const_get(:Contains), + RDF::N3::Str.containsIgnoringCase => Str.const_get(:ContainsIgnoringCase), RDF::N3::Str.containsRoughly => NotImplemented, - 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, + RDF::N3::Str.endsWith => Str.const_get(:EndsWith), + RDF::N3::Str.equalIgnoringCase => Str.const_get(:EqualIgnoringCase), + RDF::N3::Str.format => Str.const_get(:Format), + RDF::N3::Str.greaterThan => Str.const_get(:GreaterThan), + RDF::N3::Str.lessThan => Str.const_get(:LessThan), + RDF::N3::Str.matches => Str.const_get(:Matches), + RDF::N3::Str.notEqualIgnoringCase => Str.const_get(:NotEqualIgnoringCase), + RDF::N3::Str.notGreaterThan => Str.const_get(:NotGreaterThan), + RDF::N3::Str.notLessThan => Str.const_get(:NotLessThan), + RDF::N3::Str.notMatches => Str.const_get(:NotMatches), + RDF::N3::Str.replace => Str.const_get(:Replace), + RDF::N3::Str.scrape => Str.const_get(:Scrape), + RDF::N3::Str.startsWith => Str.const_get(:StartsWith), - RDF::N3::Time.dayOfWeek => Time::DayOfWeek, - RDF::N3::Time.day => Time::Day, - RDF::N3::Time.gmTime => Time::GmTime, - RDF::N3::Time.hour => Time::Hour, - RDF::N3::Time.inSeconds => Time::InSeconds, - RDF::N3::Time.localTime => Time::LocalTime, - RDF::N3::Time.minute => Time::Minute, - RDF::N3::Time.month => Time::Month, - RDF::N3::Time.second => Time::Second, - RDF::N3::Time.timeZone => Time::Timezone, - RDF::N3::Time.year => Time::Year, + RDF::N3::Time.dayOfWeek => Time.const_get(:DayOfWeek), + RDF::N3::Time.day => Time.const_get(:Day), + RDF::N3::Time.gmTime => Time.const_get(:GmTime), + RDF::N3::Time.hour => Time.const_get(:Hour), + RDF::N3::Time.inSeconds => Time.const_get(:InSeconds), + RDF::N3::Time.localTime => Time.const_get(:LocalTime), + RDF::N3::Time.minute => Time.const_get(:Minute), + RDF::N3::Time.month => Time.const_get(:Month), + RDF::N3::Time.second => Time.const_get(:Second), + RDF::N3::Time.timeZone => Time.const_get(:Timezone), + RDF::N3::Time.year => Time.const_get(:Year), }[uri] end module_function :for diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb index 88bccf4..d82e736 100644 --- a/lib/rdf/n3/algebra/builtin.rb +++ b/lib/rdf/n3/algebra/builtin.rb @@ -39,5 +39,10 @@ def each(&block) def hash ([self.class.const_get(:NAME)] + operands).hash end + + # The URI of this operator. + def to_uri + Kernel.const_get('::' + self.class.to_s.split('::')[0..-2].join('::')).vocab + self.class.const_get(:NAME) + end end end diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index c2fdf0e..1dff1ee 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -322,6 +322,11 @@ def solutions=(solutions) # @return [RDF::Resource] def graph_name; @options[:graph_name]; end + ## + # The URI of a formula is its graph name + # @return [RDF::URI] + alias_method :to_uri, :graph_name + # Assign a graph name to this formula # @param [RDF::Resource] name # @return [RDF::Resource] diff --git a/lib/rdf/n3/algebra/log/parsed_as_n3.rb b/lib/rdf/n3/algebra/log/parsed_as_n3.rb index 6e104f3..119c97e 100644 --- a/lib/rdf/n3/algebra/log/parsed_as_n3.rb +++ b/lib/rdf/n3/algebra/log/parsed_as_n3.rb @@ -17,7 +17,7 @@ def evaluate(resource, position: :subject) return nil unless resource.literal? begin repo = RDF::N3::Repository.new - repo << RDF::N3::Reader.new(resource.to_s, list_terms: true, **@options) + repo << RDF::N3::Reader.new(resource.to_s, **@options.merge(list_terms: true, logger: false)) log_debug("logParsedAsN3") {SXP::Generator.string repo.statements.to_sxp_bin} content_hash = resource.hash # used as name of resulting formula form = RDF::N3::Algebra::Formula.from_enumerable(repo, graph_name: RDF::Node.intern(content_hash)) diff --git a/lib/rdf/n3/algebra/log/semantics.rb b/lib/rdf/n3/algebra/log/semantics.rb index 5851162..be81550 100644 --- a/lib/rdf/n3/algebra/log/semantics.rb +++ b/lib/rdf/n3/algebra/log/semantics.rb @@ -21,7 +21,7 @@ def evaluate(resource, position: :subject) return nil unless resource.literal? || resource.uri? begin repo = RDF::N3::Repository.new - repo << RDF::Reader.open(resource, list_terms: true, **@options) + repo << RDF::Reader.open(resource, **@options.merge(list_terms: true, base_uri: resource, logger: false)) content_hash = repo.statements.hash # used as name of resulting formula form = RDF::N3::Algebra::Formula.from_enumerable(repo, graph_name: RDF::Node.intern(content_hash)) log_info(NAME) {"form hash (#{resource}): #{form.hash}"} From a893b5e20b1fe576492b718936dc2dd98bcbf83d Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 18 Oct 2020 16:56:40 -0700 Subject: [PATCH 147/193] Change `#evaluate` to `#resolve` for builtins, to allow `#evaluate` to be used for applying solution bindings. --- lib/rdf/n3/algebra/list/append.rb | 4 ++-- lib/rdf/n3/algebra/list/first.rb | 4 ++-- lib/rdf/n3/algebra/list/last.rb | 4 ++-- lib/rdf/n3/algebra/list/length.rb | 4 ++-- lib/rdf/n3/algebra/list_operator.rb | 6 +++--- lib/rdf/n3/algebra/log/conclusion.rb | 4 ++-- lib/rdf/n3/algebra/log/conjunction.rb | 4 ++-- lib/rdf/n3/algebra/log/content.rb | 2 +- lib/rdf/n3/algebra/log/n3_string.rb | 2 +- lib/rdf/n3/algebra/log/parsed_as_n3.rb | 2 +- lib/rdf/n3/algebra/log/semantics.rb | 2 +- lib/rdf/n3/algebra/math/absolute_value.rb | 2 +- lib/rdf/n3/algebra/math/ceiling.rb | 2 +- lib/rdf/n3/algebra/math/cos.rb | 2 +- lib/rdf/n3/algebra/math/cosh.rb | 2 +- lib/rdf/n3/algebra/math/difference.rb | 2 +- lib/rdf/n3/algebra/math/exponentiation.rb | 2 +- lib/rdf/n3/algebra/math/floor.rb | 2 +- lib/rdf/n3/algebra/math/integer_quotient.rb | 2 +- lib/rdf/n3/algebra/math/negation.rb | 2 +- lib/rdf/n3/algebra/math/product.rb | 2 +- lib/rdf/n3/algebra/math/quotient.rb | 2 +- lib/rdf/n3/algebra/math/remainder.rb | 2 +- lib/rdf/n3/algebra/math/rounded.rb | 2 +- lib/rdf/n3/algebra/math/sin.rb | 2 +- lib/rdf/n3/algebra/math/sinh.rb | 2 +- lib/rdf/n3/algebra/math/sum.rb | 2 +- lib/rdf/n3/algebra/math/tan.rb | 2 +- lib/rdf/n3/algebra/math/tanh.rb | 2 +- lib/rdf/n3/algebra/resource_operator.rb | 8 ++++---- lib/rdf/n3/algebra/str/concatenation.rb | 2 +- lib/rdf/n3/algebra/str/format.rb | 2 +- lib/rdf/n3/algebra/str/replace.rb | 2 +- lib/rdf/n3/algebra/str/scrape.rb | 2 +- lib/rdf/n3/algebra/time/day.rb | 2 +- lib/rdf/n3/algebra/time/day_of_week.rb | 2 +- lib/rdf/n3/algebra/time/gm_time.rb | 2 +- lib/rdf/n3/algebra/time/hour.rb | 2 +- lib/rdf/n3/algebra/time/in_seconds.rb | 2 +- lib/rdf/n3/algebra/time/local_time.rb | 2 +- lib/rdf/n3/algebra/time/minute.rb | 2 +- lib/rdf/n3/algebra/time/month.rb | 2 +- lib/rdf/n3/algebra/time/second.rb | 2 +- lib/rdf/n3/algebra/time/timezone.rb | 2 +- lib/rdf/n3/algebra/time/year.rb | 2 +- 45 files changed, 56 insertions(+), 56 deletions(-) diff --git a/lib/rdf/n3/algebra/list/append.rb b/lib/rdf/n3/algebra/list/append.rb index 8856462..9ffe739 100644 --- a/lib/rdf/n3/algebra/list/append.rb +++ b/lib/rdf/n3/algebra/list/append.rb @@ -10,13 +10,13 @@ class Append < RDF::N3::Algebra::ListOperator NAME = :listAppend ## - # Evaluates this operator using the given variable `bindings`. + # Resolves this operator using the given variable `bindings`. # If the last operand is a variable, it creates a solution for each element in the list. # # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) flattened = list.to_a.map(&:to_a).flatten # Bind a new list based on the values, whos subject use made up from original list subjects subj = RDF::Node.intern(list.map(&:subject).hash) diff --git a/lib/rdf/n3/algebra/list/first.rb b/lib/rdf/n3/algebra/list/first.rb index 8251f1e..7716290 100644 --- a/lib/rdf/n3/algebra/list/first.rb +++ b/lib/rdf/n3/algebra/list/first.rb @@ -10,13 +10,13 @@ class First < RDF::N3::Algebra::ListOperator NAME = :listFirst ## - # Evaluates this operator using the given variable `bindings`. + # Resolves this operator using the given variable `bindings`. # If the last operand is a variable, it creates a solution for each element in the list. # # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) list.first end end diff --git a/lib/rdf/n3/algebra/list/last.rb b/lib/rdf/n3/algebra/list/last.rb index 6ea0ae1..34a0adb 100644 --- a/lib/rdf/n3/algebra/list/last.rb +++ b/lib/rdf/n3/algebra/list/last.rb @@ -10,13 +10,13 @@ class Last < RDF::N3::Algebra::ListOperator NAME = :listLast ## - # Evaluates this operator using the given variable `bindings`. + # Resolves this operator using the given variable `bindings`. # If the last operand is a variable, it creates a solution for each element in the list. # # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) list.last end end diff --git a/lib/rdf/n3/algebra/list/length.rb b/lib/rdf/n3/algebra/list/length.rb index 3c9ce23..566936e 100644 --- a/lib/rdf/n3/algebra/list/length.rb +++ b/lib/rdf/n3/algebra/list/length.rb @@ -10,13 +10,13 @@ class Length < RDF::N3::Algebra::ListOperator NAME = :listLength ## - # Evaluates this operator using the given variable `bindings`. + # Resolves this operator using the given variable `bindings`. # If the last operand is a variable, it creates a solution for each element in the list. # # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) RDF::Literal(list.length) end end diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index 4a6316a..08673f5 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -27,7 +27,7 @@ def execute(queryable, solutions:, **options) log_debug(self.class.const_get(:NAME)) {"list: #{SXP::Generator.string(list.to_sxp_bin).gsub(/\s+/m, ' ')}, object: #{SXP::Generator.string(object.to_sxp_bin).gsub(/\s+/m, ' ')}"} next unless validate(list) - lhs = evaluate(list) + lhs = resolve(list) if object.variable? solution.merge(object.to_sym => lhs) @@ -48,11 +48,11 @@ def input_operand end ## - # Subclasses implement `evaluate`. + # Subclasses implement `resolve`. # # @param [RDF::N3::List] list # @return [RDF::Term] - def evaluate(list) + def resolve(list) raise NotImplemented end diff --git a/lib/rdf/n3/algebra/log/conclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb index a4ad73d..533a78c 100644 --- a/lib/rdf/n3/algebra/log/conclusion.rb +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -14,13 +14,13 @@ class Conclusion < RDF::N3::Algebra::ResourceOperator # @param [RDF::N3::Algebra:Formula] resource # @return [RDF::N3::Algebra::Formula] # @see RDF::N3::ListOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) return resource unless position == :subject log_depth do reasoner = RDF::N3::Reasoner.new(resource, @options) conclusions = [].extend(RDF::Enumerable) - reasoner.execute(think: true) {|stmt| conclusions << stmt} + reasoner.execute(think: true, logger: false) {|stmt| conclusions << stmt} # The result is a formula containing the conclusions form = RDF::N3::Algebra::Formula.from_enumerable(conclusions).dup diff --git a/lib/rdf/n3/algebra/log/conjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb index 63863e8..2821447 100644 --- a/lib/rdf/n3/algebra/log/conjunction.rb +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -14,9 +14,9 @@ class Conjunction < RDF::N3::Algebra::ListOperator # @param [RDF::N3::List] list # @return [RDF::N3::Algebra::Formula] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) form = RDF::N3::Algebra::Formula.new(graph_name: RDF::Node.intern(list.hash)) - log_info(NAME) {"list hash: #{form.graph_name}"} + log_debug(NAME) {"list hash: #{form.graph_name}"} list.each do |f| form.operands.append(*f.operands) diff --git a/lib/rdf/n3/algebra/log/content.rb b/lib/rdf/n3/algebra/log/content.rb index 1928c19..b1ed2f0 100644 --- a/lib/rdf/n3/algebra/log/content.rb +++ b/lib/rdf/n3/algebra/log/content.rb @@ -15,7 +15,7 @@ class Content < RDF::N3::Algebra::ResourceOperator # # @param [RDF::N3::List] resource # @return [RDF::Term] - def evaluate(resource, position: :subject) + def resolve(resource, position: :subject) case position when :subject return nil unless resource.literal? || resource.uri? diff --git a/lib/rdf/n3/algebra/log/n3_string.rb b/lib/rdf/n3/algebra/log/n3_string.rb index 83be33d..bf6973b 100644 --- a/lib/rdf/n3/algebra/log/n3_string.rb +++ b/lib/rdf/n3/algebra/log/n3_string.rb @@ -9,7 +9,7 @@ class N3String < RDF::N3::Algebra::ResourceOperator # # @param [RDF::N3::List] resource # @return [RDF::Term] - def evaluate(resource, position: :subject) + def resolve(resource, position: :subject) case position when :subject return nil unless resource.formula? diff --git a/lib/rdf/n3/algebra/log/parsed_as_n3.rb b/lib/rdf/n3/algebra/log/parsed_as_n3.rb index 119c97e..97414a6 100644 --- a/lib/rdf/n3/algebra/log/parsed_as_n3.rb +++ b/lib/rdf/n3/algebra/log/parsed_as_n3.rb @@ -11,7 +11,7 @@ class ParsedAsN3 < RDF::N3::Algebra::ResourceOperator # # @param [RDF::N3::List] resource # @return [RDF::Term] - def evaluate(resource, position: :subject) + def resolve(resource, position: :subject) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/log/semantics.rb b/lib/rdf/n3/algebra/log/semantics.rb index be81550..43452ac 100644 --- a/lib/rdf/n3/algebra/log/semantics.rb +++ b/lib/rdf/n3/algebra/log/semantics.rb @@ -15,7 +15,7 @@ class Semantics < RDF::N3::Algebra::ResourceOperator # # @param [RDF::N3::List] resource # @return [RDF::Term] - def evaluate(resource, position: :subject) + def resolve(resource, position: :subject) case position when :subject return nil unless resource.literal? || resource.uri? diff --git a/lib/rdf/n3/algebra/math/absolute_value.rb b/lib/rdf/n3/algebra/math/absolute_value.rb index c1c04f9..e3f4b13 100644 --- a/lib/rdf/n3/algebra/math/absolute_value.rb +++ b/lib/rdf/n3/algebra/math/absolute_value.rb @@ -13,7 +13,7 @@ class AbsoluteValue < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/math/ceiling.rb b/lib/rdf/n3/algebra/math/ceiling.rb index e0b7a0e..fbc63e1 100644 --- a/lib/rdf/n3/algebra/math/ceiling.rb +++ b/lib/rdf/n3/algebra/math/ceiling.rb @@ -13,7 +13,7 @@ class Ceiling < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/math/cos.rb b/lib/rdf/n3/algebra/math/cos.rb index fd83851..5a926c2 100644 --- a/lib/rdf/n3/algebra/math/cos.rb +++ b/lib/rdf/n3/algebra/math/cos.rb @@ -13,7 +13,7 @@ class Cos < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case resource when RDF::Query::Variable then resource when RDF::Literal diff --git a/lib/rdf/n3/algebra/math/cosh.rb b/lib/rdf/n3/algebra/math/cosh.rb index 1569178..fb992ce 100644 --- a/lib/rdf/n3/algebra/math/cosh.rb +++ b/lib/rdf/n3/algebra/math/cosh.rb @@ -11,7 +11,7 @@ class CosH < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case resource when RDF::Query::Variable then resource when RDF::Literal diff --git a/lib/rdf/n3/algebra/math/difference.rb b/lib/rdf/n3/algebra/math/difference.rb index e0c7678..bc6dec2 100644 --- a/lib/rdf/n3/algebra/math/difference.rb +++ b/lib/rdf/n3/algebra/math/difference.rb @@ -17,7 +17,7 @@ class Difference < RDF::N3::Algebra::ListOperator # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) list.to_a.map(&:as_number).reduce(&:-) end diff --git a/lib/rdf/n3/algebra/math/exponentiation.rb b/lib/rdf/n3/algebra/math/exponentiation.rb index fa8766a..525ed5d 100644 --- a/lib/rdf/n3/algebra/math/exponentiation.rb +++ b/lib/rdf/n3/algebra/math/exponentiation.rb @@ -12,7 +12,7 @@ class Exponentiation < RDF::N3::Algebra::ListOperator # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) list.to_a.map(&:as_number).reduce(&:**) end diff --git a/lib/rdf/n3/algebra/math/floor.rb b/lib/rdf/n3/algebra/math/floor.rb index 1b1e181..47886e1 100644 --- a/lib/rdf/n3/algebra/math/floor.rb +++ b/lib/rdf/n3/algebra/math/floor.rb @@ -13,7 +13,7 @@ class Floor < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/math/integer_quotient.rb b/lib/rdf/n3/algebra/math/integer_quotient.rb index 8b3c6e9..7792046 100644 --- a/lib/rdf/n3/algebra/math/integer_quotient.rb +++ b/lib/rdf/n3/algebra/math/integer_quotient.rb @@ -13,7 +13,7 @@ class IntegerQuotient < RDF::N3::Algebra::Math::Quotient # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) RDF::Literal::Integer.new(super) end end diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb index 4a52038..7e07945 100644 --- a/lib/rdf/n3/algebra/math/negation.rb +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -15,7 +15,7 @@ class Negation < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case resource when RDF::Query::Variable resource diff --git a/lib/rdf/n3/algebra/math/product.rb b/lib/rdf/n3/algebra/math/product.rb index 84c5714..29b1a90 100644 --- a/lib/rdf/n3/algebra/math/product.rb +++ b/lib/rdf/n3/algebra/math/product.rb @@ -12,7 +12,7 @@ class Product < RDF::N3::Algebra::ListOperator # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) list.to_a.map(&:as_number).reduce(&:*) || RDF::Literal(1) # Empty list product is 1 end end diff --git a/lib/rdf/n3/algebra/math/quotient.rb b/lib/rdf/n3/algebra/math/quotient.rb index ac606d1..acef83b 100644 --- a/lib/rdf/n3/algebra/math/quotient.rb +++ b/lib/rdf/n3/algebra/math/quotient.rb @@ -13,7 +13,7 @@ class Quotient < RDF::N3::Algebra::ListOperator # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) list.to_a.map(&:as_number).reduce(&:/) end diff --git a/lib/rdf/n3/algebra/math/remainder.rb b/lib/rdf/n3/algebra/math/remainder.rb index 3ac6334..d8ff4f9 100644 --- a/lib/rdf/n3/algebra/math/remainder.rb +++ b/lib/rdf/n3/algebra/math/remainder.rb @@ -12,7 +12,7 @@ class Remainder < RDF::N3::Algebra::ListOperator # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) list.to_a.map(&:as_number).reduce(&:%) end diff --git a/lib/rdf/n3/algebra/math/rounded.rb b/lib/rdf/n3/algebra/math/rounded.rb index 3e3f065..8c5063e 100644 --- a/lib/rdf/n3/algebra/math/rounded.rb +++ b/lib/rdf/n3/algebra/math/rounded.rb @@ -11,7 +11,7 @@ class Rounded < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/math/sin.rb b/lib/rdf/n3/algebra/math/sin.rb index c0ef617..01ece9d 100644 --- a/lib/rdf/n3/algebra/math/sin.rb +++ b/lib/rdf/n3/algebra/math/sin.rb @@ -13,7 +13,7 @@ class Sin < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case resource when RDF::Query::Variable then resource when RDF::Literal diff --git a/lib/rdf/n3/algebra/math/sinh.rb b/lib/rdf/n3/algebra/math/sinh.rb index 117de9d..789707c 100644 --- a/lib/rdf/n3/algebra/math/sinh.rb +++ b/lib/rdf/n3/algebra/math/sinh.rb @@ -11,7 +11,7 @@ class SinH < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case resource when RDF::Query::Variable then resource when RDF::Literal diff --git a/lib/rdf/n3/algebra/math/sum.rb b/lib/rdf/n3/algebra/math/sum.rb index 65a097a..5d80018 100644 --- a/lib/rdf/n3/algebra/math/sum.rb +++ b/lib/rdf/n3/algebra/math/sum.rb @@ -32,7 +32,7 @@ class Sum < RDF::N3::Algebra::ListOperator # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) list.to_a.map(&:as_number).reduce(&:+) || RDF::Literal(0) # Empty list sums to 0 end end diff --git a/lib/rdf/n3/algebra/math/tan.rb b/lib/rdf/n3/algebra/math/tan.rb index 5d94f0a..dd68ba8 100644 --- a/lib/rdf/n3/algebra/math/tan.rb +++ b/lib/rdf/n3/algebra/math/tan.rb @@ -13,7 +13,7 @@ class Tan < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case resource when RDF::Query::Variable then resource when RDF::Literal diff --git a/lib/rdf/n3/algebra/math/tanh.rb b/lib/rdf/n3/algebra/math/tanh.rb index 4200f16..22e23d2 100644 --- a/lib/rdf/n3/algebra/math/tanh.rb +++ b/lib/rdf/n3/algebra/math/tanh.rb @@ -11,7 +11,7 @@ class TanH < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case resource when RDF::Query::Variable then resource when RDF::Literal diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index d69ce7e..2d4bd2e 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -24,13 +24,13 @@ def execute(queryable, solutions:, **options) log_debug(self.class.const_get(:NAME)) {"subject: #{subject.to_sxp}, object: #{object.to_sxp}"} next unless valid?(subject, object) - lhs = evaluate(subject, position: :subject) + lhs = resolve(subject, position: :subject) if lhs.nil? log_error(self.class.const_get(:NAME)) {"subject is invalid: #{subject.inspect}"} next end - rhs = evaluate(object, position: :object) + rhs = resolve(object, position: :object) if rhs.nil? log_error(self.class.const_get(:NAME)) {"object is invalid: #{object.inspect}"} next @@ -57,13 +57,13 @@ def input_operand end ## - # Subclasses implement `evaluate`. + # Subclasses implement `resolve`. # # Returns nil if resource does not validate, given its position # # @param [RDF::Term] resource # @return [RDF::Term] - def evaluate(resource, position: :subject) + def resolve(resource, position: :subject) raise NotImplemented end diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb index dd35710..d90cfe2 100644 --- a/lib/rdf/n3/algebra/str/concatenation.rb +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -13,7 +13,7 @@ class Concatenation < RDF::N3::Algebra::ListOperator # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) RDF::Literal(list.to_a.map(&:canonicalize).join("")) end end diff --git a/lib/rdf/n3/algebra/str/format.rb b/lib/rdf/n3/algebra/str/format.rb index a0e83bc..0697338 100644 --- a/lib/rdf/n3/algebra/str/format.rb +++ b/lib/rdf/n3/algebra/str/format.rb @@ -8,7 +8,7 @@ class Format < RDF::N3::Algebra::ListOperator # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) format, *args = list.to_a.map(&:value) str = format % args end diff --git a/lib/rdf/n3/algebra/str/replace.rb b/lib/rdf/n3/algebra/str/replace.rb index 58e8144..9d187d8 100644 --- a/lib/rdf/n3/algebra/str/replace.rb +++ b/lib/rdf/n3/algebra/str/replace.rb @@ -11,7 +11,7 @@ class Replace < RDF::N3::Algebra::ListOperator # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) format, *args = list.to_a.map(&:value) input, old_str, new_str = list.to_a RDF::Literal(input.to_s.gsub(old_str.to_s, new_str.to_s)) diff --git a/lib/rdf/n3/algebra/str/scrape.rb b/lib/rdf/n3/algebra/str/scrape.rb index 692fe86..c57a609 100644 --- a/lib/rdf/n3/algebra/str/scrape.rb +++ b/lib/rdf/n3/algebra/str/scrape.rb @@ -11,7 +11,7 @@ class Scrape < RDF::N3::Algebra::ListOperator # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate - def evaluate(list) + def resolve(list) input, regex = list.to_a md = Regexp.new(regex.to_s).match(input.to_s) RDF::Literal(md[1]) if md diff --git a/lib/rdf/n3/algebra/time/day.rb b/lib/rdf/n3/algebra/time/day.rb index 2eae593..97e1496 100644 --- a/lib/rdf/n3/algebra/time/day.rb +++ b/lib/rdf/n3/algebra/time/day.rb @@ -13,7 +13,7 @@ class Day < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/time/day_of_week.rb b/lib/rdf/n3/algebra/time/day_of_week.rb index 987378e..bd48e0c 100644 --- a/lib/rdf/n3/algebra/time/day_of_week.rb +++ b/lib/rdf/n3/algebra/time/day_of_week.rb @@ -11,7 +11,7 @@ class DayOfWeek < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/time/gm_time.rb b/lib/rdf/n3/algebra/time/gm_time.rb index 050b437..b2cbfcd 100644 --- a/lib/rdf/n3/algebra/time/gm_time.rb +++ b/lib/rdf/n3/algebra/time/gm_time.rb @@ -13,7 +13,7 @@ class GmTime < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/time/hour.rb b/lib/rdf/n3/algebra/time/hour.rb index 4355b58..977d5e7 100644 --- a/lib/rdf/n3/algebra/time/hour.rb +++ b/lib/rdf/n3/algebra/time/hour.rb @@ -13,7 +13,7 @@ class Hour < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/time/in_seconds.rb b/lib/rdf/n3/algebra/time/in_seconds.rb index d205b29..9bdcf66 100644 --- a/lib/rdf/n3/algebra/time/in_seconds.rb +++ b/lib/rdf/n3/algebra/time/in_seconds.rb @@ -13,7 +13,7 @@ class InSeconds < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject case resource diff --git a/lib/rdf/n3/algebra/time/local_time.rb b/lib/rdf/n3/algebra/time/local_time.rb index c3714d1..9fc9ed6 100644 --- a/lib/rdf/n3/algebra/time/local_time.rb +++ b/lib/rdf/n3/algebra/time/local_time.rb @@ -13,7 +13,7 @@ class LocalTime < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/time/minute.rb b/lib/rdf/n3/algebra/time/minute.rb index 9d83e7f..a66f0fa 100644 --- a/lib/rdf/n3/algebra/time/minute.rb +++ b/lib/rdf/n3/algebra/time/minute.rb @@ -13,7 +13,7 @@ class Minute < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/time/month.rb b/lib/rdf/n3/algebra/time/month.rb index 10a8312..aa8620d 100644 --- a/lib/rdf/n3/algebra/time/month.rb +++ b/lib/rdf/n3/algebra/time/month.rb @@ -13,7 +13,7 @@ class Month < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/time/second.rb b/lib/rdf/n3/algebra/time/second.rb index 1d254e8..ed8a3e8 100644 --- a/lib/rdf/n3/algebra/time/second.rb +++ b/lib/rdf/n3/algebra/time/second.rb @@ -13,7 +13,7 @@ class Second < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/time/timezone.rb b/lib/rdf/n3/algebra/time/timezone.rb index c218d26..16b57a6 100644 --- a/lib/rdf/n3/algebra/time/timezone.rb +++ b/lib/rdf/n3/algebra/time/timezone.rb @@ -13,7 +13,7 @@ class Timezone < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? diff --git a/lib/rdf/n3/algebra/time/year.rb b/lib/rdf/n3/algebra/time/year.rb index 44adc1a..a31cac1 100644 --- a/lib/rdf/n3/algebra/time/year.rb +++ b/lib/rdf/n3/algebra/time/year.rb @@ -13,7 +13,7 @@ class Year < RDF::N3::Algebra::ResourceOperator # @param [:subject, :object] position # @return [RDF::Term] # @see RDF::N3::ResourceOperator#evaluate - def evaluate(resource, position:) + def resolve(resource, position:) case position when :subject return nil unless resource.literal? From 4c0509e1428f990fb51e9d0442e06b30268b151a Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 18 Oct 2020 17:01:11 -0700 Subject: [PATCH 148/193] Add `#evaluate` to builtins and other RDF structures to bind variable solutions, resulting in a duplicated object with variables replaced with their bound values. --- lib/rdf/n3/algebra/builtin.rb | 19 +++++++++ lib/rdf/n3/algebra/formula.rb | 9 +++- lib/rdf/n3/extensions.rb | 80 +++++++++++++++++++++++++++++++++++ lib/rdf/n3/list.rb | 2 - 4 files changed, 107 insertions(+), 3 deletions(-) diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb index d82e736..cdd7ea1 100644 --- a/lib/rdf/n3/algebra/builtin.rb +++ b/lib/rdf/n3/algebra/builtin.rb @@ -27,6 +27,25 @@ def input_operand RDF::N3::List.new(values: operands) end + ## + # Evaluates the builtin using the given variable `bindings` by cloning the builtin replacing variables with their bindings recursively. + # + # @param [Hash{Symbol => RDF::Term}] bindings + # a query solution containing zero or more variable bindings + # @param [Hash{Symbol => Object}] options ({}) + # options passed from query + # @return [RDF::N3::Algebra::Builtin] + # Returns a new builtin with bound values. + # @see SPARQL::Algebra::Expression.evaluate + def evaluate(bindings, formulae:, **options) + args = operands.map { |operand| operand.evaluate(bindings, formulae: formulae, **options) } + + # Replace operands with bound operands + this = dup + this.operands = args + this + end + ## # By default, operators do not yield statements def each(&block) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 1dff1ee..eb40692 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -208,7 +208,14 @@ def evaluate(bindings, formulae:, **options) this = dup # Maintain formula relationships formulae {|k, v| this.formulae[k] ||= v} - this.solutions = RDF::Query::Solutions(bindings) + + # Bind solutions to formula + this.solutions = RDF::Query::Solutions(bindings) + + # Replace operands with bound operands + this.operands = operands.map do |op| + op.evaluate(bindings, formulae: formulae, **options) + end this end diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index 4a9106d..5fa1a40 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -52,6 +52,19 @@ def to_sxp_bin def to_sxp to_sxp_bin.to_sxp end + + ## + # As a statement is constant, this returns itself. + # + # @param [Hash{Symbol => RDF::Term}] bindings + # a query solution containing zero or more variable bindings + # @param [Hash{Symbol => Object}] options ({}) + # options passed from query + # @return [RDF::Statement] + # @see SPARQL::Algebra::Expression.evaluate + def evaluate(bindings, formulae:, **options) + self + end end module Value @@ -94,6 +107,19 @@ def as_number def as_datetime RDF::Literal::DateTime.new(DateTime.now) end + + ## + # As a term is constant, this returns itself. + # + # @param [Hash{Symbol => RDF::Term}] bindings + # a query solution containing zero or more variable bindings + # @param [Hash{Symbol => Object}] options ({}) + # options passed from query + # @return [RDF::Term] + # @see SPARQL::Algebra::Expression.evaluate + def evaluate(bindings, formulae:, **options) + self + end end class Literal @@ -149,6 +175,19 @@ def to_ndvar(scope) label = "#{id}_#{scope ? scope.id : 'base'}_undext" RDF::Query::Variable.new(label, existential: true, distinguished: false) end + + ## + # Blank node may refer to a formula. + # + # @param [Hash{Symbol => RDF::Term}] bindings + # a query solution containing zero or more variable bindings + # @param [Hash{Symbol => Object}] options ({}) + # options passed from query + # @return [RDF::Term] + # @see SPARQL::Algebra::Expression.evaluate + def evaluate(bindings, formulae:, **options) + node? ? formulae.fetch(self, self) : self + end end class Query::Pattern @@ -191,6 +230,25 @@ def eql?(other) end true end + + ## + # Evaluates the pattern using the given variable `bindings` by cloning the pattern replacing variables with their bindings recursively. If the resulting pattern is constant, it is cast as a statement. + # + # @param [Hash{Symbol => RDF::Term}] bindings + # a query solution containing zero or more variable bindings + # @param [Hash{Symbol => Object}] options ({}) + # options passed from query + # @return [RDF::Statement] + # @see SPARQL::Algebra::Expression.evaluate + def evaluate(bindings, formulae:, **options) + elements = self.to_quad.map do |term| + term.evaluate(bindings, formulae: formulae, **options) + end.compact.map do |term| + term.node? ? formulae.fetch(term, term) : term + end + + self.class.from(elements) + end end class Query::Solution @@ -232,6 +290,20 @@ def as_number def to_sxp to_s end + + ## + # If variable is bound, replace with the bound value, otherwise, returns itself + # + # @param [Hash{Symbol => RDF::Term}] bindings + # a query solution containing zero or more variable bindings + # @param [Hash{Symbol => Object}] options ({}) + # options passed from query + # @return [RDF::Term] + # @see SPARQL::Algebra::Expression.evaluate + def evaluate(bindings, formulae:, **options) + value = bindings.has_key?(name) ? bindings[name] : self + value.node? ? formulae.fetch(value, value) : value + end end class SPARQL::Algebra::Operator @@ -242,5 +314,13 @@ class SPARQL::Algebra::Operator def formulae @options.fetch(:formulae, {}) end + + # Updates the operands for this operator. + # + # @param [Array] ary + # @return [Array] + def operands=(ary) + @operands = ary + end end end diff --git a/lib/rdf/n3/list.rb b/lib/rdf/n3/list.rb index ae6babb..244d673 100644 --- a/lib/rdf/n3/list.rb +++ b/lib/rdf/n3/list.rb @@ -564,8 +564,6 @@ def evaluate(bindings, formulae: {}, **options) subj = "#{subject.id}_#{bindings.values.sort.hash}" values = to_a.map do |o| o = o.evaluate(bindings, formulae: formulae, **options) || o - # Map graph names to graphs - o.node? ? formulae.fetch(o, o).dup : o end RDF::N3::List.new(subject: RDF::Node.intern(subj), values: values) end From cbfe13afc49b096e995816c3d04bb74b375a2c83 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 18 Oct 2020 17:51:51 -0700 Subject: [PATCH 149/193] Change Evaluatable builtins to Executable. --- lib/rdf/n3/algebra/formula.rb | 28 +++---------- lib/rdf/n3/algebra/log/equal_to.rb | 29 +++++++++++++- lib/rdf/n3/algebra/math/equal_to.rb | 26 +++++++++---- lib/rdf/n3/algebra/math/greater_than.rb | 24 +++++++++--- lib/rdf/n3/algebra/math/less_than.rb | 24 +++++++++--- lib/rdf/n3/algebra/math/not_equal_to.rb | 2 +- lib/rdf/n3/algebra/math/not_greater_than.rb | 2 +- lib/rdf/n3/algebra/math/not_less_than.rb | 2 +- lib/rdf/n3/algebra/resource_operator.rb | 3 ++ lib/rdf/n3/algebra/str/contains.rb | 33 +++++++++------- .../n3/algebra/str/contains_ignoring_case.rb | 33 +++++++++------- lib/rdf/n3/algebra/str/ends_with.rb | 29 ++++++++------ lib/rdf/n3/algebra/str/equal_ignoring_case.rb | 28 ++++++++----- lib/rdf/n3/algebra/str/greater_than.rb | 21 ++++++++-- lib/rdf/n3/algebra/str/less_than.rb | 28 ++++++++----- lib/rdf/n3/algebra/str/matches.rb | 39 +++++++++++-------- lib/rdf/n3/algebra/str/starts_with.rb | 29 ++++++++------ 17 files changed, 246 insertions(+), 134 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index eb40692..f46b635 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -99,7 +99,7 @@ def self.from_enumerable(enumerable, **options) def dup new_ops = operands.map(&:dup) graph_name = RDF::Node.intern(new_ops.hash) - that = self.class.new(*new_ops, graph_name: graph_name, formulae: formulae) + that = self.class.new(*new_ops, **@options.merge(graph_name: graph_name, formulae: formulae)) that.formulae[graph_name] = that that end @@ -117,8 +117,8 @@ def dup # any additional keyword options # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options) - log_info("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin} - log_info("(formula bindings)") { solutions.bindings.map {|k,v| RDF::Query::Variable.new(k,v)}.to_sxp} + log_debug("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin} + log_debug("(formula bindings)") { solutions.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! @@ -137,7 +137,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new memo.merge(name => value) end) end - log_info("(formula query solutions)") { SXP::Generator.string(these_solutions.to_sxp_bin)} + log_debug("(formula query solutions)") { SXP::Generator.string(these_solutions.to_sxp_bin)} solutions.merge(these_solutions) end @@ -157,12 +157,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new last_op = nil ops.each do |op| log_debug("(formula built-in)") {SXP::Generator.string op.to_sxp_bin} - solutions = if op.executable? - op.execute(queryable, solutions: @solutions) - else # Evaluatable - @solutions.filter {|s| op.evaluate(s.bindings) == RDF::Literal::TRUE} - end - log_debug("(formula intermediate solutions)") {"after #{op.class.const_get(:NAME)}: " + SXP::Generator.string(solutions.to_sxp_bin)} + solutions = op.execute(queryable, solutions: @solutions) # If there are no solutions, try the next one, until we either run out of operations, or we have solutions next if solutions.empty? last_op = op @@ -181,22 +176,11 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new end end log_info("(formula sub-op solutions)") {SXP::Generator.string @solutions.to_sxp_bin} - - # Only return solutions with universal variables and distinguished existential variables. - # FIXME: also filter in-coming existential variables. - #unless undistinguished_vars.empty? - # @solutions = if distinguished_vars.empty? - # # No remaining variables, this only an empty solution - # RDF::Query::Solutions(RDF::Query::Solution.new) - # else - # @solutions.dup.project(*distinguished_vars) - # end - #end @solutions end ## - # Evaluates the formula using the given variable `bindings` by cloning the formula and setting the solutions to `bindings` so that `#each` will generate statements from those bindings. + # Evaluates the formula using the given variable `bindings` by cloning the formula replacing variables with their bindings recursively. # # @param [Hash{Symbol => RDF::Term}] bindings # a query solution containing zero or more variable bindings diff --git a/lib/rdf/n3/algebra/log/equal_to.rb b/lib/rdf/n3/algebra/log/equal_to.rb index d07e9d1..9abfaf7 100644 --- a/lib/rdf/n3/algebra/log/equal_to.rb +++ b/lib/rdf/n3/algebra/log/equal_to.rb @@ -1,8 +1,33 @@ 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 EqualTo < SPARQL::Algebra::Operator::SameTerm - include RDF::N3::Algebra::Builtin + class EqualTo < RDF::N3::Algebra::ResourceOperator NAME = :logEqualTo + + ## + # Resolves inputs as terms. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Literal] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + resource if resource.term? + end + + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end + + ## + # @param [RDF::Literal] left + # a literal + # @param [RDF::Literal] right + # a literal + # @return [RDF::Literal::Boolean] + def apply(left, right) + left.sameTerm?(right) + end end end diff --git a/lib/rdf/n3/algebra/math/equal_to.rb b/lib/rdf/n3/algebra/math/equal_to.rb index de945fd..4cf28d1 100644 --- a/lib/rdf/n3/algebra/math/equal_to.rb +++ b/lib/rdf/n3/algebra/math/equal_to.rb @@ -16,13 +16,27 @@ module RDF::N3::Algebra::Math # * `$a2`: `xs:decimal` (or its derived types), `xs:float`, or `xs:double` (see note on type promotion, and casting from string) # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-equal - class EqualTo < SPARQL::Algebra::Operator::Compare - include RDF::N3::Algebra::Builtin + class EqualTo < RDF::N3::Algebra::ResourceOperator + NAME = :mathEqualTo - NAME = :'=' + ## + # Resolves inputs as numbers. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + resource.as_number if resource.term? + end + + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end ## - # The math:equalTo operator takes a pair of strings or numbers and determines if they are the same numeric value. + # Returns TRUE if `term1` and `term2` are the same numeric value. # # @param [RDF::Term] term1 # an RDF term @@ -33,9 +47,7 @@ class EqualTo < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) - return RDF::Literal::FALSE unless term1.is_a?(RDF::Term) && term2.is_a?(RDF::Term) - log_debug(NAME) { "term1: #{term1.to_sxp} == term2: #{term2.to_sxp} ? #{(term1.as_number == term2.as_number).inspect}"} - RDF::Literal(term1.as_number == term2.as_number) + RDF::Literal(term1 == term2) end end end diff --git a/lib/rdf/n3/algebra/math/greater_than.rb b/lib/rdf/n3/algebra/math/greater_than.rb index d2e267f..94208b8 100644 --- a/lib/rdf/n3/algebra/math/greater_than.rb +++ b/lib/rdf/n3/algebra/math/greater_than.rb @@ -3,10 +3,24 @@ 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. # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-greater-than - class GreaterThan < SPARQL::Algebra::Operator::Compare - include RDF::N3::Algebra::Builtin + class GreaterThan < RDF::N3::Algebra::ResourceOperator + NAME = :mathGreaterThan - NAME = :'>' + ## + # Resolves inputs as numbers. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + resource.as_number if resource.term? + end + + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end ## # Returns TRUE if `term1` is greater than `term2`. @@ -20,9 +34,7 @@ class GreaterThan < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) - return RDF::Literal::FALSE unless term1.is_a?(RDF::Term) && term2.is_a?(RDF::Term) - log_debug(NAME) { "term1: #{term1.to_sxp} > term2: #{term2.to_sxp} ? #{(term1.as_number > term2.as_number).inspect}"} - RDF::Literal(term1.as_number > term2.as_number) + RDF::Literal(term1 > term2) end end end diff --git a/lib/rdf/n3/algebra/math/less_than.rb b/lib/rdf/n3/algebra/math/less_than.rb index 9b9bf09..2d37b37 100644 --- a/lib/rdf/n3/algebra/math/less_than.rb +++ b/lib/rdf/n3/algebra/math/less_than.rb @@ -3,10 +3,24 @@ module RDF::N3::Algebra::Math # True iff the subject is a string representation of a number which is less than the number of which the object is a string representation. # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-less-than - class LessThan < SPARQL::Algebra::Operator::Compare - include RDF::N3::Algebra::Builtin + class LessThan < RDF::N3::Algebra::ResourceOperator + NAME = :mathLessThan - NAME = :'<' + ## + # Resolves inputs as numbers. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + resource.as_number if resource.term? + end + + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end ## # Returns TRUE if `term1` is less than `term2`. @@ -20,9 +34,7 @@ class LessThan < SPARQL::Algebra::Operator::Compare # # @see RDF::Term#== def apply(term1, term2) - return RDF::Literal::FALSE unless term1.is_a?(RDF::Term) && term2.is_a?(RDF::Term) - log_debug(NAME) { "term1: #{term1.to_sxp} < term2: #{term2.to_sxp} ? #{(term1.as_number < term2.as_number).inspect}"} - RDF::Literal(term1.as_number < term2.as_number) + RDF::Literal(term1 < term2) end end end diff --git a/lib/rdf/n3/algebra/math/not_equal_to.rb b/lib/rdf/n3/algebra/math/not_equal_to.rb index ba6c150..5b2f87b 100644 --- a/lib/rdf/n3/algebra/math/not_equal_to.rb +++ b/lib/rdf/n3/algebra/math/not_equal_to.rb @@ -4,7 +4,7 @@ module RDF::N3::Algebra::Math # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-equal class NotEqualTo < EqualTo - NAME = :'!=' + NAME = :mathNotEqualTo ## # The math:notEqualTo operator takes a pair of strings or numbers and determines if they are not the same numeric value. diff --git a/lib/rdf/n3/algebra/math/not_greater_than.rb b/lib/rdf/n3/algebra/math/not_greater_than.rb index 80a826f..c1fed10 100644 --- a/lib/rdf/n3/algebra/math/not_greater_than.rb +++ b/lib/rdf/n3/algebra/math/not_greater_than.rb @@ -4,7 +4,7 @@ module RDF::N3::Algebra::Math # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-greater-than class NotGreaterThan < GreaterThan - NAME = :'<=' + NAME = :mathNotGreaterThan ## # Returns TRUE if `term1` is less than or equal to `term2`. diff --git a/lib/rdf/n3/algebra/math/not_less_than.rb b/lib/rdf/n3/algebra/math/not_less_than.rb index bda837a..a2e4e15 100644 --- a/lib/rdf/n3/algebra/math/not_less_than.rb +++ b/lib/rdf/n3/algebra/math/not_less_than.rb @@ -4,7 +4,7 @@ module RDF::N3::Algebra::Math # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-less-than class NotLessThan < LessThan - NAME = :'>=' + NAME = :mathNotLessThan ## # Returns TRUE if `term1` is greater than or equal to `term2`. diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 2d4bd2e..4804f31 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -40,6 +40,9 @@ def execute(queryable, solutions:, **options) solution.merge(object.to_sym => lhs) elsif subject.variable? solution.merge(subject.to_sym => rhs) + elsif respond_to?(:apply) + # Return the result applying subject and object + solution if apply(lhs, rhs) == RDF::Literal::TRUE elsif rhs != lhs nil else diff --git a/lib/rdf/n3/algebra/str/contains.rb b/lib/rdf/n3/algebra/str/contains.rb index 9377223..a0f21cc 100644 --- a/lib/rdf/n3/algebra/str/contains.rb +++ b/lib/rdf/n3/algebra/str/contains.rb @@ -1,25 +1,32 @@ module RDF::N3::Algebra::Str # True iff the subject string contains the object string. - class Contains < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Evaluatable - include RDF::N3::Algebra::Builtin - + class Contains < RDF::N3::Algebra::ResourceOperator NAME = :strContains ## - # @param [RDF::Literal] left + # Resolves inputs as strings. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Literal] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + resource if resource.term? + end + + # Neither subect nor object are considered inputs, and must be resolved before evaluation. + def input_operand + RDF::N3::List.new + end + + ## + # @param [String] left # a literal - # @param [RDF::Literal] right + # @param [String] right # a literal # @return [RDF::Literal::Boolean] def apply(left, right) - case - when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) - log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} - RDF::Literal::FALSE - when left.to_s.include?(right.to_s) then RDF::Literal::TRUE - else RDF::Literal::FALSE - end + RDF::Literal(left.to_s.include?(right.to_s)) end end end diff --git a/lib/rdf/n3/algebra/str/contains_ignoring_case.rb b/lib/rdf/n3/algebra/str/contains_ignoring_case.rb index 3378a08..c958290 100644 --- a/lib/rdf/n3/algebra/str/contains_ignoring_case.rb +++ b/lib/rdf/n3/algebra/str/contains_ignoring_case.rb @@ -1,25 +1,32 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::N3::Algebra::Builtin - + class ContainsIgnoringCase < RDF::N3::Algebra::ResourceOperator NAME = :strContainsIgnoringCase ## - # @param [RDF::Literal] left + # Resolves inputs as lower-case strings. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Literal] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + RDF::Literal(resource.to_s.downcase) if resource.term? + end + + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end + + ## + # @param [String] left # a literal - # @param [RDF::Literal] right + # @param [String] right # a literal # @return [RDF::Literal::Boolean] def apply(left, right) - case - when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) - log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} - RDF::Literal::FALSE - when left.to_s.downcase.include?(right.to_s.downcase) then RDF::Literal::TRUE - else RDF::Literal::FALSE - end + RDF::Literal(left.to_s.include?(right.to_s)) end end end diff --git a/lib/rdf/n3/algebra/str/ends_with.rb b/lib/rdf/n3/algebra/str/ends_with.rb index a21a4ca..b14b3af 100644 --- a/lib/rdf/n3/algebra/str/ends_with.rb +++ b/lib/rdf/n3/algebra/str/ends_with.rb @@ -1,11 +1,24 @@ module RDF::N3::Algebra::Str # True iff the subject string ends with the object string. - class EndsWith < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Evaluatable - include RDF::N3::Algebra::Builtin - + class EndsWith < RDF::N3::Algebra::ResourceOperator NAME = :strEndsWith + ## + # Resolves inputs as strings. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Literal] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + resource if resource.term? + end + + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end + ## # @param [RDF::Literal] left # a literal @@ -13,13 +26,7 @@ class EndsWith < SPARQL::Algebra::Operator::Binary # a literal # @return [RDF::Literal::Boolean] def apply(left, right) - case - when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) - log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} - RDF::Literal::FALSE - when left.to_s.end_with?(right.to_s) then RDF::Literal::TRUE - else RDF::Literal::FALSE - end + RDF::Literal(left.to_s.end_with?(right.to_s)) end end end diff --git a/lib/rdf/n3/algebra/str/equal_ignoring_case.rb b/lib/rdf/n3/algebra/str/equal_ignoring_case.rb index 55cc6cb..654ef2f 100644 --- a/lib/rdf/n3/algebra/str/equal_ignoring_case.rb +++ b/lib/rdf/n3/algebra/str/equal_ignoring_case.rb @@ -1,10 +1,23 @@ module RDF::N3::Algebra::Str - class EqualIgnoringCase < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Evaluatable - include RDF::N3::Algebra::Builtin - + class EqualIgnoringCase < RDF::N3::Algebra::ResourceOperator NAME = :strEqualIgnoringCase + ## + # Resolves inputs as lower-case strings. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Literal] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + RDF::Literal(resource.to_s.downcase) if resource.term? + end + + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end + ## # True iff the subject string is the same as object string ignoring differences between upper and lower case. # @@ -14,12 +27,7 @@ class EqualIgnoringCase < SPARQL::Algebra::Operator::Binary # a literal # @return [RDF::Literal::Boolean] def apply(left, right) - case - when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) - log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} - when left.to_s.downcase == right.to_s.downcase then RDF::Literal::TRUE - else RDF::Literal::FALSE - end + RDF::Literal(left.to_s == right.to_s) end end end diff --git a/lib/rdf/n3/algebra/str/greater_than.rb b/lib/rdf/n3/algebra/str/greater_than.rb index fa8bbaa..a61850c 100644 --- a/lib/rdf/n3/algebra/str/greater_than.rb +++ b/lib/rdf/n3/algebra/str/greater_than.rb @@ -1,11 +1,24 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::N3::Algebra::Builtin - + class GreaterThan < RDF::N3::Algebra::ResourceOperator NAME = :strGreaterThan + ## + # The string:greaterThan compares subject with object as strings. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + resource if resource.literal? + end + + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end + ## # @param [RDF::Literal] left # a literal diff --git a/lib/rdf/n3/algebra/str/less_than.rb b/lib/rdf/n3/algebra/str/less_than.rb index 5b74c6b..d428f5c 100644 --- a/lib/rdf/n3/algebra/str/less_than.rb +++ b/lib/rdf/n3/algebra/str/less_than.rb @@ -1,11 +1,24 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::N3::Algebra::Builtin - + class LessThan < RDF::N3::Algebra::ResourceOperator NAME = :strLessThan + ## + # Resolves inputs as strings. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Literal] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + resource if resource.term? + end + + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end + ## # @param [RDF::Literal] left # a literal @@ -13,12 +26,7 @@ class LessThan < SPARQL::Algebra::Operator::Binary # a literal # @return [RDF::Literal::Boolean] def apply(left, right) - case - when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) - log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} - when left < right then RDF::Literal::TRUE - else RDF::Literal::FALSE - end + RDF::Literal(left.to_s < right.to_s) end end end diff --git a/lib/rdf/n3/algebra/str/matches.rb b/lib/rdf/n3/algebra/str/matches.rb index 6024daa..f375cae 100644 --- a/lib/rdf/n3/algebra/str/matches.rb +++ b/lib/rdf/n3/algebra/str/matches.rb @@ -1,29 +1,36 @@ 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 SPARQL::Algebra::Evaluatable - include RDF::N3::Algebra::Builtin - + class Matches < RDF::N3::Algebra::ResourceOperator NAME = :strMatches ## - # @param [RDF::Literal] text + # Resolves inputs as strings. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Literal] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + resource if resource.literal? + end + + # Neither subect nor object are considered inputs, and must be resolved before evaluation. + def input_operand + RDF::N3::List.new + end + + ## + # Tre if right, treated as a regular expression, matches left + # + # @param [RDF::Literal] left # a simple literal - # @param [RDF::Literal] pattern + # @param [RDF::Literal] right # a simple literal # @return [RDF::Literal::Boolean] `true` or `false` # @see https://www.w3.org/TR/xpath-functions/#regex-syntax - def apply(text, pattern) - log_error(NAME) {"expected a plain RDF::Literal, but got #{text.inspect}"} unless text.is_a?(RDF::Literal) && text.plain? - text = text.to_s - # TODO: validate text syntax - - # @see https://www.w3.org/TR/xpath-functions/#regex-syntax - log_error(NAME) {"expected a plain RDF::Literal, but got #{pattern.inspect}"} unless pattern.is_a?(RDF::Literal) && pattern.plain? - pattern = pattern.to_s - - RDF::Literal(Regexp.new(pattern).match?(text)) + def apply(left, right) + RDF::Literal(Regexp.new(right.to_s).match?(left.to_s)) end end end diff --git a/lib/rdf/n3/algebra/str/starts_with.rb b/lib/rdf/n3/algebra/str/starts_with.rb index c36d35b..5d63eb1 100644 --- a/lib/rdf/n3/algebra/str/starts_with.rb +++ b/lib/rdf/n3/algebra/str/starts_with.rb @@ -1,11 +1,24 @@ 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::N3::Algebra::Builtin - + class StartsWith < RDF::N3::Algebra::ResourceOperator NAME = :strStartsWith + ## + # Resolves inputs as strings. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Literal] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + resource if resource.term? + end + + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end + ## # @param [RDF::Literal] left # a literal @@ -13,13 +26,7 @@ class StartsWith < SPARQL::Algebra::Operator::Binary # a literal # @return [RDF::Literal::Boolean] def apply(left, right) - case - when !left.is_a?(RDF::Term) || !right.is_a?(RDF::Term) || !left.compatible?(right) - log_error(NAME) {"expected two RDF::Literal operands, but got #{left.inspect} and #{right.inspect}"} - RDF::Literal::FALSE - when left.to_s.start_with?(right.to_s) then RDF::Literal::TRUE - else RDF::Literal::FALSE - end + RDF::Literal(left.to_s.start_with?(right.to_s)) end end end From 22b3f3e8e3545d7338b77474a19a6ccb06b83bb8 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 19 Oct 2020 16:54:38 -0700 Subject: [PATCH 150/193] Incremental work towards log:conclusions. --- lib/rdf/n3/algebra/builtin.rb | 14 +- lib/rdf/n3/algebra/formula.rb | 13 +- lib/rdf/n3/algebra/list_operator.rb | 3 + lib/rdf/n3/algebra/log/implies.rb | 18 ++- lib/rdf/n3/algebra/resource_operator.rb | 5 + lib/rdf/n3/reasoner.rb | 8 +- lib/rdf/n3/repository.rb | 3 + script/tc | 67 +++++---- spec/reasoner_spec.rb | 188 +++++++++++++++++++++--- spec/suite_reasoner_spec.rb | 9 +- 10 files changed, 252 insertions(+), 76 deletions(-) mode change 100755 => 100644 script/tc diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb index cdd7ea1..67ebe24 100644 --- a/lib/rdf/n3/algebra/builtin.rb +++ b/lib/rdf/n3/algebra/builtin.rb @@ -47,8 +47,20 @@ def evaluate(bindings, formulae:, **options) end ## - # By default, operators do not yield statements + # By default, operators yield themselves and the operands, recursively. def each(&block) + log_depth do + subject, object = operands.map {|op| op.formula? ? op.graph_name : op} + block.call(RDF::Statement(subject, self.to_uri, object)) + operands.each do |op| + next unless op.is_a?(Builtin) + op.each do |st| + # Maintain formula graph name for formula operands + st.graph_name ||= op.graph_name if op.formula? + block.call(st) + end + end + end end ## diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index f46b635..4d2c87d 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -122,7 +122,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new # Only query as patterns if this is an embedded formula @query ||= RDF::Query.new(patterns).optimize! - log_debug("(formula query)") { @query.patterns.to_sxp} + log_debug("(formula query)") { SXP::Generator.string(@query.patterns.to_sxp_bin)} solutions = if @query.patterns.empty? solutions @@ -241,9 +241,6 @@ def each(&block) log_debug("(formula apply)") {solution.to_sxp} # Yield each variable statement which is constant after applying solution patterns.each do |pattern| - # Skip builtins - next if RDF::N3::Algebra.for(pattern.predicate) - terms = {} [:subject, :predicate, :object].each do |part| terms[part] = case o = pattern.send(part) @@ -276,7 +273,6 @@ def each(&block) o.each do |stmt| stmt.graph_name = o.graph_name log_debug("(formula add from form)") {stmt.to_sxp} - #require 'byebug'; byebug if o.graph_name.to_s == '_:.form_0' block.call(stmt) end end @@ -332,16 +328,13 @@ def graph_name=(name) alias_method :statements, :operands ## - # Patterns memoizer - # - # * Patterns exclude builtins. - # * Blank nodes are replaced with existential variables. + # Patterns memoizer, from the operands which are statements. def patterns # BNodes in statements are existential variables. @patterns ||= begin # Operations/Builtins are not statements. operands. - select {|op| op.is_a?(RDF::Statement) && !RDF::N3::Algebra.for(op.predicate)}. + select {|op| op.is_a?(RDF::Statement)}. map {|st| RDF::Query::Pattern.from(st)} end end diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index 08673f5..d575bce 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -30,10 +30,13 @@ def execute(queryable, solutions:, **options) lhs = resolve(list) if object.variable? + log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(lhs.to_sxp_bin).gsub(/\s+/m, ' ')}"} solution.merge(object.to_sym => lhs) elsif object != lhs + log_debug(self.class.const_get(:NAME)) {"result: false"} nil else + log_debug(self.class.const_get(:NAME)) {"result: true"} solution end end.compact) diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index a12dac1..d6803f2 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -25,25 +25,25 @@ class Implies < SPARQL::Algebra::Operator::Binary def execute(queryable, solutions:, **options) @queryable = queryable @solutions = RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME) {"solution: #{SXP::Generator.string solution.to_sxp_bin}"} subject = operand(0).evaluate(solution.bindings, formulae: formulae) object = operand(1).evaluate(solution.bindings, formulae: formulae) - log_debug(NAME) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} - log_debug(NAME) {"object: #{SXP::Generator.string operand(1).to_sxp_bin}"} + log_info(NAME) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} + log_info(NAME) {"object: #{SXP::Generator.string object.to_sxp_bin}"} # Nothing to do if variables aren't resolved. next unless subject && object solns = log_depth {subject.execute(queryable, solutions: RDF::Query::Solutions(solution), **options)} - log_debug("(logImplies solutions pre-filter)") {SXP::Generator.string solns.to_sxp_bin} # filter solutions where not all variables in antecedant are bound. vars = subject.universal_vars solns = solns.filter do |soln| vars.all? {|v| soln.bound?(v)} end - log_info("(logImplies solutions)") {SXP::Generator.string solns.to_sxp_bin} solns end.flatten.compact) + log_info(NAME) {"solutions: #{SXP::Generator.string @solutions.to_sxp_bin}"} # Return original solutions, without bindings solutions @@ -58,16 +58,20 @@ def execute(queryable, solutions:, **options) # @yieldreturn [void] ignored def each(&block) @solutions ||= RDF::Query::Solutions.new - log_debug {"logImplies each #{SXP::Generator.string @solutions.to_sxp_bin}"} + #super { |st| + # log_debug {"logImplies super #{st.to_sxp}"} + # block.call(st) + #} log_depth do @solutions.each do |solution| + log_debug("(logImplies each) solution") {SXP::Generator.string @solutions.to_sxp_bin} object = operand(1).evaluate(solution.bindings, formulae: formulae) - next unless object # shouldn't happen + log_info(("(logImplies each) object")) {SXP::Generator.string object.to_sxp_bin} object.solutions = RDF::Query::Solutions(solution) - # Yield statements into the default graph + # Yield inferred statements object.each do |statement| block.call(RDF::Statement.from(statement.to_quad, inferred: true)) end diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 4804f31..868b1e2 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -37,15 +37,20 @@ def execute(queryable, solutions:, **options) end if object.variable? + log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(lhs.to_sxp_bin).gsub(/\s+/m, ' ')}"} solution.merge(object.to_sym => lhs) elsif subject.variable? + log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(rhs.to_sxp_bin).gsub(/\s+/m, ' ')}"} solution.merge(subject.to_sym => rhs) elsif respond_to?(:apply) + log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(apply(lhs, rhs).to_sxp_bin).gsub(/\s+/m, ' ')}"} # Return the result applying subject and object solution if apply(lhs, rhs) == RDF::Literal::TRUE elsif rhs != lhs + log_debug(self.class.const_get(:NAME)) {"result: false"} nil else + log_debug(self.class.const_get(:NAME)) {"result: true"} solution end end.compact) diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index d0c3ce9..d4aef59 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -120,13 +120,17 @@ def execute(**options, &block) if options[:think] count = -1 log_info("reasoner: think start") { "count: #{count}"} + solutions = RDF::Query::Solutions(RDF::Query::Solution.new) while knowledge_base.count > count log_info("reasoner: think do") { "count: #{count}"} count = knowledge_base.count - log_depth {formula.execute(knowledge_base, **options)} + log_depth {formula.execute(knowledge_base, solutions: solutions, **options)} knowledge_base << formula + solutions = RDF::Query::Solutions(RDF::Query::Solution.new) if solutions.empty? + log_debug("reasoner: solutions") {SXP::Generator.string solutions.to_sxp_bin} log_debug("reasoner: datastore") {SXP::Generator.string knowledge_base.statements.to_sxp_bin} - log_debug("reasoner: inferred") {SXP::Generator.string knowledge_base.statements.select(&:inferred?).to_sxp_bin} + log_info("reasoner: inferred") {SXP::Generator.string knowledge_base.statements.select(&:inferred?).to_sxp_bin} + @formula = nil # cause formula to be re-calculated from knowledge-base end log_info("reasoner: think end") { "count: #{count}"} else diff --git a/lib/rdf/n3/repository.rb b/lib/rdf/n3/repository.rb index 3f2953c..d204dfc 100644 --- a/lib/rdf/n3/repository.rb +++ b/lib/rdf/n3/repository.rb @@ -216,6 +216,7 @@ def query_pattern(pattern, **options, &block) [] end ss.each do |s, ps| + next if s.is_a?(RDF::Query::Variable) ps = if predicate.nil? || predicate.is_a?(RDF::Query::Variable) ps elsif predicate.is_a?(RDF::N3::List) @@ -229,7 +230,9 @@ def query_pattern(pattern, **options, &block) [] end ps.each do |p, os| + next if p.is_a?(RDF::Query::Variable) os.each do |o, object_options| + next if o.is_a?(RDF::Query::Variable) next unless object.nil? || object.eql?(o) yield RDF::Statement.new(s, p, o, object_options.merge(graph_name: c.equal?(DEFAULT_GRAPH) ? nil : c)) end diff --git a/script/tc b/script/tc old mode 100755 new mode 100644 index ded3e7b..7a09a1c --- a/script/tc +++ b/script/tc @@ -183,43 +183,50 @@ options = { type: :all } -opts = GetoptLong.new( - ["--level", GetoptLong::REQUIRED_ARGUMENT], - ["--earl", GetoptLong::NO_ARGUMENT], - ["--help", "-?", GetoptLong::NO_ARGUMENT], - ["--live", GetoptLong::NO_ARGUMENT], - ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT], - ["--quiet", "-q", GetoptLong::NO_ARGUMENT], - ["--skip-slow", "-s", GetoptLong::NO_ARGUMENT], - ["--type", GetoptLong::REQUIRED_ARGUMENT], - ["--validate", GetoptLong::NO_ARGUMENT], - ["--verbose", "-v", GetoptLong::NO_ARGUMENT], -) - -def help(**options) - puts "Usage: #{$0} [options] [test-number ...]" - puts "Options:" - puts " --earl: Generate EARL report" - puts " --level: Log level 0-5" - puts " --live: Show live parsing results, not buffered" - puts " --ntriples: Run N-Triples tests" - puts " --output: Output to specified file" - puts " --quiet: Minimal output" - puts " --skip-slow: Avoid files taking too much time" - puts " --type: Test type (`parser`, `reasoner`, or `all`)" - puts " --validate: Validate input" - puts " --verbose: Verbose processing" - puts " --help,-?: This message" - exit(0) +OPT_ARGS = [ + ["--earl", GetoptLong::NO_ARGUMENT, "Generate EARL report"], + ["--debug", GetoptLong::NO_ARGUMENT, "Debugging output"], + ["--help", "-?", GetoptLong::NO_ARGUMENT, "print this message"], + ["--info", GetoptLong::NO_ARGUMENT, "Show progress on execution"], + ["--live", GetoptLong::NO_ARGUMENT, "Show live parsing results, not buffered"], + ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT, "Output to specified file"], + ["--quiet", "-q", GetoptLong::NO_ARGUMENT, "Minimal output"], + ["--skip-slow", "-s", GetoptLong::NO_ARGUMENT, "Avoid files taking too much time"], + ["--type", GetoptLong::REQUIRED_ARGUMENT, "Test type (`parser`, `reasoner`, or `all`)"], + ["--validate", GetoptLong::NO_ARGUMENT, "Validate input"], + ["--verbose", "-v", GetoptLong::NO_ARGUMENT, "Verbose output"], +] + +def usage + STDERR.puts %{ + n3 version #{RDF::N3::VERSION} + Run N3 tests. + + Usage: #{$0} [options] [test-number ...] + }.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 '--help' then help(**options) - when '--level' then options[:level] = arg.to_i + when '--help' then usage() + when '--debug' then options[:level] = Logger::DEBUG when '--earl' options[:quiet] = options[:earl] = true options[:level] = Logger::FATAL + when '--info' then options[:level] = Logger::INFO when '--live' then options[:live] = true when '--output' then options[:output] = File.open(arg, "w") when '--quiet' diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index aff2f0d..6c543fa 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -29,8 +29,13 @@ } => { ?y a :TestResult }. ), expect: %( - { a } a :TestResult . - ) + { + . + a . + { .} => { a .} . + } a :TestResult . + ), + pending: true }, "conclusion-simple" => { input: %( @@ -42,12 +47,18 @@ { ?x a :TestRule; log:conclusion ?y } => { ?y a :TestResult }. ), expect: %( - { a } a :TestResult . - ) + { + . + a . + { .} => { a .} . + } a :TestResult . + ), + pending: true }, }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] expected = parse(options[:expect]) result = reason(options[:input], data: false, filter: true) expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) @@ -71,8 +82,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {data: false, filter: true}.merge(options) expected = parse(options[:expect]) - result = reason(options[:input], data: false, filter: true) + result = reason(options[:input], **options) expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end end @@ -149,6 +162,7 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] expected = parse(options[:expect]) result = reason(options[:input]) expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) @@ -160,7 +174,7 @@ { "t1" => { input: %( - {{ :a :b :c } log:includes { :a :b :c }} log:implies { :test1 a :success } . + {{ :a :b :c } log:includes { :a :b :c }} => { :test1 a :success } . ), expect: %( :test1 a :success . @@ -168,16 +182,53 @@ }, "t2" => { input: %( - { { <#theSky> <#is> <#blue> } log:includes {<#theSky> <#is> <#blue>} } log:implies { :test3 a :success } . - { { <#theSky> <#is> <#blue> } log:notIncludes {<#theSky> <#is> <#blue>} } log:implies { :test3_bis a :FAILURE } . + { { <#theSky> <#is> <#blue> } log:includes {<#theSky> <#is> <#blue>} } => { :test3 a :success } . + { { <#theSky> <#is> <#blue> } log:notIncludes {<#theSky> <#is> <#blue>} } => { :test3_bis a :FAILURE } . ), expect: %( :test3 a :success . ) }, + "quantifiers-limited-a1" => { + input: %( + {{ :foo :bar :baz } log:includes { :foo :bar :baz }} + => { :testa1 a :success } . + ), + expect: %( + :testa1 a :success . + ) + }, + #"quantifiers-limited-a2" => { + # input: %( + # {{ :foo :bar :baz } log:includes { @forSome :foo. :foo :bar :baz }} + # => { :testa2 a :success } . + # ), + # expect: %( + # :testa2 a :success . + # ), + # pending: "Variable substitution" + #}, + #"quantifiers-limited-b2" => { + # input: %( + # {{ @forSome :foo. :foo :bar :baz } log:includes {@forSome :foo. :foo :bar :baz }} + # => { :testb2 a :success } . + # ), + # expect: %( + # :testb2 a :success . + # ), + # pending: "Variable substitution" + #}, + #"quantifiers-limited-a1d" => { + # input: %( + # {{ :fee :bar :baz } log:includes { :foo :bar :baz }} + # => { :testa1d a :FAILURE } . + # ), + # expect: %() + #}, }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] expected = parse(options[:expect]) result = reason(options[:input]) expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) @@ -227,8 +278,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {data: false, filter: true}.merge(options) expected = parse(options[:expect]) - result = reason(options[:input], data: false, filter: true) + result = reason(options[:input], **options) expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end end @@ -247,6 +300,7 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] result = reason(options[:input]) n3str = RDF::N3::Writer.buffer {|writer| writer << result} @@ -316,8 +370,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end @@ -374,8 +430,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end @@ -414,8 +472,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end @@ -435,11 +495,13 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) if options[:exception] - expect {reason(options[:input], filter: true)}.to raise_error options[:exception] + expect {reason(options[:input], **options)}.to raise_error options[:exception] else expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end @@ -466,8 +528,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end @@ -499,8 +563,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end @@ -526,8 +592,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end @@ -551,8 +619,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end @@ -586,8 +656,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end @@ -621,8 +693,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end @@ -742,12 +816,76 @@ "3.1415926 + 3.1415926 = 6.2831852" a :RESULT . ) }, + "Combinatorial test - worksWith": { + input: %( + "3.1415926" a :testValue. + 3.1415926 a :testValue. + "1729" a :testValue. + 1729 a :testValue. + "0" a :testValue. + 0 a :testValue. + { ?x a :testValue. ?y a :testValue. + ?z is math:sum of (?x (?y ?x)!math:difference). + ?z math:equalTo ?y } => {?x :worksWith ?y}. + ), + expect: %( + 0 a :testValue; + :worksWith "1729", + 1729, + 0, + "3.1415926", + "0", + 3.1415926 . + + "0" a :testValue; + :worksWith "1729", + 1729, + 0, + "3.1415926", + "0", + 3.1415926 . + + "3.1415926" a :testValue; + :worksWith "1729", + 1729, + 0, + "3.1415926", + "0", + 3.1415926 . + + 3.1415926 a :testValue; + :worksWith "1729", + 1729, + 0, + "3.1415926", + "0", + 3.1415926 . + + 1729 a :testValue; + :worksWith "1729", + 1729, + 0, + "3.1415926", + "0", + 3.1415926 . + + "1729" a :testValue; + :worksWith "1729", + 1729, + 0, + "3.1415926", + "0", + 3.1415926 . + ), + filter: false, data: true + }, }.each do |name, options| it name do pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) logger.info "input: #{options[:input]}" expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end @@ -782,8 +920,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end @@ -801,8 +941,10 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {filter: true}.merge(options) expected = parse(options[:expect]) - expect(reason(options[:input], filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end end end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index ac4c3ef..898afe0 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -6,7 +6,7 @@ describe "w3c n3 tests" do require_relative 'suite_helper' let(:logger) {RDF::Spec.logger} - before {logger.level = Logger::DEBUG} + before {logger.level = Logger::INFO} #after(:each) do |example| # puts logger.to_s if @@ -21,10 +21,13 @@ specify "#{t.name}: #{t.comment}" do case t.id.split('#').last when *%w{cwm_unify_unify1 cwm_includes_builtins - cwm_includes_t10 cwm_includes_t11 cwm_includes_quantifiers_limited} + cwm_includes_t10 cwm_includes_t11 cwm_includes_quantifiers_limited + cwm_includes_conclusion_simple cwm_includes_conclusion} pending "log:includes etc." - when *%w{cwm_supports_simple cwm_string_roughly cwm_string_uriEncode} + when *%w{cwm_supports_simple cwm_string_roughly} pending "Uses unsupported builtin" + when *%w{cwm_string_uriEncode} + skip "Blows up" when *%w{cwm_list_builtin_generated_match} skip("List reification") end From afafaa84dfb9e24052ce6a60d1b9994a730eb383 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 21 Oct 2020 16:42:36 -0700 Subject: [PATCH 151/193] Fix Builtin#to_uri by adding a URI constant to each builtin. --- lib/rdf/n3/algebra/builtin.rb | 2 +- lib/rdf/n3/algebra/list/append.rb | 1 + lib/rdf/n3/algebra/list/first.rb | 1 + lib/rdf/n3/algebra/list/in.rb | 1 + lib/rdf/n3/algebra/list/last.rb | 1 + lib/rdf/n3/algebra/list/length.rb | 1 + lib/rdf/n3/algebra/list/member.rb | 1 + lib/rdf/n3/algebra/log/conclusion.rb | 1 + lib/rdf/n3/algebra/log/conjunction.rb | 3 +-- lib/rdf/n3/algebra/log/content.rb | 1 + lib/rdf/n3/algebra/log/equal_to.rb | 1 + lib/rdf/n3/algebra/log/implies.rb | 1 + lib/rdf/n3/algebra/log/includes.rb | 1 + lib/rdf/n3/algebra/log/n3_string.rb | 1 + lib/rdf/n3/algebra/log/not_equal_to.rb | 1 + lib/rdf/n3/algebra/log/not_includes.rb | 6 ++---- lib/rdf/n3/algebra/log/output_string.rb | 7 ++----- lib/rdf/n3/algebra/log/parsed_as_n3.rb | 1 + lib/rdf/n3/algebra/log/semantics.rb | 1 + lib/rdf/n3/algebra/math/absolute_value.rb | 1 + lib/rdf/n3/algebra/math/ceiling.rb | 1 + lib/rdf/n3/algebra/math/cos.rb | 1 + lib/rdf/n3/algebra/math/cosh.rb | 1 + lib/rdf/n3/algebra/math/difference.rb | 1 + lib/rdf/n3/algebra/math/equal_to.rb | 1 + lib/rdf/n3/algebra/math/exponentiation.rb | 1 + lib/rdf/n3/algebra/math/floor.rb | 1 + lib/rdf/n3/algebra/math/greater_than.rb | 1 + lib/rdf/n3/algebra/math/integer_quotient.rb | 1 + lib/rdf/n3/algebra/math/less_than.rb | 1 + lib/rdf/n3/algebra/math/negation.rb | 1 + lib/rdf/n3/algebra/math/not_equal_to.rb | 1 + lib/rdf/n3/algebra/math/not_greater_than.rb | 1 + lib/rdf/n3/algebra/math/not_less_than.rb | 1 + lib/rdf/n3/algebra/math/product.rb | 1 + lib/rdf/n3/algebra/math/quotient.rb | 1 + lib/rdf/n3/algebra/math/remainder.rb | 1 + lib/rdf/n3/algebra/math/rounded.rb | 1 + lib/rdf/n3/algebra/math/sin.rb | 1 + lib/rdf/n3/algebra/math/sinh.rb | 1 + lib/rdf/n3/algebra/math/sum.rb | 1 + lib/rdf/n3/algebra/math/tan.rb | 1 + lib/rdf/n3/algebra/math/tanh.rb | 1 + lib/rdf/n3/algebra/str/concatenation.rb | 1 + lib/rdf/n3/algebra/str/contains.rb | 1 + lib/rdf/n3/algebra/str/contains_ignoring_case.rb | 1 + lib/rdf/n3/algebra/str/ends_with.rb | 1 + lib/rdf/n3/algebra/str/equal_ignoring_case.rb | 1 + lib/rdf/n3/algebra/str/format.rb | 1 + lib/rdf/n3/algebra/str/greater_than.rb | 1 + lib/rdf/n3/algebra/str/less_than.rb | 1 + lib/rdf/n3/algebra/str/matches.rb | 1 + lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb | 1 + lib/rdf/n3/algebra/str/not_greater_than.rb | 1 + lib/rdf/n3/algebra/str/not_less_than.rb | 1 + lib/rdf/n3/algebra/str/not_matches.rb | 1 + lib/rdf/n3/algebra/str/replace.rb | 1 + lib/rdf/n3/algebra/str/scrape.rb | 1 + lib/rdf/n3/algebra/str/starts_with.rb | 1 + lib/rdf/n3/algebra/time/day.rb | 1 + lib/rdf/n3/algebra/time/day_of_week.rb | 1 + lib/rdf/n3/algebra/time/gm_time.rb | 1 + lib/rdf/n3/algebra/time/hour.rb | 1 + lib/rdf/n3/algebra/time/in_seconds.rb | 1 + lib/rdf/n3/algebra/time/local_time.rb | 1 + lib/rdf/n3/algebra/time/minute.rb | 1 + lib/rdf/n3/algebra/time/month.rb | 1 + lib/rdf/n3/algebra/time/second.rb | 1 + lib/rdf/n3/algebra/time/timezone.rb | 1 + lib/rdf/n3/algebra/time/year.rb | 1 + 70 files changed, 72 insertions(+), 12 deletions(-) diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb index 67ebe24..933e38a 100644 --- a/lib/rdf/n3/algebra/builtin.rb +++ b/lib/rdf/n3/algebra/builtin.rb @@ -73,7 +73,7 @@ def hash # The URI of this operator. def to_uri - Kernel.const_get('::' + self.class.to_s.split('::')[0..-2].join('::')).vocab + self.class.const_get(:NAME) + self.class.const_get(:URI) end end end diff --git a/lib/rdf/n3/algebra/list/append.rb b/lib/rdf/n3/algebra/list/append.rb index 9ffe739..1d4ef2b 100644 --- a/lib/rdf/n3/algebra/list/append.rb +++ b/lib/rdf/n3/algebra/list/append.rb @@ -8,6 +8,7 @@ module RDF::N3::Algebra::List # The object can be calculated as a function of the subject. class Append < RDF::N3::Algebra::ListOperator NAME = :listAppend + URI = RDF::N3::List.append ## # Resolves this operator using the given variable `bindings`. diff --git a/lib/rdf/n3/algebra/list/first.rb b/lib/rdf/n3/algebra/list/first.rb index 7716290..976dbab 100644 --- a/lib/rdf/n3/algebra/list/first.rb +++ b/lib/rdf/n3/algebra/list/first.rb @@ -8,6 +8,7 @@ module RDF::N3::Algebra::List # The object can be calculated as a function of the list. class First < RDF::N3::Algebra::ListOperator NAME = :listFirst + URI = RDF::N3::List.first ## # Resolves this operator using the given variable `bindings`. diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index 8ac268c..5b39238 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -6,6 +6,7 @@ module RDF::N3::Algebra::List # { 1 list:in ( 1 2 3 4 5 ) } => { :test4a a :SUCCESS }. class In < RDF::N3::Algebra::ListOperator NAME = :listIn + URI = RDF::N3::List.in ## # Evaluates this operator using the given variable `bindings`. diff --git a/lib/rdf/n3/algebra/list/last.rb b/lib/rdf/n3/algebra/list/last.rb index 34a0adb..b5017e5 100644 --- a/lib/rdf/n3/algebra/list/last.rb +++ b/lib/rdf/n3/algebra/list/last.rb @@ -8,6 +8,7 @@ module RDF::N3::Algebra::List # The object can be calculated as a function of the list. class Last < RDF::N3::Algebra::ListOperator NAME = :listLast + URI = RDF::N3::List.last ## # Resolves this operator using the given variable `bindings`. diff --git a/lib/rdf/n3/algebra/list/length.rb b/lib/rdf/n3/algebra/list/length.rb index 566936e..a6ecbf7 100644 --- a/lib/rdf/n3/algebra/list/length.rb +++ b/lib/rdf/n3/algebra/list/length.rb @@ -8,6 +8,7 @@ module RDF::N3::Algebra::List # The object can be calculated as a function of the list. class Length < RDF::N3::Algebra::ListOperator NAME = :listLength + URI = RDF::N3::List.length ## # Resolves this operator using the given variable `bindings`. diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index eaa10a7..85ed4cd 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -3,6 +3,7 @@ module RDF::N3::Algebra::List # Iff the subject is a list and the object is in that list, then this is true. class Member < RDF::N3::Algebra::ListOperator NAME = :listMember + URI = RDF::N3::List.member ## # Evaluates this operator using the given variable `bindings`. diff --git a/lib/rdf/n3/algebra/log/conclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb index 533a78c..16b2eaf 100644 --- a/lib/rdf/n3/algebra/log/conclusion.rb +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Log # 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 Conclusion < RDF::N3::Algebra::ResourceOperator NAME = :logConclusion + URI = RDF::N3::Log.conclusion ## # Evaluates this operator by creating a new formula containing the triples generated by reasoning over the input formula using think. diff --git a/lib/rdf/n3/algebra/log/conjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb index 2821447..44db7c5 100644 --- a/lib/rdf/n3/algebra/log/conjunction.rb +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -4,9 +4,8 @@ module RDF::N3::Algebra::Log # # 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 Conjunction < RDF::N3::Algebra::ListOperator - include RDF::N3::Algebra::Builtin - NAME = :logConjunction + URI = RDF::N3::Log.conjunction ## # Evaluates this operator by creating a new formula containing the triples from each of the formulae in the list. diff --git a/lib/rdf/n3/algebra/log/content.rb b/lib/rdf/n3/algebra/log/content.rb index b1ed2f0..538393c 100644 --- a/lib/rdf/n3/algebra/log/content.rb +++ b/lib/rdf/n3/algebra/log/content.rb @@ -7,6 +7,7 @@ module RDF::N3::Algebra::Log # Note that the content-type of the information is not given and so must be known or guessed. class Content < RDF::N3::Algebra::ResourceOperator NAME = :logContent + URI = RDF::N3::Log.content ## # Reads the subject into the object. diff --git a/lib/rdf/n3/algebra/log/equal_to.rb b/lib/rdf/n3/algebra/log/equal_to.rb index 9abfaf7..0b9320c 100644 --- a/lib/rdf/n3/algebra/log/equal_to.rb +++ b/lib/rdf/n3/algebra/log/equal_to.rb @@ -3,6 +3,7 @@ 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 EqualTo < RDF::N3::Algebra::ResourceOperator NAME = :logEqualTo + URI = RDF::N3::Log.equalTo ## # Resolves inputs as terms. diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index d6803f2..965880d 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -11,6 +11,7 @@ class Implies < SPARQL::Algebra::Operator::Binary include RDF::N3::Algebra::Builtin NAME = :logImplies + URI = RDF::N3::Log.implies ## # Returns solutions from subject. Solutions are created by evaluating subject against `queryable`. diff --git a/lib/rdf/n3/algebra/log/includes.rb b/lib/rdf/n3/algebra/log/includes.rb index 83a3c1f..5305b85 100644 --- a/lib/rdf/n3/algebra/log/includes.rb +++ b/lib/rdf/n3/algebra/log/includes.rb @@ -13,6 +13,7 @@ class Includes < SPARQL::Algebra::Operator::Binary include RDF::N3::Algebra::Builtin NAME = :logIncludes + URI = RDF::N3::Log.includes ## # Creates a repository constructed by evaluating the subject against queryable and queries object against that repository. Either retuns a single solution, or no solutions diff --git a/lib/rdf/n3/algebra/log/n3_string.rb b/lib/rdf/n3/algebra/log/n3_string.rb index bf6973b..c2e8a00 100644 --- a/lib/rdf/n3/algebra/log/n3_string.rb +++ b/lib/rdf/n3/algebra/log/n3_string.rb @@ -3,6 +3,7 @@ module RDF::N3::Algebra::Log # The subject formula, expressed as N3, gives this string. class N3String < RDF::N3::Algebra::ResourceOperator NAME = :logN3String + URI = RDF::N3::Log.n3String ## # Serializes the subject formula into an N3 string representation. diff --git a/lib/rdf/n3/algebra/log/not_equal_to.rb b/lib/rdf/n3/algebra/log/not_equal_to.rb index eaa1aff..ddf54e0 100644 --- a/lib/rdf/n3/algebra/log/not_equal_to.rb +++ b/lib/rdf/n3/algebra/log/not_equal_to.rb @@ -4,6 +4,7 @@ module RDF::N3::Algebra::Log class NotEqualTo < SPARQL::Algebra::Operator::SameTerm include RDF::N3::Algebra::Builtin NAME = :logNotEqualTo + URI = RDF::N3::Log.notEqualto ## # Returns `true` if the operands are not the same RDF term; returns diff --git a/lib/rdf/n3/algebra/log/not_includes.rb b/lib/rdf/n3/algebra/log/not_includes.rb index 8f2c97e..d7516f8 100644 --- a/lib/rdf/n3/algebra/log/not_includes.rb +++ b/lib/rdf/n3/algebra/log/not_includes.rb @@ -7,11 +7,9 @@ module RDF::N3::Algebra::Log # # Related: See includes class NotIncludes < Includes - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::N3::Algebra::Builtin - NAME = :logNotIncludes + URI = RDF::N3::Log.notIncludes + ## # Uses log:includes and returns a solution if log:includes fails # diff --git a/lib/rdf/n3/algebra/log/output_string.rb b/lib/rdf/n3/algebra/log/output_string.rb index 04f282c..8b082c0 100644 --- a/lib/rdf/n3/algebra/log/output_string.rb +++ b/lib/rdf/n3/algebra/log/output_string.rb @@ -1,11 +1,8 @@ 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 OutputString < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::N3::Algebra::Builtin - + class OutputString < RDF::N3::Algebra::ResourceOperator NAME = :logOutputString + URI = RDF::N3::Log.outputString end end diff --git a/lib/rdf/n3/algebra/log/parsed_as_n3.rb b/lib/rdf/n3/algebra/log/parsed_as_n3.rb index 97414a6..3ab6ddc 100644 --- a/lib/rdf/n3/algebra/log/parsed_as_n3.rb +++ b/lib/rdf/n3/algebra/log/parsed_as_n3.rb @@ -3,6 +3,7 @@ module RDF::N3::Algebra::Log # The subject string, parsed as N3, gives this formula. class ParsedAsN3 < RDF::N3::Algebra::ResourceOperator NAME = :logParsedAsN3 + URI = RDF::N3::Log.parsedAsN3 ## # Parses the subject into a new formula. diff --git a/lib/rdf/n3/algebra/log/semantics.rb b/lib/rdf/n3/algebra/log/semantics.rb index 43452ac..55bb1bb 100644 --- a/lib/rdf/n3/algebra/log/semantics.rb +++ b/lib/rdf/n3/algebra/log/semantics.rb @@ -7,6 +7,7 @@ module RDF::N3::Algebra::Log # (Cwm knows how to go get a document and parse N3 and RDF/XML it in order to evaluate this. Other languages for web documents may be defined whose N3 semantics are therefore also calculable, and so they could be added in due course. See for example GRDDL, RDFa, etc) class Semantics < RDF::N3::Algebra::ResourceOperator NAME = :logSemantics + URI = RDF::N3::Log.semantics ## # Parses the subject into a new formula. diff --git a/lib/rdf/n3/algebra/math/absolute_value.rb b/lib/rdf/n3/algebra/math/absolute_value.rb index e3f4b13..74008c9 100644 --- a/lib/rdf/n3/algebra/math/absolute_value.rb +++ b/lib/rdf/n3/algebra/math/absolute_value.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-abs class AbsoluteValue < RDF::N3::Algebra::ResourceOperator NAME = :mathAbsoluteValue + URI = RDF::N3::Math.absoluteValue ## # The math:absoluteValue operator takes string or number and calculates its absolute value. diff --git a/lib/rdf/n3/algebra/math/ceiling.rb b/lib/rdf/n3/algebra/math/ceiling.rb index fbc63e1..0e27a2d 100644 --- a/lib/rdf/n3/algebra/math/ceiling.rb +++ b/lib/rdf/n3/algebra/math/ceiling.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-ceiling class Ceiling < RDF::N3::Algebra::ResourceOperator NAME = :mathCeiling + URI = RDF::N3::Math.ceiling ## # The math:ceiling operator takes string or number and calculates its ceiling. diff --git a/lib/rdf/n3/algebra/math/cos.rb b/lib/rdf/n3/algebra/math/cos.rb index 5a926c2..7ed88b1 100644 --- a/lib/rdf/n3/algebra/math/cos.rb +++ b/lib/rdf/n3/algebra/math/cos.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-math-cos class Cos < RDF::N3::Algebra::ResourceOperator NAME = :mathCos + URI = RDF::N3::Math.cos ## # The math:cos operator takes string or number and calculates its cosine. The arc cosine of a concrete object can also calculate a variable subject. diff --git a/lib/rdf/n3/algebra/math/cosh.rb b/lib/rdf/n3/algebra/math/cosh.rb index fb992ce..a857b4a 100644 --- a/lib/rdf/n3/algebra/math/cosh.rb +++ b/lib/rdf/n3/algebra/math/cosh.rb @@ -3,6 +3,7 @@ module RDF::N3::Algebra::Math # The subject is an angle expressed in radians. The object is calulated as the hyperbolic cosine value of the subject. class CosH < RDF::N3::Algebra::ResourceOperator NAME = :mathCosH + URI = RDF::N3::Math.cosh ## # The math:cosh operator takes string or number and calculates its hyperbolic cosine. The inverse hyperbolic cosine of a concrete object can also calculate a variable subject. diff --git a/lib/rdf/n3/algebra/math/difference.rb b/lib/rdf/n3/algebra/math/difference.rb index bc6dec2..01dd69a 100644 --- a/lib/rdf/n3/algebra/math/difference.rb +++ b/lib/rdf/n3/algebra/math/difference.rb @@ -10,6 +10,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-subtract class Difference < RDF::N3::Algebra::ListOperator NAME = :mathDifference + URI = RDF::N3::Math.difference ## # The math:difference operator takes a pair of strings or numbers and calculates their difference. diff --git a/lib/rdf/n3/algebra/math/equal_to.rb b/lib/rdf/n3/algebra/math/equal_to.rb index 4cf28d1..d745d96 100644 --- a/lib/rdf/n3/algebra/math/equal_to.rb +++ b/lib/rdf/n3/algebra/math/equal_to.rb @@ -18,6 +18,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-equal class EqualTo < RDF::N3::Algebra::ResourceOperator NAME = :mathEqualTo + URI = RDF::N3::Math.equalTo ## # Resolves inputs as numbers. diff --git a/lib/rdf/n3/algebra/math/exponentiation.rb b/lib/rdf/n3/algebra/math/exponentiation.rb index 525ed5d..5b22e17 100644 --- a/lib/rdf/n3/algebra/math/exponentiation.rb +++ b/lib/rdf/n3/algebra/math/exponentiation.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-math-exp class Exponentiation < RDF::N3::Algebra::ListOperator NAME = :mathExponentiation + URI = RDF::N3::Math.exponentiation ## # The math:difference operator takes a pair of strings or numbers and calculates the exponent. diff --git a/lib/rdf/n3/algebra/math/floor.rb b/lib/rdf/n3/algebra/math/floor.rb index 47886e1..13a439b 100644 --- a/lib/rdf/n3/algebra/math/floor.rb +++ b/lib/rdf/n3/algebra/math/floor.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-floor class Floor < RDF::N3::Algebra::ResourceOperator NAME = :mathFloor + URI = RDF::N3::Math.floor ## # The math:floor operator takes string or number and calculates its floor. diff --git a/lib/rdf/n3/algebra/math/greater_than.rb b/lib/rdf/n3/algebra/math/greater_than.rb index 94208b8..77f9a91 100644 --- a/lib/rdf/n3/algebra/math/greater_than.rb +++ b/lib/rdf/n3/algebra/math/greater_than.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-greater-than class GreaterThan < RDF::N3::Algebra::ResourceOperator NAME = :mathGreaterThan + URI = RDF::N3::Math.greaterThan ## # Resolves inputs as numbers. diff --git a/lib/rdf/n3/algebra/math/integer_quotient.rb b/lib/rdf/n3/algebra/math/integer_quotient.rb index 7792046..cf29671 100644 --- a/lib/rdf/n3/algebra/math/integer_quotient.rb +++ b/lib/rdf/n3/algebra/math/integer_quotient.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-integer-divide class IntegerQuotient < RDF::N3::Algebra::Math::Quotient NAME = :mathIntegerQuotient + URI = RDF::N3::Math.integerQuotient ## # The math:quotient operator takes a pair of strings or numbers and calculates their quotient. diff --git a/lib/rdf/n3/algebra/math/less_than.rb b/lib/rdf/n3/algebra/math/less_than.rb index 2d37b37..c2ace32 100644 --- a/lib/rdf/n3/algebra/math/less_than.rb +++ b/lib/rdf/n3/algebra/math/less_than.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-less-than class LessThan < RDF::N3::Algebra::ResourceOperator NAME = :mathLessThan + URI = RDF::N3::Math.lessThan ## # Resolves inputs as numbers. diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb index 7e07945..ae1e1a3 100644 --- a/lib/rdf/n3/algebra/math/negation.rb +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -7,6 +7,7 @@ class Negation < RDF::N3::Algebra::ResourceOperator include RDF::N3::Algebra::Builtin NAME = :mathNegation + URI = RDF::N3::Math.negation ## # The math:negation operator takes may have either a bound subject or object. diff --git a/lib/rdf/n3/algebra/math/not_equal_to.rb b/lib/rdf/n3/algebra/math/not_equal_to.rb index 5b2f87b..058e2c5 100644 --- a/lib/rdf/n3/algebra/math/not_equal_to.rb +++ b/lib/rdf/n3/algebra/math/not_equal_to.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-equal class NotEqualTo < EqualTo NAME = :mathNotEqualTo + URI = RDF::N3::Math.notEqualTo ## # The math:notEqualTo operator takes a pair of strings or numbers and determines if they are not the same numeric value. diff --git a/lib/rdf/n3/algebra/math/not_greater_than.rb b/lib/rdf/n3/algebra/math/not_greater_than.rb index c1fed10..e939c96 100644 --- a/lib/rdf/n3/algebra/math/not_greater_than.rb +++ b/lib/rdf/n3/algebra/math/not_greater_than.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-greater-than class NotGreaterThan < GreaterThan NAME = :mathNotGreaterThan + URI = RDF::N3::Math.notGreaterThan ## # Returns TRUE if `term1` is less than or equal to `term2`. diff --git a/lib/rdf/n3/algebra/math/not_less_than.rb b/lib/rdf/n3/algebra/math/not_less_than.rb index a2e4e15..a373e5b 100644 --- a/lib/rdf/n3/algebra/math/not_less_than.rb +++ b/lib/rdf/n3/algebra/math/not_less_than.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-less-than class NotLessThan < LessThan NAME = :mathNotLessThan + URI = RDF::N3::Math.notLessThan ## # Returns TRUE if `term1` is greater than or equal to `term2`. diff --git a/lib/rdf/n3/algebra/math/product.rb b/lib/rdf/n3/algebra/math/product.rb index 29b1a90..9db6b6d 100644 --- a/lib/rdf/n3/algebra/math/product.rb +++ b/lib/rdf/n3/algebra/math/product.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-multiply class Product < RDF::N3::Algebra::ListOperator NAME = :mathProduct + URI = RDF::N3::Math.product ## # The math:product operator takes a list of strings or numbers and calculates their sum. diff --git a/lib/rdf/n3/algebra/math/quotient.rb b/lib/rdf/n3/algebra/math/quotient.rb index acef83b..8fd0c54 100644 --- a/lib/rdf/n3/algebra/math/quotient.rb +++ b/lib/rdf/n3/algebra/math/quotient.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-divide class Quotient < RDF::N3::Algebra::ListOperator NAME = :mathQuotient + URI = RDF::N3::Math.quotient ## # The math:quotient operator takes a pair of strings or numbers and calculates their quotient. diff --git a/lib/rdf/n3/algebra/math/remainder.rb b/lib/rdf/n3/algebra/math/remainder.rb index d8ff4f9..7ec6c90 100644 --- a/lib/rdf/n3/algebra/math/remainder.rb +++ b/lib/rdf/n3/algebra/math/remainder.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-mod class Remainder < RDF::N3::Algebra::ListOperator NAME = :mathRemainder + URI = RDF::N3::Math.remainder ## # The math:remainder operator takes a pair of strings or numbers and calculates their remainder. diff --git a/lib/rdf/n3/algebra/math/rounded.rb b/lib/rdf/n3/algebra/math/rounded.rb index 8c5063e..b2f2087 100644 --- a/lib/rdf/n3/algebra/math/rounded.rb +++ b/lib/rdf/n3/algebra/math/rounded.rb @@ -3,6 +3,7 @@ module RDF::N3::Algebra::Math # The object is calulated as the subject rounded to the nearest integer. class Rounded < RDF::N3::Algebra::ResourceOperator NAME = :mathRounded + URI = RDF::N3::Math.rounded ## # The math:rounded operator takes string or number rounds it to the next integer. diff --git a/lib/rdf/n3/algebra/math/sin.rb b/lib/rdf/n3/algebra/math/sin.rb index 01ece9d..c7a91df 100644 --- a/lib/rdf/n3/algebra/math/sin.rb +++ b/lib/rdf/n3/algebra/math/sin.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-math-sin class Sin < RDF::N3::Algebra::ResourceOperator NAME = :mathSin + URI = RDF::N3::Math.sin ## # The math:sin operator takes string or number and calculates its sine. The arc sine of a concrete object can also calculate a variable subject. diff --git a/lib/rdf/n3/algebra/math/sinh.rb b/lib/rdf/n3/algebra/math/sinh.rb index 789707c..e58e3a4 100644 --- a/lib/rdf/n3/algebra/math/sinh.rb +++ b/lib/rdf/n3/algebra/math/sinh.rb @@ -3,6 +3,7 @@ module RDF::N3::Algebra::Math # The subject is an angle expressed in radians. The object is calulated as the hyperbolic sine value of the subject. class SinH < RDF::N3::Algebra::ResourceOperator NAME = :mathSinH + URI = RDF::N3::Math.sinh ## # The math:sinh operator takes string or number and calculates its hyperbolic sine. The inverse hyperbolic sine of a concrete object can also calculate a variable subject. diff --git a/lib/rdf/n3/algebra/math/sum.rb b/lib/rdf/n3/algebra/math/sum.rb index 5d80018..64dbd4f 100644 --- a/lib/rdf/n3/algebra/math/sum.rb +++ b/lib/rdf/n3/algebra/math/sum.rb @@ -25,6 +25,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-add class Sum < RDF::N3::Algebra::ListOperator NAME = :mathSum + URI = RDF::N3::Math.sum ## # Evaluates to the sum of the list elements diff --git a/lib/rdf/n3/algebra/math/tan.rb b/lib/rdf/n3/algebra/math/tan.rb index dd68ba8..4132791 100644 --- a/lib/rdf/n3/algebra/math/tan.rb +++ b/lib/rdf/n3/algebra/math/tan.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-math-tan class Tan < RDF::N3::Algebra::ResourceOperator NAME = :mathTan + URI = RDF::N3::Math.tan ## # The math:tan operator takes string or number and calculates its tangent. The arc tangent of a concrete object can also calculate a variable subject. diff --git a/lib/rdf/n3/algebra/math/tanh.rb b/lib/rdf/n3/algebra/math/tanh.rb index 22e23d2..8a81073 100644 --- a/lib/rdf/n3/algebra/math/tanh.rb +++ b/lib/rdf/n3/algebra/math/tanh.rb @@ -3,6 +3,7 @@ module RDF::N3::Algebra::Math # The subject is an angle expressed in radians. The object is calulated as the tangent value of the subject. class TanH < RDF::N3::Algebra::ResourceOperator NAME = :mathTanH + URI = RDF::N3::Math.tanh ## # The math:tanh operator takes string or number and calculates its hyperbolic tangent. The inverse hyperbolic tangent of a concrete object can also calculate a variable subject. diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb index d90cfe2..be28c04 100644 --- a/lib/rdf/n3/algebra/str/concatenation.rb +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -6,6 +6,7 @@ module RDF::N3::Algebra::Str # ("a" "b") string:concatenation :s class Concatenation < RDF::N3::Algebra::ListOperator NAME = :strConcatenation + URI = RDF::N3::Str.concatenation ## # The string:concatenation operator takes a list of terms evaluating to strings and either binds the result of concatenating them to the output variable, removes a solution that does equal. diff --git a/lib/rdf/n3/algebra/str/contains.rb b/lib/rdf/n3/algebra/str/contains.rb index a0f21cc..1df7a7e 100644 --- a/lib/rdf/n3/algebra/str/contains.rb +++ b/lib/rdf/n3/algebra/str/contains.rb @@ -2,6 +2,7 @@ module RDF::N3::Algebra::Str # True iff the subject string contains the object string. class Contains < RDF::N3::Algebra::ResourceOperator NAME = :strContains + URI = RDF::N3::Str.contains ## # Resolves inputs as strings. diff --git a/lib/rdf/n3/algebra/str/contains_ignoring_case.rb b/lib/rdf/n3/algebra/str/contains_ignoring_case.rb index c958290..430e0e0 100644 --- a/lib/rdf/n3/algebra/str/contains_ignoring_case.rb +++ b/lib/rdf/n3/algebra/str/contains_ignoring_case.rb @@ -2,6 +2,7 @@ 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 < RDF::N3::Algebra::ResourceOperator NAME = :strContainsIgnoringCase + URI = RDF::N3::Str.containsIgnoringCase ## # Resolves inputs as lower-case strings. diff --git a/lib/rdf/n3/algebra/str/ends_with.rb b/lib/rdf/n3/algebra/str/ends_with.rb index b14b3af..16a6a10 100644 --- a/lib/rdf/n3/algebra/str/ends_with.rb +++ b/lib/rdf/n3/algebra/str/ends_with.rb @@ -2,6 +2,7 @@ module RDF::N3::Algebra::Str # True iff the subject string ends with the object string. class EndsWith < RDF::N3::Algebra::ResourceOperator NAME = :strEndsWith + URI = RDF::N3::Str.endsWith ## # Resolves inputs as strings. diff --git a/lib/rdf/n3/algebra/str/equal_ignoring_case.rb b/lib/rdf/n3/algebra/str/equal_ignoring_case.rb index 654ef2f..865f235 100644 --- a/lib/rdf/n3/algebra/str/equal_ignoring_case.rb +++ b/lib/rdf/n3/algebra/str/equal_ignoring_case.rb @@ -1,6 +1,7 @@ module RDF::N3::Algebra::Str class EqualIgnoringCase < RDF::N3::Algebra::ResourceOperator NAME = :strEqualIgnoringCase + URI = RDF::N3::Str.equalIgnoringCase ## # Resolves inputs as lower-case strings. diff --git a/lib/rdf/n3/algebra/str/format.rb b/lib/rdf/n3/algebra/str/format.rb index 0697338..391eb24 100644 --- a/lib/rdf/n3/algebra/str/format.rb +++ b/lib/rdf/n3/algebra/str/format.rb @@ -3,6 +3,7 @@ module RDF::N3::Algebra::Str class Format < RDF::N3::Algebra::ListOperator include RDF::N3::Algebra::Builtin NAME = :strFormat + URI = RDF::N3::Str.format ## # @param [RDF::N3::List] list diff --git a/lib/rdf/n3/algebra/str/greater_than.rb b/lib/rdf/n3/algebra/str/greater_than.rb index a61850c..f535847 100644 --- a/lib/rdf/n3/algebra/str/greater_than.rb +++ b/lib/rdf/n3/algebra/str/greater_than.rb @@ -2,6 +2,7 @@ module RDF::N3::Algebra::Str # True iff the string is greater than the object when ordered according to Unicode(tm) code order. class GreaterThan < RDF::N3::Algebra::ResourceOperator NAME = :strGreaterThan + URI = RDF::N3::Str.greaterThan ## # The string:greaterThan compares subject with object as strings. diff --git a/lib/rdf/n3/algebra/str/less_than.rb b/lib/rdf/n3/algebra/str/less_than.rb index d428f5c..f821f88 100644 --- a/lib/rdf/n3/algebra/str/less_than.rb +++ b/lib/rdf/n3/algebra/str/less_than.rb @@ -2,6 +2,7 @@ module RDF::N3::Algebra::Str # True iff the string is less than the object when ordered according to Unicode(tm) code order. class LessThan < RDF::N3::Algebra::ResourceOperator NAME = :strLessThan + URI = RDF::N3::Str.lessThan ## # Resolves inputs as strings. diff --git a/lib/rdf/n3/algebra/str/matches.rb b/lib/rdf/n3/algebra/str/matches.rb index f375cae..28f136b 100644 --- a/lib/rdf/n3/algebra/str/matches.rb +++ b/lib/rdf/n3/algebra/str/matches.rb @@ -3,6 +3,7 @@ module RDF::N3::Algebra::Str # It is true iff the string matches the regexp. class Matches < RDF::N3::Algebra::ResourceOperator NAME = :strMatches + URI = RDF::N3::Str.matches ## # Resolves inputs as strings. diff --git a/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb b/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb index 2fa4498..d2652bc 100644 --- a/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb +++ b/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb @@ -2,6 +2,7 @@ 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 < EqualIgnoringCase NAME = :strNotEqualIgnoringCase + URI = RDF::N3::Str.notEqualIgnoringCase ## # @param [RDF::Literal] left diff --git a/lib/rdf/n3/algebra/str/not_greater_than.rb b/lib/rdf/n3/algebra/str/not_greater_than.rb index 001c197..04d8f86 100644 --- a/lib/rdf/n3/algebra/str/not_greater_than.rb +++ b/lib/rdf/n3/algebra/str/not_greater_than.rb @@ -2,6 +2,7 @@ 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 < GreaterThan NAME = :strNotGreaterThan + URI = RDF::N3::Str.notGreaterThan ## # @param [RDF::Literal] left diff --git a/lib/rdf/n3/algebra/str/not_less_than.rb b/lib/rdf/n3/algebra/str/not_less_than.rb index 1b5a83f..e09db2b 100644 --- a/lib/rdf/n3/algebra/str/not_less_than.rb +++ b/lib/rdf/n3/algebra/str/not_less_than.rb @@ -2,6 +2,7 @@ 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 < LessThan NAME = :strNotLessThan + URI = RDF::N3::Str.notLessThan ## # @param [RDF::Literal] left diff --git a/lib/rdf/n3/algebra/str/not_matches.rb b/lib/rdf/n3/algebra/str/not_matches.rb index 728392b..41d6d01 100644 --- a/lib/rdf/n3/algebra/str/not_matches.rb +++ b/lib/rdf/n3/algebra/str/not_matches.rb @@ -2,6 +2,7 @@ module RDF::N3::Algebra::Str # The subject string; the object is a regular expression in the perl, python style. It is true iff the string does NOT match the regexp. class NotMatches < Matches NAME = :strNotMatches + URI = RDF::N3::Str.notMatches ## # @param [RDF::Literal] text diff --git a/lib/rdf/n3/algebra/str/replace.rb b/lib/rdf/n3/algebra/str/replace.rb index 9d187d8..5aef0bc 100644 --- a/lib/rdf/n3/algebra/str/replace.rb +++ b/lib/rdf/n3/algebra/str/replace.rb @@ -6,6 +6,7 @@ module RDF::N3::Algebra::Str class Replace < RDF::N3::Algebra::ListOperator include RDF::N3::Algebra::Builtin NAME = :strReplace + URI = RDF::N3::Str.replace ## # @param [RDF::N3::List] list diff --git a/lib/rdf/n3/algebra/str/scrape.rb b/lib/rdf/n3/algebra/str/scrape.rb index c57a609..53e8227 100644 --- a/lib/rdf/n3/algebra/str/scrape.rb +++ b/lib/rdf/n3/algebra/str/scrape.rb @@ -6,6 +6,7 @@ module RDF::N3::Algebra::Str class Scrape < RDF::N3::Algebra::ListOperator include RDF::N3::Algebra::Builtin NAME = :strScrape + URI = RDF::N3::Str.scrape ## # @param [RDF::N3::List] list diff --git a/lib/rdf/n3/algebra/str/starts_with.rb b/lib/rdf/n3/algebra/str/starts_with.rb index 5d63eb1..41bfd87 100644 --- a/lib/rdf/n3/algebra/str/starts_with.rb +++ b/lib/rdf/n3/algebra/str/starts_with.rb @@ -2,6 +2,7 @@ module RDF::N3::Algebra::Str # True iff the subject string starts with the object string. class StartsWith < RDF::N3::Algebra::ResourceOperator NAME = :strStartsWith + URI = RDF::N3::Str.startsWith ## # Resolves inputs as strings. diff --git a/lib/rdf/n3/algebra/time/day.rb b/lib/rdf/n3/algebra/time/day.rb index 97e1496..4e15591 100644 --- a/lib/rdf/n3/algebra/time/day.rb +++ b/lib/rdf/n3/algebra/time/day.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Time # @see https://www.w3.org/TR/xpath-functions/#func-day-from-dateTime class Day < RDF::N3::Algebra::ResourceOperator NAME = :timeDay + URI = RDF::N3::Time.day ## # The time:day operator takes string or dateTime and extracts the day component. diff --git a/lib/rdf/n3/algebra/time/day_of_week.rb b/lib/rdf/n3/algebra/time/day_of_week.rb index bd48e0c..9eda297 100644 --- a/lib/rdf/n3/algebra/time/day_of_week.rb +++ b/lib/rdf/n3/algebra/time/day_of_week.rb @@ -3,6 +3,7 @@ module RDF::N3::Algebra::Time # For a date-time, its time:dayOfWeek is the the day number within the week, Sunday being 0. class DayOfWeek < RDF::N3::Algebra::ResourceOperator NAME = :timeDayOfWeek + URI = RDF::N3::Time.dayOfWeek ## # The time:dayOfWeek operator takes string or dateTime and returns the 0-based day of the week. diff --git a/lib/rdf/n3/algebra/time/gm_time.rb b/lib/rdf/n3/algebra/time/gm_time.rb index b2cbfcd..9b170ef 100644 --- a/lib/rdf/n3/algebra/time/gm_time.rb +++ b/lib/rdf/n3/algebra/time/gm_time.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Time # @see https://www.w3.org/TR/xpath-functions/#func-current-dateTime class GmTime < RDF::N3::Algebra::ResourceOperator NAME = :timeGmTime + URI = RDF::N3::Time.gmTime ## # The time:gmTime operator takes string or dateTime and returns current time formatted according to the subject. diff --git a/lib/rdf/n3/algebra/time/hour.rb b/lib/rdf/n3/algebra/time/hour.rb index 977d5e7..1a36e8c 100644 --- a/lib/rdf/n3/algebra/time/hour.rb +++ b/lib/rdf/n3/algebra/time/hour.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Time # @see https://www.w3.org/TR/xpath-functions/#func-hours-from-dateTime class Hour < RDF::N3::Algebra::ResourceOperator NAME = :timeHour + URI = RDF::N3::Time.hour ## # The time:hour operator takes string or dateTime and extracts the hour component. diff --git a/lib/rdf/n3/algebra/time/in_seconds.rb b/lib/rdf/n3/algebra/time/in_seconds.rb index 9bdcf66..8fc346b 100644 --- a/lib/rdf/n3/algebra/time/in_seconds.rb +++ b/lib/rdf/n3/algebra/time/in_seconds.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Time # @see https://www.w3.org/TR/xpath-functions/#func-timezone-from-dateTime class InSeconds < RDF::N3::Algebra::ResourceOperator NAME = :timeInSeconds + URI = RDF::N3::Time.inSeconds ## # The time:inseconds operator takes may have either a bound subject or object. diff --git a/lib/rdf/n3/algebra/time/local_time.rb b/lib/rdf/n3/algebra/time/local_time.rb index 9fc9ed6..4a28e41 100644 --- a/lib/rdf/n3/algebra/time/local_time.rb +++ b/lib/rdf/n3/algebra/time/local_time.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Time # @see https://www.w3.org/TR/xpath-functions/#func-current-dateTime class LocalTime < RDF::N3::Algebra::ResourceOperator NAME = :timeLocalTime + URI = RDF::N3::Time.localTime ## # The time:localTime operator takes string or dateTime and returns current time formatted according to the subject. diff --git a/lib/rdf/n3/algebra/time/minute.rb b/lib/rdf/n3/algebra/time/minute.rb index a66f0fa..02dc5cf 100644 --- a/lib/rdf/n3/algebra/time/minute.rb +++ b/lib/rdf/n3/algebra/time/minute.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Time # @see https://www.w3.org/TR/xpath-functions/#func-minutes-from-dateTime class Minute < RDF::N3::Algebra::ResourceOperator NAME = :timeMinute + URI = RDF::N3::Time.minute ## # The time:minute operator takes string or dateTime and extracts the minute component. diff --git a/lib/rdf/n3/algebra/time/month.rb b/lib/rdf/n3/algebra/time/month.rb index aa8620d..222fa54 100644 --- a/lib/rdf/n3/algebra/time/month.rb +++ b/lib/rdf/n3/algebra/time/month.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Time # @see https://www.w3.org/TR/xpath-functions/#func-month-from-dateTime class Month < RDF::N3::Algebra::ResourceOperator NAME = :timeMonth + URI = RDF::N3::Time.month ## # The time:month operator takes string or dateTime and extracts the month component. diff --git a/lib/rdf/n3/algebra/time/second.rb b/lib/rdf/n3/algebra/time/second.rb index ed8a3e8..db1a4b1 100644 --- a/lib/rdf/n3/algebra/time/second.rb +++ b/lib/rdf/n3/algebra/time/second.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Time # @see https://www.w3.org/TR/xpath-functions/#func-seconds-from-dateTime class Second < RDF::N3::Algebra::ResourceOperator NAME = :timeSecond + URI = RDF::N3::Time.second ## # The time:second operator takes string or dateTime and extracts the seconds component. diff --git a/lib/rdf/n3/algebra/time/timezone.rb b/lib/rdf/n3/algebra/time/timezone.rb index 16b57a6..f1de423 100644 --- a/lib/rdf/n3/algebra/time/timezone.rb +++ b/lib/rdf/n3/algebra/time/timezone.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Time # @see https://www.w3.org/TR/xpath-functions/#func-timezone-from-dateTime class Timezone < RDF::N3::Algebra::ResourceOperator NAME = :timeTimezone + URI = RDF::N3::Time.timeZone ## # The time:timeZone operator takes string or dateTime and extracts the timeZone component. diff --git a/lib/rdf/n3/algebra/time/year.rb b/lib/rdf/n3/algebra/time/year.rb index a31cac1..58832bd 100644 --- a/lib/rdf/n3/algebra/time/year.rb +++ b/lib/rdf/n3/algebra/time/year.rb @@ -5,6 +5,7 @@ module RDF::N3::Algebra::Time # @see https://www.w3.org/TR/xpath-functions/#func-year-from-dateTime class Year < RDF::N3::Algebra::ResourceOperator NAME = :timeYear + URI = RDF::N3::Time.year ## # The time:year operator takes string or dateTime and extracts the year component. From a733aa75a2888f5a6f85fae38a41d54c04fb1aa8 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 25 Oct 2020 16:38:02 -0700 Subject: [PATCH 152/193] Update PDD info in the README. --- CONTRIBUTING.md | 6 ++++-- README.md | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cabeb41..6b3faa2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,9 +28,11 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to manage devel enough, be assured we will eventually add you in there. * Do note that in order for us to merge any non-trivial changes (as a rule of thumb, additions larger than about 15 lines of code), we need an - explicit [public domain dedication][PDD] on record from you. + explicit [public domain dedication][PDD] on record from you, + which you will be asked to agree to on the first commit to a repo within the organization. + Note that the agreement applies to all repos in the [Ruby RDF](https://github.com/ruby-rdf/) organization. [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 +[PDD]: https://unlicense.org/#unlicensing-contributions [pr]: https://github.com/ruby-rdf/rdf-n3/compare/ diff --git a/README.md b/README.md index 07ef4e6..28b4d8a 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,9 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange develo list in the the `README`. Alphabetical order applies. * Do note that in order for us to merge any non-trivial changes (as a rule of thumb, additions larger than about 15 lines of code), we need an - explicit [public domain dedication][PDD] on record from you. + explicit [public domain dedication][PDD] on record from you, + which you will be asked to agree to on the first commit to a repo within the organization. + Note that the agreement applies to all repos in the [Ruby RDF](https://github.com/ruby-rdf/) organization. ## License @@ -257,7 +259,7 @@ see or the accompanying {file:UNLICENSE} file. [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 +[PDD]: https://unlicense.org/#unlicensing-contributions [SPARQL S-Expressions]: https://jena.apache.org/documentation/notes/sse.html [W3C N3 Community Group]: https://www.w3.org/community/n3-dev/ [N3]: https://w3c.github.io/N3/spec/ From b9723e62a54df844e9e574be886e7ccf9d9068b9 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 27 Oct 2020 15:05:18 -0700 Subject: [PATCH 153/193] Results for log:conclusion including inferred recursive formulae. (Note: infers extra triples). --- lib/rdf/n3/algebra/formula.rb | 96 ++++++++++++------------- lib/rdf/n3/algebra/list/in.rb | 2 +- lib/rdf/n3/algebra/list/member.rb | 2 +- lib/rdf/n3/algebra/list_operator.rb | 4 +- lib/rdf/n3/algebra/log/conclusion.rb | 19 +++-- lib/rdf/n3/algebra/log/implies.rb | 9 ++- lib/rdf/n3/algebra/math/sum.rb | 2 +- lib/rdf/n3/algebra/resource_operator.rb | 4 +- lib/rdf/n3/algebra/str/replace.rb | 2 +- lib/rdf/n3/reasoner.rb | 44 +++++------- lib/rdf/n3/repository.rb | 11 +-- script/tc | 4 +- spec/reasoner_spec.rb | 18 ++--- spec/suite_reasoner_spec.rb | 4 +- 14 files changed, 117 insertions(+), 104 deletions(-) mode change 100644 => 100755 script/tc diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 4d2c87d..e0b2ee7 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -99,6 +99,7 @@ def self.from_enumerable(enumerable, **options) def dup new_ops = operands.map(&:dup) graph_name = RDF::Node.intern(new_ops.hash) + log_debug("formula") {"dup: #{self.graph_name} to #{graph_name}"} that = self.class.new(*new_ops, **@options.merge(graph_name: graph_name, formulae: formulae)) that.formulae[graph_name] = that that @@ -189,6 +190,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new # @return [RDF::N3::List] # @see SPARQL::Algebra::Expression.evaluate def evaluate(bindings, formulae:, **options) + return self if bindings.empty? this = dup # Maintain formula relationships formulae {|k, v| this.formulae[k] ||= v} @@ -240,63 +242,67 @@ def each(&block) log_debug("(formula apply)") {solution.to_sxp} # Yield each variable statement which is constant after applying solution - patterns.each do |pattern| - terms = {} - [:subject, :predicate, :object].each do |part| - terms[part] = case o = pattern.send(part) - when RDF::Query::Variable - if solution[o] && solution[o].list? - solution[o].each_statement(&block) - # Bind the list subject, and emit list statements - solution[o].subject - elsif solution[o] && solution[o].formula? - form = solution[o] - # uses the graph_name of the formula, and yields statements from the formula - log_depth do - form.solutions = RDF::Query::Solutions(solution) - form.each do |stmt| - stmt.graph_name = form.graph_name - log_debug("(formula add from var form)") {stmt.to_sxp} - block.call(stmt) + log_depth do + patterns.each do |pattern| + terms = {} + [:subject, :predicate, :object].each do |part| + terms[part] = case o = pattern.send(part) + when RDF::Query::Variable + if solution[o] && solution[o].list? + solution[o].each_statement(&block) + # Bind the list subject, and emit list statements + solution[o].subject + elsif solution[o] && solution[o].formula? + form = solution[o] + # uses the graph_name of the formula, and yields statements from the formula + log_depth do + form.solutions = RDF::Query::Solutions(solution) + log_debug("(formula from var form)") {form.graph_name.to_sxp} + form.each do |stmt| + stmt.graph_name ||= form.graph_name + log_debug("(formula add from var form)") {stmt.to_sxp} + block.call(stmt) + end end + form.graph_name + else + solution[o] || o end - form.graph_name - else - solution[o] - end - when RDF::N3::List - o.variable? ? o.evaluate(solution.bindings, formulae: formulae) : o - when RDF::N3::Algebra::Formula - # uses the graph_name of the formula, and yields statements from the formula. No solutions are passed in. - log_depth do + when RDF::N3::List + o.variable? ? o.evaluate(solution.bindings, formulae: formulae) : o + when RDF::N3::Algebra::Formula + # uses the graph_name of the formula, and yields statements from the formula. No solutions are passed in. + log_debug("(formula from form)") {o.graph_name.to_sxp} o.solutions = RDF::Query::Solutions(solution) o.each do |stmt| - stmt.graph_name = o.graph_name + stmt.graph_name ||= o.graph_name log_debug("(formula add from form)") {stmt.to_sxp} block.call(stmt) end + o.graph_name + else + o end - o.graph_name - else - o end - end - statement = RDF::Statement.from(terms) + statement = RDF::Statement.from(terms) + log_debug("(formula add)") {statement.to_sxp} - # Sanity checking on statement - if statement.variable? - log_debug("(formula skip)") {statement.to_sxp} - next + block.call(statement) 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)}} + log_depth do + sub_ops.each do |op| + log_debug("(formula sub_op)") {SXP::Generator.string op.to_sxp_bin} + op.each do |stmt| + log_debug("(formula add from sub_op)") {stmt.to_sxp} + block.call(stmt) + end + end + end end # Set solutions @@ -321,12 +327,6 @@ def graph_name=(name) @options[:graph_name] = name end - ## - # Statements are the operands - # - # @return [Array] - alias_method :statements, :operands - ## # Patterns memoizer, from the operands which are statements. def patterns @@ -364,7 +364,7 @@ def sub_ops # Return the variables contained within this formula # @return [Array] def vars - (statements.vars + sub_ops.vars).flatten.compact + operands.vars.flatten.compact end ## diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index 5b39238..d2afe9e 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -42,7 +42,7 @@ def execute(queryable, solutions:, **options) else nil end - end.flatten.compact) + end.flatten.compact.uniq) end end end diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index 85ed4cd..9585ebc 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -38,7 +38,7 @@ def execute(queryable, solutions:, **options) else nil end - end.flatten.compact) + end.flatten.compact.uniq) end end end diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index d575bce..864c96d 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -6,6 +6,8 @@ class ListOperator < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Update include RDF::N3::Algebra::Builtin + NAME = :listOperator + ## # The operator takes a list and provides a mechanism for subclasses to operate over (and validate) that list argument. # @@ -39,7 +41,7 @@ def execute(queryable, solutions:, **options) log_debug(self.class.const_get(:NAME)) {"result: true"} solution end - end.compact) + end.compact.uniq) end ## diff --git a/lib/rdf/n3/algebra/log/conclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb index 16b2eaf..e2fa621 100644 --- a/lib/rdf/n3/algebra/log/conclusion.rb +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -20,13 +20,11 @@ def resolve(resource, position:) log_depth do reasoner = RDF::N3::Reasoner.new(resource, @options) - conclusions = [].extend(RDF::Enumerable) + conclusions = RDF::N3::Repository.new reasoner.execute(think: true, logger: false) {|stmt| conclusions << stmt} # The result is a formula containing the conclusions - form = RDF::N3::Algebra::Formula.from_enumerable(conclusions).dup - log_info('(logConclusion result)') {SXP::Generator.string(form.to_sxp_bin).gsub(/\s+/m, ' ')} - form + RDF::N3::Algebra::Formula.from_enumerable(conclusions).dup end end @@ -47,5 +45,18 @@ def valid?(subject, object) def input_operand operands.first end + + ## + # Yields statements, and de-asserts `inferred` from the subject. + # + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + def each(&block) + super do |stmt| + block.call(RDF::Statement.from(stmt.to_quad)) + end + end end end diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 965880d..f6d5829 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -59,12 +59,9 @@ def execute(queryable, solutions:, **options) # @yieldreturn [void] ignored def each(&block) @solutions ||= RDF::Query::Solutions.new - #super { |st| - # log_debug {"logImplies super #{st.to_sxp}"} - # block.call(st) - #} - log_depth do + super + @solutions.each do |solution| log_debug("(logImplies each) solution") {SXP::Generator.string @solutions.to_sxp_bin} object = operand(1).evaluate(solution.bindings, formulae: formulae) @@ -73,7 +70,9 @@ def each(&block) object.solutions = RDF::Query::Solutions(solution) # Yield inferred statements + #require 'byebug'; byebug if solution[:y] object.each do |statement| + log_debug(("(logImplies each) infer\s")) {statement.to_sxp} block.call(RDF::Statement.from(statement.to_quad, inferred: true)) end end diff --git a/lib/rdf/n3/algebra/math/sum.rb b/lib/rdf/n3/algebra/math/sum.rb index 64dbd4f..dac1f86 100644 --- a/lib/rdf/n3/algebra/math/sum.rb +++ b/lib/rdf/n3/algebra/math/sum.rb @@ -25,7 +25,7 @@ module RDF::N3::Algebra::Math # @see https://www.w3.org/TR/xpath-functions/#func-numeric-add class Sum < RDF::N3::Algebra::ListOperator NAME = :mathSum - URI = RDF::N3::Math.sum + URI = RDF::N3::Math[:sum] ## # Evaluates to the sum of the list elements diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 868b1e2..a468cae 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -6,6 +6,8 @@ class ResourceOperator < SPARQL::Algebra::Operator::Binary include SPARQL::Algebra::Update include RDF::N3::Algebra::Builtin + NAME = :resourceOperator + ## # The operator takes a literal and provides a mechanism for subclasses to operate over (and validate) that argument. # @@ -53,7 +55,7 @@ def execute(queryable, solutions:, **options) log_debug(self.class.const_get(:NAME)) {"result: true"} solution end - end.compact) + end.compact.uniq) end ## diff --git a/lib/rdf/n3/algebra/str/replace.rb b/lib/rdf/n3/algebra/str/replace.rb index 5aef0bc..fe8e619 100644 --- a/lib/rdf/n3/algebra/str/replace.rb +++ b/lib/rdf/n3/algebra/str/replace.rb @@ -24,7 +24,7 @@ def resolve(list) # @param [RDF::N3::List] list # @return [Boolean] def validate(list) - if super && list.length == 3 + if super && list.length == 3 && list.to_a.all?(&:literal?) true else log_error(NAME) {"list must have exactly three entries: #{list.to_sxp}"} diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index d4aef59..7977145 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -117,37 +117,31 @@ def execute(**options, &block) log_debug("reasoner: knowledge_base") {SXP::Generator.string(knowledge_base.statements.to_sxp_bin)} # If thinking, continuously execute until results stop growing - if options[:think] - count = -1 - log_info("reasoner: think start") { "count: #{count}"} - solutions = RDF::Query::Solutions(RDF::Query::Solution.new) - while knowledge_base.count > count - log_info("reasoner: think do") { "count: #{count}"} + count = -1 + log_info("reasoner: start") { "count: #{count}"} + solutions = RDF::Query::Solutions(RDF::Query::Solution.new) + while knowledge_base.count > count + log_info("reasoner: do") { "count: #{count}"} + count = knowledge_base.count + log_depth {formula.execute(knowledge_base, solutions: solutions, **options)} + knowledge_base << formula + solutions = RDF::Query::Solutions(RDF::Query::Solution.new) if solutions.empty? + log_debug("reasoner: solutions") {SXP::Generator.string solutions.to_sxp_bin} + log_debug("reasoner: datastore") {SXP::Generator.string knowledge_base.statements.to_sxp_bin} + log_info("reasoner: inferred") {SXP::Generator.string knowledge_base.statements.select(&:inferred?).to_sxp_bin} + @formula = nil # cause formula to be re-calculated from knowledge-base + unless options[:think] count = knowledge_base.count - log_depth {formula.execute(knowledge_base, solutions: solutions, **options)} - knowledge_base << formula - solutions = RDF::Query::Solutions(RDF::Query::Solution.new) if solutions.empty? - log_debug("reasoner: solutions") {SXP::Generator.string solutions.to_sxp_bin} - log_debug("reasoner: datastore") {SXP::Generator.string knowledge_base.statements.to_sxp_bin} - log_info("reasoner: inferred") {SXP::Generator.string knowledge_base.statements.select(&:inferred?).to_sxp_bin} - @formula = nil # cause formula to be re-calculated from knowledge-base + break end - log_info("reasoner: think end") { "count: #{count}"} - else - # Run one iteration - log_info("reasoner: rules start") { "count: #{count}"} - log_depth {formula.execute(knowledge_base, **options)} - knowledge_base << formula - log_info("reasoner: rules end") { "count: #{count}"} end - - log_debug("reasoner: datastore") {SXP::Generator.string knowledge_base.statements.to_sxp_bin} - log_debug("reasoner: inferred") {SXP::Generator.string knowledge_base.statements.select(&:inferred?).to_sxp_bin} + log_info("reasoner: end") { "count: #{count}"} # Add updates back to mutable, containg builtins and variables. @mutable << knowledge_base - block_given? ? conclusions(&block) : self + each(&block) if block_given? + self end alias_method :reason!, :execute @@ -197,7 +191,7 @@ def data(&block) alias_method :each_datum, :data ## - # Returns an enumerator for {#conclusions}. + # Returns an enumerator for {#data}. # FIXME: enum_for doesn't seem to be working properly # in JRuby 1.7, so specs are marked pending # diff --git a/lib/rdf/n3/repository.rb b/lib/rdf/n3/repository.rb index d204dfc..ae7201f 100644 --- a/lib/rdf/n3/repository.rb +++ b/lib/rdf/n3/repository.rb @@ -300,15 +300,18 @@ def has_statement_in?(data, statement) def insert_to(data, statement) raise ArgumentError, "Statement #{statement.inspect} is incomplete" if statement.incomplete? + s, p, o, c = statement.to_quad + c ||= DEFAULT_GRAPH unless has_statement_in?(data, statement) - s, p, o, c = statement.to_quad - c ||= DEFAULT_GRAPH - data = data.has_key?(c) ? data.dup : data.merge(c => {}) data[c] = data[c].has_key?(s) ? data[c].dup : data[c].merge(s => {}) data[c][s] = data[c][s].has_key?(p) ? data[c][s].dup : data[c][s].merge(p => {}) data[c][s][p] = data[c][s][p].merge(o => statement.options) end + + # If statement is inferred, make sure that it is marked as inferred in the dataset. + data[c][s][p][o][:inferred] = true if statement.options[:inferred] + data end @@ -321,7 +324,7 @@ def delete_from(data, statement) g = DEFAULT_GRAPH unless supports?(:graph_name) g ||= DEFAULT_GRAPH - os = data[g][s][p].dup.delete(o) + os = data[g][s].dup.delete_if {|k,v| k == o} ps = os.empty? ? data[g][s].dup.delete_if {|k,v| k == p} : data[g][s].merge(p => os) ss = ps.empty? ? data[g].dup.delete_if {|k,v| k == s} : data[g].merge(s => ps) return ss.empty? ? data.dup.delete_if {|k,v| k == g} : data.merge(g => ss) diff --git a/script/tc b/script/tc old mode 100644 new mode 100755 index 7a09a1c..1286975 --- a/script/tc +++ b/script/tc @@ -68,7 +68,9 @@ def run_tc(man, tc, **options) logger: logger }.merge(options) - reader = RDF::N3::Reader.new(tc.input, **options) + reader_options = options.dup + reader_options[:logger] = false if tc.reason? + reader = RDF::N3::Reader.new(tc.input, **reader_options) graph = RDF::N3::Repository.new result = nil diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 6c543fa..e8511cd 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -21,12 +21,12 @@ { "conclusion-super-simple" => { input: %( - { { - { } => { a } . - . - } log:conclusion ?y - } => { ?y a :TestResult }. + { + { } => { a } . + . + } log:conclusion ?y + } => { ?y a :TestResult }. ), expect: %( { @@ -34,8 +34,7 @@ a . { .} => { a .} . } a :TestResult . - ), - pending: true + ) }, "conclusion-simple" => { input: %( @@ -53,14 +52,15 @@ { .} => { a .} . } a :TestResult . ), - pending: true + pending: "extra triple" }, }.each do |name, options| it name do logger.info "input: #{options[:input]}" + options = {data: false, filter: true}.merge(options) pending(options[:pending]) if options[:pending] expected = parse(options[:expect]) - result = reason(options[:input], data: false, filter: true) + result = reason(options[:input], **options) expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end end diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 898afe0..5c9eac8 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -21,12 +21,12 @@ specify "#{t.name}: #{t.comment}" do case t.id.split('#').last when *%w{cwm_unify_unify1 cwm_includes_builtins - cwm_includes_t10 cwm_includes_t11 cwm_includes_quantifiers_limited + cwm_includes_t10 cwm_includes_t11 cwm_includes_conclusion_simple cwm_includes_conclusion} pending "log:includes etc." when *%w{cwm_supports_simple cwm_string_roughly} pending "Uses unsupported builtin" - when *%w{cwm_string_uriEncode} + when *%w{cwm_string_uriEncode cwm_includes_quantifiers_limited} skip "Blows up" when *%w{cwm_list_builtin_generated_match} skip("List reification") From dcc7d6dd8caaf7db351aa3c0f235541cf2d78403 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 28 Oct 2020 14:03:21 -0700 Subject: [PATCH 154/193] Fixes repository issue ignoring variables and adds some debug level updates for understanding writing. --- lib/rdf/n3/repository.rb | 5 +---- lib/rdf/n3/writer.rb | 30 +++++++++++++++++------------- spec/extensions_spec.rb | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/rdf/n3/repository.rb b/lib/rdf/n3/repository.rb index ae7201f..a50b8e6 100644 --- a/lib/rdf/n3/repository.rb +++ b/lib/rdf/n3/repository.rb @@ -216,7 +216,6 @@ def query_pattern(pattern, **options, &block) [] end ss.each do |s, ps| - next if s.is_a?(RDF::Query::Variable) ps = if predicate.nil? || predicate.is_a?(RDF::Query::Variable) ps elsif predicate.is_a?(RDF::N3::List) @@ -230,9 +229,7 @@ def query_pattern(pattern, **options, &block) [] end ps.each do |p, os| - next if p.is_a?(RDF::Query::Variable) os.each do |o, object_options| - next if o.is_a?(RDF::Query::Variable) next unless object.nil? || object.eql?(o) yield RDF::Statement.new(s, p, o, object_options.merge(graph_name: c.equal?(DEFAULT_GRAPH) ? nil : c)) end @@ -324,7 +321,7 @@ def delete_from(data, statement) g = DEFAULT_GRAPH unless supports?(:graph_name) g ||= DEFAULT_GRAPH - os = data[g][s].dup.delete_if {|k,v| k == o} + os = data[g][s][p].dup.delete_if {|k,v| k == o} ps = os.empty? ? data[g][s].dup.delete_if {|k,v| k == p} : data[g][s].merge(p => os) ss = ps.empty? ? data[g].dup.delete_if {|k,v| k == s} : data[g].merge(s => ps) return ss.empty? ? data.dup.delete_if {|k,v| k == g} : data.merge(g => ss) diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 0ae3e4b..814432d 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -566,7 +566,7 @@ def predicate(resource) when RDF::N3::Log.implies @output.write("=>") else - path(resource, :predicate) + log_depth {path(resource, :predicate)} end end @@ -575,13 +575,15 @@ def objectList(objects) log_debug("objectList") {objects.inspect} return if objects.empty? - objects.each_with_index do |obj, i| - if i > 0 && (formula?(obj, :object) || blankNodePropertyList?(obj, :object)) - @output.write ", " - elsif i > 0 - @output.write ",\n#{indent(4)}" + log_depth do + objects.each_with_index do |obj, i| + if i > 0 && (formula?(obj, :object) || blankNodePropertyList?(obj, :object)) + @output.write ", " + elsif i > 0 + @output.write ",\n#{indent(4)}" + end + path(obj, :object) end - path(obj, :object) end end @@ -599,12 +601,14 @@ def predicateObjectList(subject, from_bpl = false) 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 - predicate(prop) - @output.write(" ") - objectList(properties[prop]) + log_depth do + prop_list.each_with_index do |prop, i| + begin + @output.write(";\n#{indent(2)}") if i > 0 + predicate(prop) + @output.write(" ") + objectList(properties[prop]) + end end end properties.keys.length diff --git a/spec/extensions_spec.rb b/spec/extensions_spec.rb index d71a2d4..50772c6 100644 --- a/spec/extensions_spec.rb +++ b/spec/extensions_spec.rb @@ -83,7 +83,7 @@ let(:node) {RDF::Node.intern("a")} it "returns itself if not bound" do - expect(node.evaluate({})).to eq node + expect(node.evaluate({}, formulae: {})).to eq node end end end From 93f20af1f6988ca6f67b0b22fb6d8550a08e47ca Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 28 Oct 2020 16:21:58 -0700 Subject: [PATCH 155/193] Add support for log:outputString; requires log:implies to evaluate object as well as subject. --- Gemfile | 2 +- README.md | 2 +- lib/rdf/n3/algebra/log/implies.rb | 5 ++++ lib/rdf/n3/algebra/log/output_string.rb | 32 +++++++++++++++++++++++++ lib/rdf/n3/reasoner.rb | 13 +++++++++- script/parse | 13 ++++++++-- script/tc | 26 +++++++++++++------- spec/suite_helper.rb | 1 + spec/suite_reasoner_spec.rb | 7 +++++- 9 files changed, 87 insertions(+), 14 deletions(-) diff --git a/Gemfile b/Gemfile index 425f827..d5b08d9 100644 --- a/Gemfile +++ b/Gemfile @@ -24,6 +24,6 @@ group :development, :test do end group :debug do - gem 'awesome_print', github: 'MatthiasWinkelmann/awesome_print' + gem 'awesome_print', github: 'akshaymohite/awesome_print' gem "byebug", platform: :mri end diff --git a/README.md b/README.md index 28b4d8a..9c45c6d 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Reasoning is discussed in the [Design Issues][] document. * `log:n3String` (See {RDF::N3::Algebra::Log::N3String}) * `log:notEqualTo` (See {RDF::N3::Algebra::Log::NotEqualTo}) * `log:notIncludes` (See {RDF::N3::Algebra::Log::NotIncludes}) - * `log:outputString` (not implemented yet - See {RDF::N3::Algebra::Log::OutputString}) + * `log:outputString` See {RDF::N3::Algebra::Log::OutputString}) * `log:parsedAsN3` (See {RDF::N3::Algebra::Log::ParsedAsN3}) * `log:semantics` (See {RDF::N3::Algebra::Log::Semantics}) diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index f6d5829..0244435 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -37,6 +37,11 @@ def execute(queryable, solutions:, **options) solns = log_depth {subject.execute(queryable, solutions: RDF::Query::Solutions(solution), **options)} + # Execute object as well (typically used for log:outputString) + solns = solns.map do |soln| + log_depth {object.execute(queryable, solutions: RDF::Query::Solutions(soln), **options)} + end.flatten.compact + # filter solutions where not all variables in antecedant are bound. vars = subject.universal_vars solns = solns.filter do |soln| diff --git a/lib/rdf/n3/algebra/log/output_string.rb b/lib/rdf/n3/algebra/log/output_string.rb index 8b082c0..5bc533b 100644 --- a/lib/rdf/n3/algebra/log/output_string.rb +++ b/lib/rdf/n3/algebra/log/output_string.rb @@ -4,5 +4,37 @@ module RDF::N3::Algebra::Log class OutputString < RDF::N3::Algebra::ResourceOperator NAME = :logOutputString URI = RDF::N3::Log.outputString + + ## + # Resolves inputs as strings. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + SPARQL::Algebra::Expression.cast(RDF::XSD.string, resource) if resource.term? + end + + ## + # Returns `term2`, but adds `term2` as an output keyed on `term1`. + # + # @param [RDF::Term] term1 + # an RDF term + # @param [RDF::Term] term2 + # an RDF term + # @return [RDF::Literal::Boolean] `true` or `false` + # @raise [TypeError] if either operand is not an RDF term or operands are not comperable + # + # @see RDF::Term#== + def apply(term1, term2) + (@options[:strings][term1.to_s] ||= []) << term2.to_s + term2 + end + + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end end end diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 7977145..3626124 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -60,7 +60,7 @@ def self.open(file) # @yieldreturn [void] ignored # @return [RDF::N3::Reasoner] def initialize(input, **options, &block) - @options = options + @options = options.merge(strings: {}) # for --strings and log:outputString @mutable = case input when RDF::Mutable then input when RDF::Enumerable then RDF::N3::Repository.new {|r| r << input} @@ -244,6 +244,17 @@ def enum_conclusions end end + ## + # Returns the concatenated strings from log:outputString + # + # @return [String] + def strings + @options[:strings]. + sort_by {|k, v| k}. + map {|(k,v)| v.join("")}. + join("") + end + ## # Returns the top-level formula for this file. # diff --git a/script/parse b/script/parse index 5db6cc6..a62e5e6 100755 --- a/script/parse +++ b/script/parse @@ -24,9 +24,9 @@ def run(input, **options) STDERR.puts "Reason" if $verbose # Parse into a new reasoner and evaluate reader_class.new(input, **parser_options.merge(logger: nil)) do |reader| + repo = RDF::N3::Repository.new reasoner = RDF::N3::Reasoner.new(reader, **parser_options) reasoner.reason!(**options) - repo = RDF::N3::Repository.new if options[:conclusions] repo << reasoner.conclusions elsif options[:data] @@ -35,7 +35,14 @@ def run(input, **options) repo << reasoner end num = repo.count - options[:output].puts repo.dump(options[:output_format], prefixes: reader.prefixes, standard_prefixes: true, logger: options[:logger]) + if options[:strings] + options[:output].puts reasoner.strings + else + options[:output].puts repo.dump(options[:output_format], + prefixes: reader.prefixes, + standard_prefixes: true, + logger: options[:logger]) + end end elsif options[:output_format] == :ntriples || options[:quiet] STDERR.puts "Parse nt/quiet" if $verbose @@ -124,6 +131,7 @@ OPT_ARGS = [ ["--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"], + ["--strings", GetoptLong::NO_ARGUMENT, "Dump :s to stdout ordered by :k whereever { :k log:outputString :s }"], ["--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"], @@ -173,6 +181,7 @@ opts.each do |opt, arg| options[:quiet] = true logger.level = Logger::FATAL when '--rules' then options[:rules] = true + when '--strings' then options[:strings] = true when '--think' then options[:think] = true when '--uri' then parser_options[:base_uri] = arg when '--validate' then parser_options[:validate] = true diff --git a/script/tc b/script/tc index 1286975..da9f3e5 100755 --- a/script/tc +++ b/script/tc @@ -128,18 +128,28 @@ def run_tc(man, tc, **options) end result = "failed" end - STDERR.puts "\nResult: #{repo.dump(:n3, base_uri: RDF::URI(tc.base).parent, standard_prefixes: true)}" if options[:verbose] + if options[:verbose] + if tc.options["strings"] + STDERR.puts "\nResult: #{reasoner.strings}" + else + STDERR.puts "\nResult: #{repo.dump(:n3, base_uri: RDF::URI(tc.base).parent, standard_prefixes: true)}" + end + end - output_graph = RDF::N3::Repository.load(tc.result, base_uri: tc.base) + if tc.options["strings"] + result = reasoner.strings == tc.expected + else + output_graph = RDF::N3::Repository.load(tc.result, base_uri: tc.base) - # Check against expanded triples from repo - expanded_repo = RDF::Repository.new do |r| - repo.each_expanded_statement do |st| - r << st + # Check against expanded triples from repo + expanded_repo = RDF::Repository.new do |r| + repo.each_expanded_statement do |st| + r << st + end end - end - result = expanded_repo.isomorphic_with?(output_graph) ? "passed" : "failed" + result = expanded_repo.isomorphic_with?(output_graph) ? "passed" : "failed" + end else result ||= "passed" end diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index e3f80dc..0065b58 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -86,6 +86,7 @@ module SuiteTest "options": {"@id": "test:options", "@type": "@id"}, "result": {"@id": "mf:result", "@type": "@id"}, "rules": {"@id": "test:rules", "@type": "xsd:boolean"}, + "strings": {"@id": "test:strings", "@type": "xsd:boolean"}, "think": {"@id": "test:think", "@type": "xsd:boolean"} }, "@type": "mf:Manifest", diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 5c9eac8..335c56e 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -26,7 +26,8 @@ pending "log:includes etc." when *%w{cwm_supports_simple cwm_string_roughly} pending "Uses unsupported builtin" - when *%w{cwm_string_uriEncode cwm_includes_quantifiers_limited} + when *%w{cwm_string_uriEncode cwm_includes_quantifiers_limited + cwm_list_append} skip "Blows up" when *%w{cwm_list_builtin_generated_match} skip("List reification") @@ -56,6 +57,10 @@ repo << reasoner.conclusions elsif t.options["data"] repo << reasoner.data + elsif t.options["strings"] + t.logger.info "result:\n#{reasoner.strings}" + expect(reasoner.strings).to produce(t.expected, t) + next else repo << reasoner end From 859d8d99e202f23cca49b581b260f49ea4b43a41 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 30 Oct 2020 12:55:15 -0700 Subject: [PATCH 156/193] Make quickvars have global scope, and write back out using `?var` syntax. --- lib/rdf/n3/algebra/log/conclusion.rb | 2 +- lib/rdf/n3/reader.rb | 11 +++-------- lib/rdf/n3/writer.rb | 14 ++++++-------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/rdf/n3/algebra/log/conclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb index e2fa621..2280c1b 100644 --- a/lib/rdf/n3/algebra/log/conclusion.rb +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -19,7 +19,7 @@ def resolve(resource, position:) return resource unless position == :subject log_depth do - reasoner = RDF::N3::Reasoner.new(resource, @options) + reasoner = RDF::N3::Reasoner.new(resource, **@options) conclusions = RDF::N3::Repository.new reasoner.execute(think: true, logger: false) {|stmt| conclusions << stmt} diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index b3fd477..4e1a6ad 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -637,7 +637,7 @@ def read_blankNode end ## - # Read a quickVar + # Read a quickVar, having global scope. # # [30] quickVar ::= QUICK_VAR_NAME # @@ -647,13 +647,8 @@ def read_quickVar prod(:quickVar) do token = @lexer.shift value = token.value.sub('?', '') - var = find_var(value) - return var if var - - # Create a new variable, scoped to the parent formula and add it to the variables list for the parent and this formula. - add_var_to_formula(formulae[-2], value, - add_var_to_formula(formulae.last, value, - univar(value, scope: @formulae[-2]))) + iri = ns(nil, "#{value}_quick") + variables[nil][iri] ||= univar(iri, scope: nil) end end end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 814432d..08a1717 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -232,12 +232,6 @@ def get_pname(resource) nil end - # if resource is a variable (universal or extential), map to a shorter name - if (@universals + @existentials).include?(resource) && - resource.to_s.match(/#([^_]+)_(?:(?:\.form[^_]+)_)?_ext$/) - pname = $1 - end - # Make sure pname is a valid pname if pname md = PNAME_LN.match(pname) || PNAME_NS.match(pname) @@ -431,7 +425,7 @@ def preprocess @options[:prefixes] = {} # Will define actual used when matched repo.each {|statement| preprocess_statement(statement)} - vars = repo.enum_term.to_a.uniq.select {|r| r.is_a?(RDF::Query::Variable)} + vars = repo.enum_term.to_a.uniq.select {|r| r.is_a?(RDF::Query::Variable) && !r.to_s.end_with?('_quick')} @universals = vars.reject(&:existential?) @existentials = vars - @universals end @@ -529,7 +523,11 @@ 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.sub(/_ext$/, ''))) + if resource.to_s.end_with?('_quick') + '?' + RDF::URI(resource.name).fragment.sub(/_quick$/, '') + else + format_term(RDF::URI(resource.name.to_s.sub(/_ext$/, ''))) + end elsif resource == RDF.nil "()" else From 91292b0ab1e179c2b4192a5bc78257b9cdb7bba9 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 31 Oct 2020 13:18:32 -0700 Subject: [PATCH 157/193] Rename all bnodes when reading. Move setting default prefix for `:` to catch other use cases. Fix writing native lists containing bnodes. --- lib/rdf/n3/reader.rb | 35 ++++++++++++++++++++++------------- lib/rdf/n3/writer.rb | 25 +++++++++++++------------ script/parse | 2 ++ spec/reader_spec.rb | 33 ++++++++++++++++----------------- spec/writer_spec.rb | 26 +++++++++++--------------- 5 files changed, 64 insertions(+), 57 deletions(-) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 4e1a6ad..06c10f9 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -77,8 +77,9 @@ def initialize(input = $stdin, **options, &block) @formulae = [] @formula_nodes = {} @label_uniquifier = "0" - @bnodes = {} # allocated bnodes by formula - @bn_labler = ".anon0" + @bnodes = {} + @bn_labler = @options[:anon_base].dup + @bn_mapper = {} @variables = {} if options[:base_uri] @@ -251,7 +252,7 @@ def read_directive iri = @lexer.shift error("Expected IRIREF", production: :base, token: iri) unless iri === :IRIREF @options[:base_uri] = process_iri(iri.value[1..-2].gsub(/\s/, '')) - namespace(nil, base_uri.to_s.match?(%r{[#/]$}) ? base_uri : iri("#{base_uri}#")) + namespace(nil, base_uri.to_s.end_with?('#') ? base_uri : iri("#{base_uri}#")) error("base", "#{token} should be downcased") if token.value.start_with?('@') && token.value != '@base' if terminated @@ -500,6 +501,7 @@ def read_blankNodePropertyList @lexer.shift progress("blankNodePropertyList", depth: options[:depth], token: token) node = bnode + debug("blankNodePropertyList: subject", depth: options[:depth]) {node.to_sxp} read_predicateObjectList(node) error("blankNodePropertyList", "Expected closing ']'") unless @lexer.first === ']' @lexer.shift @@ -551,7 +553,7 @@ def read_formula if @lexer.first === '{' prod(:formula, %(})) do @lexer.shift - node = RDF::Node.intern(".form_#{unique_label}") + node = RDF::Node.intern("_form_#{unique_label}") formulae.push(node) formula_nodes[node] = true debug(:formula, depth: @options[:depth]) {"id: #{node}, depth: #{formulae.length}"} @@ -743,8 +745,6 @@ def process_pname(value) error("process_pname", "Use of undefined prefix #{prefix.inspect}") ns(nil, name) else - #debug('process_pname(default_ns)', name, depth: @options[:depth]) - namespace(nil, iri("#{base_uri}#")) unless prefix(nil) ns(nil, name) end debug('process_pname', depth: @options[:depth]) {iri.inspect} @@ -754,18 +754,26 @@ def process_pname(value) # Keep track of allocated BNodes. Blank nodes are allocated to the formula. # Unnnamed bnodes are created using an incrementing labeler for repeatability. def bnode(label = nil) - if label.nil? - label = @bn_labler - @bn_labler.succ! + form_id = formulae.last ? formulae.last.id : '_bn_ground' + if label + # Return previously allocated blank node for. + @bn_mapper[form_id] ||= {} + return @bn_mapper[form_id][label] if @bn_mapper[form_id][label] end - fl = "#{label}_#{formulae.last ? formulae.last.id : 'bn_ground'}" - @bnodes[fl] ||= RDF::Node.intern(fl) + + # Get a fresh label + @bn_labler.succ! while @bnodes[@bn_labler] + + bn = RDF::Node.intern(@bn_labler.to_sym) + @bnodes[@bn_labler] = bn + @bn_mapper[form_id][label] = bn if label + bn end # If not in ground formula, note scope, and if existential def univar(label, scope:, existential: false) value = existential ? "#{label}_ext" : label - value = "#{value}_#{scope.id}" if scope + value = "#{value}#{scope.id}" if scope RDF::Query::Variable.new(value, existential: existential) end @@ -829,9 +837,10 @@ def literal(value, **options) # Decode a PName def ns(prefix = nil, suffix = nil) + namespace(nil, iri("#{base_uri}#")) if prefix.nil? && !prefix(nil) + base = prefix(prefix).to_s suffix = suffix.to_s.sub(/^\#/, "") if base.index("#") - #debug("ns", depth: @options[:depth]) {"base: '#{base}', suffix: '#{suffix}'"} iri(base + suffix.to_s) end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 08a1717..7d420da 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -498,7 +498,6 @@ def collection(node, position) "pos: #{position}, " + "rc: #{ref_count(node)}" end - # return false if position == :subject && ref_count(node) > 0 # recursive lists @output.write("(") log_depth do @@ -510,9 +509,8 @@ def collection(node, position) list.each do |li| log_debug("(list first)") {"#{li}[#{position}]"} @output.write(" ") if index > 0 - path(li, position) + path(li, :object) subject_done(li) - position = :object index += 1 end end @@ -625,12 +623,14 @@ def blankNodePropertyList?(resource, position) def blankNodePropertyList(resource, position) return false unless blankNodePropertyList?(resource, position) - log_debug("blankNodePropertyList") {resource.to_sxp} - subject_done(resource) - @output.write((position == :subject ? "\n#{indent}[" : '[')) - num_props = log_depth {predicateObjectList(resource, true)} - @output.write((num_props > 1 ? "\n#{indent(2)}" : "") + (position == :subject ? '] .' : ']')) - true + #log_depth do + log_debug("blankNodePropertyList") {resource.to_sxp} + subject_done(resource) + @output.write((position == :subject ? "\n#{indent}[" : '[')) + num_props = log_depth {predicateObjectList(resource, true)} + @output.write((num_props > 1 ? "\n#{indent(2)}" : "") + (position == :subject ? '] .' : ']')) + true + #end end # Can subject be represented as a formula? @@ -729,14 +729,15 @@ def with_graph(graph_name) [statement.subject, statement.object].each do |resource| @formulae[resource] = true if resource.node? && - (formula_names.include?(resource) || resource.id.start_with?('.form_')) + (formula_names.include?(resource) || resource.id.start_with?('_form_')) - # First-class list may have members which are formulae + # First-class list may have members which are formulae, and need reference counts if resource.list? resource.each_descendant do |term| + bump_reference(term) @formulae[term] = true if term.node? && - (formula_names.include?(term) || term.id.start_with?('.form_')) + (formula_names.include?(term) || term.id.start_with?('_form_')) end end end diff --git a/script/parse b/script/parse index a62e5e6..948e3c2 100755 --- a/script/parse +++ b/script/parse @@ -127,6 +127,7 @@ OPT_ARGS = [ ["--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"], + ["--no-list-terms", GetoptLong::NO_ARGUMENT, "Use first/rest chain for lists"], ["--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"], @@ -175,6 +176,7 @@ opts.each do |opt, arg| when "--help" then usage() when '--info' then logger.level = Logger::INFO when '--input-format' then options[:input_format] = arg.to_sym + when '--no-list-terms' then parser_options[:list_terms] = false when '--output' then options[:output] = File.open(arg, "w") when '--profile' then options[:profile] = true when '--quiet' diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 372f10a..cdecab6 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -220,7 +220,7 @@ expect(graph.size).to eq 1 statement = graph.statements.first expect(statement.subject).to be_a(RDF::Node) - expect(statement.subject.id).to match(/anon/) + expect(statement.subject.id).to match(/^b\d+/) expect(statement.predicate.to_s).to eq "http://example.org/property" expect(statement.object.to_s).to eq "http://example.org/resource2" end @@ -231,7 +231,7 @@ statement = graph.statements.first expect(statement.subject.to_s).to eq "http://example.org/resource2" expect(statement.predicate).to be_a(RDF::Node) - expect(statement.predicate.id).to match(/anon/) + expect(statement.predicate.id).to match(/^b\d+/) expect(statement.object.to_s).to eq "http://example.org/object" end @@ -242,7 +242,7 @@ expect(statement.subject.to_s).to eq "http://example.org/resource2" expect(statement.predicate.to_s).to eq "http://example.org/property" expect(statement.object).to be_a(RDF::Node) - expect(statement.object.id).to match(/anon/) + expect(statement.object.id).to match(/^b\d+/) end { @@ -598,8 +598,8 @@ it "should set absolute base (trailing /)" do n3 = %(@base . <> :a . <#c> :d .) nt = %( - . - . + . + . ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end @@ -623,21 +623,20 @@ <> :a , <#e>. ) nt = %( - . - . - . - . - . - . + . + . + . + . + . + . ) 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 n3 = %( - @prefix rdf: . + @base . @prefix rdfs: . - @prefix : . :foo a rdfs:Class. :bar :d :c. :a :d :c. @@ -645,9 +644,8 @@ 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#", + nil => "http://test/#", rdfs: "http://www.w3.org/2000/01/rdf-schema#", - nil => "http://test/" }) end end @@ -676,7 +674,7 @@ ) nt = %( . - . + . . ) expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) @@ -1061,9 +1059,10 @@ ) result = parse(n3, repo: @repo, base_uri: "http://a/b") statements = result.statements + logger.debug('sxp') {SXP::Generator.string statements.to_sxp_bin} expect(statements.length).to produce(4, logger) # All three bnodes should be distinct - nodes_of_a = statements.map(&:to_a).flatten.select {|r| r.node? && r.to_s.start_with?('_:a')} + nodes_of_a = statements.map(&:to_a).flatten.select {|r| r.node? && !r.to_s.include?('_form')} expect(nodes_of_a.uniq.count).to produce(3, logger) end diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 8300fca..5e3f6e5 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -116,10 +116,6 @@ 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 \.$)] - }, "standard prefixes" => { input: %( a ; @@ -326,11 +322,10 @@ } => { ?F a :result} . ), regexp: [ - %r(@forAll \.), %r[{\s+\(\s*{\s*:sky :color :blue \.\s+}\s+{]m, %r[{\s+:sky :color :green \.\s+}\s*\)]m, - %r[}\)\s+log:conjunction\s+\s+\.\s+} =>]m, - %r[=>\s+{\s+ a :result \.\s*}]m + %r[}\)\s+log:conjunction\s+\?F\s+\.\s+} =>]m, + %r[=>\s+{\s+\?F a :result \.\s*}]m ] }, }.each do |name, params| @@ -544,7 +539,7 @@ "implies" => { input: %({ _:x :is _:x } => {_:x :is _:x } .), regexp: [ - %r({\s+_:x(\d*) :is _:x\1 \.\s+} => {\s+_:x(\d*) :is _:x\2 \.\s+} \.)m + %r({\s+_:b0 :is _:b0 \.\s+} => {\s+_:b1 :is _:b1 \.\s+} \.)m ] }, "formula simple" => { @@ -607,8 +602,8 @@ ), regexp: [ %r{\(17\) a :TestCase \.}, - %r{\(\) a :TestCase \.}, - %r{ a :RESULT \.}, + %r{\(?x\) a :TestCase \.}, + %r{\?x a :RESULT \.}, ] }, }.each do |name, params| @@ -637,8 +632,7 @@ "?o": { input: %(:s :p ?o .), regexp: [ - %r(@forAll \.), - %r(:s :p \.), + %r(:s :p \?o \.), ] }, }.each do |name, params| @@ -685,7 +679,7 @@ cwm_list_unify5-ref.n3 cwm_other_lists-simple.n3 cwm_syntax_qual-after-user.n3 cwm_other_lists.n3 # empty list with extra properties - cwm_includes_concat.n3 new_syntax_inverted_properties.n3 + new_syntax_inverted_properties.n3 cwm_other_dec-div.n3 cwm_syntax_sep-term.n3 ) pending "Investigate" @@ -707,7 +701,9 @@ repo = parse(t.input, base_uri: t.base, logger: false) logger.info("sxp: #{SXP::Generator.string(repo.to_sxp_bin)}") n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true, logger: logger) - g2 = parse(n3, base_uri: t.base, logger: false) + g2 = parse(n3, validate: true, base_uri: t.base, logger: logger) + #require 'byebug'; byebug + expect(g2.count).to produce(repo.count, logger) expect(g2.isomorphic?(repo)).to produce(true, logger) end @@ -725,7 +721,7 @@ format = detect_format(t.expected) repo = parse(t.expected, base_uri: t.base, format: format, logger: false) n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true, logger: logger) - g2 = parse(n3, base_uri: t.base, logger: false) + g2 = parse(n3, validate: true, base_uri: t.base, logger: false) expect(g2.isomorphic?(repo)).to produce(true, logger) end end From 2ab92b73806a6bae3845ccecee44d617e3bac9e8 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 31 Oct 2020 16:25:57 -0700 Subject: [PATCH 158/193] In `Formula#each`, don't emit list statements, just native lists. --- lib/rdf/n3/algebra/formula.rb | 6 +----- spec/writer_spec.rb | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index e0b2ee7..7b6c0f3 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -248,11 +248,7 @@ def each(&block) [:subject, :predicate, :object].each do |part| terms[part] = case o = pattern.send(part) when RDF::Query::Variable - if solution[o] && solution[o].list? - solution[o].each_statement(&block) - # Bind the list subject, and emit list statements - solution[o].subject - elsif solution[o] && solution[o].formula? + if solution[o] && solution[o].formula? form = solution[o] # uses the graph_name of the formula, and yields statements from the formula log_depth do diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 5e3f6e5..7113897 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -702,7 +702,6 @@ logger.info("sxp: #{SXP::Generator.string(repo.to_sxp_bin)}") n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true, logger: logger) g2 = parse(n3, validate: true, base_uri: t.base, logger: logger) - #require 'byebug'; byebug expect(g2.count).to produce(repo.count, logger) expect(g2.isomorphic?(repo)).to produce(true, logger) end From 119765e4ebd8d42652565edd880d25df7dc6213f Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 1 Nov 2020 15:37:40 -0800 Subject: [PATCH 159/193] Don't dup formulae --- lib/rdf/n3/algebra/formula.rb | 4 ++-- lib/rdf/n3/algebra/list_operator.rb | 2 +- lib/rdf/n3/algebra/resource_operator.rb | 4 ++-- spec/reasoner_spec.rb | 27 ------------------------- spec/suite_reasoner_spec.rb | 2 ++ 5 files changed, 7 insertions(+), 32 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 7b6c0f3..3f738b5 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -58,7 +58,7 @@ def self.from_enumerable(enumerable, **options) end when RDF::N3::List # Transform blank nodes denoting formulae into those formulae - term = term.transform {|t| t.node? ? formulae.fetch(t, t).dup : t} + term = term.transform {|t| t.node? ? formulae.fetch(t, t) : t} # If we're in a quoted graph, transform blank node components into existential variables if graph_name && term.has_nodes? @@ -132,7 +132,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new these_solutions.map! do |solution| RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)| # Replace blank node bindings with lists and formula references with formula, where those blank nodes are associated with lists. - value = formulae.fetch(value, value).dup if value.node? + value = formulae.fetch(value, value) if value.node? l = RDF::N3::List.try_list(value, queryable) value = l if l.constant? memo.merge(name => value) diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index 864c96d..5e4476c 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -24,7 +24,7 @@ def execute(queryable, solutions:, **options) # If it evaluated to a BNode, re-expand as a list list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) - object = formulae.fetch(object, object).dup if object.node? + object = formulae.fetch(object, object) if object.node? log_debug(self.class.const_get(:NAME)) {"list: #{SXP::Generator.string(list.to_sxp_bin).gsub(/\s+/m, ' ')}, object: #{SXP::Generator.string(object.to_sxp_bin).gsub(/\s+/m, ' ')}"} next unless validate(list) diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index a468cae..166de60 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -20,8 +20,8 @@ def execute(queryable, solutions:, **options) @solutions = RDF::Query::Solutions(solutions.map do |solution| subject = operand(0).evaluate(solution.bindings, formulae: formulae) || operand(0) object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) - subject = formulae.fetch(subject, subject).dup if subject.node? - object = formulae.fetch(object, object).dup if object.node? + subject = formulae.fetch(subject, subject) if subject.node? + object = formulae.fetch(object, object) if object.node? log_debug(self.class.const_get(:NAME)) {"subject: #{subject.to_sxp}, object: #{object.to_sxp}"} next unless valid?(subject, object) diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index e8511cd..3ac73df 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -763,33 +763,6 @@ 9.5 :valueOf "(7 / 2) + ((7 % 2)^10000000) + 5 [should be 9.5]" . ) }, - "Combinatorial test - SumDifferenceFAILS": { - input: %( - "3.1415926" a :testValue. - 3.1415926 a :testValue. - "1729" a :testValue. - 1729 a :testValue. - "0" a :testValue. - 0 a :testValue. - "1.0e7" a :testValue. - 1.0e7 a :testValue. - { ?x a :testValue. ?y a :testValue. - ?z is math:sum of (?x (?y ?x)!math:difference). - ?z math:notEqualTo ?y } => {(?x ?y) :is :SumDifferenceFAILS}. - ), - expect: %( - @prefix rdf: . - @prefix xsd: . - - ("1.0e7" "3.1415926"^^xsd:decimal) <#is> <#SumDifferenceFAILS> . - - ("1.0e7"^^xsd:double "3.1415926"^^xsd:decimal) <#is> <#SumDifferenceFAILS> . - - ("1.0e7" "3.1415926") <#is> <#SumDifferenceFAILS> . - - ("1.0e7"^^xsd:double "3.1415926") <#is> <#SumDifferenceFAILS> . - ) - }, "Combinatorial test - concatenation": { input: %( @prefix string: . diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 335c56e..1a53419 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -24,6 +24,8 @@ cwm_includes_t10 cwm_includes_t11 cwm_includes_conclusion_simple cwm_includes_conclusion} pending "log:includes etc." + when *%w{cwm_includes_t9br} + pending "extra triples in anticedent because solutions are applied." when *%w{cwm_supports_simple cwm_string_roughly} pending "Uses unsupported builtin" when *%w{cwm_string_uriEncode cwm_includes_quantifiers_limited From 444a3c2ac3c82ea9cc3c2f9ce0b91393811f1699 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 1 Nov 2020 15:38:12 -0800 Subject: [PATCH 160/193] Errors introduced from debug statements fixed. --- lib/rdf/n3/algebra/resource_operator.rb | 5 +++-- lib/rdf/n3/algebra/str/format.rb | 2 +- lib/rdf/n3/writer.rb | 14 ++++++-------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 166de60..d8febde 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -45,9 +45,10 @@ def execute(queryable, solutions:, **options) log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(rhs.to_sxp_bin).gsub(/\s+/m, ' ')}"} solution.merge(subject.to_sym => rhs) elsif respond_to?(:apply) - log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(apply(lhs, rhs).to_sxp_bin).gsub(/\s+/m, ' ')}"} + res = apply(lhs, rhs) + log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(res.to_sxp_bin).gsub(/\s+/m, ' ')}"} # Return the result applying subject and object - solution if apply(lhs, rhs) == RDF::Literal::TRUE + solution if res == RDF::Literal::TRUE elsif rhs != lhs log_debug(self.class.const_get(:NAME)) {"result: false"} nil diff --git a/lib/rdf/n3/algebra/str/format.rb b/lib/rdf/n3/algebra/str/format.rb index 391eb24..f3b493d 100644 --- a/lib/rdf/n3/algebra/str/format.rb +++ b/lib/rdf/n3/algebra/str/format.rb @@ -11,7 +11,7 @@ class Format < RDF::N3::Algebra::ListOperator # @see RDF::N3::ListOperator#evaluate def resolve(list) format, *args = list.to_a.map(&:value) - str = format % args + str = RDF::Literal(format % args) end end end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index 7d420da..58a4360 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -623,14 +623,12 @@ def blankNodePropertyList?(resource, position) def blankNodePropertyList(resource, position) return false unless blankNodePropertyList?(resource, position) - #log_depth do - log_debug("blankNodePropertyList") {resource.to_sxp} - subject_done(resource) - @output.write((position == :subject ? "\n#{indent}[" : '[')) - num_props = log_depth {predicateObjectList(resource, true)} - @output.write((num_props > 1 ? "\n#{indent(2)}" : "") + (position == :subject ? '] .' : ']')) - true - #end + log_debug("blankNodePropertyList") {resource.to_sxp} + subject_done(resource) + @output.write((position == :subject ? "\n#{indent}[" : '[')) + num_props = log_depth {predicateObjectList(resource, true)} + @output.write((num_props > 1 ? "\n#{indent(2)}" : "") + (position == :subject ? '] .' : ']')) + true end # Can subject be represented as a formula? From 81c8a4abf78e8904c01001dc8ec19a8c2f83078d Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 2 Nov 2020 10:50:16 -0800 Subject: [PATCH 161/193] Use `Array#select` instead of `Array#filter` for backwards compatibility. --- lib/rdf/n3/algebra/log/implies.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 0244435..4ba3e60 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -44,7 +44,7 @@ def execute(queryable, solutions:, **options) # filter solutions where not all variables in antecedant are bound. vars = subject.universal_vars - solns = solns.filter do |soln| + solns = solns.select do |soln| vars.all? {|v| soln.bound?(v)} end solns From 69075975082fcc0573fa71a7552372aaf6ab622b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 2 Nov 2020 11:04:32 -0800 Subject: [PATCH 162/193] Fix `Literal#as_datetime` for older ruby versions. --- lib/rdf/n3/extensions.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index 5fa1a40..b646680 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -145,7 +145,10 @@ def as_number # # @return [RDF::Literal::DateTime] def as_datetime - mvalue = value.match?(%r(^\d{4}$)) ? "#{value}-" : value + return self if is_a?(RDF::Literal::DateTime) + mvalue = value + mvalue = "#{mvalue}-01" if mvalue.match?(%r(^\d{4}$)) + mvalue = "#{mvalue}-01" if mvalue.match?(%r(^\d{4}-\d{2}$)) RDF::Literal::DateTime.new(::DateTime.iso8601(mvalue), lexical: value) rescue RDF::Literal(0) From 7bbda4f0dfdb237c1a5c9a302c8966e00fe5f4f0 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 2 Nov 2020 13:09:14 -0800 Subject: [PATCH 163/193] Use `Array#push` instead of `Array#append` for Ruby 2.4 compatibility. --- lib/rdf/n3/algebra/log/conjunction.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdf/n3/algebra/log/conjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb index 44db7c5..193acf8 100644 --- a/lib/rdf/n3/algebra/log/conjunction.rb +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -18,7 +18,7 @@ def resolve(list) log_debug(NAME) {"list hash: #{form.graph_name}"} list.each do |f| - form.operands.append(*f.operands) + form.operands.push(*f.operands) end form.dup end From 02b778d7c3d76f30e5a189aec37d90e33903fc17 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 2 Nov 2020 16:24:40 -0800 Subject: [PATCH 164/193] No solutions if subject resolves to nil. --- lib/rdf/n3/algebra/list_operator.rb | 6 +++++- lib/rdf/n3/algebra/resource_operator.rb | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index 5e4476c..a5aed10 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -17,7 +17,7 @@ class ListOperator < SPARQL::Algebra::Operator::Binary # solutions for chained queries # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) - @solutions = RDF::Query::Solutions(solutions.map do |solution| + RDF::Query::Solutions(solutions.map do |solution| # Might be a variable or node evaluating to a list in queryable, or might be a list with variables list = operand(0).evaluate(solution.bindings, formulae: formulae) next unless list @@ -30,6 +30,10 @@ def execute(queryable, solutions:, **options) next unless validate(list) lhs = resolve(list) + if lhs.nil? + log_error(self.class.const_get(:NAME)) {"subject evaluates to null: #{list.inspect}"} + next + end if object.variable? log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(lhs.to_sxp_bin).gsub(/\s+/m, ' ')}"} diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index d8febde..294e1c9 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -17,7 +17,7 @@ class ResourceOperator < SPARQL::Algebra::Operator::Binary # solutions for chained queries # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) - @solutions = RDF::Query::Solutions(solutions.map do |solution| + RDF::Query::Solutions(solutions.map do |solution| subject = operand(0).evaluate(solution.bindings, formulae: formulae) || operand(0) object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) subject = formulae.fetch(subject, subject) if subject.node? @@ -28,13 +28,13 @@ def execute(queryable, solutions:, **options) lhs = resolve(subject, position: :subject) if lhs.nil? - log_error(self.class.const_get(:NAME)) {"subject is invalid: #{subject.inspect}"} + log_error(self.class.const_get(:NAME)) {"subject evaluates to null: #{subject.inspect}"} next end rhs = resolve(object, position: :object) if rhs.nil? - log_error(self.class.const_get(:NAME)) {"object is invalid: #{object.inspect}"} + log_error(self.class.const_get(:NAME)) {"object evaluates to null: #{object.inspect}"} next end From e6a80d002a63bd9f611c1c273809febb1793f4c6 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 2 Nov 2020 16:25:24 -0800 Subject: [PATCH 165/193] Other than for log:implies, don't retain `@solutions`, but pass them in from previous evaluation. --- lib/rdf/n3/algebra/builtin.rb | 6 ++-- lib/rdf/n3/algebra/formula.rb | 43 +++++++++----------------- lib/rdf/n3/algebra/list/in.rb | 2 +- lib/rdf/n3/algebra/list/member.rb | 2 +- lib/rdf/n3/algebra/log/conclusion.rb | 2 +- lib/rdf/n3/algebra/log/implies.rb | 13 ++++---- lib/rdf/n3/algebra/log/includes.rb | 2 +- lib/rdf/n3/algebra/log/not_includes.rb | 3 +- spec/reasoner_spec.rb | 33 ++++++++++++++------ 9 files changed, 55 insertions(+), 51 deletions(-) diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb index 933e38a..aa9bfa8 100644 --- a/lib/rdf/n3/algebra/builtin.rb +++ b/lib/rdf/n3/algebra/builtin.rb @@ -48,13 +48,15 @@ def evaluate(bindings, formulae:, **options) ## # By default, operators yield themselves and the operands, recursively. - def each(&block) + # + # Pass in solutions to have quantifiers resolved to those solutions. + def each(solutions: RDF::Query::Solutions(), &block) log_depth do subject, object = operands.map {|op| op.formula? ? op.graph_name : op} block.call(RDF::Statement(subject, self.to_uri, object)) operands.each do |op| next unless op.is_a?(Builtin) - op.each do |st| + op.each(solutions: solutions) do |st| # Maintain formula graph name for formula operands st.graph_name ||= op.graph_name if op.formula? block.call(st) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 3f738b5..1f9451d 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -143,7 +143,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new end # Reject solutions which include variables as values - @solutions = solutions.dup.filter {|s| s.enum_value.none?(&:variable?)} + solutions.filter! {|s| s.enum_value.none?(&:variable?)} # Use our solutions for sub-ops # Join solutions from other operands @@ -153,31 +153,31 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new # * Re-calculate inputs with bound inputs after each built-in is run. log_depth do # Iterate over sub_ops using evaluation heuristic - ops = sub_ops.sort_by {|op| op.rank(@solutions)} + ops = sub_ops.sort_by {|op| op.rank(solutions)} while !ops.empty? last_op = nil ops.each do |op| log_debug("(formula built-in)") {SXP::Generator.string op.to_sxp_bin} - solutions = op.execute(queryable, solutions: @solutions) + these_solutions = op.execute(queryable, solutions: solutions) # If there are no solutions, try the next one, until we either run out of operations, or we have solutions - next if solutions.empty? + next if these_solutions.empty? last_op = op - @solutions = RDF::Query::Solutions(solutions) + solutions = RDF::Query::Solutions(these_solutions) break end # If there is no last_op, there are no solutions. unless last_op - @solutions = RDF::Query::Solutions.new + solutions = RDF::Query::Solutions.new break end # Remove op from list, and re-order remaining ops. - ops = (ops - [last_op]).sort_by {|op| op.rank(@solutions)} + ops = (ops - [last_op]).sort_by {|op| op.rank(solutions)} end end - log_info("(formula sub-op solutions)") {SXP::Generator.string @solutions.to_sxp_bin} - @solutions + log_info("(formula sub-op solutions)") {SXP::Generator.string solutions.to_sxp_bin} + solutions end ## @@ -195,9 +195,6 @@ def evaluate(bindings, formulae:, **options) # Maintain formula relationships formulae {|k, v| this.formulae[k] ||= v} - # Bind solutions to formula - this.solutions = RDF::Query::Solutions(bindings) - # Replace operands with bound operands this.operands = operands.map do |op| op.evaluate(bindings, formulae: formulae, **options) @@ -228,13 +225,11 @@ def hash # each matching statement # @yieldparam [RDF::Statement] solution # @yieldreturn [void] ignored - def each(&block) - # If there are no solutions, create a single solution - @solutions ||= RDF::Query::Solutions(RDF::Query::Solution.new) - log_debug("formula #{graph_name} each") {SXP::Generator.string @solutions.to_sxp_bin} + def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) + log_debug("formula #{graph_name} each") {SXP::Generator.string solutions.to_sxp_bin} # Yield patterns by binding variables - @solutions.each do |solution| + 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(/^\$+/, '')) @@ -252,9 +247,8 @@ def each(&block) form = solution[o] # uses the graph_name of the formula, and yields statements from the formula log_depth do - form.solutions = RDF::Query::Solutions(solution) log_debug("(formula from var form)") {form.graph_name.to_sxp} - form.each do |stmt| + form.each(solutions: RDF::Query::Solutions(solution)) do |stmt| stmt.graph_name ||= form.graph_name log_debug("(formula add from var form)") {stmt.to_sxp} block.call(stmt) @@ -269,8 +263,7 @@ def each(&block) when RDF::N3::Algebra::Formula # uses the graph_name of the formula, and yields statements from the formula. No solutions are passed in. log_debug("(formula from form)") {o.graph_name.to_sxp} - o.solutions = RDF::Query::Solutions(solution) - o.each do |stmt| + o.each(solutions: RDF::Query::Solutions(solution)) do |stmt| stmt.graph_name ||= o.graph_name log_debug("(formula add from form)") {stmt.to_sxp} block.call(stmt) @@ -293,7 +286,7 @@ def each(&block) log_depth do sub_ops.each do |op| log_debug("(formula sub_op)") {SXP::Generator.string op.to_sxp_bin} - op.each do |stmt| + op.each(solutions: solutions) do |stmt| log_debug("(formula add from sub_op)") {stmt.to_sxp} block.call(stmt) end @@ -301,12 +294,6 @@ def each(&block) end 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 diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb index d2afe9e..4a08102 100644 --- a/lib/rdf/n3/algebra/list/in.rb +++ b/lib/rdf/n3/algebra/list/in.rb @@ -18,7 +18,7 @@ class In < RDF::N3::Algebra::ListOperator # solutions for chained queries # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) - @solutions = RDF::Query::Solutions(solutions.map do |solution| + RDF::Query::Solutions(solutions.map do |solution| subject = operand(0).evaluate(solution.bindings, formulae: formulae) || operand(0) # Might be a variable or node evaluating to a list in queryable, or might be a list with variables list = operand(1).evaluate(solution.bindings, formulae: formulae) diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index 9585ebc..4d1c42b 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -15,7 +15,7 @@ class Member < RDF::N3::Algebra::ListOperator # solutions for chained queries # @return [RDF::Query::Solutions] def execute(queryable, solutions:, **options) - @solutions = RDF::Query::Solutions(solutions.map do |solution| + RDF::Query::Solutions(solutions.map do |solution| list = operand(0).evaluate(solution.bindings, formulae: formulae) next unless list list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) diff --git a/lib/rdf/n3/algebra/log/conclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb index 2280c1b..9308a8d 100644 --- a/lib/rdf/n3/algebra/log/conclusion.rb +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -53,7 +53,7 @@ def input_operand # each matching statement # @yieldparam [RDF::Statement] solution # @yieldreturn [void] ignored - def each(&block) + def each(solutions:, &block) super do |stmt| block.call(RDF::Statement.from(stmt.to_quad)) end diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 4ba3e60..ca71a54 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -16,6 +16,8 @@ class Implies < SPARQL::Algebra::Operator::Binary ## # Returns solutions from subject. Solutions are created by evaluating subject against `queryable`. # + # Solutions are kept within this instance, and used for conclusions. Note that the evaluated solutions do not affect that of the invoking formula, as the solution spaces are disjoint. + # # @param [RDF::Queryable] queryable # the graph or repository to query # @param [Hash{Symbol => Object}] options @@ -62,21 +64,20 @@ def execute(queryable, solutions:, **options) # each matching statement # @yieldparam [RDF::Statement] solution # @yieldreturn [void] ignored - def each(&block) - @solutions ||= RDF::Query::Solutions.new + def each(solutions: RDF::Query::Solutions(), &block) + # Merge solutions in with those for the evaluation of this implication + solutions = Array(@solutions) log_depth do super - @solutions.each do |solution| + solutions.each do |solution| log_debug("(logImplies each) solution") {SXP::Generator.string @solutions.to_sxp_bin} object = operand(1).evaluate(solution.bindings, formulae: formulae) log_info(("(logImplies each) object")) {SXP::Generator.string object.to_sxp_bin} - object.solutions = RDF::Query::Solutions(solution) - # Yield inferred statements #require 'byebug'; byebug if solution[:y] - object.each do |statement| + object.each(solutions: RDF::Query::Solutions(solution)) do |statement| log_debug(("(logImplies each) infer\s")) {statement.to_sxp} block.call(RDF::Statement.from(statement.to_quad, inferred: true)) end diff --git a/lib/rdf/n3/algebra/log/includes.rb b/lib/rdf/n3/algebra/log/includes.rb index 5305b85..f4df679 100644 --- a/lib/rdf/n3/algebra/log/includes.rb +++ b/lib/rdf/n3/algebra/log/includes.rb @@ -27,7 +27,7 @@ class Includes < SPARQL::Algebra::Operator::Binary # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions:, **options) @queryable = queryable - @solutions = RDF::Query::Solutions(solutions.map do |solution| + RDF::Query::Solutions(solutions.map do |solution| subject = operand(0).evaluate(solution.bindings, formulae: formulae) object = operand(1).evaluate(solution.bindings, formulae: formulae) log_debug(NAME) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} diff --git a/lib/rdf/n3/algebra/log/not_includes.rb b/lib/rdf/n3/algebra/log/not_includes.rb index d7516f8..3789bb3 100644 --- a/lib/rdf/n3/algebra/log/not_includes.rb +++ b/lib/rdf/n3/algebra/log/not_includes.rb @@ -21,8 +21,7 @@ class NotIncludes < Includes # optional initial solutions for chained queries # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions:, **options) - super - @solutions = solutions.empty? ? RDF::Query::Solutions(RDF::Query::Solution.new) : RDF::Query::Solutions.new + super.empty? ? RDF::Query::Solutions(RDF::Query::Solution.new) : RDF::Query::Solutions.new end end end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 3ac73df..45a01dc 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -4,7 +4,7 @@ describe "RDF::N3::Reasoner" do let(:logger) {RDF::Spec.logger} - before {logger.level = Logger::DEBUG} + before {logger.level = Logger::INFO} context "variables" do context "universals" do @@ -29,35 +29,50 @@ } => { ?y a :TestResult }. ), expect: %( + { + { + { } => { a } . + . + } log:conclusion ?y + } => { ?y a :TestResult }. + { . a . { .} => { a .} . } a :TestResult . - ) + ), + pending: "inferred triples in conclusion premis" }, "conclusion-simple" => { input: %( - { - { } => { a } . - . - } a :TestRule. + { + { } => { a } . + . + } a :TestRule. - { ?x a :TestRule; log:conclusion ?y } => { ?y a :TestResult }. + { ?x a :TestRule; log:conclusion ?y } => { ?y a :TestResult }. ), expect: %( + { + { } => { a } . + . + } a :TestRule. + + { ?x a :TestRule; log:conclusion ?y } => { ?y a :TestResult }. + { . a . { .} => { a .} . } a :TestResult . ), - pending: "extra triple" + pending: "inferred triples in conclusion premis" }, }.each do |name, options| it name do logger.info "input: #{options[:input]}" - options = {data: false, filter: true}.merge(options) + options = {data: false, filter: false}.merge(options) pending(options[:pending]) if options[:pending] expected = parse(options[:expect]) result = reason(options[:input], **options) From 509bf66ad9fabc8849c7fbd8c2c5db4336cc1fef Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 3 Nov 2020 13:31:00 -0800 Subject: [PATCH 166/193] Minor tweaks. --- lib/rdf/n3/algebra/formula.rb | 3 ++- lib/rdf/n3/algebra/list_operator.rb | 17 +++++++++-------- lib/rdf/n3/algebra/log/implies.rb | 2 +- lib/rdf/n3/algebra/resource_operator.rb | 9 +++++---- lib/rdf/n3/extensions.rb | 19 ++++++++++++++++++- lib/rdf/n3/reasoner.rb | 3 +++ 6 files changed, 38 insertions(+), 15 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 1f9451d..a8340cd 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -118,7 +118,7 @@ def dup # any additional keyword options # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options) - log_debug("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin} + log_info("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin} log_debug("(formula bindings)") { solutions.bindings.map {|k,v| RDF::Query::Variable.new(k,v)}.to_sxp} # Only query as patterns if this is an embedded formula @@ -307,6 +307,7 @@ def graph_name; @options[:graph_name]; end # @param [RDF::Resource] name # @return [RDF::Resource] def graph_name=(name) + formulae[name] = self @options[:graph_name] = name end diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index a5aed10..6ea0914 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -19,24 +19,25 @@ class ListOperator < SPARQL::Algebra::Operator::Binary def execute(queryable, solutions:, **options) RDF::Query::Solutions(solutions.map do |solution| # Might be a variable or node evaluating to a list in queryable, or might be a list with variables - list = operand(0).evaluate(solution.bindings, formulae: formulae) - next unless list + subject = operand(0).evaluate(solution.bindings, formulae: formulae) + next unless subject # If it evaluated to a BNode, re-expand as a list - list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) + subject = RDF::N3::List.try_list(subject, queryable).evaluate(solution.bindings, formulae: formulae) object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) object = formulae.fetch(object, object) if object.node? - log_debug(self.class.const_get(:NAME)) {"list: #{SXP::Generator.string(list.to_sxp_bin).gsub(/\s+/m, ' ')}, object: #{SXP::Generator.string(object.to_sxp_bin).gsub(/\s+/m, ' ')}"} - next unless validate(list) + log_info(self.class.const_get(:NAME)) {"subject: #{SXP::Generator.string(subject.to_sxp_bin).strip}"} + log_info(self.class.const_get(:NAME)) {"object: #{SXP::Generator.string(object.to_sxp_bin).strip}"} + next unless validate(subject) - lhs = resolve(list) + lhs = resolve(subject) if lhs.nil? - log_error(self.class.const_get(:NAME)) {"subject evaluates to null: #{list.inspect}"} + log_error(self.class.const_get(:NAME)) {"subject evaluates to null: #{subject.inspect}"} next end if object.variable? - log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(lhs.to_sxp_bin).gsub(/\s+/m, ' ')}"} + log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(lhs.to_sxp_bin).strip}"} solution.merge(object.to_sym => lhs) elsif object != lhs log_debug(self.class.const_get(:NAME)) {"result: false"} diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index ca71a54..d66e72f 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -71,7 +71,7 @@ def each(solutions: RDF::Query::Solutions(), &block) super solutions.each do |solution| - log_debug("(logImplies each) solution") {SXP::Generator.string @solutions.to_sxp_bin} + log_debug("(logImplies each) solution") {SXP::Generator.string solution.to_sxp_bin} object = operand(1).evaluate(solution.bindings, formulae: formulae) log_info(("(logImplies each) object")) {SXP::Generator.string object.to_sxp_bin} diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 294e1c9..34da4af 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -23,7 +23,8 @@ def execute(queryable, solutions:, **options) subject = formulae.fetch(subject, subject) if subject.node? object = formulae.fetch(object, object) if object.node? - log_debug(self.class.const_get(:NAME)) {"subject: #{subject.to_sxp}, object: #{object.to_sxp}"} + log_info(self.class.const_get(:NAME)) {"subject: #{SXP::Generator.string(subject.to_sxp_bin).strip}"} + log_info(self.class.const_get(:NAME)) {"object: #{SXP::Generator.string(object.to_sxp_bin).strip}"} next unless valid?(subject, object) lhs = resolve(subject, position: :subject) @@ -39,14 +40,14 @@ def execute(queryable, solutions:, **options) end if object.variable? - log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(lhs.to_sxp_bin).gsub(/\s+/m, ' ')}"} + log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(lhs.to_sxp_bin).strip}"} solution.merge(object.to_sym => lhs) elsif subject.variable? - log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(rhs.to_sxp_bin).gsub(/\s+/m, ' ')}"} + log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(rhs.to_sxp_bin).strip}"} solution.merge(subject.to_sym => rhs) elsif respond_to?(:apply) res = apply(lhs, rhs) - log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(res.to_sxp_bin).gsub(/\s+/m, ' ')}"} + log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(res.to_sxp_bin).strip}"} # Return the result applying subject and object solution if res == RDF::Literal::TRUE elsif rhs != lhs diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index b646680..f6d1755 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -42,7 +42,13 @@ class Statement # Transform Statement into an SXP # @return [Array] def to_sxp_bin - [(variable? ? :pattern : (has_graph? ? :quad : :triple)), subject, predicate, object, graph_name].compact + [ (has_graph? ? :quad : :triple), + (:inferred if inferred?), + subject, + predicate, + object, + graph_name + ].compact.map(&:to_sxp_bin) end ## @@ -252,6 +258,17 @@ def evaluate(bindings, formulae:, **options) self.class.from(elements) end + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + [ :pattern, + (:inferred if inferred?), + subject, + predicate, + object, + graph_name + ].compact.map(&:to_sxp_bin) + end end class Query::Solution diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 3626124..8d4eab0 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -136,6 +136,9 @@ def execute(**options, &block) end end log_info("reasoner: end") { "count: #{count}"} + log_info("reasoner: formula") do + SXP::Generator.string RDF::N3::Algebra::Formula.from_enumerable(knowledge_base).to_sxp_bin + end # Add updates back to mutable, containg builtins and variables. @mutable << knowledge_base From 655fac4d2f9aeb4f6aa1dc08b568f241802a6028 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 8 Nov 2020 14:02:58 -0800 Subject: [PATCH 167/193] Move `#evaluate` definition from extensions to refinements to not interfere with SPARQL. --- lib/rdf/n3/extensions.rb | 261 ++++++++++++++------------------------ lib/rdf/n3/refinements.rb | 110 +++++++++++++++- 2 files changed, 203 insertions(+), 168 deletions(-) diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index f6d1755..b6f3667 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -58,19 +58,6 @@ def to_sxp_bin def to_sxp to_sxp_bin.to_sxp end - - ## - # As a statement is constant, this returns itself. - # - # @param [Hash{Symbol => RDF::Term}] bindings - # a query solution containing zero or more variable bindings - # @param [Hash{Symbol => Object}] options ({}) - # options passed from query - # @return [RDF::Statement] - # @see SPARQL::Algebra::Expression.evaluate - def evaluate(bindings, formulae:, **options) - self - end end module Value @@ -113,19 +100,6 @@ def as_number def as_datetime RDF::Literal::DateTime.new(DateTime.now) end - - ## - # As a term is constant, this returns itself. - # - # @param [Hash{Symbol => RDF::Term}] bindings - # a query solution containing zero or more variable bindings - # @param [Hash{Symbol => Object}] options ({}) - # options passed from query - # @return [RDF::Term] - # @see SPARQL::Algebra::Expression.evaluate - def evaluate(bindings, formulae:, **options) - self - end end class Literal @@ -184,163 +158,124 @@ def to_ndvar(scope) label = "#{id}_#{scope ? scope.id : 'base'}_undext" RDF::Query::Variable.new(label, existential: true, distinguished: false) end - - ## - # Blank node may refer to a formula. - # - # @param [Hash{Symbol => RDF::Term}] bindings - # a query solution containing zero or more variable bindings - # @param [Hash{Symbol => Object}] options ({}) - # options passed from query - # @return [RDF::Term] - # @see SPARQL::Algebra::Expression.evaluate - def evaluate(bindings, formulae:, **options) - node? ? formulae.fetch(self, self) : self - end end - class Query::Pattern - ## - # Overrides `#initialize!` to turn blank nodes into non-distinguished variables, if the `:ndvars` option is set. - alias_method :orig_initialize!, :initialize! - def initialize! - if @options[:ndvars] - @graph_name = @graph_name.to_ndvar(nil) if @graph_name - @subject = @subject.to_ndvar(@graph_name) - @predicate = @predicate.to_ndvar(@graph_name) - @object = @object.to_ndvar(@graph_name) + class Query + class Pattern + ## + # Overrides `#initialize!` to turn blank nodes into non-distinguished variables, if the `:ndvars` option is set. + alias_method :orig_initialize!, :initialize! + def initialize! + if @options[:ndvars] + @graph_name = @graph_name.to_ndvar(nil) if @graph_name + @subject = @subject.to_ndvar(@graph_name) + @predicate = @predicate.to_ndvar(@graph_name) + @object = @object.to_ndvar(@graph_name) + end + orig_initialize! end - orig_initialize! - end - - ## - # Checks pattern equality against a statement, considering nesting an lists. - # - # * A pattern which has a pattern as a subject or an object, matches - # a statement having a statement as a subject or an object using {#eql?}. - # - # @param [Statement] other - # @return [Boolean] - # - # @see RDF::URI#== - # @see RDF::Node#== - # @see RDF::Literal#== - # @see RDF::Query::Variable#== - def eql?(other) - return false unless other.is_a?(RDF::Statement) && (self.graph_name || false) == (other.graph_name || false) - [:subject, :predicate, :object].each do |part| - case o = self.send(part) - when RDF::Query::Pattern, RDF::List - return false unless o.eql?(other.send(part)) - else - return false unless o == other.send(part) + ## + # Checks pattern equality against a statement, considering nesting an lists. + # + # * A pattern which has a pattern as a subject or an object, matches + # a statement having a statement as a subject or an object using {#eql?}. + # + # @param [Statement] other + # @return [Boolean] + # + # @see RDF::URI#== + # @see RDF::Node#== + # @see RDF::Literal#== + # @see RDF::Query::Variable#== + def eql?(other) + return false unless other.is_a?(RDF::Statement) && (self.graph_name || false) == (other.graph_name || false) + + [:subject, :predicate, :object].each do |part| + case o = self.send(part) + when RDF::Query::Pattern, RDF::List + return false unless o.eql?(other.send(part)) + else + return false unless o == other.send(part) + end end + true end - true - end - ## - # Evaluates the pattern using the given variable `bindings` by cloning the pattern replacing variables with their bindings recursively. If the resulting pattern is constant, it is cast as a statement. - # - # @param [Hash{Symbol => RDF::Term}] bindings - # a query solution containing zero or more variable bindings - # @param [Hash{Symbol => Object}] options ({}) - # options passed from query - # @return [RDF::Statement] - # @see SPARQL::Algebra::Expression.evaluate - def evaluate(bindings, formulae:, **options) - elements = self.to_quad.map do |term| - term.evaluate(bindings, formulae: formulae, **options) - end.compact.map do |term| - term.node? ? formulae.fetch(term, term) : term + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + [ :triple, + (:inferred if inferred?), + subject, + predicate, + object, + graph_name + ].compact.map(&:to_sxp_bin) end - - self.class.from(elements) end - # Transform Statement into an SXP - # @return [Array] - def to_sxp_bin - [ :pattern, - (:inferred if inferred?), - subject, - predicate, - object, - graph_name - ].compact.map(&:to_sxp_bin) - end - end - class Query::Solution - # Transform Statement into an SXP - # @return [Array] - def to_sxp_bin - [:solution] + bindings.map do |k, v| - existential = k.to_s.end_with?('ext') - k = k.to_s.sub(/_(?:und)?ext$/, '').to_sym - distinguished = !k.to_s.end_with?('undext') - Query::Variable.new(k, v, existential: existential, distinguished: distinguished).to_sxp_bin + class Solution + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + [:solution] + bindings.map do |k, v| + existential = k.to_s.end_with?('ext') + k = k.to_s.sub(/_(?:und)?ext$/, '').to_sym + distinguished = !k.to_s.end_with?('undext') + Query::Variable.new(k, v, existential: existential, distinguished: distinguished).to_sxp_bin + end end - end - - ## - # Returns an S-Expression (SXP) representation - # - # @return [String] - def to_sxp - to_sxp_bin.to_sxp - end - end - class Query::Variable - ## - # True if the other is the same variable - def sameTerm?(other) - other.is_a?(::RDF::Query::Variable) && name.eql?(other.name) + ## + # Returns an S-Expression (SXP) representation + # + # @return [String] + def to_sxp + to_sxp_bin.to_sxp + end end - ## - # Parse the value as a numeric literal, or return 0. - # - # @return [RDF::Literal::Numeric] - def as_number - RDF::Literal(0) - end + class Variable + ## + # True if the other is the same variable + def sameTerm?(other) + other.is_a?(::RDF::Query::Variable) && name.eql?(other.name) + end - def to_sxp - to_s - end + ## + # Parse the value as a numeric literal, or return 0. + # + # @return [RDF::Literal::Numeric] + def as_number + RDF::Literal(0) + end - ## - # If variable is bound, replace with the bound value, otherwise, returns itself - # - # @param [Hash{Symbol => RDF::Term}] bindings - # a query solution containing zero or more variable bindings - # @param [Hash{Symbol => Object}] options ({}) - # options passed from query - # @return [RDF::Term] - # @see SPARQL::Algebra::Expression.evaluate - def evaluate(bindings, formulae:, **options) - value = bindings.has_key?(name) ? bindings[name] : self - value.node? ? formulae.fetch(value, value) : value + def to_sxp + to_s + end end end +end - class SPARQL::Algebra::Operator - ## - # Map of related formulae, indexed by graph name. - # - # @return [Hash{RDF::Resource => RDF::N3::Algebra::Formula}] - def formulae - @options.fetch(:formulae, {}) - end +module SPARQL + module Algebra + class Operator + ## + # Map of related formulae, indexed by graph name. + # + # @return [Hash{RDF::Resource => RDF::N3::Algebra::Formula}] + def formulae + @options.fetch(:formulae, {}) + end - # Updates the operands for this operator. - # - # @param [Array] ary - # @return [Array] - def operands=(ary) - @operands = ary + # Updates the operands for this operator. + # + # @param [Array] ary + # @return [Array] + def operands=(ary) + @operands = ary + end end end end diff --git a/lib/rdf/n3/refinements.rb b/lib/rdf/n3/refinements.rb index 743f0f9..c8d6112 100644 --- a/lib/rdf/n3/refinements.rb +++ b/lib/rdf/n3/refinements.rb @@ -1,9 +1,47 @@ -# Refinements on core RDF class behavior -# @see ::RDF::Statement#valid? -# @see ::RDF::Statement#invalid? -# @see ::RDF::Statement#validate! -# @see ::RDF::Query::Pattern#valid? +# Refinements on core RDF class behavior for RDF::N3. module RDF::N3::Refinements + # @!parse + # # Refinements on RDF::Term + # module RDF::Term + # ## + # # As a term is constant, this returns itself. + # # + # # @param [Hash{Symbol => RDF::Term}] bindings + # # a query solution containing zero or more variable bindings + # # @param [Hash{Symbol => Object}] options ({}) + # # options passed from query + # # @return [RDF::Term] + # # @see SPARQL::Algebra::Expression.evaluate + # def evaluate(bindings, formulae: nil, **options); end + # end + refine ::RDF::Term do + def evaluate(bindings, formulae:, **options) + self + end + end + + # @!parse + # # Refinements on RDF::Node + # module RDF::Term + # ## + # # Blank node may refer to a formula. + # # + # # @param [Hash{Symbol => RDF::Term}] bindings + # # a query solution containing zero or more variable bindings + # # @param [Hash{Symbol => Object}] options ({}) + # # options passed from query + # # @return [RDF::Node, RDF::N3::Algebra::Formula] + # # @see SPARQL::Algebra::Expression.evaluate + # def evaluate(bindings, formulae:, **options); end + # end + refine ::RDF::Node do + ## + # @return [RDF::Node, RDF::N3::Algebra::Formula] + def evaluate(bindings, formulae:, **options) + node? ? formulae.fetch(self, self) : self + end + end + # @!parse # # Refinements on RDF::Statement # class ::RDF::Statement @@ -19,6 +57,17 @@ module RDF::N3::Refinements # # @return [RDF::Value] `self` # # @raise [ArgumentError] if the value is invalid # def validate!; end + # + # ## + # # As a statement is constant, this returns itself. + # # + # # @param [Hash{Symbol => RDF::Term}] bindings + # # a query solution containing zero or more variable bindings + # # @param [Hash{Symbol => Object}] options ({}) + # # options passed from query + # # @return [RDF::Statement] + # # @see SPARQL::Algebra::Expression.evaluate + # def evaluate(bindings, formulae:, **options); end # end refine ::RDF::Statement do ## @@ -47,6 +96,12 @@ def validate! self end alias_method :validate, :validate! + + ## + # @return [RDF::Statement] + def evaluate(bindings, formulae:, **options) + self + end end # @!parse @@ -55,6 +110,17 @@ def validate! # # Refines `#valid?` to allow literal subjects and BNode predicates. # # @return [Boolean] # def valid?; end + # + # ## + # # Evaluates the pattern using the given variable `bindings` by cloning the pattern replacing variables with their bindings recursively. If the resulting pattern is constant, it is cast as a statement. + # # + # # @param [Hash{Symbol => RDF::Term}] bindings + # # a query solution containing zero or more variable bindings + # # @param [Hash{Symbol => Object}] options ({}) + # # options passed from query + # # @return [RDF::Statement, RDF::N3::Algebra::Formula] + # # @see SPARQL::Algebra::Expression.evaluate + # def evaluate(bindings, formulae:, **options); end # end refine ::RDF::Query::Pattern do ## @@ -69,6 +135,40 @@ def valid? rescue NoMethodError false end + + # @return [RDF::Statement, RDF::N3::Algebra::Formula] + def evaluate(bindings, formulae:, **options) + elements = self.to_quad.map do |term| + term.evaluate(bindings, formulae: formulae, **options) + end.compact.map do |term| + term.node? ? formulae.fetch(term, term) : term + end + + self.class.from(elements) + end + end + + # @!parse + # # Refinements on RDF::Query::Variable + # class RDF::Query::Variable + # ## + # # If variable is bound, replace with the bound value, otherwise, returns itself + # # + # # @param [Hash{Symbol => RDF::Term}] bindings + # # a query solution containing zero or more variable bindings + # # @param [Hash{Symbol => Object}] options ({}) + # # options passed from query + # # @return [RDF::Term] + # # @see SPARQL::Algebra::Expression.evaluate + # def evaluate(bindings, formulae:, **options); end + # end + refine ::RDF::Query::Variable do + ## + # @return [RDF::Term] + def evaluate(bindings, formulae:, **options) + value = bindings.has_key?(name) ? bindings[name] : self + value.node? ? formulae.fetch(value, value) : value + end end refine ::RDF::Graph do From edf63ce7750f6a5a6cf9cb71a3b990445e2df46b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 8 Nov 2020 14:46:02 -0800 Subject: [PATCH 168/193] Dpcumentation updates. --- README.md | 6 +++--- lib/rdf/n3/reader.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9c45c6d..65d8beb 100644 --- a/README.md +++ b/README.md @@ -93,14 +93,14 @@ Reasoning is discussed in the [Design Issues][] document. * `math:cosh` (See {RDF::N3::Algebra::Math::CosH}) * `math:cos` (See {RDF::N3::Algebra::Math::Cos}) * `math:difference` (See {RDF::N3::Algebra::Math::Difference}) - * `math:equalTo` (See {RDF::N3::Algebra::Math::Equal}) + * `math:equalTo` (See {RDF::N3::Algebra::Math::EqualTo}) * `math:exponentiation` (See {RDF::N3::Algebra::Math::Exponentiation}) * `math:floor` (See {RDF::N3::Algebra::Math::Floor}) * `math:greaterThan` (See {RDF::N3::Algebra::Math::GreaterThan}) * `math:integerQuotient` (See {RDF::N3::Algebra::Math::IntegerQuotient}) * `math:lessThan` (See {RDF::N3::Algebra::Math::LessThan}) - * `math:negation` (See {RDF::N3::Algebra::Math::Negate}) - * `math:notEqualTo` (See {RDF::N3::Algebra::Math::NotEqual}) + * `math:negation` (See {RDF::N3::Algebra::Math::Negation}) + * `math:notEqualTo` (See {RDF::N3::Algebra::Math::NotEqualTo}) * `math:notGreaterThan` (See {RDF::N3::Algebra::Math::NotGreaterThan}) * `math:notLessThan` (See {RDF::N3::Algebra::Math::NotLessThan}) * `math:product` (See {RDF::N3::Algebra::Math::Product}) diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 06c10f9..1a49f6c 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -851,7 +851,7 @@ def unique_label end # Find any variable that may be defined in the formula identified by `bn` - # @param [RDF::Node] sym name of formula + # @param [RDF::Node] name of formula # @return [RDF::Query::Variable] def find_var(name) (variables[@formulae.last] ||= {})[name.to_s] From bbc20e8ca426e6a404c435806a0519b97f81d3a8 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 9 Nov 2020 16:38:24 -0800 Subject: [PATCH 169/193] Add `#clear_solutions` to recursively clear solutions from operands. --- lib/rdf/n3/algebra/builtin.rb | 7 +++++++ lib/rdf/n3/algebra/log/conclusion.rb | 6 +++++- lib/rdf/n3/extensions.rb | 27 ++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb index aa9bfa8..06abcaa 100644 --- a/lib/rdf/n3/algebra/builtin.rb +++ b/lib/rdf/n3/algebra/builtin.rb @@ -65,6 +65,13 @@ def each(solutions: RDF::Query::Solutions(), &block) end end + ## + # Clear out any cached solutions. + # This principaly is for log:conclusions + def clear_solutions + operands.each(&:clear_solutions) + end + ## # The builtin hash is the hash of it's operands and NAME. # diff --git a/lib/rdf/n3/algebra/log/conclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb index 9308a8d..2e655e2 100644 --- a/lib/rdf/n3/algebra/log/conclusion.rb +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -24,7 +24,11 @@ def resolve(resource, position:) reasoner.execute(think: true, logger: false) {|stmt| conclusions << stmt} # The result is a formula containing the conclusions - RDF::N3::Algebra::Formula.from_enumerable(conclusions).dup + form = RDF::N3::Algebra::Formula.from_enumerable(conclusions, **@options).deep_dup + form.clear_solutions + + log_info("#{NAME} resolved") {SXP::Generator.string form.to_sxp_bin} + form end end diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index b6f3667..669b557 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -36,6 +36,13 @@ def to_sxp_bin def to_sxp to_a.to_sxp_bin.to_sxp end + + ## + # Clear out any cached solutions. + # This principaly is for log:conclusions + def clear_solutions + to_a.each(&:clear_solutions) + end end class Statement @@ -58,6 +65,13 @@ def to_sxp_bin def to_sxp to_sxp_bin.to_sxp end + + ## + # Clear out any cached solutions. + # This principaly is for log:conclusions + def clear_solutions + to_a.each(&:clear_solutions) + end end module Value @@ -76,6 +90,12 @@ def formula? def to_ndvar(scope) self end + + ## + # Clear out any cached solutions. + # Overriden by sub-classes. + def clear_solutions + end end module Term @@ -251,8 +271,13 @@ def as_number RDF::Literal(0) end + def to_sxp_bin + prefix = distinguished? ? (existential? ? '$' : '?') : (existential? ? '$$' : '??') + unbound? ? "#{prefix}#{name}" : ["#{prefix}#{name}", value].to_sxp_bin + end + def to_sxp - to_s + to_sxp_bin.to_sxp end end end From 26a410fe35782f7a3f9f9553f1ff4625e039941f Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 9 Nov 2020 16:40:33 -0800 Subject: [PATCH 170/193] Use `#deep_dup` when cloning a formula tree. --- lib/rdf/n3/algebra/builtin.rb | 6 ++--- lib/rdf/n3/algebra/formula.rb | 13 +++++----- lib/rdf/n3/algebra/list/member.rb | 2 +- lib/rdf/n3/algebra/list_operator.rb | 12 +++++----- lib/rdf/n3/algebra/log/implies.rb | 32 ++++++++++++++++--------- lib/rdf/n3/algebra/resource_operator.rb | 18 +++++++------- lib/rdf/n3/reasoner.rb | 6 ++--- script/parse | 9 +++++-- spec/suite_reasoner_spec.rb | 2 -- 9 files changed, 56 insertions(+), 44 deletions(-) diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb index 06abcaa..25cdd72 100644 --- a/lib/rdf/n3/algebra/builtin.rb +++ b/lib/rdf/n3/algebra/builtin.rb @@ -39,11 +39,8 @@ def input_operand # @see SPARQL::Algebra::Expression.evaluate def evaluate(bindings, formulae:, **options) args = operands.map { |operand| operand.evaluate(bindings, formulae: formulae, **options) } - # Replace operands with bound operands - this = dup - this.operands = args - this + self.class.new(*args, formulae: formulae, **options) end ## @@ -51,6 +48,7 @@ def evaluate(bindings, formulae:, **options) # # Pass in solutions to have quantifiers resolved to those solutions. def each(solutions: RDF::Query::Solutions(), &block) + log_info("(#{self.class.const_get(:NAME)} each)") log_depth do subject, object = operands.map {|op| op.formula? ? op.graph_name : op} block.call(RDF::Statement(subject, self.to_uri, object)) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index a8340cd..3335daa 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -96,13 +96,14 @@ def self.from_enumerable(enumerable, **options) # Duplicate this formula, recursively, renaming graph names using hash function. # # @return [RDF::N3::Algebra::Formula] - def dup - new_ops = operands.map(&:dup) + def deep_dup + #new_ops = operands.map(&:dup) + new_ops = operands.map do |op| + op.deep_dup + end graph_name = RDF::Node.intern(new_ops.hash) log_debug("formula") {"dup: #{self.graph_name} to #{graph_name}"} - that = self.class.new(*new_ops, **@options.merge(graph_name: graph_name, formulae: formulae)) - that.formulae[graph_name] = that - that + self.class.new(*new_ops, **@options.merge(graph_name: graph_name, formulae: formulae)) end ## @@ -226,7 +227,7 @@ def hash # @yieldparam [RDF::Statement] solution # @yieldreturn [void] ignored def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) - log_debug("formula #{graph_name} each") {SXP::Generator.string solutions.to_sxp_bin} + log_info("(formula each)") {SXP::Generator.string([self, solutions].to_sxp_bin)} # Yield patterns by binding variables solutions.each do |solution| diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb index 4d1c42b..acb8200 100644 --- a/lib/rdf/n3/algebra/list/member.rb +++ b/lib/rdf/n3/algebra/list/member.rb @@ -20,7 +20,7 @@ def execute(queryable, solutions:, **options) next unless list list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae) object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) - object = formulae.fetch(object, object).dup if object.node? + object = formulae[object].deep_dup if object.node? && formulae.has_key?(object) log_debug(NAME) {"list: #{list.to_sxp}, object: #{object.to_sxp}"} unless list.list? && list.valid? diff --git a/lib/rdf/n3/algebra/list_operator.rb b/lib/rdf/n3/algebra/list_operator.rb index 6ea0914..5ae1ab8 100644 --- a/lib/rdf/n3/algebra/list_operator.rb +++ b/lib/rdf/n3/algebra/list_operator.rb @@ -26,24 +26,24 @@ def execute(queryable, solutions:, **options) object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1) object = formulae.fetch(object, object) if object.node? - log_info(self.class.const_get(:NAME)) {"subject: #{SXP::Generator.string(subject.to_sxp_bin).strip}"} - log_info(self.class.const_get(:NAME)) {"object: #{SXP::Generator.string(object.to_sxp_bin).strip}"} + log_info(self.class.const_get(:NAME), "subject") {SXP::Generator.string(subject.to_sxp_bin).strip} + log_info(self.class.const_get(:NAME), "object") {SXP::Generator.string(object.to_sxp_bin).strip} next unless validate(subject) lhs = resolve(subject) if lhs.nil? - log_error(self.class.const_get(:NAME)) {"subject evaluates to null: #{subject.inspect}"} + log_error(self.class.const_get(:NAME), "subject evaluates to null") {subject.inspect} next end if object.variable? - log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(lhs.to_sxp_bin).strip}"} + log_debug(self.class.const_get(:NAME), "result") {SXP::Generator.string(lhs.to_sxp_bin).strip} solution.merge(object.to_sym => lhs) elsif object != lhs - log_debug(self.class.const_get(:NAME)) {"result: false"} + log_debug(self.class.const_get(:NAME), "result: false") nil else - log_debug(self.class.const_get(:NAME)) {"result: true"} + log_debug(self.class.const_get(:NAME), "result: true") solution end end.compact.uniq) diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index d66e72f..4f0ac5a 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -28,11 +28,11 @@ class Implies < SPARQL::Algebra::Operator::Binary def execute(queryable, solutions:, **options) @queryable = queryable @solutions = RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME) {"solution: #{SXP::Generator.string solution.to_sxp_bin}"} + log_debug(NAME, "solution") {SXP::Generator.string(solution.to_sxp_bin)} subject = operand(0).evaluate(solution.bindings, formulae: formulae) object = operand(1).evaluate(solution.bindings, formulae: formulae) - log_info(NAME) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} - log_info(NAME) {"object: #{SXP::Generator.string object.to_sxp_bin}"} + log_info(NAME, "subject") {SXP::Generator.string(subject.to_sxp_bin)} + log_info(NAME, "object") {SXP::Generator.string(object.to_sxp_bin)} # Nothing to do if variables aren't resolved. next unless subject && object @@ -51,12 +51,20 @@ def execute(queryable, solutions:, **options) end solns end.flatten.compact) - log_info(NAME) {"solutions: #{SXP::Generator.string @solutions.to_sxp_bin}"} + log_info(NAME) {SXP::Generator.string(@solutions.to_sxp_bin)} # Return original solutions, without bindings solutions end + ## + # Clear out any cached solutions. + # This principaly is for log:conclusions + def clear_solutions + super + @solutions = nil + 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. # @@ -68,18 +76,20 @@ def each(solutions: RDF::Query::Solutions(), &block) # Merge solutions in with those for the evaluation of this implication solutions = Array(@solutions) log_depth do - super + #operand(0).clear_solutions + super(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) solutions.each do |solution| - log_debug("(logImplies each) solution") {SXP::Generator.string solution.to_sxp_bin} + log_info("(logImplies each) solution") {SXP::Generator.string solution.to_sxp_bin} object = operand(1).evaluate(solution.bindings, formulae: formulae) - log_info(("(logImplies each) object")) {SXP::Generator.string object.to_sxp_bin} + log_info("(logImplies each) object") {SXP::Generator.string object.to_sxp_bin} # Yield inferred statements - #require 'byebug'; byebug if solution[:y] - object.each(solutions: RDF::Query::Solutions(solution)) do |statement| - log_debug(("(logImplies each) infer\s")) {statement.to_sxp} - block.call(RDF::Statement.from(statement.to_quad, inferred: true)) + log_depth do + object.each(solutions: RDF::Query::Solutions(solution)) do |statement| + log_debug(("(logImplies each) infer\s")) {statement.to_sxp} + block.call(RDF::Statement.from(statement.to_quad, inferred: true)) + end end end end diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 34da4af..1e63600 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -23,38 +23,38 @@ def execute(queryable, solutions:, **options) subject = formulae.fetch(subject, subject) if subject.node? object = formulae.fetch(object, object) if object.node? - log_info(self.class.const_get(:NAME)) {"subject: #{SXP::Generator.string(subject.to_sxp_bin).strip}"} - log_info(self.class.const_get(:NAME)) {"object: #{SXP::Generator.string(object.to_sxp_bin).strip}"} + log_info(self.class.const_get(:NAME), "subject") {SXP::Generator.string(subject.to_sxp_bin).strip} + log_info(self.class.const_get(:NAME), "object") {SXP::Generator.string(object.to_sxp_bin).strip} next unless valid?(subject, object) lhs = resolve(subject, position: :subject) if lhs.nil? - log_error(self.class.const_get(:NAME)) {"subject evaluates to null: #{subject.inspect}"} + log_error(self.class.const_get(:NAME), "subject evaluates to null") {subject.inspect} next end rhs = resolve(object, position: :object) if rhs.nil? - log_error(self.class.const_get(:NAME)) {"object evaluates to null: #{object.inspect}"} + log_error(self.class.const_get(:NAME), "object evaluates to null") {object.inspect} next end if object.variable? - log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(lhs.to_sxp_bin).strip}"} + log_debug(self.class.const_get(:NAME), "result") {SXP::Generator.string(lhs.to_sxp_bin).strip} solution.merge(object.to_sym => lhs) elsif subject.variable? - log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(rhs.to_sxp_bin).strip}"} + log_debug(self.class.const_get(:NAME), "result") {SXP::Generator.string(rhs.to_sxp_bin).strip} solution.merge(subject.to_sym => rhs) elsif respond_to?(:apply) res = apply(lhs, rhs) - log_debug(self.class.const_get(:NAME)) {"result: #{SXP::Generator.string(res.to_sxp_bin).strip}"} + log_debug(self.class.const_get(:NAME), "result") {SXP::Generator.string(res.to_sxp_bin).strip} # Return the result applying subject and object solution if res == RDF::Literal::TRUE elsif rhs != lhs - log_debug(self.class.const_get(:NAME)) {"result: false"} + log_debug(self.class.const_get(:NAME), "result: false") nil else - log_debug(self.class.const_get(:NAME)) {"result: true"} + log_debug(self.class.const_get(:NAME), "result: true") solution end end.compact.uniq) diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb index 8d4eab0..ea31d9b 100644 --- a/lib/rdf/n3/reasoner.rb +++ b/lib/rdf/n3/reasoner.rb @@ -129,6 +129,9 @@ def execute(**options, &block) log_debug("reasoner: solutions") {SXP::Generator.string solutions.to_sxp_bin} log_debug("reasoner: datastore") {SXP::Generator.string knowledge_base.statements.to_sxp_bin} log_info("reasoner: inferred") {SXP::Generator.string knowledge_base.statements.select(&:inferred?).to_sxp_bin} + log_info("reasoner: formula") do + SXP::Generator.string RDF::N3::Algebra::Formula.from_enumerable(knowledge_base).to_sxp_bin + end @formula = nil # cause formula to be re-calculated from knowledge-base unless options[:think] count = knowledge_base.count @@ -136,9 +139,6 @@ def execute(**options, &block) end end log_info("reasoner: end") { "count: #{count}"} - log_info("reasoner: formula") do - SXP::Generator.string RDF::N3::Algebra::Formula.from_enumerable(knowledge_base).to_sxp_bin - end # Add updates back to mutable, containg builtins and variables. @mutable << knowledge_base diff --git a/script/parse b/script/parse index 948e3c2..a803aeb 100755 --- a/script/parse +++ b/script/parse @@ -40,6 +40,7 @@ def run(input, **options) else options[:output].puts repo.dump(options[:output_format], prefixes: reader.prefixes, + base_uri: reader.base_uri, standard_prefixes: true, logger: options[:logger]) end @@ -76,9 +77,13 @@ def run(input, **options) num = repo.count if options[:output_format] == :n3 # Extra debugging - options[:logger].debug SXP::Generator.string(repo.to_sxp_bin) + options[:logger].debug SXP::Generator.string(repo.to_sxp_bin).strip end - options[:output].puts repo.dump(options[:output_format], prefixes: reader.prefixes, standard_prefixes: true, logger: options[:logger]) + options[:output].puts repo.dump(options[:output_format], + prefixes: reader.prefixes, + base_uri: reader.base_uri, + standard_prefixes: true, + logger: options[:logger]) end if options[:profile] Profiler__::stop_profile diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 1a53419..335c56e 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -24,8 +24,6 @@ cwm_includes_t10 cwm_includes_t11 cwm_includes_conclusion_simple cwm_includes_conclusion} pending "log:includes etc." - when *%w{cwm_includes_t9br} - pending "extra triples in anticedent because solutions are applied." when *%w{cwm_supports_simple cwm_string_roughly} pending "Uses unsupported builtin" when *%w{cwm_string_uriEncode cwm_includes_quantifiers_limited From 451f06fa054b51693bf10dfa8487f46cdce620dd Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Mon, 9 Nov 2020 16:42:15 -0800 Subject: [PATCH 171/193] Extract common code in formula sub-formulae iteration. Also, iterate on formula in statements resulting from sub-operations. --- Gemfile | 20 +++++------ lib/rdf/n3/algebra/formula.rb | 67 +++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/Gemfile b/Gemfile index d5b08d9..d39376e 100644 --- a/Gemfile +++ b/Gemfile @@ -2,20 +2,20 @@ 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 'sparql', git: "https://github.com/ruby-rdf/sparql", branch: "develop" gem 'sxp', git: "https://github.com/dryruby/sxp.rb", branch: "develop" group :development do - gem "ebnf", git: "https://github.com/dryruby/ebnf", branch: "develop" - gem "rdf-aggregate-repo", git: "https://github.com/ruby-rdf/rdf-aggregate-repo", 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-turtle", git: "https://github.com/ruby-rdf/rdf-turtle", branch: "develop" + gem 'ebnf', git: "https://github.com/dryruby/ebnf", branch: "develop" + gem 'rdf-aggregate-repo', git: "https://github.com/ruby-rdf/rdf-aggregate-repo", 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-turtle', git: "https://github.com/ruby-rdf/rdf-turtle", 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 '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" end group :development, :test do @@ -25,5 +25,5 @@ end group :debug do gem 'awesome_print', github: 'akshaymohite/awesome_print' - gem "byebug", platform: :mri + gem 'byebug', platform: :mri end diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 3335daa..bd51dc8 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -245,17 +245,8 @@ def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) terms[part] = case o = pattern.send(part) when RDF::Query::Variable if solution[o] && solution[o].formula? - form = solution[o] - # uses the graph_name of the formula, and yields statements from the formula - log_depth do - log_debug("(formula from var form)") {form.graph_name.to_sxp} - form.each(solutions: RDF::Query::Solutions(solution)) do |stmt| - stmt.graph_name ||= form.graph_name - log_debug("(formula add from var form)") {stmt.to_sxp} - block.call(stmt) - end - end - form.graph_name + log_info("(formula from var form)") {solution[o].graph_name.to_sxp} + form_statements(solution[o], solution: solution, &block) else solution[o] || o end @@ -263,13 +254,8 @@ def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) o.variable? ? o.evaluate(solution.bindings, formulae: formulae) : o when RDF::N3::Algebra::Formula # uses the graph_name of the formula, and yields statements from the formula. No solutions are passed in. - log_debug("(formula from form)") {o.graph_name.to_sxp} - o.each(solutions: RDF::Query::Solutions(solution)) do |stmt| - stmt.graph_name ||= o.graph_name - log_debug("(formula add from form)") {stmt.to_sxp} - block.call(stmt) - end - o.graph_name + log_info("(formula from form)") {o.graph_name.to_sxp} + form_statements(o, solution: solution, &block) else o end @@ -280,16 +266,19 @@ def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) block.call(statement) end - end - end - # statements from sub-operands - log_depth do - sub_ops.each do |op| - log_debug("(formula sub_op)") {SXP::Generator.string op.to_sxp_bin} - op.each(solutions: solutions) do |stmt| - log_debug("(formula add from sub_op)") {stmt.to_sxp} - block.call(stmt) + # statements from sub-operands + sub_ops.each do |op| + log_info("(formula sub_op)") {SXP::Generator.string [op, solution].to_sxp_bin} + op.each(solutions: RDF::Query::Solutions(solution)) do |stmt| + log_debug("(formula add from sub_op)") {stmt.to_sxp} + block.call(stmt) + # Add statements for any term which is a formula + stmt.to_a.select(&:node?).map {|n| formulae[n]}.compact.each do |ef| + log_info("(formula from form)") {ef.graph_name.to_sxp} + form_statements(ef, solution: solution, &block) + end + end end end end @@ -396,5 +385,29 @@ def to_base def inspect sprintf("#<%s:%s(%d)>", self.class.name, self.graph_name, self.operands.count) end + + private + # Get statements from a sub-form + # @return [RDF::Resource] graph name of form + def form_statements(form, solution:, &block) + # uses the graph_name of the formula, and yields statements from the formula + log_depth do + form.each(solutions: RDF::Query::Solutions(solution)) do |stmt| + stmt.graph_name ||= form.graph_name + log_debug("(form statements add)") {stmt.to_sxp} + block.call(stmt) + ## Recurse over embedded formulae + #stmt.to_a.select(&:node?).map {|n| formulae[n]}.compact.each do |ef| + # log_debug("(formula from var form embedded)") {ef.graph_name.to_sxp} + # ef.each(solutions: RDF::Query::Solutions(solution)) do |es| + # es.graph_name ||= ef.graph_name + # log_debug("(formula add from var form embedded)") {es.to_sxp} + # end + #end + end + end + + form.graph_name + end end end From 90211857139fc98b6b270687bb3d0516663fb4d8 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 17 Nov 2020 10:27:26 -0800 Subject: [PATCH 172/193] Remove `#clear_solutions`, and instead clear out `@solutions` from `log:implies` after `#each`, so that it does not get remembered when not in the context of a query. --- lib/rdf/n3/algebra/builtin.rb | 7 ------- lib/rdf/n3/algebra/formula.rb | 2 +- lib/rdf/n3/algebra/log/conclusion.rb | 3 +-- lib/rdf/n3/algebra/log/implies.rb | 6 +++--- lib/rdf/n3/extensions.rb | 20 -------------------- spec/reasoner_spec.rb | 6 ++---- spec/suite_reasoner_spec.rb | 2 +- 7 files changed, 8 insertions(+), 38 deletions(-) diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb index 25cdd72..00dac85 100644 --- a/lib/rdf/n3/algebra/builtin.rb +++ b/lib/rdf/n3/algebra/builtin.rb @@ -63,13 +63,6 @@ def each(solutions: RDF::Query::Solutions(), &block) end end - ## - # Clear out any cached solutions. - # This principaly is for log:conclusions - def clear_solutions - operands.each(&:clear_solutions) - end - ## # The builtin hash is the hash of it's operands and NAME. # diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index bd51dc8..d455f99 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -120,7 +120,7 @@ def deep_dup # @return [RDF::Solutions] distinct solutions def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options) log_info("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin} - log_debug("(formula bindings)") { solutions.bindings.map {|k,v| RDF::Query::Variable.new(k,v)}.to_sxp} + log_debug("(formula bindings)") { SXP::Generator.string solutions.to_sxp_bin} # Only query as patterns if this is an embedded formula @query ||= RDF::Query.new(patterns).optimize! diff --git a/lib/rdf/n3/algebra/log/conclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb index 2e655e2..e5e790a 100644 --- a/lib/rdf/n3/algebra/log/conclusion.rb +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -21,11 +21,10 @@ def resolve(resource, position:) log_depth do reasoner = RDF::N3::Reasoner.new(resource, **@options) conclusions = RDF::N3::Repository.new - reasoner.execute(think: true, logger: false) {|stmt| conclusions << stmt} + reasoner.execute(think: true) {|stmt| conclusions << stmt} # The result is a formula containing the conclusions form = RDF::N3::Algebra::Formula.from_enumerable(conclusions, **@options).deep_dup - form.clear_solutions log_info("#{NAME} resolved") {SXP::Generator.string form.to_sxp_bin} form diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index 4f0ac5a..b1dbb2b 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -50,7 +50,7 @@ def execute(queryable, solutions:, **options) vars.all? {|v| soln.bound?(v)} end solns - end.flatten.compact) + end.flatten.compact.uniq) log_info(NAME) {SXP::Generator.string(@solutions.to_sxp_bin)} # Return original solutions, without bindings @@ -74,9 +74,9 @@ def clear_solutions # @yieldreturn [void] ignored def each(solutions: RDF::Query::Solutions(), &block) # Merge solutions in with those for the evaluation of this implication - solutions = Array(@solutions) + # Clear out solutions so they don't get remembered erroneously. + solutions, @solutions = Array(@solutions), nil log_depth do - #operand(0).clear_solutions super(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) solutions.each do |solution| diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index 669b557..ec4aebf 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -36,13 +36,6 @@ def to_sxp_bin def to_sxp to_a.to_sxp_bin.to_sxp end - - ## - # Clear out any cached solutions. - # This principaly is for log:conclusions - def clear_solutions - to_a.each(&:clear_solutions) - end end class Statement @@ -65,13 +58,6 @@ def to_sxp_bin def to_sxp to_sxp_bin.to_sxp end - - ## - # Clear out any cached solutions. - # This principaly is for log:conclusions - def clear_solutions - to_a.each(&:clear_solutions) - end end module Value @@ -90,12 +76,6 @@ def formula? def to_ndvar(scope) self end - - ## - # Clear out any cached solutions. - # Overriden by sub-classes. - def clear_solutions - end end module Term diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 45a01dc..0cedf74 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -41,8 +41,7 @@ a . { .} => { a .} . } a :TestResult . - ), - pending: "inferred triples in conclusion premis" + ) }, "conclusion-simple" => { input: %( @@ -66,8 +65,7 @@ a . { .} => { a .} . } a :TestResult . - ), - pending: "inferred triples in conclusion premis" + ) }, }.each do |name, options| it name do diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 335c56e..64bc0bf 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -22,7 +22,7 @@ case t.id.split('#').last when *%w{cwm_unify_unify1 cwm_includes_builtins cwm_includes_t10 cwm_includes_t11 - cwm_includes_conclusion_simple cwm_includes_conclusion} + cwm_includes_conclusion} pending "log:includes etc." when *%w{cwm_supports_simple cwm_string_roughly} pending "Uses unsupported builtin" From 2cf09a3e04a8cfd3d458d2c6b194e758758ac7b1 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 17 Nov 2020 10:56:31 -0800 Subject: [PATCH 173/193] Use test:conclusions test option instead of test:filter, which means something different. test:conclusions replaces the store with the conclusions of running the rules (similar to log:conclusion). --- script/tc | 2 +- spec/reasoner_spec.rb | 40 ++++++++++++++++++------------------- spec/suite_helper.rb | 3 ++- spec/suite_reasoner_spec.rb | 2 +- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/script/tc b/script/tc index da9f3e5..f751dd3 100755 --- a/script/tc +++ b/script/tc @@ -115,7 +115,7 @@ def run_tc(man, tc, **options) begin reasoner.execute(logger: logger, think: !!tc.options['think']) - if tc.options["filter"] + if tc.options["conclusions"] repo << reasoner.conclusions elsif tc.options["data"] repo << reasoner.data diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 0cedf74..ef63afb 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -70,7 +70,7 @@ }.each do |name, options| it name do logger.info "input: #{options[:input]}" - options = {data: false, filter: false}.merge(options) + options = {data: false, conclusions: false}.merge(options) pending(options[:pending]) if options[:pending] expected = parse(options[:expect]) result = reason(options[:input], **options) @@ -96,7 +96,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {data: false, filter: true}.merge(options) + options = {data: false, conclusions: true}.merge(options) expected = parse(options[:expect]) result = reason(options[:input], **options) expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) @@ -292,7 +292,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {data: false, filter: true}.merge(options) + options = {data: false, conclusions: true}.merge(options) expected = parse(options[:expect]) result = reason(options[:input], **options) expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) @@ -384,7 +384,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end @@ -444,7 +444,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end @@ -486,7 +486,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end @@ -509,7 +509,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) if options[:exception] expect {reason(options[:input], **options)}.to raise_error options[:exception] else @@ -542,7 +542,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end @@ -577,7 +577,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end @@ -606,7 +606,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end @@ -633,7 +633,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end @@ -670,7 +670,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end @@ -707,7 +707,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end @@ -746,7 +746,7 @@ end logger.info "input: #{input}" expected = parse(expect) - expect(reason(input, filter: true)).to be_equivalent_graph(expected, logger: logger) + expect(reason(input, conclusions: true)).to be_equivalent_graph(expected, logger: logger) end end end @@ -863,12 +863,12 @@ "0", 3.1415926 . ), - filter: false, data: true + conclusions: false, data: true }, }.each do |name, options| it name do pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) logger.info "input: #{options[:input]}" expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) @@ -907,7 +907,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end @@ -928,7 +928,7 @@ it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {filter: true}.merge(options) + options = {conclusions: true}.merge(options) expected = parse(options[:expect]) expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) end @@ -946,13 +946,13 @@ def parse(input, **options) end # Reason over input, returning a repo - def reason(input, base_uri: 'http://example.com/', filter: false, data: true, think: true, **options) + def reason(input, base_uri: 'http://example.com/', conclusions: false, data: true, think: true, **options) input = parse(input, list_terms: true, **options) if input.is_a?(String) reasoner = RDF::N3::Reasoner.new(input, base_uri: base_uri, logger: logger) repo = RDF::N3:: Repository.new reasoner.execute(think: think) - if filter + if conclusions repo << reasoner.conclusions elsif data repo << reasoner.data diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 0065b58..ee7a2a8 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -79,9 +79,10 @@ module SuiteTest "action": {"@id": "mf:action", "@type": "@id"}, "approval": {"@id": "rdft:approval", "@type": "@vocab"}, "comment": "rdfs:comment", + "conclusions": {"@id": "test:conclusions", "@type": "xsd:boolean"}, "data": {"@id": "test:data", "@type": "xsd:boolean"}, "entries": {"@id": "mf:entries", "@container": "@list"}, - "filter": {"@id": "test:filter", "@type": "xsd:boolean"}, + "filter": {"@id": "test:filter", "@type": "@id"}, "name": "mf:name", "options": {"@id": "test:options", "@type": "@id"}, "result": {"@id": "mf:result", "@type": "@id"}, diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 64bc0bf..0f5680b 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -53,7 +53,7 @@ if t.positive_test? begin reasoner.execute(logger: t.logger, think: !!t.options['think']) - if t.options["filter"] + if t.options["conclusions"] repo << reasoner.conclusions elsif t.options["data"] repo << reasoner.data From 044743f8d6ec866e2f78f1ec8203bbfb59027188 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 18 Nov 2020 12:23:36 -0800 Subject: [PATCH 174/193] Update string:concatenation to use XPath casting functions from SPARQL. --- lib/rdf/n3/algebra/resource_operator.rb | 2 +- lib/rdf/n3/algebra/str/concatenation.rb | 10 ++- spec/reasoner_spec.rb | 84 +++++++++++++++++++++++-- 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 1e63600..9a7c3e6 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -97,7 +97,7 @@ def valid?(subject, object) end ## - # Returns a literal for the numeric argument, with doubles canonicalized using a lower-case 'e'. + # Returns a literal for the numeric argument. def as_literal(object) case object when Float diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb index be28c04..8f37f91 100644 --- a/lib/rdf/n3/algebra/str/concatenation.rb +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -9,13 +9,19 @@ class Concatenation < RDF::N3::Algebra::ListOperator URI = RDF::N3::Str.concatenation ## - # The string:concatenation operator takes a list of terms evaluating to strings and either binds the result of concatenating them to the output variable, removes a solution that does equal. + # The string:concatenation operator takes a list of terms cast to strings and either binds the result of concatenating them to the output variable, removes a solution that does equal the literal object. + # + # List entries are stringified using {SPARQL::Algebra::Expression.cast}. # # @param [RDF::N3::List] list # @return [RDF::Term] # @see RDF::N3::ListOperator#evaluate def resolve(list) - RDF::Literal(list.to_a.map(&:canonicalize).join("")) + RDF::Literal( + list.to_a.map do |o| + SPARQL::Algebra::Expression.cast(RDF::XSD.string, o) + end.join("") + ) end end end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index ef63afb..8724f1e 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -285,14 +285,14 @@ ; ; "32746213462187364732164732164321" . - } a <#result> . + } a . ) } }.each do |name, options| it name do logger.info "input: #{options[:input]}" pending(options[:pending]) if options[:pending] - options = {data: false, conclusions: true}.merge(options) + options = {data: false, conclusions: true, base_uri: 'http://example.com/'}.merge(options) expected = parse(options[:expect]) result = reason(options[:input], **options) expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) @@ -878,6 +878,80 @@ end context "n3:string" do + context "string:concatenation" do + { + "string": { + input: %( + @prefix string: . + {("foo" "bar") string:concatenation ?x} => {:test :is ?x}. + ), + expect: %(:test :is "foobar" .) + }, + "integer": { + input: %( + @prefix string: . + {(1 01) string:concatenation ?x} => {:test :is ?x}. + ), + expect: %(:test :is "11" .) + }, + "decimal": { + input: %( + @prefix string: . + {(0.0 1.0 2.5 -2.5) string:concatenation ?x} => {:test :is ?x}. + ), + expect: %(:test :is "012.5-2.5" .) + }, + "boolean": { + input: %( + @prefix string: . + @prefix xsd: . + {( + true + false + "0"^^xsd:boolean + ) string:concatenation ?x} => {:test :is ?x}. + ), + expect: %(:test :is "truefalsefalse" .) + }, + "float": { + input: %( + @prefix string: . + @prefix xsd: . + {( + "0E1"^^xsd:float + "1E0"^^xsd:float + "1.25"^^xsd:float + "-7.875"^^xsd:float + ) string:concatenation ?x} => {:test :is ?x}. + ), + expect: %(:test :is "011.25-7.875" .) + }, + "double": { + input: %( + @prefix string: . + {(0E1 1E0 1.23E3) string:concatenation ?x} => {:test :is ?x}. + ), + expect: %(:test :is "011230" .) + }, + "IRI": { + input: %( + @prefix string: . + {(:a " " :b) string:concatenation ?x} => {:test :is ?x}. + ), + expect: %(:test :is "http://example.org/a http://example.org/b" .), + base_uri: "http://example.org/" + }, + }.each do |name, options| + it name do + logger.info "input: #{options[:input]}" + pending(options[:pending]) if options[:pending] + options = {conclusions: true}.merge(options) + expected = parse(options[:expect], base_uri: options[:base_uri]) + expect(reason(options[:input], **options)).to be_equivalent_graph(expected, logger: logger) + end + end + end + context "string:startsWith" do { "literal starts with literal" => { @@ -946,9 +1020,9 @@ def parse(input, **options) end # Reason over input, returning a repo - def reason(input, base_uri: 'http://example.com/', conclusions: false, data: true, think: true, **options) - input = parse(input, list_terms: true, **options) if input.is_a?(String) - reasoner = RDF::N3::Reasoner.new(input, base_uri: base_uri, logger: logger) + def reason(input, base_uri: nil, conclusions: false, data: true, think: true, **options) + input = parse(input, list_terms: true, base_uri: base_uri, **options) if input.is_a?(String) + reasoner = RDF::N3::Reasoner.new(input, logger: logger, base_uri: base_uri) repo = RDF::N3:: Repository.new reasoner.execute(think: think) From 73bb29263df772e8ae7ef8d3fdd84390ecc130ed Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 20 Nov 2020 15:39:58 -0800 Subject: [PATCH 175/193] Fix logEqualTo when operands are literals. --- lib/rdf/n3/algebra/log/equal_to.rb | 2 +- spec/reasoner_spec.rb | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/rdf/n3/algebra/log/equal_to.rb b/lib/rdf/n3/algebra/log/equal_to.rb index 0b9320c..ba623e5 100644 --- a/lib/rdf/n3/algebra/log/equal_to.rb +++ b/lib/rdf/n3/algebra/log/equal_to.rb @@ -28,7 +28,7 @@ def input_operand # a literal # @return [RDF::Literal::Boolean] def apply(left, right) - left.sameTerm?(right) + RDF::Literal(left.sameTerm?(right)) end end end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 8724f1e..6c74a02 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -4,7 +4,7 @@ describe "RDF::N3::Reasoner" do let(:logger) {RDF::Spec.logger} - before {logger.level = Logger::INFO} + before {logger.level = Logger::DEBUG} context "variables" do context "universals" do @@ -941,6 +941,15 @@ expect: %(:test :is "http://example.org/a http://example.org/b" .), base_uri: "http://example.org/" }, + "test13g": { + input: %( + @prefix string: . + { "" log:equalTo [ is string:concatenation of () ] } => {:test13g a :success}. + ), + expect: %( + :test13g a :success . + ) + } }.each do |name, options| it name do logger.info "input: #{options[:input]}" From a79ad7d30af0c08726306c01ee8a4edc0f7a5453 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 20 Nov 2020 15:45:51 -0800 Subject: [PATCH 176/193] Do not imply facts not in evidence. Antecedent containing constant patterns does not match a knowledge base not including those facts. Breaks some previously "passing" tests. --- lib/rdf/n3/algebra/formula.rb | 26 ++++++++++++++++---------- lib/rdf/n3/algebra/log/implies.rb | 8 ++++---- lib/rdf/n3/algebra/log/includes.rb | 10 +++++----- spec/reasoner_spec.rb | 24 ++++++++++++++++++++---- spec/suite_reasoner_spec.rb | 5 +++-- 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index d455f99..4dcf87d 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -130,17 +130,23 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new solutions else these_solutions = queryable.query(@query, solutions: solutions, **options) - these_solutions.map! do |solution| - RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)| - # Replace blank node bindings with lists and formula references with formula, where those blank nodes are associated with lists. - value = formulae.fetch(value, value) if value.node? - l = RDF::N3::List.try_list(value, queryable) - value = l if l.constant? - memo.merge(name => value) - end) + if these_solutions.empty? + # Pattern doesn't match, so there can be no solutions + log_debug("(formula query solutions)") { SXP::Generator.string([].to_sxp_bin)} + return RDF::Query::Solutions.new + else + these_solutions.map! do |solution| + RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)| + # Replace blank node bindings with lists and formula references with formula, where those blank nodes are associated with lists. + value = formulae.fetch(value, value) if value.node? + l = RDF::N3::List.try_list(value, queryable) + value = l if l.constant? + memo.merge(name => value) + end) + end + log_debug("(formula query solutions)") { SXP::Generator.string(these_solutions.to_sxp_bin)} + solutions.merge(these_solutions) end - log_debug("(formula query solutions)") { SXP::Generator.string(these_solutions.to_sxp_bin)} - solutions.merge(these_solutions) end # Reject solutions which include variables as values diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb index b1dbb2b..0cd23e7 100644 --- a/lib/rdf/n3/algebra/log/implies.rb +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -40,15 +40,15 @@ def execute(queryable, solutions:, **options) solns = log_depth {subject.execute(queryable, solutions: RDF::Query::Solutions(solution), **options)} # Execute object as well (typically used for log:outputString) - solns = solns.map do |soln| + solns.each do |soln| log_depth {object.execute(queryable, solutions: RDF::Query::Solutions(soln), **options)} - end.flatten.compact + end # filter solutions where not all variables in antecedant are bound. vars = subject.universal_vars - solns = solns.select do |soln| + solns = RDF::Query::Solutions(solns.to_a.select do |soln| vars.all? {|v| soln.bound?(v)} - end + end) solns end.flatten.compact.uniq) log_info(NAME) {SXP::Generator.string(@solutions.to_sxp_bin)} diff --git a/lib/rdf/n3/algebra/log/includes.rb b/lib/rdf/n3/algebra/log/includes.rb index f4df679..19aba9e 100644 --- a/lib/rdf/n3/algebra/log/includes.rb +++ b/lib/rdf/n3/algebra/log/includes.rb @@ -28,23 +28,23 @@ class Includes < SPARQL::Algebra::Operator::Binary def execute(queryable, solutions:, **options) @queryable = queryable RDF::Query::Solutions(solutions.map do |solution| + log_debug(NAME, "solution") {SXP::Generator.string(solution.to_sxp_bin)} subject = operand(0).evaluate(solution.bindings, formulae: formulae) object = operand(1).evaluate(solution.bindings, formulae: formulae) - log_debug(NAME) {"subject: #{SXP::Generator.string subject.to_sxp_bin}"} - log_debug(NAME) {"object: #{SXP::Generator.string operand(1).to_sxp_bin}"} + log_info(NAME, "subject") {SXP::Generator.string(subject.to_sxp_bin)} + log_info(NAME, "object") {SXP::Generator.string(object.to_sxp_bin)} # Nothing to do if variables aren't resolved. next unless subject && object solns = log_depth {subject.execute(queryable, solutions: RDF::Query::Solutions(solution), **options)} - log_debug("(logIncludes solutions pre-filter)") {SXP::Generator.string solns.to_sxp_bin} # filter solutions where not all variables in antecedant are bound. vars = subject.universal_vars solns = solns.filter do |solution| vars.all? {|v| solution.bound?(v)} end - log_debug("(logIncludes subject)") {SXP::Generator.string solns.to_sxp_bin} + log_info("(logIncludes subject)") {SXP::Generator.string solns.to_sxp_bin} next if solns.empty? repo = RDF::N3::Repository.new << subject @@ -57,7 +57,7 @@ def execute(queryable, solutions:, **options) solns = solns.filter do |soln| vars.all? {|v| soln.bound?(v)} end - log_debug("(logIncludes object)") {SXP::Generator.string solns.to_sxp_bin} + log_info("(logIncludes object)") {SXP::Generator.string solns.to_sxp_bin} solns end.flatten.compact) end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 6c74a02..764e261 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -65,7 +65,8 @@ a . { .} => { a .} . } a :TestResult . - ) + ), + pending: "broken after not matching non-existant patterns" }, }.each do |name, options| it name do @@ -171,6 +172,12 @@ expect: %( :a :b :c; :d :e. ) + }, + "does not imply facts not in evidence" => { + input: %( + {:s :p :o} => {:test a :Failure}. + ), + expect: %() } }.each do |name, options| it name do @@ -187,32 +194,39 @@ { "t1" => { input: %( + @prefix log: . {{ :a :b :c } log:includes { :a :b :c }} => { :test1 a :success } . ), expect: %( :test1 a :success . - ) + ), + pending: "broken after not matching non-existant patterns" }, "t2" => { input: %( + @prefix log: . { { <#theSky> <#is> <#blue> } log:includes {<#theSky> <#is> <#blue>} } => { :test3 a :success } . { { <#theSky> <#is> <#blue> } log:notIncludes {<#theSky> <#is> <#blue>} } => { :test3_bis a :FAILURE } . ), expect: %( :test3 a :success . - ) + ), + pending: "broken after not matching non-existant patterns" }, "quantifiers-limited-a1" => { input: %( + @prefix log: . {{ :foo :bar :baz } log:includes { :foo :bar :baz }} => { :testa1 a :success } . ), expect: %( :testa1 a :success . - ) + ), + pending: "broken after not matching non-existant patterns" }, #"quantifiers-limited-a2" => { # input: %( + # @prefix log: . # {{ :foo :bar :baz } log:includes { @forSome :foo. :foo :bar :baz }} # => { :testa2 a :success } . # ), @@ -223,6 +237,7 @@ #}, #"quantifiers-limited-b2" => { # input: %( + # @prefix log: . # {{ @forSome :foo. :foo :bar :baz } log:includes {@forSome :foo. :foo :bar :baz }} # => { :testb2 a :success } . # ), @@ -233,6 +248,7 @@ #}, #"quantifiers-limited-a1d" => { # input: %( + # @prefix log: . # {{ :fee :bar :baz } log:includes { :foo :bar :baz }} # => { :testa1d a :FAILURE } . # ), diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 0f5680b..274130d 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -20,9 +20,10 @@ next if t.approval == 'rdft:Rejected' specify "#{t.name}: #{t.comment}" do case t.id.split('#').last - when *%w{cwm_unify_unify1 cwm_includes_builtins + when *%w{cwm_unify_unify1 cwm_unify_unify2 cwm_includes_builtins + cwm_includes_t1 cwm_includes_t3 cwm_includes_t9br cwm_includes_t10 cwm_includes_t11 - cwm_includes_conclusion} + cwm_includes_conclusion_simple cwm_includes_conclusion} pending "log:includes etc." when *%w{cwm_supports_simple cwm_string_roughly} pending "Uses unsupported builtin" From e0c2b6c4eaaae456905b52090b152600314be127 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 21 Nov 2020 11:10:05 -0800 Subject: [PATCH 177/193] Fix void value expression error. --- lib/rdf/n3/algebra/formula.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 4dcf87d..974eacc 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -133,7 +133,7 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new if these_solutions.empty? # Pattern doesn't match, so there can be no solutions log_debug("(formula query solutions)") { SXP::Generator.string([].to_sxp_bin)} - return RDF::Query::Solutions.new + RDF::Query::Solutions.new else these_solutions.map! do |solution| RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)| @@ -149,6 +149,8 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new end end + return solutions if solutions.empty? + # Reject solutions which include variables as values solutions.filter! {|s| s.enum_value.none?(&:variable?)} From a2d7be838fb7e7ce5cb3793265e6d6a78213009b Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 21 Nov 2020 13:05:23 -0800 Subject: [PATCH 178/193] Run CI on ruby-head. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 83a126b..67fd26c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,12 @@ rvm: - 2.5 - 2.6 - 2.7 + - ruby-head - jruby cache: bundler sudo: false matrix: allow_failures: + - rvm: ruby-head - rvm: jruby dist: trusty From 3cb35b8bf1abf1e31747650e29c59a3d1e55c6b8 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 22 Nov 2020 15:57:16 -0800 Subject: [PATCH 179/193] Rewrite log:includes. --- lib/rdf/n3/algebra/builtin.rb | 2 +- lib/rdf/n3/algebra/formula.rb | 6 +- lib/rdf/n3/algebra/log/includes.rb | 91 +++++++++++++------------ lib/rdf/n3/algebra/log/semantics.rb | 2 +- lib/rdf/n3/algebra/resource_operator.rb | 13 +++- lib/rdf/n3/extensions.rb | 2 + spec/reasoner_spec.rb | 78 +++++++++++---------- spec/suite_reasoner_spec.rb | 3 +- 8 files changed, 110 insertions(+), 87 deletions(-) diff --git a/lib/rdf/n3/algebra/builtin.rb b/lib/rdf/n3/algebra/builtin.rb index 00dac85..4e1e743 100644 --- a/lib/rdf/n3/algebra/builtin.rb +++ b/lib/rdf/n3/algebra/builtin.rb @@ -48,7 +48,7 @@ def evaluate(bindings, formulae:, **options) # # Pass in solutions to have quantifiers resolved to those solutions. def each(solutions: RDF::Query::Solutions(), &block) - log_info("(#{self.class.const_get(:NAME)} each)") + log_debug("(#{self.class.const_get(:NAME)} each)") log_depth do subject, object = operands.map {|op| op.formula? ? op.graph_name : op} block.call(RDF::Statement(subject, self.to_uri, object)) diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index 974eacc..a820624 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -235,7 +235,7 @@ def hash # @yieldparam [RDF::Statement] solution # @yieldreturn [void] ignored def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) - log_info("(formula each)") {SXP::Generator.string([self, solutions].to_sxp_bin)} + log_debug("(formula each)") {SXP::Generator.string([self, solutions].to_sxp_bin)} # Yield patterns by binding variables solutions.each do |solution| @@ -277,13 +277,13 @@ def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) # statements from sub-operands sub_ops.each do |op| - log_info("(formula sub_op)") {SXP::Generator.string [op, solution].to_sxp_bin} + log_debug("(formula sub_op)") {SXP::Generator.string [op, solution].to_sxp_bin} op.each(solutions: RDF::Query::Solutions(solution)) do |stmt| log_debug("(formula add from sub_op)") {stmt.to_sxp} block.call(stmt) # Add statements for any term which is a formula stmt.to_a.select(&:node?).map {|n| formulae[n]}.compact.each do |ef| - log_info("(formula from form)") {ef.graph_name.to_sxp} + log_debug("(formula from form)") {ef.graph_name.to_sxp} form_statements(ef, solution: solution, &block) end end diff --git a/lib/rdf/n3/algebra/log/includes.rb b/lib/rdf/n3/algebra/log/includes.rb index 19aba9e..949ef78 100644 --- a/lib/rdf/n3/algebra/log/includes.rb +++ b/lib/rdf/n3/algebra/log/includes.rb @@ -7,59 +7,64 @@ module RDF::N3::Algebra::Log # 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 Includes < SPARQL::Algebra::Operator::Binary - include SPARQL::Algebra::Query - include SPARQL::Algebra::Update - include RDF::N3::Algebra::Builtin - + class Includes < RDF::N3::Algebra::ResourceOperator NAME = :logIncludes URI = RDF::N3::Log.includes ## - # Creates a repository constructed by evaluating the subject against queryable and queries object against that repository. Either retuns a single solution, or no solutions + # Both subject and object must be formulae. # - # @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 - # @return [RDF::Solutions] distinct solutions - def execute(queryable, solutions:, **options) - @queryable = queryable - RDF::Query::Solutions(solutions.map do |solution| - log_debug(NAME, "solution") {SXP::Generator.string(solution.to_sxp_bin)} - subject = operand(0).evaluate(solution.bindings, formulae: formulae) - object = operand(1).evaluate(solution.bindings, formulae: formulae) - log_info(NAME, "subject") {SXP::Generator.string(subject.to_sxp_bin)} - log_info(NAME, "object") {SXP::Generator.string(object.to_sxp_bin)} - - # Nothing to do if variables aren't resolved. - next unless subject && object + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + resource if resource.formula? + end - solns = log_depth {subject.execute(queryable, solutions: RDF::Query::Solutions(solution), **options)} + # Both subject and object are inputs. + def input_operand + RDF::N3::List.new(values: operands) + end - # filter solutions where not all variables in antecedant are bound. - vars = subject.universal_vars - solns = solns.filter do |solution| - vars.all? {|v| solution.bound?(v)} + ## + # Creates a repository constructed by substituting variables and in that subject with known IRIs and queries object against that repository. Either retuns a single solution, or no solutions. + # + # @note this does allow object to have variables not in the subject, if they could have been substituted away. + # + # @param [RDF::N3::Algebra::Formula] subject + # a formula + # @param [RDF::N3::Algebra::Formula] object + # a formula + # @return [RDF::Literal::Boolean] + def apply(subject, object) + subject_var_map = subject.variables.values.inject({}) {|memo, v| memo.merge(v => RDF::URI(v.name))} + object_vars = object.variables.keys + log_debug(NAME, "subject var map") {SXP::Generator.string(subject_var_map.to_sxp_bin)} + log_debug(NAME, "object vars") {SXP::Generator.string(object_vars.to_sxp_bin)} + # create a queryable from subject, replacing variables with IRIs for thsoe variables. + queryable = RDF::Repository.new do |r| + log_depth do + subject.each do |stmt| + parts = stmt.to_quad.map do |part| + part.is_a?(RDF::Query::Variable) ? subject_var_map.fetch(part) : part + end + r << RDF::Statement.from(parts) + end end - log_info("(logIncludes subject)") {SXP::Generator.string solns.to_sxp_bin} - next if solns.empty? + end - repo = RDF::N3::Repository.new << subject + # Query object against subject + solns = log_depth {queryable.query(object, **@options)} + log_info("(#{NAME} solutions)") {SXP::Generator.string solns.to_sxp_bin} - # Query object against repo - solns = log_depth {object.execute(repo, solutions: solns, **options)} - - # filter solutions where not all variables in antecedant are bound. - vars = object.universal_vars - solns = solns.filter do |soln| - vars.all? {|v| soln.bound?(v)} - end - log_info("(logIncludes object)") {SXP::Generator.string solns.to_sxp_bin} - solns - end.flatten.compact) + if !solns.empty? && (object_vars - solns.variable_names).empty? + # Return solution + solns.first + else + # Return false, + RDF::Literal::FALSE + end end end end diff --git a/lib/rdf/n3/algebra/log/semantics.rb b/lib/rdf/n3/algebra/log/semantics.rb index 55bb1bb..31b90de 100644 --- a/lib/rdf/n3/algebra/log/semantics.rb +++ b/lib/rdf/n3/algebra/log/semantics.rb @@ -25,7 +25,7 @@ def resolve(resource, position: :subject) repo << RDF::Reader.open(resource, **@options.merge(list_terms: true, base_uri: resource, logger: false)) content_hash = repo.statements.hash # used as name of resulting formula form = RDF::N3::Algebra::Formula.from_enumerable(repo, graph_name: RDF::Node.intern(content_hash)) - log_info(NAME) {"form hash (#{resource}): #{form.hash}"} + log_debug(NAME) {"form hash (#{resource}): #{form.hash}"} form rescue IOError, RDF::ReaderError => e log_error(NAME) {"error loading #{resource}: #{e}"} diff --git a/lib/rdf/n3/algebra/resource_operator.rb b/lib/rdf/n3/algebra/resource_operator.rb index 9a7c3e6..0d1d3c1 100644 --- a/lib/rdf/n3/algebra/resource_operator.rb +++ b/lib/rdf/n3/algebra/resource_operator.rb @@ -49,7 +49,18 @@ def execute(queryable, solutions:, **options) res = apply(lhs, rhs) log_debug(self.class.const_get(:NAME), "result") {SXP::Generator.string(res.to_sxp_bin).strip} # Return the result applying subject and object - solution if res == RDF::Literal::TRUE + #require 'byebug'; byebug + case res + when RDF::Literal::TRUE + solution + when RDF::Literal::FALSE + nil + when RDF::Query::Solution + solution.merge(res) + else + log_error(self.class.const_get(:NAME), "unexpected result type") + nil + end elsif rhs != lhs log_debug(self.class.const_get(:NAME), "result: false") nil diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index ec4aebf..dd30cf0 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -58,6 +58,8 @@ def to_sxp_bin def to_sxp to_sxp_bin.to_sxp end + + def executable?; false; end end module Value diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 764e261..4cf0ade 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -4,7 +4,7 @@ describe "RDF::N3::Reasoner" do let(:logger) {RDF::Spec.logger} - before {logger.level = Logger::DEBUG} + before {logger.level = Logger::INFO} context "variables" do context "universals" do @@ -199,8 +199,7 @@ ), expect: %( :test1 a :success . - ), - pending: "broken after not matching non-existant patterns" + ) }, "t2" => { input: %( @@ -210,8 +209,7 @@ ), expect: %( :test3 a :success . - ), - pending: "broken after not matching non-existant patterns" + ) }, "quantifiers-limited-a1" => { input: %( @@ -221,39 +219,47 @@ ), expect: %( :testa1 a :success . + ) + }, + "quantifiers-limited-a2" => { + input: %( + @prefix log: . + {{ :foo :bar :baz } log:includes { @forSome :foo. :foo :bar :baz }} + => { :testa2 a :success } . ), - pending: "broken after not matching non-existant patterns" + expect: %( + :testa2 a :success . + ) + }, + "quantifiers-limited-b2" => { + input: %( + @prefix log: . + {{ @forSome :foo. :foo :bar :baz } log:includes {@forSome :foo. :foo :bar :baz }} + => { :testb2 a :success } . + ), + expect: %( + :testb2 a :success . + ) }, - #"quantifiers-limited-a2" => { - # input: %( - # @prefix log: . - # {{ :foo :bar :baz } log:includes { @forSome :foo. :foo :bar :baz }} - # => { :testa2 a :success } . - # ), - # expect: %( - # :testa2 a :success . - # ), - # pending: "Variable substitution" - #}, - #"quantifiers-limited-b2" => { - # input: %( - # @prefix log: . - # {{ @forSome :foo. :foo :bar :baz } log:includes {@forSome :foo. :foo :bar :baz }} - # => { :testb2 a :success } . - # ), - # expect: %( - # :testb2 a :success . - # ), - # pending: "Variable substitution" - #}, - #"quantifiers-limited-a1d" => { - # input: %( - # @prefix log: . - # {{ :fee :bar :baz } log:includes { :foo :bar :baz }} - # => { :testa1d a :FAILURE } . - # ), - # expect: %() - #}, + "quantifiers-limited-a1d" => { + input: %( + @prefix log: . + {{ :fee :bar :baz } log:includes { :foo :bar :baz }} + => { :testa1d a :FAILURE } . + ), + expect: %() + }, + "t10b" => { + input: %( + @prefix log: . + { {:theSky :is :blue} log:includes { :theSky :is ?x} } log:implies { :fred :favoriteColor ?x } . + { :fred :favoriteColor :blue } log:implies { :test10b a :success}. + ), + expect: %( + :fred :favoriteColor :blue. + :test10b a :success. + ) + } }.each do |name, options| it name do logger.info "input: #{options[:input]}" diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 274130d..bbcf2a9 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -21,8 +21,7 @@ specify "#{t.name}: #{t.comment}" do case t.id.split('#').last when *%w{cwm_unify_unify1 cwm_unify_unify2 cwm_includes_builtins - cwm_includes_t1 cwm_includes_t3 cwm_includes_t9br - cwm_includes_t10 cwm_includes_t11 + cwm_includes_t11 cwm_includes_conclusion_simple cwm_includes_conclusion} pending "log:includes etc." when *%w{cwm_supports_simple cwm_string_roughly} From 41361c137291d160489ae12c0180519a2adabb38 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 22 Nov 2020 17:06:01 -0800 Subject: [PATCH 180/193] Some extensions moved to sparql gem. --- lib/rdf/n3/extensions.rb | 67 ---------------------------------------- 1 file changed, 67 deletions(-) diff --git a/lib/rdf/n3/extensions.rb b/lib/rdf/n3/extensions.rb index dd30cf0..c4fb176 100644 --- a/lib/rdf/n3/extensions.rb +++ b/lib/rdf/n3/extensions.rb @@ -38,30 +38,6 @@ def to_sxp end end - class Statement - # Transform Statement into an SXP - # @return [Array] - def to_sxp_bin - [ (has_graph? ? :quad : :triple), - (:inferred if inferred?), - subject, - predicate, - object, - graph_name - ].compact.map(&:to_sxp_bin) - end - - ## - # Returns an S-Expression (SXP) representation - # - # @return [String] - def to_sxp - to_sxp_bin.to_sxp - end - - def executable?; false; end - end - module Value ## # Returns `true` if `self` is a {RDF::N3::Algebra::Formula}. @@ -135,20 +111,6 @@ def as_datetime rescue RDF::Literal(0) end - - class Double - ## - # Returns the SXP representation of this object. - # - # @return [String] - def to_sxp - case - when nan? then 'nan.0' - when infinite? then (infinite? > 0 ? '+inf.0' : '-inf.0') - else canonicalize.to_s.downcase - end - end - end end class Node @@ -203,18 +165,6 @@ def eql?(other) end true end - - # Transform Statement into an SXP - # @return [Array] - def to_sxp_bin - [ :triple, - (:inferred if inferred?), - subject, - predicate, - object, - graph_name - ].compact.map(&:to_sxp_bin) - end end class Solution @@ -228,14 +178,6 @@ def to_sxp_bin Query::Variable.new(k, v, existential: existential, distinguished: distinguished).to_sxp_bin end end - - ## - # Returns an S-Expression (SXP) representation - # - # @return [String] - def to_sxp - to_sxp_bin.to_sxp - end end class Variable @@ -252,15 +194,6 @@ def sameTerm?(other) def as_number RDF::Literal(0) end - - def to_sxp_bin - prefix = distinguished? ? (existential? ? '$' : '?') : (existential? ? '$$' : '??') - unbound? ? "#{prefix}#{name}" : ["#{prefix}#{name}", value].to_sxp_bin - end - - def to_sxp - to_sxp_bin.to_sxp - end end end end From 28d5daa8e891e005ddf6871eacc8e2b0d917a3d1 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 24 Nov 2020 12:29:17 -0800 Subject: [PATCH 181/193] Add back arc trig functions. --- README.md | 6 ++++++ lib/rdf/n3/algebra.rb | 13 +++++++++++++ lib/rdf/n3/algebra/math/acos.rb | 26 ++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/acosh.rb | 26 ++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/asin.rb | 26 ++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/asinh.rb | 26 ++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/atan.rb | 26 ++++++++++++++++++++++++++ lib/rdf/n3/algebra/math/atanh.rb | 26 ++++++++++++++++++++++++++ spec/reasoner_spec.rb | 2 ++ 9 files changed, 177 insertions(+) create mode 100644 lib/rdf/n3/algebra/math/acos.rb create mode 100644 lib/rdf/n3/algebra/math/acosh.rb create mode 100644 lib/rdf/n3/algebra/math/asin.rb create mode 100644 lib/rdf/n3/algebra/math/asinh.rb create mode 100644 lib/rdf/n3/algebra/math/atan.rb create mode 100644 lib/rdf/n3/algebra/math/atanh.rb diff --git a/README.md b/README.md index 65d8beb..90cf50e 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,12 @@ Reasoning is discussed in the [Design Issues][] document. #### RDF Math vocabulary * `math:absoluteValue` (See {RDF::N3::Algebra::Math::AbsoluteValue}) + * `math:acos` (See {RDF::N3::Algebra::Math::ACos}) + * `math:asin` (See {RDF::N3::Algebra::Math::ASin}) + * `math:atan` (See {RDF::N3::Algebra::Math::ATan}) + * `math:acosh` (See {RDF::N3::Algebra::Math::ACosH}) + * `math:asinh` (See {RDF::N3::Algebra::Math::ASinH}) + * `math:atanh` (See {RDF::N3::Algebra::Math::ATanH}) * `math:ceiling` (See {RDF::N3::Algebra::Math::Ceiling}) * `math:cosh` (See {RDF::N3::Algebra::Math::CosH}) * `math:cos` (See {RDF::N3::Algebra::Math::Cos}) diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index 49462cb..dc3fc1d 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -45,6 +45,12 @@ module Math def vocab; RDF::N3::Math.to_uri; end module_function :vocab autoload :AbsoluteValue, 'rdf/n3/algebra/math/absolute_value' + autoload :ACos, 'rdf/n3/algebra/math/acos' + autoload :ASin, 'rdf/n3/algebra/math/asin' + autoload :ATan, 'rdf/n3/algebra/math/atan' + autoload :ACosH, 'rdf/n3/algebra/math/acosh' + autoload :ASinH, 'rdf/n3/algebra/math/asinh' + autoload :ATanH, 'rdf/n3/algebra/math/atanh' autoload :Ceiling, 'rdf/n3/algebra/math/ceiling' autoload :Cos, 'rdf/n3/algebra/math/cos' autoload :CosH, 'rdf/n3/algebra/math/cosh' @@ -131,6 +137,13 @@ def for(uri) RDF::N3::Log.supports => NotImplemented, RDF::N3::Math.absoluteValue => Math.const_get(:AbsoluteValue), + RDF::N3::Math.acos => Math.const_get(:ACos), + RDF::N3::Math.asin => Math.const_get(:ASin), + RDF::N3::Math.atan => Math.const_get(:ATan), + RDF::N3::Math.acosh => Math.const_get(:ACosH), + RDF::N3::Math.asinh => Math.const_get(:ASinH), + RDF::N3::Math.atanh => Math.const_get(:ATanH), + RDF::N3::Math.ceiling => Math.const_get(:Ceiling), RDF::N3::Math.ceiling => Math.const_get(:Ceiling), RDF::N3::Math.cos => Math.const_get(:Cos), RDF::N3::Math.cosh => Math.const_get(:CosH), diff --git a/lib/rdf/n3/algebra/math/acos.rb b/lib/rdf/n3/algebra/math/acos.rb new file mode 100644 index 0000000..5e950f0 --- /dev/null +++ b/lib/rdf/n3/algebra/math/acos.rb @@ -0,0 +1,26 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the arc cosine value of the subject. + class ACos < RDF::N3::Algebra::ResourceOperator + NAME = :mathACos + URI = RDF::N3::Math.acos + + ## + # The math:acos operator takes string or number and calculates its arc cosine. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + case position + when :subject + return nil unless resource.literal? + as_literal(Math.acos(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + end +end diff --git a/lib/rdf/n3/algebra/math/acosh.rb b/lib/rdf/n3/algebra/math/acosh.rb new file mode 100644 index 0000000..c0edc1e --- /dev/null +++ b/lib/rdf/n3/algebra/math/acosh.rb @@ -0,0 +1,26 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the inverse hyperbolic cosine value of the subject. + class ACosH < RDF::N3::Algebra::ResourceOperator + NAME = :mathACosH + URI = RDF::N3::Math.acosh + + ## + # The math:acosh operator takes string or number and calculates its inverse hyperbolic cosine. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + case position + when :subject + return nil unless resource.literal? + as_literal(Math.acosh(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + end +end diff --git a/lib/rdf/n3/algebra/math/asin.rb b/lib/rdf/n3/algebra/math/asin.rb new file mode 100644 index 0000000..4b262ea --- /dev/null +++ b/lib/rdf/n3/algebra/math/asin.rb @@ -0,0 +1,26 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the arc sine value of the subject. + class ASin < RDF::N3::Algebra::ResourceOperator + NAME = :mathASin + URI = RDF::N3::Math.asin + + ## + # The math:asin operator takes string or number and calculates its arc sine. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + case position + when :subject + return nil unless resource.literal? + as_literal(Math.asin(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + end +end diff --git a/lib/rdf/n3/algebra/math/asinh.rb b/lib/rdf/n3/algebra/math/asinh.rb new file mode 100644 index 0000000..1c5374f --- /dev/null +++ b/lib/rdf/n3/algebra/math/asinh.rb @@ -0,0 +1,26 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the inverse hyperbolic sine value of the subject. + class ASinH < RDF::N3::Algebra::ResourceOperator + NAME = :mathASinH + URI = RDF::N3::Math.asinh + + ## + # The math:asinh operator takes string or number and calculates its inverse hyperbolic sine. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + case position + when :subject + return nil unless resource.literal? + as_literal(Math.asinh(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + end +end diff --git a/lib/rdf/n3/algebra/math/atan.rb b/lib/rdf/n3/algebra/math/atan.rb new file mode 100644 index 0000000..03f4020 --- /dev/null +++ b/lib/rdf/n3/algebra/math/atan.rb @@ -0,0 +1,26 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the arc tangent value of the subject. + class ATan < RDF::N3::Algebra::ResourceOperator + NAME = :mathATan + URI = RDF::N3::Math.atan + + ## + # The math:atan operator takes string or number and calculates its arc tangent. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + case position + when :subject + return nil unless resource.literal? + as_literal(Math.atan(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + end +end diff --git a/lib/rdf/n3/algebra/math/atanh.rb b/lib/rdf/n3/algebra/math/atanh.rb new file mode 100644 index 0000000..226ad1d --- /dev/null +++ b/lib/rdf/n3/algebra/math/atanh.rb @@ -0,0 +1,26 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the inverse hyperbolic tangent value of the subject. + class ATanH < RDF::N3::Algebra::ResourceOperator + NAME = :mathATanH + URI = RDF::N3::Math.atanh + + ## + # The math:atanh operator takes string or number and calculates its inverse hyperbolic tangent. + # + # @param [RDF::Term] resource + # @param [:subject, :object] position + # @return [RDF::Term] + # @see RDF::N3::ResourceOperator#evaluate + def resolve(resource, position:) + case position + when :subject + return nil unless resource.literal? + as_literal(Math.atanh(resource.as_number.object)) + when :object + return nil unless resource.literal? || resource.variable? + resource + end + end + end +end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 4cf0ade..7ea3dab 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -738,10 +738,12 @@ context "trig" do { "0": { + asin: "0.0e0", sin: "0.0e0", sinh: "0.0e0", cos: "1.0e0", cosh: "1.0e0", + atan: "0.0e0", tan: "0.0e0", tanh: "0.0e0", }, From 8e66cec08d88a72ea0308259159be20c8ebe3f3d Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 24 Nov 2020 12:34:05 -0800 Subject: [PATCH 182/193] Remove integerQuotient. --- README.md | 1 - lib/rdf/n3/algebra.rb | 2 -- lib/rdf/n3/algebra/math/integer_quotient.rb | 21 --------------------- 3 files changed, 24 deletions(-) delete mode 100644 lib/rdf/n3/algebra/math/integer_quotient.rb diff --git a/README.md b/README.md index 90cf50e..59f14a0 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,6 @@ Reasoning is discussed in the [Design Issues][] document. * `math:exponentiation` (See {RDF::N3::Algebra::Math::Exponentiation}) * `math:floor` (See {RDF::N3::Algebra::Math::Floor}) * `math:greaterThan` (See {RDF::N3::Algebra::Math::GreaterThan}) - * `math:integerQuotient` (See {RDF::N3::Algebra::Math::IntegerQuotient}) * `math:lessThan` (See {RDF::N3::Algebra::Math::LessThan}) * `math:negation` (See {RDF::N3::Algebra::Math::Negation}) * `math:notEqualTo` (See {RDF::N3::Algebra::Math::NotEqualTo}) diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb index dc3fc1d..17319a9 100644 --- a/lib/rdf/n3/algebra.rb +++ b/lib/rdf/n3/algebra.rb @@ -59,7 +59,6 @@ def vocab; RDF::N3::Math.to_uri; end autoload :Exponentiation, 'rdf/n3/algebra/math/exponentiation' autoload :Floor, 'rdf/n3/algebra/math/floor' autoload :GreaterThan, 'rdf/n3/algebra/math/greater_than' - autoload :IntegerQuotient, 'rdf/n3/algebra/math/integer_quotient' autoload :LessThan, 'rdf/n3/algebra/math/less_than' autoload :Negation, 'rdf/n3/algebra/math/negation' autoload :NotEqualTo, 'rdf/n3/algebra/math/not_equal_to' @@ -152,7 +151,6 @@ def for(uri) RDF::N3::Math.exponentiation => Math.const_get(:Exponentiation), RDF::N3::Math.floor => Math.const_get(:Floor), RDF::N3::Math.greaterThan => Math.const_get(:GreaterThan), - RDF::N3::Math.integerQuotient => Math.const_get(:IntegerQuotient), RDF::N3::Math.lessThan => Math.const_get(:LessThan), RDF::N3::Math.negation => Math.const_get(:Negation), RDF::N3::Math.notEqualTo => Math.const_get(:NotEqualTo), diff --git a/lib/rdf/n3/algebra/math/integer_quotient.rb b/lib/rdf/n3/algebra/math/integer_quotient.rb deleted file mode 100644 index cf29671..0000000 --- a/lib/rdf/n3/algebra/math/integer_quotient.rb +++ /dev/null @@ -1,21 +0,0 @@ -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. - # - # @see https://www.w3.org/TR/xpath-functions/#func-numeric-integer-divide - class IntegerQuotient < RDF::N3::Algebra::Math::Quotient - NAME = :mathIntegerQuotient - URI = RDF::N3::Math.integerQuotient - - ## - # The math:quotient operator takes a pair of strings or numbers and calculates their quotient. - # - # - # @param [RDF::N3::List] list - # @return [RDF::Term] - # @see RDF::N3::ListOperator#evaluate - def resolve(list) - RDF::Literal::Integer.new(super) - end - end -end From 89c21c6414becade94d6f6396337921ef6abe9ae Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Wed, 25 Nov 2020 14:19:18 -0800 Subject: [PATCH 183/193] Fix Formula#patterns and separate from Formula#n3statements. Includes a statements/patterns which aren't builtins, recusively. --- Gemfile | 3 +- lib/rdf/n3/algebra/formula.rb | 65 ++++++++++++++++++--------- lib/rdf/n3/algebra/log/conjunction.rb | 6 ++- spec/reasoner_spec.rb | 2 +- spec/suite_reasoner_spec.rb | 2 +- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/Gemfile b/Gemfile index d39376e..bf581eb 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' gem 'sparql', git: "https://github.com/ruby-rdf/sparql", branch: "develop" gem 'sxp', git: "https://github.com/dryruby/sxp.rb", branch: "develop" diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb index a820624..3ada833 100644 --- a/lib/rdf/n3/algebra/formula.rb +++ b/lib/rdf/n3/algebra/formula.rb @@ -122,11 +122,10 @@ def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new log_info("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin} log_debug("(formula bindings)") { SXP::Generator.string solutions.to_sxp_bin} - # Only query as patterns if this is an embedded formula @query ||= RDF::Query.new(patterns).optimize! - log_debug("(formula query)") { SXP::Generator.string(@query.patterns.to_sxp_bin)} + log_info("(formula query)") { SXP::Generator.string(@query.to_sxp_bin)} - solutions = if @query.patterns.empty? + solutions = if @query.empty? solutions else these_solutions = queryable.query(@query, solutions: solutions, **options) @@ -232,12 +231,12 @@ def hash # # @yield [statement] # each matching statement - # @yieldparam [RDF::Statement] solution + # @yieldparam [RDF::Statement] statement # @yieldreturn [void] ignored def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) log_debug("(formula each)") {SXP::Generator.string([self, solutions].to_sxp_bin)} - # Yield patterns by binding variables + # Yield statements by binding 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| @@ -247,10 +246,10 @@ def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) log_debug("(formula apply)") {solution.to_sxp} # Yield each variable statement which is constant after applying solution log_depth do - patterns.each do |pattern| + n3statements.each do |statement| terms = {} [:subject, :predicate, :object].each do |part| - terms[part] = case o = pattern.send(part) + terms[part] = case o = statement.send(part) when RDF::Query::Variable if solution[o] && solution[o].formula? log_info("(formula from var form)") {solution[o].graph_name.to_sxp} @@ -292,6 +291,30 @@ def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block) end end + ## + # Yields each pattern which is not a builtin + # + # @yield [pattern] + # each matching pattern + # @yieldparam [RDF::Query::Pattern] pattern + # @yieldreturn [void] ignored + def each_pattern(&block) + n3statements.each do |statement| + terms = {} + [:subject, :predicate, :object].each do |part| + terms[part] = case o = statement.send(part) + when RDF::N3::Algebra::Formula + form_statements(o, solution: RDF::Query::Solution.new(), &block) + else + o + end + end + + pattern = RDF::Query::Pattern.from(terms) + block.call(pattern) + end + end + # Graph name associated with this formula # @return [RDF::Resource] def graph_name; @options[:graph_name]; end @@ -310,17 +333,27 @@ def graph_name=(name) end ## - # Patterns memoizer, from the operands which are statements. - def patterns + # Statements memoizer, from the operands which are statements. + # + # Statements may include embedded formulae. + def n3statements # BNodes in statements are existential variables. - @patterns ||= begin + @n3statements ||= begin # Operations/Builtins are not statements. operands. - select {|op| op.is_a?(RDF::Statement)}. - map {|st| RDF::Query::Pattern.from(st)} + select {|op| op.is_a?(RDF::Statement)} end end + ## + # Patterns memoizer, from the operands which are statements and not builtins. + # + # Expands statements containing formulae into their statements. + def patterns + # BNodes in statements are existential variables. + @patterns ||= enum_for(:each_pattern).to_a + end + ## # Non-statement operands memoizer def sub_ops @@ -404,14 +437,6 @@ def form_statements(form, solution:, &block) stmt.graph_name ||= form.graph_name log_debug("(form statements add)") {stmt.to_sxp} block.call(stmt) - ## Recurse over embedded formulae - #stmt.to_a.select(&:node?).map {|n| formulae[n]}.compact.each do |ef| - # log_debug("(formula from var form embedded)") {ef.graph_name.to_sxp} - # ef.each(solutions: RDF::Query::Solutions(solution)) do |es| - # es.graph_name ||= ef.graph_name - # log_debug("(formula add from var form embedded)") {es.to_sxp} - # end - #end end end diff --git a/lib/rdf/n3/algebra/log/conjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb index 193acf8..ab3fcfe 100644 --- a/lib/rdf/n3/algebra/log/conjunction.rb +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -15,12 +15,14 @@ class Conjunction < RDF::N3::Algebra::ListOperator # @see RDF::N3::ListOperator#evaluate def resolve(list) form = RDF::N3::Algebra::Formula.new(graph_name: RDF::Node.intern(list.hash)) - log_debug(NAME) {"list hash: #{form.graph_name}"} + log_debug(NAME, "list hash") {form.graph_name} list.each do |f| form.operands.push(*f.operands) end - form.dup + form = form.dup + log_info(NAME, "result") {SXP::Generator.string form.to_sxp_bin} + form end ## diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb index 7ea3dab..fb1a33f 100644 --- a/spec/reasoner_spec.rb +++ b/spec/reasoner_spec.rb @@ -66,7 +66,7 @@ { .} => { a .} . } a :TestResult . ), - pending: "broken after not matching non-existant patterns" + #pending: "broken after not matching non-existant patterns" }, }.each do |name, options| it name do diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index bbcf2a9..32366c5 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -22,7 +22,7 @@ case t.id.split('#').last when *%w{cwm_unify_unify1 cwm_unify_unify2 cwm_includes_builtins cwm_includes_t11 - cwm_includes_conclusion_simple cwm_includes_conclusion} + cwm_includes_conclusion} pending "log:includes etc." when *%w{cwm_supports_simple cwm_string_roughly} pending "Uses unsupported builtin" From df4cce6ecad380472666a61d12c4aa3fe9608a00 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 26 Nov 2020 16:24:12 -0800 Subject: [PATCH 184/193] Minor README update. --- Gemfile | 3 +-- README.md | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Gemfile b/Gemfile index bf581eb..d39376e 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" gem 'sparql', git: "https://github.com/ruby-rdf/sparql", branch: "develop" gem 'sxp', git: "https://github.com/dryruby/sxp.rb", branch: "develop" diff --git a/README.md b/README.md index 59f14a0..ed45cc0 100644 --- a/README.md +++ b/README.md @@ -189,8 +189,11 @@ Formulae are typically used to query the knowledge-base, which is set from the b Blank nodes associated with rdf:List statements used as part of a built-in are made _non-distinguished_ existential variables, and patters containing these variables become optional. If they are not bound as part of the query, the implicitly are bound as the original blank nodes defined within the formula, which allows for both constant list arguments, list arguments that contain variables, or arguments which are variables expanding to lists. ## Dependencies +* [Ruby](https://ruby-lang.org/) (>= 2.4) * [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.1, >= 3.1.4) * [EBNF][EBNF gem] (~> 2.1) +* [SPARQL][SPARQL gem] (~> 3.1) +* [SXP][SXP gem] (~> 1.1) ## Documentation Full documentation available on [RubyDoc.info](https://rubydoc.info/github/ruby-rdf/rdf-n3) @@ -255,17 +258,19 @@ see or the accompanying {file:UNLICENSE} file. * * -[RDF.rb]: https://ruby-rdf.github.com/rdf -[EBNF gem]: https://ruby-rdf.github.com/ebnf -[RDF::Turtle]: https://ruby-rdf.github.com/rdf-turtle/ -[Design Issues]: https://www.w3.org/DesignIssues/Notation3.html "Notation-3 Design Issues" -[Team Submission]: https://www.w3.org/TeamSubmission/n3/ -[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://unlicense.org/#unlicensing-contributions +[RDF::Turtle]: https://ruby-rdf.github.com/rdf-turtle/ +[Design Issues]: https://www.w3.org/DesignIssues/Notation3.html "Notation-3 Design Issues" +[Team Submission]: https://www.w3.org/TeamSubmission/n3/ +[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://unlicense.org/#unlicensing-contributions [SPARQL S-Expressions]: https://jena.apache.org/documentation/notes/sse.html [W3C N3 Community Group]: https://www.w3.org/community/n3-dev/ -[N3]: https://w3c.github.io/N3/spec/ -[PEG]: https://en.wikipedia.org/wiki/Parsing_expression_grammar \ No newline at end of file +[N3]: https://w3c.github.io/N3/spec/ +[PEG]: https://en.wikipedia.org/wiki/Parsing_expression_grammar +[RDF.rb]: https://ruby-rdf.github.com/rdf +[EBNF gem]: https://ruby-rdf.github.com/ebnf +[SPARQL gem]: https://ruby-rdf.github.com/sparql +[SXP gem]: https://ruby-rdf.github.com/sxp From b1ada1cbfaa835ebeea83259ac4e720c7e8a6955 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Thu, 17 Dec 2020 15:59:57 -0800 Subject: [PATCH 185/193] Don't run tests using action base for result; rely on appropriate definition of `@prefix :` in each result file. --- script/tc | 33 +++++++++++++++++---------------- spec/suite_reasoner_spec.rb | 10 +++++----- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/script/tc b/script/tc index f751dd3..b69f7e4 100755 --- a/script/tc +++ b/script/tc @@ -63,14 +63,13 @@ def run_tc(man, tc, **options) begin STDERR.puts "open #{tc.action}" if options[:verbose] options = { - base_uri: tc.base, validate: true, logger: logger }.merge(options) reader_options = options.dup reader_options[:logger] = false if tc.reason? - reader = RDF::N3::Reader.new(tc.input, **reader_options) + reader = RDF::N3::Reader.open(tc.action, **reader_options) graph = RDF::N3::Repository.new result = nil @@ -100,8 +99,8 @@ def run_tc(man, tc, **options) if tc.evaluate? && result.nil? begin - output_graph = RDF::N3::Repository.load(tc.result, base_uri: tc.base) - result = graph.isomorphic_with?(output_graph) ? "passed" : "failed" + result_repo = RDF::N3::Repository.load(tc.result) + result = graph.isomorphic_with?(result_repo) ? "passed" : "failed" rescue Exception => e if options[:verbose] STDERR.puts "Unexpected exception: #{e.inspect}\n#{e.backtrace.join("\n")}" @@ -132,14 +131,14 @@ def run_tc(man, tc, **options) if tc.options["strings"] STDERR.puts "\nResult: #{reasoner.strings}" else - STDERR.puts "\nResult: #{repo.dump(:n3, base_uri: RDF::URI(tc.base).parent, standard_prefixes: true)}" + STDERR.puts "\nResult: #{repo.dump(:n3, base_uri: tc.base, standard_prefixes: true)}" end end if tc.options["strings"] result = reasoner.strings == tc.expected else - output_graph = RDF::N3::Repository.load(tc.result, base_uri: tc.base) + result_repo = RDF::N3::Repository.load(tc.result) # Check against expanded triples from repo expanded_repo = RDF::Repository.new do |r| @@ -148,16 +147,12 @@ def run_tc(man, tc, **options) end end - result = expanded_repo.isomorphic_with?(output_graph) ? "passed" : "failed" + result = expanded_repo.isomorphic_with?(result_repo) ? "passed" : "failed" end else result ||= "passed" end - rescue Interrupt => e - STDERR.puts "(interrupt)" - STDERR.puts "Backtrace: " + e.backtrace.join("\n ") if $verbose - exit 1 rescue Exception => e STDERR.puts "#{"exception:" unless options[:quiet]}: #{e}" return if options[:quiet] @@ -265,13 +260,19 @@ manifests = %w( earl_preamble(**options) if options[:earl] -manifests.each do |man| - Fixtures::SuiteTest::Manifest.open(man) do |m| - m.entries.each do |tc| - next unless ARGV.empty? || ARGV.any? {|n| tc.property('@id').match?(/#{n}/) || tc.property('action').match?(/#{n}/)} - run_tc(man, tc, **options.merge(list_terms: !man.include?("TurtleTests"))) +begin + manifests.each do |man| + Fixtures::SuiteTest::Manifest.open(man) do |m| + m.entries.each do |tc| + next unless ARGV.empty? || ARGV.any? {|n| tc.property('@id').match?(/#{n}/) || tc.property('action').match?(/#{n}/)} + run_tc(man, tc, **options.merge(list_terms: !man.include?("TurtleTests"))) + end end end +rescue Interrupt => e + STDERR.puts "(interrupt)" + STDERR.puts "Backtrace: " + e.backtrace.join("\n ") if $verbose + exit 1 end options[:results].each {|k, v| puts "#{k}: #{v}"} diff --git a/spec/suite_reasoner_spec.rb b/spec/suite_reasoner_spec.rb index 32366c5..3450d5e 100644 --- a/spec/suite_reasoner_spec.rb +++ b/spec/suite_reasoner_spec.rb @@ -31,21 +31,21 @@ skip "Blows up" when *%w{cwm_list_builtin_generated_match} skip("List reification") + when *%w{log_parsedAsN3} + pending "Emergent problem comparing results" end t.logger = logger t.logger.info t.inspect t.logger.info "source:\n#{t.input}" - reader = RDF::N3::Reader.new(t.input, - base_uri: t.base, + reader = RDF::N3::Reader.open(t.action, canonicalize: false, list_terms: true, validate: false, logger: false) reasoner = RDF::N3::Reasoner.new(reader, - base_uri: t.base, logger: t.logger) repo = RDF::N3:: Repository.new @@ -70,7 +70,7 @@ t.logger.info "result:\n#{repo.dump(:n3)}" if t.evaluate? || t.reason? - output_repo = RDF:: Repository.load(t.result, format: :n3, base_uri: t.base) + result_repo = RDF:: Repository.load(t.result, format: :n3) # Check against expanded triples from repo expanded_repo = RDF::Repository.new do |r| @@ -78,7 +78,7 @@ r << st end end - expect(expanded_repo).to be_equivalent_graph(output_repo, t) + expect(expanded_repo).to be_equivalent_graph(result_repo, t) else end else From 5484fa2f5a0f69e84cff08fbec357593e87e39c8 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 18 Dec 2020 17:17:17 -0800 Subject: [PATCH 186/193] Add CLI options and a "reason" command. --- lib/rdf/n3/format.rb | 66 ++++++++++++++++++++++++++++++++++++++++++++ lib/rdf/n3/reader.rb | 15 ++++++++++ 2 files changed, 81 insertions(+) diff --git a/lib/rdf/n3/format.rb b/lib/rdf/n3/format.rb index 2d1ec6f..3394c8b 100644 --- a/lib/rdf/n3/format.rb +++ b/lib/rdf/n3/format.rb @@ -27,5 +27,71 @@ class Format < RDF::Format def self.symbols [:n3, :notation3] end + + ## + # Hash of CLI commands appropriate for this format + # @return [Hash{Symbol => Hash}] + def self.cli_commands + { + reason: { + description: "Reason over formulae.", + help: "reason\nPerform Notation-3 reasoning.", + parse: false, + help: "reason [--think] file", + # Only shows when input and output format set + filter: {format: :n3}, + repository: RDF::N3::Repository.new, + lambda: ->(argv, **options) do + repository = options[:repository] + result_repo = RDF::N3::Repository.new + RDF::CLI.parse(argv, format: :n3, list_terms: true, **options) do |reader| + reasoner = RDF::N3::Reasoner.new(reader, **options) + reasoner.reason!(**options) + if options[:conclusions] + result_repo << reasoner.conclusions + elsif options[:data] + result_repo << reasoner.data + else + result_repo << reasoner + end + end + + # Replace input repository with results + repository.clear! + repository << result_repo + end, + options: [ + RDF::CLI::Option.new( + symbol: :conclusions, + datatype: TrueClass, + control: :checkbox, + use: :optional, + on: ["--conclusions"], + description: "Exclude formulae and statements in the original dataset."), + RDF::CLI::Option.new( + symbol: :data, + datatype: TrueClass, + control: :checkbox, + use: :optional, + on: ["--data"], + description: "Only results from default graph, excluding formulae or variables."), + RDF::CLI::Option.new( + symbol: :strings, + datatype: TrueClass, + control: :checkbox, + use: :optional, + on: ["--strings"], + description: "Returns the concatenated strings from log:outputString."), + RDF::CLI::Option.new( + symbol: :think, + datatype: TrueClass, + control: :checkbox, + use: :optional, + on: ["--think"], + description: "Continuously execute until results stop growing."), + ] + }, + } + end end end diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 1a49f6c..8c2864a 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -44,6 +44,21 @@ class Reader < RDF::Reader # @return [Hash{Symbol => RDF::Node}] attr_reader :variables + ## + # N3 Reader options + # @see http://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Reader#options-class_method + def self.options + super + [ + RDF::CLI::Option.new( + symbol: :list_terms, + datatype: TrueClass, + default: true, + control: :checkbox, + on: ["--list-terms CONTEXT"], + description: "Use native collections (lists), not first/rest ladder.") + ] + end + ## # Initializes the N3 reader instance. # From b8bc8b9b695e8451f01f718aca78fc9db758f61a Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 19 Dec 2020 17:28:04 -0800 Subject: [PATCH 187/193] Run CI using GitHub Actions. --- .github/workflows/ci.yml | 47 ++++++++++++++++++++++++++++++++++++++++ README.md | 5 +++-- lib/rdf/n3/format.rb | 3 +-- 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9c47cf4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +# This workflow runs continuous CI across different versions of ruby on all branches and pull requests to develop. + +name: CI + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the develop branch + push: + branches: [ '**' ] + pull_request: + branches: [ develop ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + tests: + name: Ruby ${{ matrix.ruby }} + if: "contains(github.event.commits[0].message, '[ci skip]') == false" + runs-on: ubuntu-latest + env: + CI: true + ALLOW_FAILURES: false # ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'jruby' }} + strategy: + fail-fast: false + matrix: + ruby: + - 2.4 + - 2.5 + - 2.6 + - 2.7 + - ruby-head + - jruby + steps: + - name: Clone repository + uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + - name: Run tests + run: bundle exec rspec spec || $ALLOW_FAILURES + diff --git a/README.md b/README.md index ed45cc0..ac518d0 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ Notation-3 reader/writer for [RDF.rb][RDF.rb] . [![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) -[![Coverage Status](https://coveralls.io/repos/ruby-rdf/rdf-n3/badge.svg)](https://coveralls.io/r/ruby-rdf/rdf-n3) +[![Build Status](https://github.com/ruby-rdf/rdf-n3/workflows/CI/badge.svg?branch=develop)](https://github.com/ruby-rdf/rdf-n3/actions?query=workflow%3ACI) +[![Coverage Status](https://coveralls.io/repos/ruby-rdf/rdf-n3/badge.svg)](https://coveralls.io/github/ruby-rdf/rdf-n3) +[![Gitter chat](https://badges.gitter.im/ruby-rdf/rdf.png)](https://gitter.im/ruby-rdf/rdf) ## Description RDF::N3 is an Notation-3 parser and reasoner for Ruby using the [RDF.rb][RDF.rb] library suite. diff --git a/lib/rdf/n3/format.rb b/lib/rdf/n3/format.rb index 3394c8b..b67ac76 100644 --- a/lib/rdf/n3/format.rb +++ b/lib/rdf/n3/format.rb @@ -35,9 +35,8 @@ def self.cli_commands { reason: { description: "Reason over formulae.", - help: "reason\nPerform Notation-3 reasoning.", + help: "reason [--think] file\nPerform Notation-3 reasoning.", parse: false, - help: "reason [--think] file", # Only shows when input and output format set filter: {format: :n3}, repository: RDF::N3::Repository.new, From c71e1f11b2d576987ee439ba646c90aa2b134655 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 19 Dec 2020 17:30:55 -0800 Subject: [PATCH 188/193] Allow failure for ruby-head. Awaiting Ruby 3 release of net-http-persistent. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c47cf4..978be66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest env: CI: true - ALLOW_FAILURES: false # ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'jruby' }} + ALLOW_FAILURES: false ${{ endsWith(matrix.ruby, 'head') }} strategy: fail-fast: false matrix: From b80d2ace224ba8c6fa286d06609763743a40d27a Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 20 Dec 2020 11:30:58 -0800 Subject: [PATCH 189/193] Retry ALLOWW_FAILURES. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 978be66..91a4718 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest env: CI: true - ALLOW_FAILURES: false ${{ endsWith(matrix.ruby, 'head') }} + ALLOW_FAILURES: ${{ endsWith(matrix.ruby, 'head') }} strategy: fail-fast: false matrix: From 1089c0c58f61cab3031b5b12f1d9da812ba65691 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sun, 20 Dec 2020 12:11:03 -0800 Subject: [PATCH 190/193] Don't try to run ruby-head at all, until either net-http-persistent is updated or Actions supports soft failure modes. --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91a4718..8e7d1d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,6 @@ jobs: runs-on: ubuntu-latest env: CI: true - ALLOW_FAILURES: ${{ endsWith(matrix.ruby, 'head') }} strategy: fail-fast: false matrix: @@ -31,7 +30,7 @@ jobs: - 2.5 - 2.6 - 2.7 - - ruby-head + #- ruby-head # Commented out until net-http-persistent is updaated - jruby steps: - name: Clone repository @@ -43,5 +42,5 @@ jobs: - name: Install dependencies run: bundle install --jobs 4 --retry 3 - name: Run tests - run: bundle exec rspec spec || $ALLOW_FAILURES + run: bundle exec rspec spec From 735d0b2b9469479c4ea6d70945d775dabfc69b25 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 25 Dec 2020 19:10:18 -0800 Subject: [PATCH 191/193] Update dependencies. --- rdf-n3.gemspec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rdf-n3.gemspec b/rdf-n3.gemspec index f7dac3b..21b65f8 100755 --- a/rdf-n3.gemspec +++ b/rdf-n3.gemspec @@ -22,18 +22,18 @@ Gem::Specification.new do |gem| gem.requirements = [] gem.add_dependency 'ebnf', '~> 2.1' - gem.add_dependency 'rdf', '~> 3.1' - gem.add_dependency 'sparql', '~> 3.1' + gem.add_dependency 'rdf', '~> 3.1', '>= 3.1.8' + gem.add_dependency 'sparql', '~> 3.1', '>= 3.1.4' gem.add_runtime_dependency 'sxp', '~> 1.1' gem.add_development_dependency 'json-ld', '~> 3.1' - gem.add_development_dependency 'rspec', '~> 3.9' - gem.add_development_dependency 'rspec-its', '~> 1.3' gem.add_development_dependency 'rdf-spec', '~> 3.1' gem.add_development_dependency 'rdf-isomorphic', '~> 3.1' gem.add_development_dependency 'rdf-trig', '~> 3.1' gem.add_development_dependency 'rdf-vocab', '~> 3.1' - gem.add_development_dependency 'yard' , '~> 0.9.20' + gem.add_development_dependency 'rspec', '~> 3.10' + gem.add_development_dependency 'rspec-its', '~> 1.3' + gem.add_development_dependency 'yard' , '~> 0.9' gem.post_install_message = nil end From 339b6c7113b87b1be5148b28bc757c7ede9daf20 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 25 Dec 2020 19:15:56 -0800 Subject: [PATCH 192/193] Update README. --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ac518d0..a56a506 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ This version tracks the [W3C N3 Community Group][] [Specification][N3] which has * Terminals adhere closely to their counterparts in [Turtle][]. * The modifier `<-` is introduced as a synonym for `is ... of`. * The SPARQL `BASE` and `PREFIX` declarations are supported. +* Implicit universal variables are defined at the top-level, rather than in the parent formula of the one in which they are defined. This brings N3 closer to compatibility with Turtle. @@ -152,7 +153,7 @@ Reasoning is discussed in the [Design Issues][] document. * `time:timeZone` (See {RDF::N3::Algebra::Time::Timezone}) * `time:year` (See {RDF::N3::Algebra::Time::Year}) -### Formulae +### Formulae / Quoted Graphs 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: @@ -184,6 +185,9 @@ results in: 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. +* Variables, themselves, cannot be part of a solution, which limits the ability to generate updated rules for reasoning. +* Both Existentials and Universals are treated as simple variables, and there is really no preference given based on the order in which they are introduced. + ### Query Formulae are typically used to query the knowledge-base, which is set from the base-formula/default graph. A formula is composed of both constant statements, and variable statements. When running as a query, such as for the antecedent formula in `log:implies`, statements including either explicit variables or blank nodes are treated as query patterns and are used to query the knowledge base to create a solution set, which is used either prove the formula correct, or create bindings passed to the consequent formula. From 3a038b3de1bb59d5783d326e7aebed6636c95669 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 25 Dec 2020 19:16:30 -0800 Subject: [PATCH 193/193] Version 3.1.2. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 94ff29c..ef538c2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.1 +3.1.2