diff --git a/.travis.yml b/.travis.yml index 80f381d..3a1eb50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,17 @@ language: ruby bundler_args: --without debug script: "bundle exec rspec spec" -before_install: "gem update --system" - +before_install: + - 'gem update --system --conservative || (gem i "rubygems-update:~>2.7" --no-document && update_rubygems)' + - 'gem update bundler --conservative' env: - CI=true rvm: - - 2.2 + - 2.2.2 - 2.3 - 2.4 - 2.5 + - 2.6 - jruby-9 - rbx-3 cache: bundler diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3eb40fa..cabeb41 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ Community contributions are essential for keeping Ruby RDF great. We want to kee This repository uses [Git Flow](https://github.com/nvie/gitflow) to manage development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration. -* create or respond to an issue on the [Github Repository](http://github.com/ruby-rdf/rdf-n3/issues) +* create or respond to an issue on the [Github Repository](https://github.com/ruby-rdf/rdf-n3/issues) * Fork and clone the repo: `git clone git@github.com:your-username/rdf-n3.git` * Install bundle: @@ -30,7 +30,7 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to manage devel of thumb, additions larger than about 15 lines of code), we need an explicit [public domain dedication][PDD] on record from you. -[YARD]: http://yardoc.org/ -[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md -[PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html +[YARD]: https://yardoc.org/ +[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md +[PDD]: https://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html [pr]: https://github.com/ruby-rdf/rdf-n3/compare/ diff --git a/Gemfile b/Gemfile index 6e29b8e..60de3c8 100644 --- a/Gemfile +++ b/Gemfile @@ -2,13 +2,17 @@ source "https://rubygems.org" gemspec -gem "rdf", github: "ruby-rdf/rdf", branch: "develop" +gem "rdf", git: "https://github.com/ruby-rdf/rdf", branch: "develop" group :development do - gem "rdf-spec", github: "ruby-rdf/rdf-spec", branch: "develop" - gem "rdf-isomorphic", github: "ruby-rdf/rdf-isomorphic", branch: "develop" - gem "rdf-xsd", github: "ruby-rdf/rdf-xsd", branch: "develop" - gem "json-ld", github: "ruby-rdf/json-ld", branch: "develop" + gem "rdf-spec", git: "https://github.com/ruby-rdf/rdf-spec", branch: "develop" + gem "rdf-isomorphic", git: "https://github.com/ruby-rdf/rdf-isomorphic", branch: "develop" + gem "rdf-trig", git: "https://github.com/ruby-rdf/rdf-trig", branch: "develop" + gem 'rdf-vocab', git: "https://github.com/ruby-rdf/rdf-vocab", branch: "develop" + gem "rdf-xsd", git: "https://github.com/ruby-rdf/rdf-xsd", branch: "develop" + gem "json-ld", git: "https://github.com/ruby-rdf/json-ld", branch: "develop" + gem 'sparql', git: "https://github.com/ruby-rdf/sparql", branch: "develop" + gem 'sxp', git: "https://github.com/dryruby/sxp.rb", branch: "develop" end group :debug do diff --git a/README.md b/README.md index ad96fc4..dde8f4d 100755 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# RDF::N3 reader/writer +# RDF::N3 reader/writer and reasoner Notation-3 reader/writer for [RDF.rb][RDF.rb] . -[![Gem Version](https://badge.fury.io/rb/rdf-n3.png)](http://badge.fury.io/rb/rdf-n3) -[![Build Status](https://travis-ci.org/ruby-rdf/rdf-n3.png?branch=master)](http://travis-ci.org/ruby-rdf/rdf-n3) +[![Gem Version](https://badge.fury.io/rb/rdf-n3.png)](https://badge.fury.io/rb/rdf-n3) +[![Build Status](https://travis-ci.org/ruby-rdf/rdf-n3.png?branch=master)](https://travis-ci.org/ruby-rdf/rdf-n3) ## Description -RDF::N3 is an Notation-3 parser for Ruby using the [RDF.rb][RDF.rb] library suite. +RDF::N3 is an Notation-3 parser for Ruby using the [RDF.rb][RDF.rb] library suite. Also implements N3 Entailment. Reader inspired from TimBL predictiveParser and Python librdf implementation. @@ -14,14 +14,12 @@ Support for Turtle mime-types and specific format support has been deprecated fr as Turtle is now implemented using [RDF::Turtle][RDF::Turtle]. ## Features -RDF::N3 parses [Notation-3][N3], [Turtle][Turtle] and [N-Triples][N-Triples] into statements or triples. It also serializes to Turtle. +RDF::N3 parses [Notation-3][N3], [Turtle][Turtle] and [N-Triples][N-Triples] into statements or quads. It also performs reasoning and serializes to N3. Install with `gem install rdf-n3` ## Limitations -* Full support of Unicode input requires Ruby version 2.0 or greater. -* Support for Variables in Formulae dependent on underlying repository. Existential variables are quantified to RDF::Node instances, Universals to RDF::Query::Variable, with the URI of the variable target used as the variable name. -* No support for N3 Reification. If there were, it would be through a :reify option to the reader. +* Support for Variables in Formulae. Existential variables are quantified to RDF::Node instances, Universals to RDF::Query::Variable, with the URI of the variable target used as the variable name. ## Usage Instantiate a reader from a local file: @@ -40,23 +38,53 @@ Write a graph to a file: writer << graph end -### Formulae +### Reasoning +Experimental N3 reasoning is supported. Instantiate a reasoner from a dataset: + + RDF::N3::Reasoner.new do |reasoner| + RDF::N3::Reader.open("etc/foaf.n3") {|reader| reasoner << reader} + + reader.each_statement do |statement| + puts statement.inspect + end + end + +Reasoning is performed by turning a repository containing formula and predicate operators into an executable set of operators (similar to the executable SPARQL Algebra). Reasoning adds statements to the base dataset, marked with `:inferred` (e.g. `statement.inferred?`). Predicate operators are defined from the following vocabularies: + +* RDF List vocabulary + * list:append (not implemented yet - See {RDF::N3::Algebra::ListAppend}) + * list:in (not implemented yet - See {RDF::N3::Algebra::ListIn}) + * list:last (not implemented yet - See {RDF::N3::Algebra::ListLast}) + * list:member (not implemented yet - See {RDF::N3::Algebra::ListMember}) +* RDF Log vocabulary + * log:conclusion (not implemented yet - See {RDF::N3::Algebra::LogConclusion}) + * log:conjunction (not implemented yet - See {RDF::N3::Algebra::LogConjunction}) + * log:equalTo (See {not implemented yet - RDF::N3::Algebra::LogEqualTo}) + * log:implies (See {RDF::N3::Algebra::LogImplies}) + * log:includes (not implemented yet - See {RDF::N3::Algebra::LogIncludes}) + * log:notEqualTo (not implemented yet - See {RDF::N3::Algebra::LogNotEqualTo}) + * log:notIncludes (not implemented yet - See {RDF::N3::Algebra::LogNotIncludes}) + * log:outputString (not implemented yet - See {RDF::N3::Algebra::LogOutputString}) + N3 Formulae are introduced with the { statement-list } syntax. A given formula is assigned an RDF::Node instance, which is also used as the graph_name for RDF::Statement instances provided to RDF::N3::Reader#each_statement. For example, the following N3 generates the associated statements: - { [ x:firstname "Ora" ] dc:wrote [ dc:title "Moby Dick" ] } a n3:falsehood . + @prefix x: . + @prefix log: . + @prefix dc: . + + { [ x:firstname "Ora" ] dc:wrote [ dc:title "Moby Dick" ] } a log:falsehood . -results in +when turned into an RDF Repository results in the following quads + + _:form . + _:moby "Moby Dick" _:form . + _:ora _:moby _:form . + _:ora "Ora" _:form . - f = RDF::Node.new - s = RDF::Node.new - o = RDF::Node.new - RDF::Statement(f, rdf:type n3:falsehood) - RDF::Statement(s, x:firstname, "Ora", graph_name: f) - RDF::Statement(s, dc:wrote, o, graph_name: f) - RDF::Statement(o, dc:title, "Moby Dick", graph_name: f) +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, @forEach, or ?x. Variables reference URIs described in formulae, typically defined in the default vocabulary (e.g., ":x"). Existential variables are replaced with an allocated RDF::Node instance. Universal variables are replaced with a RDF::Query::Variable instance. For example, the following N3 generates the associated statements: +N3 Variables are introduced with @forAll, @forSome, or ?x. Variables reference URIs described in formulae, typically defined in the default vocabulary (e.g., ":x"). Existential variables are replaced with an allocated RDF::Node instance. Universal variables are replaced with a RDF::Query::Variable instance. For example, the following N3 generates the associated statements: @forAll <#h>. @forSome <#g>. <#g> <#loves> <#h> . @@ -66,6 +94,8 @@ results in: g = RDF::Node.new() RDF::Statement(f, <#loves>, h) +Note that the behavior of both existential and universal variables is not entirely in keeping with the [Team Submission][], and neither work quite like SPARQL variables. When used in the antecedent part of an implication, universal variables should behave much like SPARQL variables. This area is subject to a fair amount of change. + ## Implementation Notes The parser is driven through a rules table contained in lib/rdf/n3/reader/meta.rb. This includes branch rules to indicate productions to be taken based on a current production. Terminals are denoted @@ -79,48 +109,60 @@ http://www.w3.org/2000/10/swap/grammar/n3.n3 (along with bnf-rules.n3) using cwm [n3-selectors.n3][file:lib/rdf/n3/reader/n3-selectors.rb] is itself used to generate meta.rb using script/build_meta. -## TODO -* Generate Formulae and solutions using BGP and SPARQL CONSTRUCT mechanisms -* Create equivalent to `--think` to iterate on solutions. - ## Dependencies -* [RDF.rb](http://rubygems.org/gems/rdf) (>= 3.0) +* [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.0, >= 3.0.10) ## Documentation -Full documentation available on [RubyDoc.info](http://rubydoc.info/github/ruby-rdf/rdf-n3/frames) +Full documentation available on [RubyDoc.info](https://rubydoc.info/github/ruby-rdf/rdf-n3) ### Principle Classes * {RDF::N3} * {RDF::N3::Format} * {RDF::N3::Reader} +* {RDF::N3::Reasoner} * {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} ### Additional vocabularies -* {RDF::LOG} -* {RDF::REI} - -### Patches -* `Array` -* `RDF::List` +* {RDF::N3::Log} +* {RDF::N3::Rei} +* {RDF::N3::Crypto} +* {RDF::N3::List} +* {RDF::N3::Math} +* {RDF::N3::Str} +* {RDF::N3::Time} ## Resources * [RDF.rb][RDF.rb] * [Distiller](http://rdf.greggkellogg.net/distiller) -* [Documentation](http://rubydoc.info/github/ruby-rdf/rdf-n3/master/frames) +* [Documentation](https://rubydoc.info/github/ruby-rdf/rdf-n3/) * [History](file:file.History.html) * [Notation-3][N3] -* [N3 Primer](http://www.w3.org/2000/10/swap/Primer.html) -* [N3 Reification](http://www.w3.org/DesignIssues/Reify.html) +* [N3 Primer](https://www.w3.org/2000/10/swap/Primer.html) +* [N3 Reification](https://www.w3.org/DesignIssues/Reify.html) * [Turtle][Turtle] -* [W3C SWAP Test suite](http://www.w3.org/2000/10/swap/test/README.html) -* [W3C Turtle Test suite](http://www.w3.org/2001/sw/DataAccess/df1/tests/README.txt) +* [W3C SWAP Test suite](https://www.w3.org/2000/10/swap/test/README.html) +* [W3C Turtle Test suite](https://www.w3.org/2001/sw/DataAccess/df1/tests/README.txt) * [N-Triples][N-Triples] ## Author -* [Gregg Kellogg](http://github.com/gkellogg) - +* [Gregg Kellogg](https://github.com/gkellogg) - ## Contributors -* [Nicholas Humfrey](http://github.com/njh) - +* [Nicholas Humfrey](https://github.com/njh) - ## Contributing This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration. @@ -140,19 +182,21 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange develo ## License This is free and unencumbered public domain software. For more information, -see or the accompanying {file:UNLICENSE} file. +see or the accompanying {file:UNLICENSE} file. ## Feedback * -* -* -* - -[RDF.rb]: http://ruby-rdf.github.com/rdf -[RDF::Turtle]: http://ruby-rdf.github.com/rdf-turtle/ -[N3]: http://www.w3.org/DesignIssues/Notation3.html "Notation-3" -[Turtle]: http://www.w3.org/TR/turtle/ -[N-Triples]: http://www.w3.org/TR/n-triples/ -[YARD]: http://yardoc.org/ -[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md -[PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html +* +* +* + +[RDF.rb]: https://ruby-rdf.github.com/rdf +[RDF::Turtle]: https://ruby-rdf.github.com/rdf-turtle/ +[N3]: https://www.w3.org/DesignIssues/Notation3.html "Notation-3" +[Team Submission]: https://www.w3.org/TeamSubmission/n3/ +[Turtle]: https://www.w3.org/TR/turtle/ +[N-Triples]: https://www.w3.org/TR/n-triples/ +[YARD]: https://yardoc.org/ +[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md +[PDD]: https://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html +[SPARQL S-Expressions]: https://jena.apache.org/documentation/notes/sse.html diff --git a/VERSION b/VERSION index cb2b00e..fd2a018 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.0.1 +3.1.0 diff --git a/etc/doap.n3 b/etc/doap.n3 index 363c6cf..84226c7 100644 --- a/etc/doap.n3 +++ b/etc/doap.n3 @@ -9,10 +9,10 @@ <> a doap:Project ; doap:name "RDF::N3" ; - doap:homepage ; + doap:homepage ; doap:license ; doap:shortdesc "N3 reader/writer for Ruby."@en ; - doap:description "RDF::N3 is an Notation-3 reader/writer for the RDF.rb library suite."@en ; + doap:description "RDF::N3 is an Notation-3 reader/writer and reasoner for the RDF.rb library suite."@en ; doap:created "2010-06-03"^^xsd:date; doap:programming-language "Ruby" ; doap:implements ; @@ -20,7 +20,7 @@ ; doap:download-page ; doap:mailing-list ; - doap:bug-database ; + doap:bug-database ; doap:blog ; doap:developer ; doap:helper ; diff --git a/etc/doap.nt b/etc/doap.nt index 9a85eb8..0449e43 100644 --- a/etc/doap.nt +++ b/etc/doap.nt @@ -1,8 +1,8 @@ . "RDF::N3" . - . + . . - "N3 reader/writer for Ruby."@en . + "N3 reader/writer reader/writer and reasoner for Ruby."@en . "RDF::N3 is an Notation-3 reader/writer for the RDF.rb library suite."@en . "2010-06-03"^^ . "Ruby" . @@ -11,7 +11,7 @@ . . . - . + . . . . diff --git a/etc/notation3.ebnf b/etc/notation3.ebnf new file mode 100644 index 0000000..13a5a6a --- /dev/null +++ b/etc/notation3.ebnf @@ -0,0 +1,106 @@ +# Notation3 Grammar updated with some Turtle poductions and terminals. +# From swap/notation3/notation3.bnf + +[1] document ::= (statement ".")* + +/* Formula does NOT need period on last statement */ + +[2] formulacontent ::= (statement ("." statement)*)? + +[3] statement ::= declaration + | universal + | existential + | simpleStatement + +[4] universal ::= "@forAll" varlist + +[5] existential ::= "@forSome" varlist + +[6] varlist ::= (symbol ("," symbol)*)? + +[7] declaration ::= "@prefix" PNAME_NS IRIREF + | "@keywords" (barename ("," barename)*)? + +[8] barename ::= PNAME_LN +/* barename constraint: no colon */ + +[9] simpleStatement ::= term propertylist + +[10] propertylist ::= (property (";" property)*)? +[11] property ::= (verb | inverb) term ("," term)* + +[12] verb ::= "@has"? term + | "@a" + | "=" + | "=>" + | "<=" + +[12a] inverb ::= "@is" term "@of" + +[13] term ::= pathitem pathtail? + +[14] pathtail ::= ("!" | "^") term + +[15] pathitem ::= symbol + | BLANK_NODE_LABEL + | UVAR + | literal + | "{" formulacontent "}" + | "[" propertylist "]" + | "(" term* ")" + +[13] literal ::= RDFLiteral | NumericLiteral | BooleanLiteral +[16] NumericLiteral ::= INTEGER | DECIMAL | DOUBLE +[128s] RDFLiteral ::= String ( LANGTAG | ( "^^" iri ) )? +[133s] BooleanLiteral ::= "@true" | "@false" +[17t] String ::= STRING_LITERAL_QUOTE | STRING_LITERAL_SINGLE_QUOTE | STRING_LITERAL_LONG_SINGLE_QUOTE | + STRING_LITERAL_LONG_QUOTE + +[18] symbol ::= IRIREF | PNAME_LN + +/***********/ + +@terminals + +[35] UVAR ::= "?" PN_LOCAL + +/* borrowed from SPARQL spec, which excludes newlines and other nastiness */ +[18] IRIREF ::= '<' ([^#x00-#x20<>"{}|^`\] | UCHAR)* '>' +[139s] PNAME_NS ::= PN_PREFIX? ":" +[140s] PNAME_LN ::= PNAME_NS PN_LOCAL +[141s] BLANK_NODE_LABEL ::= '_:' ( PN_CHARS_U | [0-9] ) ((PN_CHARS|'.')* PN_CHARS)? +[144s] LANGTAG ::= "@" [a-zA-Z]+ ( "-" [a-zA-Z0-9]+ )* +[19] INTEGER ::= [+-]? [0-9]+ +[20] DECIMAL ::= [+-]? ( ([0-9])* '.' ([0-9])+ ) +[21] DOUBLE ::= [+-]? ( [0-9]+ '.' [0-9]* EXPONENT | '.' ([0-9])+ EXPONENT | ([0-9])+ EXPONENT ) +[154s] EXPONENT ::= [eE] [+-]? [0-9]+ +[22] STRING_LITERAL_QUOTE ::= '"' ( [^#x22#x5C#xA#xD] | ECHAR | UCHAR )* '"' +[23] STRING_LITERAL_SINGLE_QUOTE ::= "'" ( [^#x27#x5C#xA#xD] | ECHAR | UCHAR )* "'" +[24] STRING_LITERAL_LONG_SINGLE_QUOTE ::= "'''" ( ( "'" | "''" )? ( [^'\] | ECHAR | UCHAR ) )* "'''" +[25] STRING_LITERAL_LONG_QUOTE ::= '"""' ( ( '"' | '""' )? ( [^"\] | ECHAR | UCHAR ) )* '"""' +[26] UCHAR ::= ( "\u" HEX HEX HEX HEX ) | ( "\U" HEX HEX HEX HEX HEX HEX HEX HEX ) +[159s] ECHAR ::= "\" [tbnrf\"'] + +[163s] PN_CHARS_BASE ::= [A-Z] + | [a-z] + | [#x00C0-#x00D6] + | [#x00D8-#x00F6] + | [#x00F8-#x02FF] + | [#x0370-#x037D] + | [#x037F-#x1FFF] + | [#x200C-#x200D] + | [#x2070-#x218F] + | [#x2C00-#x2FEF] + | [#x3001-#xD7FF] + | [#xF900-#xFDCF] + | [#xFDF0-#xFFFD] + | [#x10000-#xEFFFF] +[164s] PN_CHARS_U ::= PN_CHARS_BASE | '_' +[166s] PN_CHARS ::= PN_CHARS_U | "-" | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040] +[167s] PN_PREFIX ::= PN_CHARS_BASE ( ( PN_CHARS | "." )* PN_CHARS )? +[168s] PN_LOCAL ::= ( PN_CHARS_U | ':' | [0-9] | PLX ) ( ( PN_CHARS | '.' | ':' | PLX )* ( PN_CHARS | ':' | PLX ) ) ? +[169s] PLX ::= PERCENT | PN_LOCAL_ESC +[170s] PERCENT ::= '%' HEX HEX +[42] HEX ::= [0-9] | [A-F] | [a-f] +[172s] PN_LOCAL_ESC ::= '\' ( '_' | '~' | '.' | '-' | '!' | '$' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | ';' | '=' + | '/' | '?' | '#' | '@' | '%' ) \ No newline at end of file diff --git a/etc/notation3.ll1.sxp b/etc/notation3.ll1.sxp new file mode 100644 index 0000000..8ef9a3e --- /dev/null +++ b/etc/notation3.ll1.sxp @@ -0,0 +1,587 @@ +( + (rule _empty "0" (first _eps) (seq)) + (rule document "1" + (start #t) + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow _eof) + (cleanup star) + (alt _empty _document_2)) + (rule _document_1 "1.1" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eof "{" ) + (seq statement ".")) + (rule _document_2 "1.2" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow _eof) + (cleanup merge) + (seq _document_1 document)) + (rule _document_3 "1.3" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow _eof) + (seq document)) + (rule _document_4 "1.4" + (first ".") + (follow "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eof "{" ) + (seq ".")) + (rule formulacontent "2" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow "}") + (cleanup opt) + (alt _empty _formulacontent_1)) + (rule _formulacontent_1 "2.1" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "}") + (seq statement _formulacontent_2)) + (rule _formulacontent_2 "2.2" + (first "." _eps) + (follow "}") + (cleanup star) + (alt _empty _formulacontent_4)) + (rule _formulacontent_3 "2.3" (first ".") (follow "." "}") (seq "." statement)) + (rule _formulacontent_4 "2.4" + (first ".") + (follow "}") + (cleanup merge) + (seq _formulacontent_3 _formulacontent_2)) + (rule _formulacontent_5 "2.5" (first "." _eps) (follow "}") (seq _formulacontent_2)) + (rule _formulacontent_6 "2.6" (first "." _eps) (follow "}") (seq _formulacontent_2)) + (rule _formulacontent_7 "2.7" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." "}") + (seq statement)) + (rule statement "3" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." "}") + (alt declaration universal existential simpleStatement)) + (rule universal "4" (first "@forAll") (follow "." "}") (seq "@forAll" varlist)) + (rule _universal_1 "4.1" (first IRIREF PNAME_LN _eps) (follow "." "}") (seq varlist)) + (rule existential "5" (first "@forSome") (follow "." "}") (seq "@forSome" varlist)) + (rule _existential_1 "5.1" (first IRIREF PNAME_LN _eps) (follow "." "}") (seq varlist)) + (rule varlist "6" + (first IRIREF PNAME_LN _eps) + (follow "." "}") + (cleanup opt) + (alt _empty _varlist_1)) + (rule _varlist_1 "6.1" (first IRIREF PNAME_LN) (follow "." "}") (seq symbol _varlist_2)) + (rule _varlist_2 "6.2" + (first "," _eps) + (follow "." "}") + (cleanup star) + (alt _empty _varlist_4)) + (rule _varlist_3 "6.3" (first ",") (follow "," "." "}") (seq "," symbol)) + (rule _varlist_4 "6.4" + (first ",") + (follow "." "}") + (cleanup merge) + (seq _varlist_3 _varlist_2)) + (rule _varlist_5 "6.5" (first "," _eps) (follow "." "}") (seq _varlist_2)) + (rule _varlist_6 "6.6" (first "," _eps) (follow "." "}") (seq _varlist_2)) + (rule _varlist_7 "6.7" (first IRIREF PNAME_LN) (follow "," "." "}") (seq symbol)) + (rule declaration "7" + (first "@keywords" "@prefix") + (follow "." "}") + (alt _declaration_1 _declaration_2)) + (rule _declaration_1 "7.1" + (first "@prefix") + (follow "." "}") + (seq "@prefix" PNAME_NS IRIREF)) + (rule _declaration_10 "7.10" (first "," _eps) (follow "." "}") (seq _declaration_5)) + (rule _declaration_11 "7.11" (first "," _eps) (follow "." "}") (seq _declaration_5)) + (rule _declaration_12 "7.12" (first PNAME_LN) (follow "," "." "}") (seq barename)) + (rule _declaration_13 "7.13" (first IRIREF) (follow "." "}") (seq IRIREF)) + (rule _declaration_2 "7.2" + (first "@keywords") + (follow "." "}") + (seq "@keywords" _declaration_3)) + (rule _declaration_3 "7.3" + (first PNAME_LN _eps) + (follow "." "}") + (cleanup opt) + (alt _empty _declaration_4)) + (rule _declaration_4 "7.4" + (first PNAME_LN) + (follow "." "}") + (seq barename _declaration_5)) + (rule _declaration_5 "7.5" + (first "," _eps) + (follow "." "}") + (cleanup star) + (alt _empty _declaration_7)) + (rule _declaration_6 "7.6" (first ",") (follow "," "." "}") (seq "," barename)) + (rule _declaration_7 "7.7" + (first ",") + (follow "." "}") + (cleanup merge) + (seq _declaration_6 _declaration_5)) + (rule _declaration_8 "7.8" (first PNAME_NS) (follow "." "}") (seq PNAME_NS IRIREF)) + (rule _declaration_9 "7.9" (first PNAME_LN _eps) (follow "." "}") (seq _declaration_3)) + (rule barename "8" (first PNAME_LN) (follow "," "." "}") (seq PNAME_LN)) + (rule simpleStatement "9" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." "}") + (seq term propertylist)) + (rule _simpleStatement_1 "9.1" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow "." "}") + (seq propertylist)) + (rule propertylist "10" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow "." "]" "}") + (cleanup opt) + (alt _empty _propertylist_1)) + (rule _propertylist_1 "10.1" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." "]" "}") + (seq property _propertylist_2)) + (rule _propertylist_2 "10.2" + (first ";" _eps) + (follow "." "]" "}") + (cleanup star) + (alt _empty _propertylist_4)) + (rule _propertylist_3 "10.3" (first ";") (follow "." ";" "]" "}") (seq ";" property)) + (rule _propertylist_4 "10.4" + (first ";") + (follow "." "]" "}") + (cleanup merge) + (seq _propertylist_3 _propertylist_2)) + (rule _propertylist_5 "10.5" + (first ";" _eps) + (follow "." "]" "}") + (seq _propertylist_2)) + (rule _propertylist_6 "10.6" + (first ";" _eps) + (follow "." "]" "}") + (seq _propertylist_2)) + (rule _propertylist_7 "10.7" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." ";" "]" "}") + (seq property)) + (rule property "11" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." ";" "]" "}") + (seq _property_1 term _property_2)) + (rule _property_1 "11.1" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (alt verb inverb)) + (rule _property_2 "11.2" + (first "," _eps) + (follow "." ";" "]" "}") + (cleanup star) + (alt _empty _property_4)) + (rule _property_3 "11.3" (first ",") (follow "," "." ";" "]" "}") (seq "," term)) + (rule _property_4 "11.4" + (first ",") + (follow "." ";" "]" "}") + (cleanup merge) + (seq _property_3 _property_2)) + (rule _property_5 "11.5" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "." ";" "]" "}") + (seq term _property_2)) + (rule _property_6 "11.6" (first "," _eps) (follow "." ";" "]" "}") (seq _property_2)) + (rule _property_7 "11.7" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "," "." ";" "]" "}") + (seq term)) + (rule verb "12" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (alt _verb_1 "@a" "=" "=>" "<=")) + (rule inverb "12a" + (first "@is") + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (seq "@is" term "@of")) + (rule _inverb_2 "12a.2" + (first "@of") + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (seq "@of")) + (rule _inverb_1 "12a.1" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (seq term "@of")) + (rule _verb_1 "12.1" + (first "(" "@false" "@has" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER + IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (seq _verb_2 term)) + (rule _verb_2 "12.2" + (first "@has" _eps) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (cleanup opt) + (alt _empty "@has")) + (rule _verb_3 "12.3" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (seq term)) + (rule literal "13" + (first "@false" "@true" DECIMAL DOUBLE INTEGER STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt RDFLiteral NumericLiteral BooleanLiteral)) + (rule term "13" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@of" + "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" "}" ) + (seq pathitem _term_1)) + (rule _term_1 "13.1" + (first "!" "^" _eps) + (follow "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@of" + "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" "}" ) + (cleanup opt) + (alt _empty pathtail)) + (rule _term_2 "13.2" + (first "!" "^" _eps) + (follow "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@of" + "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" "}" ) + (seq _term_1)) + (rule pathtail "14" + (first "!" "^") + (follow "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@of" + "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" "}" ) + (seq _pathtail_1 term)) + (rule _pathtail_1 "14.1" + (first "!" "^") + (follow "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (alt "!" "^")) + (rule _pathtail_2 "14.2" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@of" + "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" "}" ) + (seq term)) + (rule pathitem "15" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt symbol BLANK_NODE_LABEL UVAR literal _pathitem_1 _pathitem_2 _pathitem_3)) + (rule _pathitem_1 "15.1" + (first "{") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "{" formulacontent "}")) + (rule _pathitem_10 "15.10" + (first "}") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "}")) + (rule _pathitem_11 "15.11" + (first "]") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "]")) + (rule _pathitem_12 "15.12" + (first ")") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq ")")) + (rule _pathitem_2 "15.2" + (first "[") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "[" propertylist "]")) + (rule _pathitem_3 "15.3" + (first "(") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "(" _pathitem_4 ")")) + (rule _pathitem_4 "15.4" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow ")") + (cleanup star) + (alt _empty _pathitem_5)) + (rule _pathitem_5 "15.5" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow ")") + (cleanup merge) + (seq term _pathitem_4)) + (rule _pathitem_6 "15.6" + (first "(" "@false" "@forAll" "@forSome" "@keywords" "@prefix" "@true" + BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" "}" ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq formulacontent "}")) + (rule _pathitem_7 "15.7" + (first "(" "<=" "=" "=>" "@a" "@false" "@has" "@is" "@true" BLANK_NODE_LABEL + DECIMAL DOUBLE INTEGER IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_QUOTE + STRING_LITERAL_SINGLE_QUOTE "[" "]" "{" ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq propertylist "]")) + (rule _pathitem_8 "15.8" + (first "(" ")" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER + IRIREF PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "{" ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq _pathitem_4 ")")) + (rule _pathitem_9 "15.9" + (first "(" "@false" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" _eps "{" ) + (follow ")") + (seq _pathitem_4)) + (rule NumericLiteral "16" + (first DECIMAL DOUBLE INTEGER) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt INTEGER DECIMAL DOUBLE)) + (rule String "17t" + (first STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF LANGTAG + PNAME_LN STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "^^" "{" "}" ) + (alt STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE + STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_LONG_QUOTE )) + (rule symbol "18" + (first IRIREF PNAME_LN) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt IRIREF PNAME_LN)) + (terminal IRIREF "18" (seq "<" (range "^#x00-#x20<>\"{}|^`] | UCHAR)* '>'"))) + (terminal INTEGER "19" (seq (opt (range "+-")) (plus (range "0-9")))) + (terminal DECIMAL "20" + (seq (opt (range "+-")) (seq (star (range "0-9")) "." (plus (range "0-9"))))) + (terminal DOUBLE "21" + (seq + (opt (range "+-")) + (alt + (seq (plus (range "0-9")) "." (star (range "0-9")) EXPONENT) + (seq "." (plus (range "0-9")) EXPONENT) + (seq (plus (range "0-9")) EXPONENT)) )) + (terminal STRING_LITERAL_QUOTE "22" + (seq "\"" (star (alt (range "^#x22#x5C#xA#xD") ECHAR UCHAR)) "\"")) + (terminal STRING_LITERAL_SINGLE_QUOTE "23" + (seq "'" (star (alt (range "^#x27#x5C#xA#xD") ECHAR UCHAR)) "'")) + (terminal STRING_LITERAL_LONG_SINGLE_QUOTE "24" + (seq "'''" (seq (opt (alt "'" "''")) (range "^'] | ECHAR | UCHAR ))* \"'''\"")))) + (terminal STRING_LITERAL_LONG_QUOTE "25" + (seq "\"\"\"" (seq (opt (alt "\"" "\"\"")) (range "^\"] | ECHAR | UCHAR ))* '\"\"\"'")))) + (terminal UCHAR "26" + (alt (seq "u" HEX HEX HEX HEX) (seq "U" HEX HEX HEX HEX HEX HEX HEX HEX))) + (terminal HEX "42" (alt (range "0-9") (range "A-F") (range "a-f"))) + (rule _RDFLiteral_5 "128s.5" + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq iri)) + (rule _RDFLiteral_4 "128s.4" + (first LANGTAG "^^" _eps) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq _RDFLiteral_1)) + (rule RDFLiteral "128s" + (first STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE ) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq String _RDFLiteral_1)) + (rule _RDFLiteral_1 "128s.1" + (first LANGTAG "^^" _eps) + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (cleanup opt) + (alt _empty _RDFLiteral_2)) + (rule _RDFLiteral_3 "128s.3" + (first "^^") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (seq "^^" iri)) + (rule _RDFLiteral_2 "128s.2" + (first LANGTAG "^^") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt LANGTAG _RDFLiteral_3)) + (rule BooleanLiteral "133s" + (first "@false" "@true") + (follow "!" "(" ")" "," "." ";" "<=" "=" "=>" "@a" "@false" "@has" "@is" + "@of" "@true" BLANK_NODE_LABEL DECIMAL DOUBLE INTEGER IRIREF PNAME_LN + STRING_LITERAL_LONG_QUOTE STRING_LITERAL_LONG_SINGLE_QUOTE + STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE "[" "]" "^" "{" "}" ) + (alt "@true" "@false")) + (terminal PNAME_NS "139s" (seq (opt PN_PREFIX) ":")) + (terminal PNAME_LN "140s" (seq PNAME_NS PN_LOCAL)) + (terminal BLANK_NODE_LABEL "141s" + (seq "_:" (alt PN_CHARS_U (range "0-9")) (opt (seq (star (alt PN_CHARS ".")) PN_CHARS)))) + (terminal LANGTAG "144s" + (seq "@" (plus (range "a-zA-Z")) (star (seq "-" (plus (range "a-zA-Z0-9")))))) + (terminal EXPONENT "154s" (seq (range "eE") (opt (range "+-")) (plus (range "0-9")))) + (terminal ECHAR "159s" (seq "\\" (range "tbnrf\"'"))) + (terminal PN_CHARS_BASE "163s" + (alt + (range "A-Z") + (range "a-z") + (range "#x00C0-#x00D6") + (range "#x00D8-#x00F6") + (range "#x00F8-#x02FF") + (range "#x0370-#x037D") + (range "#x037F-#x1FFF") + (range "#x200C-#x200D") + (range "#x2070-#x218F") + (range "#x2C00-#x2FEF") + (range "#x3001-#xD7FF") + (range "#xF900-#xFDCF") + (range "#xFDF0-#xFFFD") + (range "#x10000-#xEFFFF")) ) + (terminal PN_CHARS_U "164s" (alt PN_CHARS_BASE "_")) + (terminal PN_CHARS "166s" + (alt PN_CHARS_U "-" + (range "0-9") + (hex "#x00B7") + (range "#x0300-#x036F") + (range "#x203F-#x2040")) ) + (terminal PN_PREFIX "167s" + (seq PN_CHARS_BASE (opt (seq (star (alt PN_CHARS ".")) PN_CHARS)))) + (terminal PN_LOCAL "168s" + (seq + (alt PN_CHARS_U ":" (range "0-9") PLX) + (opt (seq (star (alt PN_CHARS "." ":" PLX)) (alt PN_CHARS ":" PLX)))) ) + (terminal PLX "169s" (alt PERCENT PN_LOCAL_ESC)) + (terminal PERCENT "170s" (seq "%" HEX HEX)) + (terminal PN_LOCAL_ESC "172s" + (seq "\\" + (alt "_" "~" "." "-" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" "/" "?" "#" + "@" "%" )) )) diff --git a/examples/andy/D-ref.n3 b/examples/andy/D-ref.n3 new file mode 100644 index 0000000..e69de29 diff --git a/examples/andy/D.n3 b/examples/andy/D.n3 new file mode 100644 index 0000000..454fc9d --- /dev/null +++ b/examples/andy/D.n3 @@ -0,0 +1,11 @@ +@prefix log: . +@prefix d: . + +:doc :is { + d:a d:p d:b . + d:a d:q d:c . +} . + +{ :doc :is ?d . + ?d log:notIncludes { d:a d:p d:xxx } . + } => { :doc a :Success . } . diff --git a/example-files/arnau-registered-vocab.rb b/examples/arnau-registered-vocab.rb similarity index 100% rename from example-files/arnau-registered-vocab.rb rename to examples/arnau-registered-vocab.rb diff --git a/example-files/arnau-stack-overflow.ttl b/examples/arnau-stack-overflow.ttl similarity index 100% rename from example-files/arnau-stack-overflow.ttl rename to examples/arnau-stack-overflow.ttl diff --git a/example-files/back-slash.nt b/examples/back-slash.nt similarity index 100% rename from example-files/back-slash.nt rename to examples/back-slash.nt diff --git a/example-files/best-buy.nt b/examples/best-buy.nt similarity index 100% rename from example-files/best-buy.nt rename to examples/best-buy.nt diff --git a/examples/dorthe2.n3 b/examples/dorthe2.n3 new file mode 100644 index 0000000..6b31eff --- /dev/null +++ b/examples/dorthe2.n3 @@ -0,0 +1,3 @@ +_:x :p {_:x :b :c}. + +{?x :p {?x2 :b :c}}=>{:s :p :o}. diff --git a/example-files/dwbutler-mj.n3 b/examples/dwbutler-mj.n3 similarity index 100% rename from example-files/dwbutler-mj.n3 rename to examples/dwbutler-mj.n3 diff --git a/example-files/dwbutler-mj.ttl b/examples/dwbutler-mj.ttl similarity index 100% rename from example-files/dwbutler-mj.ttl rename to examples/dwbutler-mj.ttl diff --git a/examples/example-1.n3 b/examples/example-1.n3 new file mode 100644 index 0000000..e039c35 --- /dev/null +++ b/examples/example-1.n3 @@ -0,0 +1,16 @@ +@prefix log: . +@keywords. +@forAll x, y, z. {x parent y. y sister z} log:implies {x aunt z}. + +# This N3 formula has three universally quantified variables and one statement. The subject of the statement, + +# {x parent y. y sister z} # is the antecedent of the rule and the object, + +# {x aunt z} # is the conclusion. Given data + +Joe parent Alan. +Alan sister Susie. + +# a rule engine would conclude + +# => Joe aunt Susie. \ No newline at end of file diff --git a/examples/example-2.n3 b/examples/example-2.n3 new file mode 100644 index 0000000..fdcc04d --- /dev/null +++ b/examples/example-2.n3 @@ -0,0 +1,19 @@ +@keywords. +@forAll x, y, z. +{ x wrote y. + y log:includes {z weather w}. + x livesIn z +} log:implies { + Boston weather y +}. + +# Here the rule fires when x is bound to a symbol denoting some person who is the author of a formula y, when the formula makes a statement about the weather in (presumably some place) z, and x's home is z. That is, we believe statements about the weather at a place only from people who live there. Given the data + +Bob livesIn Boston. +Bob wrote { Boston weather sunny }. +Alice livesIn Adelaide. +Alice wrote { Boston weather cold }. + +# a valid inference would be + +# => Boston weather sunny. diff --git a/examples/example-3.n3 b/examples/example-3.n3 new file mode 100644 index 0000000..7e55814 --- /dev/null +++ b/examples/example-3.n3 @@ -0,0 +1,2 @@ +:Woman = foo:FemaleAdult . +:Title a rdf:Property; = dc:title . diff --git a/example.rb b/examples/example.rb similarity index 100% rename from example.rb rename to examples/example.rb diff --git a/examples/existential-universal-love.n3 b/examples/existential-universal-love.n3 new file mode 100644 index 0000000..6d4c368 --- /dev/null +++ b/examples/existential-universal-love.n3 @@ -0,0 +1,8 @@ +:Raymond a :Person . +:Julie a :Person; :loves :Raymond . +:Steve a :Person; :loves :Raymond . +:Nancy a :Person; :loves :Raymond . + +@forAll :g . +@forSome :h . +{:g :loves :h} => {:g a :Lover} . diff --git a/examples/foo.n3 b/examples/foo.n3 new file mode 100644 index 0000000..32beed8 --- /dev/null +++ b/examples/foo.n3 @@ -0,0 +1,14 @@ +# Axiom for truth +# +@prefix log: . +@prefix : <#> . + +@forAll :x, :y, :z. + +{ { :x :y :z } a log:Truth . } log:implies { :x :y :z } . + +{ :sky :is :blue } a log:Truth . + +# ends + + diff --git a/examples/forAllSomeConfusion.n3 b/examples/forAllSomeConfusion.n3 new file mode 100644 index 0000000..968d500 --- /dev/null +++ b/examples/forAllSomeConfusion.n3 @@ -0,0 +1,3 @@ +:s :p :o. +@forAll :x. {:x :p :o}=>{:x :b :c}. +@forSome :x. {:s :p :o}=>{:a :b :x}. diff --git a/examples/forAllSomeConfusion2.n3 b/examples/forAllSomeConfusion2.n3 new file mode 100644 index 0000000..2c61b81 --- /dev/null +++ b/examples/forAllSomeConfusion2.n3 @@ -0,0 +1,7 @@ +@forSome :x. +:x a :Person. +@forAll :y.{:y a :Person.}=>{:y :loves :x.}. + +:Julie a :Person . +:Steve a :Person. +:Nancy a :Person. diff --git a/examples/includes/bnode-conclude-ref.n3 b/examples/includes/bnode-conclude-ref.n3 new file mode 100644 index 0000000..f884d9d --- /dev/null +++ b/examples/includes/bnode-conclude-ref.n3 @@ -0,0 +1,4 @@ + @prefix : <#> . + + [ a :Result ]. + diff --git a/examples/includes/bnodeConclude.n3 b/examples/includes/bnodeConclude.n3 new file mode 100644 index 0000000..c2f6fca --- /dev/null +++ b/examples/includes/bnodeConclude.n3 @@ -0,0 +1,32 @@ +@prefix log: . +@forAll :X . + +{ {:a :b []} log:includes {:a :b :X} } => {:X a :Result} . + +# Looks like the following: + +# (graph +# (universals ?X) +# (logImplies +# (universals ?X) +# (formula _:f0 +# (universals ?X) +# (logIncludes +# (formula _:f1 (universals ?X) (pattern :a :b _:bn0)) +# (formula _:f2 (universals ?X) (pattern :a :b ?X)) +# ) +# ) +# (formula _:f3 (universals ?X) (pattern ?X rdf:type :Result)) +# ) +# ) +# +# evaluate graph => evaluate all child operators +# evalutate log_implies => evaluate _:f0 +# evaluate _:f0 => evaluate log_includes +# evaluate log_includes => +# evaluate _:f1 +# evaluate _:f2 +# bind ?X against lhs +# evaluate _:f2 as bgp against _:f1 => true +# evaluate _:f3 +# => iterate over solutions \ No newline at end of file diff --git a/examples/includes/concat-ref.n3 b/examples/includes/concat-ref.n3 new file mode 100644 index 0000000..e69de29 diff --git a/examples/includes/concat.n3 b/examples/includes/concat.n3 new file mode 100644 index 0000000..eaa7db8 --- /dev/null +++ b/examples/includes/concat.n3 @@ -0,0 +1,75 @@ +# Test the string:concatenation function +# +# The earlier tests were written with a historical string:concat which is the INVERSE!! +# Please use string:concatenation -- or think of a better name for its inverse. + +#@prefix rdf: . +#@prefix s: . +#@prefix daml: . +#@prefix dpo: . +#@prefix ex: . + +@prefix log: . +@prefix string: . +@prefix : <#>. # Local stuff + +# Usage: cwm t13.n3 -think +# +# Output should conclude all test* a :success and no failures. +# + +#@forAll :x. + +@forAll :x, :y, :z. + +{ "" string:concat () } log:implies {:test13a a :success}. + +{ :x string:concat () } log:implies {:emptyString :is :x}. +{ :emptyString :is "" } log:implies { :test13b a :success }. + +{ :x string:concat ( "foo" ) } log:implies { :fooString :is :x }. +{ :fooString :is "foo" } log:implies { :test13c a :success }. + +{ :x string:concat ("World" [string:concat ("W" "i" "d" "e")] "Web") } log:implies {:www :is :x}. +{ :www :is "WorldWideWeb" } log:implies { :test13d a :success }. + +{ :x string:concat ("World" "Wide" "Web") } log:implies {:www2 :is :x}. +{ :www2 :is "WorldWideWeb" } log:implies { :test13e a :success }. + +{ :x string:concat ("World" :y "Web"). + :y string:concat ( "W" "I" "D""E"). + } log:implies {:www3:is :x}. +{ :www3 :is "WorldWIDEWeb" } log:implies { :test13f a :success }. + + +{ "" log:equalTo [ string:concat () ] } log:implies {:test13g a :success}. + +{ "one" log:equalTo [ string:concat () ] } log:implies {:test13a_bis a :FAILURE}. + +{ "" log:equalTo [ string:concat ( "one" ) ].} log:implies {:test13b_bis a :FAILURE}. + +{ "one" log:equalTo [ string:concat ( "World" "Wide" "Web" ) ]} + log:implies {:test13c_bis a :FAILURE}. + +{ :x is string:concatenation of ( + "World" + [is string:concatenation of ("W" "i" "d" "e")] + "Web") } log:implies {:www5 :is :x}. +{ :www5 :is "WorldWideWeb" } log:implies { :test13h a :success }. + +{ + :test13a a :success. + :test13b a :success. + :test13c a :success. + :test13d a :success. + :test13e a :success. + :test13f a :success. + :test13g a :success. + :test13h a :success. +} log:implies { :TEST13 a :success }. + +log:implies a log:Chaff. # Purge will remove rules + +# ends + + diff --git a/examples/includes/conclusion-simple-ref.n3 b/examples/includes/conclusion-simple-ref.n3 new file mode 100644 index 0000000..e69de29 diff --git a/examples/includes/conclusion-simple.n3 b/examples/includes/conclusion-simple.n3 new file mode 100644 index 0000000..5555d1b --- /dev/null +++ b/examples/includes/conclusion-simple.n3 @@ -0,0 +1,29 @@ +# Test the log:conclusion function + +# simple version for debugging + + +@prefix rdf: . +@prefix s: . +@prefix daml: . +@prefix dpo: . +@prefix ex: . +@prefix log: . + + +# Usage: cwm conclusion-simple.n3 -think +# +# + +@prefix : <#>. + + + +{{ }=>{ a }. + . +} a :TestRule. + + +{ ?x a :TestRule; log:conclusion ?y } => { ?y a :TestResult }. + +#ends diff --git a/examples/includes/conjunction-ref.n3 b/examples/includes/conjunction-ref.n3 new file mode 100644 index 0000000..14fbc97 --- /dev/null +++ b/examples/includes/conjunction-ref.n3 @@ -0,0 +1,27 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/includes/conjunction.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/includes/conjunction.n3 + @prefix : . + @prefix log: . + + @forAll :F, + :G, + :d, + :x, + :y . + { + + ( {:sky :color :blue . + } + {:sky :color :green . + } ) + log:conjunction :F . + + } log:implies {:F a :result . + } . + +#ENDS diff --git a/examples/includes/conjunction.n3 b/examples/includes/conjunction.n3 new file mode 100644 index 0000000..036215a --- /dev/null +++ b/examples/includes/conjunction.n3 @@ -0,0 +1,56 @@ +# Test the log:includes function + +@prefix rdf: . +@prefix s: . +#@prefix daml: . +#@prefix dpo: . +#@prefix ex: . +@prefix log: . +#@prefix string: . + + +@prefix foo: . # Local stuff +@prefix : . # Local stuff + +@prefix local: . + +# Usage: cwm conclusion.n3 -think +# +# See also t10a.n3 foo.n3 +# +# Output should be (in "result" formula) the same as for the cwm command below. +# + + + +@forAll :d, :x, :y, :F, :G. + +#{ "z" string:greaterThan "a" } log#:implies { :result :is :sanity }. + +{ ( { :sky :color :blue } { :sky :color :green } ) log:conjunction :F } + log:implies { :F a :result} . + + +# "Conjunction junction, what's your're function?" - schoolhouse rock. +# The conjunction of a list of formulae is the logical AND of them, ie +# the set of statements which contains each of the statements in each of the +# conjoined formulae. + + + +# +#{ ( [ is log:semantics of <../daml-ex.n3> ] +# [ is log:semantics of <../invalid-ex.n3> ] +# [ is log:semantics of <../schema-rules.n3> ] ) +# log:conjunction [ log:conclusion :G]} +# log:implies { :result :is :G }. +# +# The above is a much more complicated way of writing the cwm +# command line "cwm daml-ex.n3 invalid-ex.n3 schema-rules.n3 --think". +# + +# (Maybe conjunction should automatically reference a resource with no "#" +# so that the log:semantics becomes unnecesary. Maybe N3 should just assume +# coersion from resource to formula where that is appropriate? :o/ ) + +#ends diff --git a/examples/includes/list-in-ref.n3 b/examples/includes/list-in-ref.n3 new file mode 100644 index 0000000..4f95d0c --- /dev/null +++ b/examples/includes/list-in-ref.n3 @@ -0,0 +1,21 @@ + @prefix : <#> . + + 12 :a :Pythagorean . + + 13 :a :Pythagorean . + + 3 :a :Pythagorean, + :RESULT1 . + + 4 :a :Pythagorean, + :RESULT1 . + + 5 :a :Pythagorean, + :RESULT1 . + + :amy a :Friend . + + :fred a :Friend . + + :joe a :Friend . + diff --git a/examples/includes/list-in.n3 b/examples/includes/list-in.n3 new file mode 100644 index 0000000..2b0c966 --- /dev/null +++ b/examples/includes/list-in.n3 @@ -0,0 +1,13 @@ +@prefix li: . +@keywords. + +{ ?x li:in (3 4 5) } => { ?x a RESULT1 }. + + +{ ( joe fred amy ) li:member ?y } => { ?y @a Friend }. + + + +{ ((3 4 5) (5 12 13))!li:member li:member ?z } => { ?z a Pythagorean }. + +# ends diff --git a/example-files/jeni-ice-cream.nt b/examples/jeni-ice-cream.nt similarity index 100% rename from example-files/jeni-ice-cream.nt rename to examples/jeni-ice-cream.nt diff --git a/example-files/lee-reilly-list.rb b/examples/lee-reilly-list.rb similarity index 100% rename from example-files/lee-reilly-list.rb rename to examples/lee-reilly-list.rb diff --git a/examples/list/append-ref.n3 b/examples/list/append-ref.n3 new file mode 100644 index 0000000..7dccc2b --- /dev/null +++ b/examples/list/append-ref.n3 @@ -0,0 +1,5 @@ +@prefix : <#> . + +:A0 :RESULT [ :append ( () "only" ) ]. +:A1 :RESULT [ :append ((1) 2 ) ] . +:B0 :append ( () "onlyB" ) . \ No newline at end of file diff --git a/examples/list/append.n3 b/examples/list/append.n3 new file mode 100644 index 0000000..f19b6af --- /dev/null +++ b/examples/list/append.n3 @@ -0,0 +1,40 @@ +# $Id: append.n3,v 1.1 2003-08-30 18:04:16 timbl Exp $ + +@prefix rdf: . +@prefix rdfs: . +@prefix log: . +@prefix : <#> . + +#{ ?A :append ( ?L ?X ) => + +{ + ?A :append ( [ rdf:first ?H ; rdf:rest ?T ] ?S ) . +} + => +{ + ?A rdf:first ?H ; + rdf:rest [ :append ( ?T ?S ) ] . +} . + + +{ + ?A :append ( () ?S ) . +} + => +{ + ?A rdf:first ?S ; + rdf:rest () . +} . + + +# Test cases + + :A0 :RESULT [ :append ( () "only" ) ]. + :B0 :append ( () "onlyB" ) . + + :A1 :RESULT [ :append ( ( 1 ) 2 ) ] . + +#:A2 :append ( :A1 "L2" ) . + +#:A3 :append ( :A1 "L3" ) . + diff --git a/examples/list/builtin_generated_match-ref.n3 b/examples/list/builtin_generated_match-ref.n3 new file mode 100644 index 0000000..10d6fca --- /dev/null +++ b/examples/list/builtin_generated_match-ref.n3 @@ -0,0 +1,17 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/builtin_generated_match.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/builtin_generated_match.n3 + @prefix : <#> . + + () a :Thing . + ( ( + :q ) ) + a :Thing . + + :q a :GreatThing . + +#ENDS diff --git a/examples/list/builtin_generated_match.n3 b/examples/list/builtin_generated_match.n3 new file mode 100644 index 0000000..a9c0bba --- /dev/null +++ b/examples/list/builtin_generated_match.n3 @@ -0,0 +1,14 @@ +@keywords a, of, the, is, has, prefix . +@prefix rdf: . + +((q)) a Thing . + +{?X a Thing . + ?X rdf:rest ?Y} +=> +{?Y a Thing } . + +{?X a Thing; + rdf:first (?B)} +=> +{?B a GreatThing} . diff --git a/examples/list/last.n3 b/examples/list/last.n3 new file mode 100644 index 0000000..db7821a --- /dev/null +++ b/examples/list/last.n3 @@ -0,0 +1,30 @@ +# test list built in +@prefix rdf: . +@prefix : <#>. +@prefix list: . +@keywords a. + +{ ( 1 2 3 4 5 6 ) list:last 6 } => { test1 a SUCCESS }. + +{ ( 1 2 3 4 5 "Z" ) list:last "Z" } => { test2 a SUCCESS }. + +{ ( "wrong" "WongAgain" "Success" ) list:last ?x }=>{ test3 isa ?x}. + +{ 1 list:in ( 1 2 3 4 5 ) } => { test4a a SUCCESS }. +{ 2 list:in ( 1 2 3 4 5 ) } => { test4b a SUCCESS }. +{ 3 list:in ( 1 2 3 4 5 ) } => { test4c a SUCCESS }. +{ 4 list:in ( 1 2 3 4 5 ) } => { test4d a SUCCESS }. + +{ 1 list:in () } => { trap1 a FAILURE}. +{ 0 list:in ( 1 2 3 4 5 ) } => { trap2 a FAILURE }. + +thing1 prop1 ( test5a test5b test5c ) . +{ ?item list:in [ @is prop1 @of thing1 ] } => { ?item a SUCCESS } . + +( foo bar baz ) prop2 test6a . +( foo ) prop2 test6b . +( bar baz ) prop2 trap4 . +{ foo list:in ?a_list . + ?a_list prop2 ?thing } => { ?thing a SUCCESS } . + +#ends diff --git a/examples/list/list-bug1-ref.n3 b/examples/list/list-bug1-ref.n3 new file mode 100644 index 0000000..8f99e96 --- /dev/null +++ b/examples/list/list-bug1-ref.n3 @@ -0,0 +1,16 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/list-bug1.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/list-bug1.n3 + @prefix : <#> . + + ( + ) + a . + + :RESULT . + +#ENDS diff --git a/examples/list/list-bug1.n3 b/examples/list/list-bug1.n3 new file mode 100644 index 0000000..efeda27 --- /dev/null +++ b/examples/list/list-bug1.n3 @@ -0,0 +1,13 @@ +@prefix : <#>. +@prefix log: . + +@forAll :x, :y. + +( ) a . + +{ (:x :y) a } log:implies { :x :RESULT :y }. + + +# Result should be +# :RESULT . +# and should NOT include :RESULT . diff --git a/examples/list/list-bug2-ref.n3 b/examples/list/list-bug2-ref.n3 new file mode 100644 index 0000000..c121833 --- /dev/null +++ b/examples/list/list-bug2-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/list-bug2.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/list-bug2.n3 + @prefix : <#> . + + "fred whatever " a :RESULT . + +#ENDS diff --git a/examples/list/list-bug2.n3 b/examples/list/list-bug2.n3 new file mode 100644 index 0000000..11b7117 --- /dev/null +++ b/examples/list/list-bug2.n3 @@ -0,0 +1,21 @@ +# test the handling os lists which are bound as other things get calculated + +@prefix : <#>. +@prefix log: . +@prefix string: . + +@forAll :x, :y. + + +# Cause llyn to search the store for a list: +( "1" ) . + +{ +# log:uri ?u. + ( "fred" " whatever ") string:concatenation ?s +} => { ?s a :RESULT }. + + +# Result should be +# "fred whatever " a :RESULT . +# diff --git a/examples/list/r1-ref.n3 b/examples/list/r1-ref.n3 new file mode 100644 index 0000000..7b5cfa8 --- /dev/null +++ b/examples/list/r1-ref.n3 @@ -0,0 +1,17 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/r1.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/r1.n3 + @prefix : . + + "one" a :SUCCESS . + + "two" a :SUCCESS . + ( "one" + "two" ) + a :whatever . + +#ENDS diff --git a/examples/list/r1.n3 b/examples/list/r1.n3 new file mode 100644 index 0000000..ec4b8ef --- /dev/null +++ b/examples/list/r1.n3 @@ -0,0 +1,19 @@ +@prefix r: . +@prefix s: . + +@prefix doc: . +@prefix log: . +@prefix daml: . +@prefix xml: . +@prefix contact: . + +@prefix : . + +@forAll :a, :b, :c, :x, :y, :z. + +( "one" "two" ) a :whatever. + +{ (:a :b) a :whatever } log:implies { :a a :SUCCESS. :b a :SUCCESS }. + +# { :x a :whatever } log:implies { :x :is """ The list ( "one" "two" )""" }. + diff --git a/examples/list/unify2-ref.n3 b/examples/list/unify2-ref.n3 new file mode 100644 index 0000000..4fbee5a --- /dev/null +++ b/examples/list/unify2-ref.n3 @@ -0,0 +1,14 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/unify2.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify2.n3 + @prefix : <#> . + + 17 a :RESULT . + ( 17 ) + a :TestCase . + +#ENDS diff --git a/examples/list/unify2.n3 b/examples/list/unify2.n3 new file mode 100644 index 0000000..645dae7 --- /dev/null +++ b/examples/list/unify2.n3 @@ -0,0 +1,21 @@ +# test the handling os lists which are in the store +# +# use with --think + +@prefix : <#>. +@prefix log: . +@prefix math: . + +@forAll :x, :y. + + +# Cause llyn to search the store for a list: + +( 17 ) a :TestCase. + + +{ ( ?x ) a :TestCase} => { ?x a :RESULT }. + +# { 17 a :RESULT } => { :THIS_TEST a :SUCCESS }. + +# diff --git a/examples/list/unify3-ref.n3 b/examples/list/unify3-ref.n3 new file mode 100644 index 0000000..a6f1032 --- /dev/null +++ b/examples/list/unify3-ref.n3 @@ -0,0 +1,21 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/unify3.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify3.n3 + @prefix : <#> . + + 17 a :RESULT . + ( + ( + + 17 + ) + ) + a :TestCase . + + :THIS_TEST a :SUCCESS . + +#ENDS diff --git a/examples/list/unify3.n3 b/examples/list/unify3.n3 new file mode 100644 index 0000000..28db498 --- /dev/null +++ b/examples/list/unify3.n3 @@ -0,0 +1,22 @@ +# test the handling os lists which are in the store +# +# use with --think + +@prefix : <#>. +@prefix rdf: . +@prefix log: . +@prefix math: . + +@forAll :x, :y. + + +# Cause llyn to search the store for a list: + + +( ( 17 ) ) a :TestCase. + +{ ( ( ?x ) ) a :TestCase} => { ?x a :RESULT }. + + { 17 a :RESULT } => { :THIS_TEST a :SUCCESS }. + +# diff --git a/examples/list/unify4-ref.n3 b/examples/list/unify4-ref.n3 new file mode 100644 index 0000000..729baad --- /dev/null +++ b/examples/list/unify4-ref.n3 @@ -0,0 +1,34 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/unify4.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify4.n3 + @prefix : <#> . + + 2 a :RESULT . + ( 1 + 13 ) + :crinks ( + 2 + 14 ) . + ( 1 + 14 ) + :crinks ( + 2 + 13 ) . + ( 1 + 2 ) + :crinks ( + 7 + 2 ) . + ( 1 + 3 ) + :crinks ( + 999 + 999 ) . + + :THIS_TEST a :SUCCESS . + +#ENDS diff --git a/examples/list/unify4.n3 b/examples/list/unify4.n3 new file mode 100644 index 0000000..04df4fd --- /dev/null +++ b/examples/list/unify4.n3 @@ -0,0 +1,25 @@ +# test the handling os lists which are in the store +# +# use with --think + +@prefix : <#>. +@prefix log: . +@prefix math: . + +@forAll :x, :y. + + + + +( 1 2 ) :crinks ( 7 2 ). + +( 1 3 ) :crinks ( 999 999 ) . + +( 1 13 ) :crinks ( 2 14 ). +( 1 14 ) :crinks ( 2 13 ). + +{ ( 1 ?x ) :crinks ( 7 ?x ) } => { ?x a :RESULT }. + + { 2 a :RESULT } => { :THIS_TEST a :SUCCESS }. + +# diff --git a/examples/list/unify5-ref.n3 b/examples/list/unify5-ref.n3 new file mode 100644 index 0000000..4f7874d --- /dev/null +++ b/examples/list/unify5-ref.n3 @@ -0,0 +1,33 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/unify5.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify5.n3 + @prefix : <#> . + + :Aphrodite a :ShouldBeAphrodite, + :ShouldBeAphrodite2 . + + :Bob a :ShouldBeJunoBobJoe . + + :Joe a :ShouldBeJunoBobJoe . + + :Juno a :ShouldBeJunoBobJoe . + + :fred :siblings ( + [ + a :BnodeExtractedError; + :parents ( + :Alice + :Bob ), + ( + :Jane + :Joe ), + ( + :Zeus + :Juno ) ] + :Aphrodite ) . + +#ENDS diff --git a/examples/list/unify5.n3 b/examples/list/unify5.n3 new file mode 100644 index 0000000..b4ec62e --- /dev/null +++ b/examples/list/unify5.n3 @@ -0,0 +1,21 @@ +# Two variables and some bnodes +# And multiple values +# +@keywords is, a . + +fred siblings ( [ parents (Zeus Juno), (Alice Bob), (Jane Joe) ] Aphrodite ). + + + +{ fred siblings ( [] ?x ) } => { ?x a ShouldBeAphrodite }. + +{ fred siblings ( ?y ?x ) } => { ?y a BnodeExtractedError }. + + +{ fred siblings ( [ parents ([] ?y) ] ?x ) } => + + { ?x a ShouldBeAphrodite2. ?y a ShouldBeJunoBobJoe }. + + +#ends + diff --git a/examples/manifest.n3 b/examples/manifest.n3 new file mode 100644 index 0000000..6933b80 --- /dev/null +++ b/examples/manifest.n3 @@ -0,0 +1,339 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix mf: . +@prefix qt: . +@prefix rdft: . +@prefix test: . +@prefix xsd: . +@prefix : <#>. +@prefix x: <#>. + +<> a mf:Manifest ; + rdfs:label "Notation3 tests" ; + mf:entries + ( + # From swap/test/crypto + + # From swap/test/cwm + + # From swap/test/delta + + # From swap/test/i18n + + # From swap/test/includes + :listin :bnode :concat :conclusion-simple :conjunction + + # From swap/test/math + + # From swap/test/norm + :norm10 + + # From swap/test/list + :t1018b1 :t1018b2 :t1031 :t2004u2 :t2004u3 :t2004u4 :t2004u5 :t2005 :t2006 :t2007 + + # From swap/test/paw + + # From swap/test/ql + + # From swap/test/reason + :t01proof :t02proof :t03proof :t04proof :t05proof :t06proof :socrates :t08proof + :t09proof :t12 + + # From swap/test/string + :t103 :t104 :t105 + + # From swap/test/supports + :t01 + + # From swap/test/unify + :t553 :t554 :t555 + + ) . + +# List tests from swap/tests/includes + +:listin a test:CwmTest; + mf:name "list membership" ; + rdfs:comment "Builtins for list membership, binding multiple values." ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +:bnode a test:CwmTest; + mf:name "list membership" ; + rdfs:comment "Builtins for list membership, binding multiple values." ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +:concat a test:CwmTest; + mf:name "list concatenation" ; + rdfs:comment "Builtins for list concatenation." ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +:conclusion-simple a test:CwmTest; + mf:name "includes-conclusion-simple.n3" ; + rdfs:comment "Builtins for log:conclusion." ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +:conjunction a test:CwmTest; + mf:name "includes-conjunction.n3" ; + rdfs:comment "Builtins for log:conjunction." ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +# Tests from swap/test/norm +:norm10 a test:CwmTest; + mf:name "norm-av1.n3"; + rdfs:comment "Bug with RDF output in bySubject mode"; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +# List tests from swap/test/list + +:t1018b1 a test:CwmTest; + mf:name "list-bug1.n3" ; + rdfs:comment "List processing bug check 1" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +:t1018b2 a test:CwmTest; + mf:name "list-bug2.n3" ; + rdfs:comment "List processing bug check 2" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +:t1031 a test:CwmTest; + mf:name "li-r1.n3" ; + rdfs:comment "Inference using lists" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true;] . + +:t2004u2 a test:CwmTest; + mf:name "list-unify2.n3" ; + rdfs:comment "List unification 2 - variable in list" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2004u3 a test:CwmTest; + mf:name "list-unify3.n3" ; + rdfs:comment "List unification 3 - nested lists" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2004u4 a test:CwmTest; + mf:name "list-unify4.n3" ; + rdfs:comment "List unification 4 - nested lists" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2004u5 a test:CwmTest; + mf:name "list-unify5.n3" ; + rdfs:comment "List unification 5 - multiple values" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2005 a test:CwmTest; + mf:name "append-out.n3" ; + rdfs:comment "Iterative ops on lists" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2006 a test:CwmTest; + mf:name "list-last.n3" ; + rdfs:comment "last, in builtins on lists" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t2007 a test:CwmTest; + mf:name "list-builtin_generated_match.n3" ; + rdfs:comment "last, in builtins on lists" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +# List tests from swap/test/reason + +:t01proof a test:CwmTest; + mf:name "reason-t1.n3" ; + rdfs:comment "Proof for just loading a file" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t02proof a test:CwmTest; + mf:name "reason-t2.n3" ; + rdfs:comment "Proof for just loading a file" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t03proof a test:CwmTest; + mf:name "reason-t3.n3" ; + rdfs:comment "Proof for just loading a file" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t04proof a test:CwmTest; + mf:name "reason-t4.n3" ; + rdfs:comment "Proof for just loading a file" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t05proof a test:CwmTest; + mf:name "reason-t5.n3" ; + rdfs:comment "Proof for a little inference" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t06proof a test:CwmTest; + mf:name "reason-t6.n3" ; + rdfs:comment "Proof for a little inference" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +# This is equiv of others but easier to explain with famous example +:socrates a test:CwmTest; + mf:name "reason-socrates.n3" ; + rdfs:comment "Proof for a little inference" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t08proof a test:CwmTest; + mf:name "reason-t8.n3" ; + rdfs:comment "Proof for a little inference, --n3=B to name BNodes" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t09proof a test:CwmTest; + mf:name "reason-t9.n3" ; + rdfs:comment "Proof for a little inference - binding Bnode to symbol" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +:t12 a test:CwmTest; # too unstable! + mf:name "reason-double.n3" ; + rdfs:comment "Proof for a little inference - binding Bnode to symbol" ; + mf:action ; + mf:result ; + test:options [test:think true; test:data true] . + +# List tests from swap/test/string + +:t103 a test:CwmTest; + mf:name "string-endsWith.n3" ; + rdfs:comment "string:endsWith" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +:t104 a test:CwmTest; + mf:name "string-roughly.n3" ; + rdfs:comment "string:containsRoughly ignores case smart whitespace" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +:t108 a test:CwmTest; + mf:name "string-uriEncode.n3" ; + rdfs:comment "string:encodeForURI and encodeForFragID" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +# List tests from swap/test/supports +:t01 a test:CwmTest; + mf:name "supports-simple.n3" ; + rdfs:comment "A very simple use of log:supports" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +# List tests from swap/test/unify +:t553 a test:CwmTest; + mf:name "unify-unify1.n3" ; + rdfs:comment "log:includes looking for @forAll" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +:t554 a test:CwmTest; + mf:name "unify-unify2.n3" ; + rdfs:comment "Query looking for @forAll" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +:t555 a test:CwmTest; + mf:name "unify-reflexive.n3" ; + rdfs:comment "Include using the same var twice" ; + mf:action ; + mf:result ; + test:options [test:rules true; test:data true] . + +#### +# Test Vocabulary + +test:apply a rdf:Property; + rdfs:comment "Read rules from foo, apply to store, adding conclusions to store"; + rdfs:domain test:Options; + rdfs:range rdfs:Resource; + . + +test:data a rdf:Property; + rdfs:comment "Remove all except plain RDF triples (formulae, forAll, etc)"; + rdfs:domain test:Options; + rdfs:range xsd:boolean; + . + +test:filter a rdf:Property; + rdfs:comment "Read rules from foo, apply to store, REPLACING store with conclusions"; + rdfs:domain test:Options; + rdfs:range rdfs:Resource; + . + +test:patch a rdf:Property; + rdfs:comment "Read patches from foo, applying insertions and deletions to store"; + rdfs:domain test:Options; + rdfs:range rdfs:Resource; + . + +test:options a rdf:Property; + rdfs:domain test:CwmTest; + rdfs:range test:Options; + rdfs:comment "Options for running tests"; + . + +test:rules a rdf:Property; + rdfs:comment "Apply rules in store to store, adding conclusions to store"; + rdfs:domain test:Options; + rdfs:domain xsd:boolean; + . + +test:think a rdf:Property; + rdfs:comment "as test:rules but continue until no more rule matches (or forever!)"; + rdfs:domain test:Options; + rdfs:domain xsd:boolean; + . diff --git a/examples/norm/av-ref.n3 b/examples/norm/av-ref.n3 new file mode 100644 index 0000000..5ff1a20 --- /dev/null +++ b/examples/norm/av-ref.n3 @@ -0,0 +1,98 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/norm/av.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/norm/av.n3 + @prefix : . + @prefix gv: . + @prefix rdfs: . + + <> gv:digraph [ + gv:label "AV Diagram"; + gv:subgraph [ + gv:hasEdgeProperty :dottedEdge, + :solidEdge; + gv:hasNode :ab, + :cable, + :catv, + :dvd, + :splitter, + :tv, + :vcr; + gv:label "" ] ] . + + :AV a rdfs:Class . + + :ab a :AV; + :output :selector; + :solidEdge :selector; + :title "A/B Switch"; + gv:color "black"; + gv:label "A/B Switch"; + gv:shape "box" . + + :cable a :AV; + :output :vcr; + :solidEdge :vcr; + :title "Cable Box"; + gv:color "black"; + gv:label "Cable Box"; + gv:shape "box" . + + :catv a :AV; + :title "CATV"; + gv:color "black"; + gv:label "CATV"; + gv:shape "box" . + + :dottedEdge gv:style "dotted" . + + :dvd a :AV; + :output :selector; + :solidEdge :selector; + :title "DVD"; + gv:color "black"; + gv:label "DVD"; + gv:shape "box" . + + :input a rdfs:Property . + + :output a rdfs:Property . + + :selector a :AV; + :output :tv; + :solidEdge :tv; + :title "Signal Selector"; + gv:color "black"; + gv:label "Signal Selector"; + gv:shape "box" . + + :solidEdge gv:style "solid" . + + :splitter a :AV; + :output :ab; + :solidEdge :ab; + :title "Splitter"; + gv:color "black"; + gv:label "Splitter"; + gv:shape "box" . + + :title a rdfs:Property . + + :tv a :AV; + :title "TV"; + gv:color "black"; + gv:label "TV"; + gv:shape "box" . + + :vcr a :AV; + :output :ab; + :solidEdge :ab; + :title "VCR"; + gv:color "black"; + gv:label "VCR"; + gv:shape "box" . + +#ENDS diff --git a/examples/norm/av.n3 b/examples/norm/av.n3 new file mode 100644 index 0000000..1010cd1 --- /dev/null +++ b/examples/norm/av.n3 @@ -0,0 +1,89 @@ +@prefix : . +@prefix rdfs: . +@prefix log: . +@prefix gv: . + +# The root + +:AV a rdfs:Class . +:input a rdfs:Property . +:output a rdfs:Property . +:title a rdfs:Property . + +# Rules + +:catv a :AV; + :title "CATV" . + +:splitter a :AV; + :title "Splitter"; + :output :ab . + +:selector a :AV; + :title "Signal Selector"; + :output :tv . + +:cable a :AV; + :title "Cable Box"; + :output :vcr . + +:vcr a :AV; + :title "VCR"; + :output :ab . + +:ab a :AV; + :title "A/B Switch"; + :output :selector . + +:tv a :AV; + :title "TV" . + +:dvd a :AV; + :title "DVD"; + :output :selector . + +@forAll :p, :s, :o, :t . + +# Graph rules + +{ :p :output :s } log:implies { :p :solidEdge :s } . + +{ :p :title :s } log:implies { :p gv:label :s } . + +# Style rules + +{ :s a :AV } log:implies { :s gv:color "black"; + gv:shape "box" } . + +#{ :s a :DocumentSet } log:implies { :s gv:color "black"; +# gv:shape "polygon"; +# gv:sides "5" } . +# +#{ :s a :Result } log:implies { :s gv:color "blue"; +# gv:style "filled"; +# gv:fontcolor "white" } . +# +#{ :s a :Source } log:implies { :s gv:shape "Mdiamond" } . +# +#{ :s a :Optional } log:implies { :s gv:shape "diamond" } . +# +#{ :s a :Temporary } log:implies { :s gv:color "gray"; +# gv:style "filled"; +# gv:fontcolor "white" } . + +:solidEdge gv:style "solid" . +:dottedEdge gv:style "dotted" . + +# The graph information + +<> gv:digraph [ + gv:label "AV Diagram"; + + gv:subgraph [ + gv:label "" ; + gv:hasNode :catv, :splitter, :cable, :vcr, :ab, :tv, :dvd; + gv:hasEdgeProperty :solidEdge ; + gv:hasEdgeProperty :dottedEdge ; + ] +] . + diff --git a/examples/path-1.n3 b/examples/path-1.n3 new file mode 100644 index 0000000..9c8a4d3 --- /dev/null +++ b/examples/path-1.n3 @@ -0,0 +1,5 @@ +[is con:zipcode of [ + is con:address of [ + is con:home of [ + is off:assistant of [ + is rel:mother of :George]]]]] diff --git a/examples/reason/double-ref.n3 b/examples/reason/double-ref.n3 new file mode 100644 index 0000000..934c8cf --- /dev/null +++ b/examples/reason/double-ref.n3 @@ -0,0 +1,15 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/reason/double.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/double.n3 + @prefix : . + + :dan a :Man; + :home [ + :in :Texas ]; + :homeRegion :Texas . + +#ENDS diff --git a/examples/reason/double.n3 b/examples/reason/double.n3 new file mode 100644 index 0000000..bbda1c9 --- /dev/null +++ b/examples/reason/double.n3 @@ -0,0 +1,10 @@ +@prefix : . +@keywords is, of, a. + +dan a Man; home []. + +{ ?WHO home ?WHERE. + ?WHERE in ?REGION } => { ?WHO homeRegion ?REGION }. + + +{ dan home ?WHERE} => {?WHERE in Texas} . diff --git a/examples/reason/single_gen.n3 b/examples/reason/single_gen.n3 new file mode 100644 index 0000000..a69f513 --- /dev/null +++ b/examples/reason/single_gen.n3 @@ -0,0 +1,3 @@ +:a :b "A noun", 3.14159265359 . + +{:a :b ?X} => { [ a :Thing ] } . diff --git a/examples/reason/socrates-ref.n3 b/examples/reason/socrates-ref.n3 new file mode 100644 index 0000000..3181401 --- /dev/null +++ b/examples/reason/socrates-ref.n3 @@ -0,0 +1,13 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/reason/socrates.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/socrates.n3 + @prefix : . + + :socrates a :Man, + :Mortal . + +#ENDS diff --git a/examples/reason/socrates.n3 b/examples/reason/socrates.n3 new file mode 100644 index 0000000..c2936ed --- /dev/null +++ b/examples/reason/socrates.n3 @@ -0,0 +1,7 @@ +@prefix : . +@prefix vars: . +:socrates a :Man. +{ ?who a :Man } => { ?who a :Mortal }. + +#ends + diff --git a/examples/reason/t1-ref.n3 b/examples/reason/t1-ref.n3 new file mode 100644 index 0000000..fddcbb4 --- /dev/null +++ b/examples/reason/t1-ref.n3 @@ -0,0 +1,11 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/reason/t1.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t1.n3 + + . + +#ENDS diff --git a/examples/reason/t1.n3 b/examples/reason/t1.n3 new file mode 100644 index 0000000..5d932fc --- /dev/null +++ b/examples/reason/t1.n3 @@ -0,0 +1 @@ + . diff --git a/examples/reason/t2-ref.n3 b/examples/reason/t2-ref.n3 new file mode 100644 index 0000000..0a78798 --- /dev/null +++ b/examples/reason/t2-ref.n3 @@ -0,0 +1,13 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/reason/t2.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t2.n3 + + . + + . + +#ENDS diff --git a/examples/reason/t2.n3 b/examples/reason/t2.n3 new file mode 100644 index 0000000..b72adfb --- /dev/null +++ b/examples/reason/t2.n3 @@ -0,0 +1,4 @@ + . + . +# ends + diff --git a/examples/reason/t3-ref.n3 b/examples/reason/t3-ref.n3 new file mode 100644 index 0000000..5cbaadb --- /dev/null +++ b/examples/reason/t3-ref.n3 @@ -0,0 +1,14 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/reason/t3.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t3.n3 + @prefix : <#> . + + :a :b :c . + + :a2 :b2 :c2 . + +#ENDS diff --git a/examples/reason/t3.n3 b/examples/reason/t3.n3 new file mode 100644 index 0000000..d41a2c0 --- /dev/null +++ b/examples/reason/t3.n3 @@ -0,0 +1,6 @@ +@prefix : <#>. + +:a :b :c. +{:a :b :c} => { :a2 :b2 :c2 }. +# ends + diff --git a/examples/reason/t4-ref.n3 b/examples/reason/t4-ref.n3 new file mode 100644 index 0000000..0ae8828 --- /dev/null +++ b/examples/reason/t4-ref.n3 @@ -0,0 +1,14 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/reason/t4.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t4.n3 + @prefix : <#> . + + :a :b :c . + + :c :d :e . + +#ENDS diff --git a/examples/reason/t4.n3 b/examples/reason/t4.n3 new file mode 100644 index 0000000..7016375 --- /dev/null +++ b/examples/reason/t4.n3 @@ -0,0 +1,6 @@ +@prefix : <#>. + +:a :b :c. +{:a :b ?x} => { ?x :d :e }. +# ends + diff --git a/examples/reason/t5-ref.n3 b/examples/reason/t5-ref.n3 new file mode 100644 index 0000000..e2e3ac8 --- /dev/null +++ b/examples/reason/t5-ref.n3 @@ -0,0 +1,14 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/reason/t5.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t5.n3 + @prefix : <#> . + + :a :b :c . + + :c :b . + +#ENDS diff --git a/examples/reason/t5.n3 b/examples/reason/t5.n3 new file mode 100644 index 0000000..78ccb66 --- /dev/null +++ b/examples/reason/t5.n3 @@ -0,0 +1,7 @@ +@prefix log: . +@prefix foo: <#>. +@prefix : <#>. +:a :b :c. +{:a ?y ?x} => { ?x ?y }. +# ends + diff --git a/examples/reason/t6-ref.n3 b/examples/reason/t6-ref.n3 new file mode 100644 index 0000000..2432ecc --- /dev/null +++ b/examples/reason/t6-ref.n3 @@ -0,0 +1,14 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/reason/t6.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t6.n3 + @prefix : <#> . + + :joe a :player; + :friend :kev; + :height "1.6" . + +#ENDS diff --git a/examples/reason/t6.n3 b/examples/reason/t6.n3 new file mode 100644 index 0000000..e43afd5 --- /dev/null +++ b/examples/reason/t6.n3 @@ -0,0 +1,15 @@ +@prefix log: . +@prefix math: . +@prefix foo: <#>. +@prefix : <#>. +:joe :friend :kev; :height "1.6". + +# Currently (2002/12) the checker can't compare things with bnodes +# with different identifiers and find them equal :-( + +#{?x :friend :kev; :height [math:greaterThan "1.3"]} => { ?x a :player }. + +{?x :friend :kev; :height ?h. ?h math:greaterThan "1.3"} => { ?x a :player }. + +# ends + diff --git a/examples/reason/t8-ref.n3 b/examples/reason/t8-ref.n3 new file mode 100644 index 0000000..bbf3efe --- /dev/null +++ b/examples/reason/t8-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/reason/t8.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t8.n3 + + [ + ] . + +#ENDS diff --git a/examples/reason/t8.n3 b/examples/reason/t8.n3 new file mode 100644 index 0000000..5cfc21f --- /dev/null +++ b/examples/reason/t8.n3 @@ -0,0 +1,6 @@ +# Testing proof generation and checking. + + []. +{ ?x} => { ?x }. +# ends + diff --git a/examples/reason/t9-ref.n3 b/examples/reason/t9-ref.n3 new file mode 100644 index 0000000..89ba6f7 --- /dev/null +++ b/examples/reason/t9-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/reason/t9.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/reason/t9.n3 + + a ; + . + +#ENDS diff --git a/examples/reason/t9.n3 b/examples/reason/t9.n3 new file mode 100644 index 0000000..4ec27e9 --- /dev/null +++ b/examples/reason/t9.n3 @@ -0,0 +1,8 @@ +# Testing proof generation and checking. + + . + +{ []} => { a }. + +# ends + diff --git a/example-files/recipe.ttl b/examples/recipe.ttl similarity index 100% rename from example-files/recipe.ttl rename to examples/recipe.ttl diff --git a/example-files/sp2b.n3 b/examples/sp2b.n3 similarity index 100% rename from example-files/sp2b.n3 rename to examples/sp2b.n3 diff --git a/examples/string/endsWith-out.n3 b/examples/string/endsWith-out.n3 new file mode 100644 index 0000000..5903b75 --- /dev/null +++ b/examples/string/endsWith-out.n3 @@ -0,0 +1,27 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/string/endsWith.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/endsWith.n3 + @prefix : <#> . + @prefix contact: . + @prefix doc: . + @prefix rcs: . + + <> doc:creator [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox ]; + rcs:id "$Id: endsWith.n3,v 1.3 2004-06-25 01:27:00 timbl Exp $" . + + :test1 a :success . + + :test3 a :success . + + :test6 a :success . + + :test7 a :success . + +#ENDS diff --git a/examples/string/endsWith.n3 b/examples/string/endsWith.n3 new file mode 100644 index 0000000..628bf91 --- /dev/null +++ b/examples/string/endsWith.n3 @@ -0,0 +1,37 @@ + +@prefix contact: . +@prefix rcs: . +@prefix doc: . + + +<> rcs:id "$Id: endsWith.n3,v 1.3 2004-06-25 01:27:00 timbl Exp $"; + + doc:creator [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox + ]. + + +@prefix log: . +@prefix string: . +@prefix : <#>. + +@forAll :x, :y, :z, :p, :q. + +{ "asdfghjkl" string:endsWith "jkl" } log:implies { :test1 a :success }. + +{ "asdfghjkl" string:endsWith "jkk" } log:implies { :test2 a :FAILURE }. + +{ "jkl" string:endsWith "jkl" } log:implies { :test3 a :success }. + +{ "asdfghjkl" string:endsWith "aaajkl" } log:implies { :test4 a :FAILURE }. + +{ "asdfjhkh" string:endsWith "asd" } log:implies { :test5 a :FAILURE }. + +{ "foobar" string:endsWith "" } log:implies { :test6 a :success }. + +{ "" string:endsWith "" } log:implies { :test7 a :success }. + +#ends + diff --git a/examples/string/roughly-out.n3 b/examples/string/roughly-out.n3 new file mode 100644 index 0000000..b31845d --- /dev/null +++ b/examples/string/roughly-out.n3 @@ -0,0 +1,30 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/string/roughly.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/roughly.n3 + @prefix : <#> . + @prefix contact: . + @prefix rcs: . + + <> [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox ]; + "Test string:containsRoughly"; + "copyright (c) 2001 W3C (MIT, Keio, INRIA)"; + rcs:id "$Id: roughly.n3,v 1.2 2004-06-25 01:27:00 timbl Exp $" . + + :test1 a :success . + + :test3 a :success . + + :test6 a :success . + + :test7 a :success . + + :test8 a :success . + +#ENDS diff --git a/examples/string/roughly.n3 b/examples/string/roughly.n3 new file mode 100644 index 0000000..060e443 --- /dev/null +++ b/examples/string/roughly.n3 @@ -0,0 +1,42 @@ +@prefix dc: . +@prefix contact: . +@prefix rcs: . + +<> dc:description """Test string:containsRoughly"""; + rcs:id "$Id: roughly.n3,v 1.2 2004-06-25 01:27:00 timbl Exp $"; + dc:creator [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox + ]; + dc:rights "copyright (c) 2001 W3C (MIT, Keio, INRIA)". + + +@prefix log: . +@prefix string: . +@prefix : <#>. + +@forAll :x, :y, :z, :p, :q. + +{ "A green party" string:containsRoughly "green Party" } log:implies { :test1 a :success }. + +{ "all good people to come to" string:containsRoughly "gooood" } log:implies { :test2 a :FAILURE }. + +{ "jkl" string:containsRoughly "jkl" } log:implies { :test3 a :success }. + +{ "foo" string:containsRoughly "foo bar" } log:implies { :test4 a :FAILURE }. + +{ "asd" string:containsRoughly "asdfjhkjhasd" } log:implies { :test5 a :FAILURE }. + +{ "" string:containsRoughly "" } log:implies { :test6 a :success }. + +{ "supercalifragilisticexpialidocious" string:containsRoughly "" } log:implies { :test7 a :success }. +{ """THE + WIDE + AND + THE + narrowEST + OF PLACES""" string:containsRoughly "wide and the" } log:implies { :test8 a :success }. + +#ends + diff --git a/examples/string/uriEncode-out.n3 b/examples/string/uriEncode-out.n3 new file mode 100644 index 0000000..e223d75 --- /dev/null +++ b/examples/string/uriEncode-out.n3 @@ -0,0 +1,46 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/string/uriEncode.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/string/uriEncode.n3 + @prefix : <#> . + @prefix contact: . + @prefix doc: . + @prefix rcs: . + + "asd#jkl" :AS_FragID "asd%23jkl"; + :AS_URI "asd#jkl" . + + "asd'jkl" :AS_FragID "asd%27jkl"; + :AS_URI "asd'jkl" . + + "asd(jkl" :AS_FragID "asd%28jkl"; + :AS_URI "asd(jkl" . + + "asd)jkl" :AS_FragID "asd%29jkl"; + :AS_URI "asd)jkl" . + + "asd-jkl" :AS_FragID "asd-jkl"; + :AS_URI "asd-jkl" . + + "asd.jkl" :AS_FragID "asd.jkl"; + :AS_URI "asd.jkl" . + + "asd/jkl" :AS_FragID "asd/jkl"; + :AS_URI "asd%2Fjkl" . + + "asd_jkl" :AS_FragID "asd_jkl"; + :AS_URI "asd_jkl" . + + "asd~jkl" :AS_FragID "asd%7Ejkl"; + :AS_URI "asd~jkl" . + + <> doc:creator [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox ]; + rcs:id "$Id: uriEncode.n3,v 1.1 2006-01-05 16:02:13 timbl Exp $" . + +#ENDS diff --git a/examples/string/uriEncode.n3 b/examples/string/uriEncode.n3 new file mode 100644 index 0000000..b9e3266 --- /dev/null +++ b/examples/string/uriEncode.n3 @@ -0,0 +1,61 @@ + +@prefix contact: . +@prefix rcs: . +@prefix doc: . + + +<> rcs:id "$Id: uriEncode.n3,v 1.1 2006-01-05 16:02:13 timbl Exp $"; + + doc:creator [ + contact:fullName "Tim berners-Lee"; + contact:homePage ; + contact:mailbox + ]. + + +@prefix log: . +@prefix string: . +@prefix : <#>. + +{ "asd#jkl" string:encodeForURI ?x } log:implies { "asd#jkl" :AS_URI ?x }. + +{ "asd/jkl" string:encodeForURI ?x } log:implies { "asd/jkl" :AS_URI ?x }. + +{ "asd(jkl" string:encodeForURI ?x } log:implies { "asd(jkl" :AS_URI ?x }. + +{ "asd'jkl" string:encodeForURI ?x } log:implies { "asd'jkl" :AS_URI ?x }. + +{ "asd)jkl" string:encodeForURI ?x } log:implies { "asd)jkl" :AS_URI ?x }. + +{ "asd_jkl" string:encodeForURI ?x } log:implies { "asd_jkl" :AS_URI ?x }. + +{ "asd~jkl" string:encodeForURI ?x } log:implies { "asd~jkl" :AS_URI ?x }. + +{ "asd-jkl" string:encodeForURI ?x } log:implies { "asd-jkl" :AS_URI ?x }. + +{ "asd.jkl" string:encodeForURI ?x } log:implies { "asd.jkl" :AS_URI ?x }. + +### + + +{ "asd#jkl" string:encodeForFragID ?x } log:implies { "asd#jkl" :AS_FragID ?x }. + +{ "asd/jkl" string:encodeForFragID ?x } log:implies { "asd/jkl" :AS_FragID ?x }. + +{ "asd(jkl" string:encodeForFragID ?x } log:implies { "asd(jkl" :AS_FragID ?x }. + +{ "asd'jkl" string:encodeForFragID ?x } log:implies { "asd'jkl" :AS_FragID ?x }. + +{ "asd)jkl" string:encodeForFragID ?x } log:implies { "asd)jkl" :AS_FragID ?x }. + +{ "asd_jkl" string:encodeForFragID ?x } log:implies { "asd_jkl" :AS_FragID ?x }. + +{ "asd~jkl" string:encodeForFragID ?x } log:implies { "asd~jkl" :AS_FragID ?x }. + +{ "asd-jkl" string:encodeForFragID ?x } log:implies { "asd-jkl" :AS_FragID ?x }. + +{ "asd.jkl" string:encodeForFragID ?x } log:implies { "asd.jkl" :AS_FragID ?x }. + + +#ends + diff --git a/examples/supports/simple-ref.n3 b/examples/supports/simple-ref.n3 new file mode 100644 index 0000000..93ec3d3 --- /dev/null +++ b/examples/supports/simple-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/supports/simple.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/supports/simple.n3 + @prefix : <#> . + + :Q :Q :Q . + +#ENDS diff --git a/examples/supports/simple.n3 b/examples/supports/simple.n3 new file mode 100644 index 0000000..38ba23e --- /dev/null +++ b/examples/supports/simple.n3 @@ -0,0 +1,5 @@ +@prefix log: . + +@forAll :X . + +{ {:a :b :c . :r :e :g . {:a :b :c} => {:d :e :f} } log:supports {:a :b :c. :d :e :f} } => {:Q :Q :Q} . diff --git a/examples/unboundExistentialConsequent.n3 b/examples/unboundExistentialConsequent.n3 new file mode 100644 index 0000000..d37cc48 --- /dev/null +++ b/examples/unboundExistentialConsequent.n3 @@ -0,0 +1,5 @@ +@prefix : . + +:s :p :o. + @forAll :x. {:x :p :o}=>{:x :b :c}. + @forSome :y. {:s :p :y}=>{:a :b :y}. \ No newline at end of file diff --git a/examples/unify/reflexive-ref.n3 b/examples/unify/reflexive-ref.n3 new file mode 100644 index 0000000..be0eb88 --- /dev/null +++ b/examples/unify/reflexive-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/unify/reflexive.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/reflexive.n3 + @prefix : <#> . + + :Steve :talksTo :Joe . + +#ENDS diff --git a/examples/unify/reflexive.n3 b/examples/unify/reflexive.n3 new file mode 100644 index 0000000..3e205d9 --- /dev/null +++ b/examples/unify/reflexive.n3 @@ -0,0 +1,6 @@ +@prefix log: . +@prefix : <#>. + +:Steve :talksTo :Joe . + +{ ?s :talksTo ?s } log:implies {?s :admits "I talk to myself"} . diff --git a/examples/unify/unify1-ref.n3 b/examples/unify/unify1-ref.n3 new file mode 100644 index 0000000..bd93c4c --- /dev/null +++ b/examples/unify/unify1-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/unify/unify1.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/unify1.n3 + @prefix : <#> . + + :test a :Successful . + +#ENDS diff --git a/examples/unify/unify1.n3 b/examples/unify/unify1.n3 new file mode 100644 index 0000000..c73a3cf --- /dev/null +++ b/examples/unify/unify1.n3 @@ -0,0 +1,13 @@ +# Two variables and some bnodes +# +@keywords is, a . + +Juno says { Mars too Successful }. + +@forAll x. + +{ Juno says { Mars too x } } => { test a x }. + + +#ends + diff --git a/examples/unify/unify2-ref.n3 b/examples/unify/unify2-ref.n3 new file mode 100644 index 0000000..97b1e85 --- /dev/null +++ b/examples/unify/unify2-ref.n3 @@ -0,0 +1,12 @@ +#Processed by Id: cwm.py,v 1.197 2007-12-13 15:38:39 syosi Exp + # using base test-files/unify/unify2.n3 + +# Notation3 generation by +# notation3.py,v 1.201 2010-10-23 04:14:48 timbl Exp + +# Base was: file:///Users/gregg/Projects/rdf-n3/spec/test-files/unify/unify2.n3 + @prefix : <#> . + + :test a :SUCCESS . + +#ENDS diff --git a/examples/unify/unify2.n3 b/examples/unify/unify2.n3 new file mode 100644 index 0000000..d7fb77f --- /dev/null +++ b/examples/unify/unify2.n3 @@ -0,0 +1,11 @@ +# For All in the template for unification +# +@keywords is, a . + +Juno says {@forAll y. Mars fights y}. + +{ Juno says {@forAll y. Mars fights y } } => { test a SUCCESS }. + + +#ends + diff --git a/lib/rdf/n3.rb b/lib/rdf/n3.rb index 29d2f77..1f948fc 100644 --- a/lib/rdf/n3.rb +++ b/lib/rdf/n3.rb @@ -16,17 +16,20 @@ module RDF # end # # @see http://www.rubydoc.info/github/ruby-rdf/rdf/ - # @see http://www.w3.org/TR/REC-rdf-syntax/ + # @see https://www.w3.org/TR/REC-rdf-syntax/ # # @author [Gregg Kellogg](http://greggkellogg.net/) module N3 + require 'rdf/n3/algebra' require 'rdf/n3/format' require 'rdf/n3/vocab' + require 'rdf/n3/extensions' require 'rdf/n3/patches/array_hacks' - autoload :Meta, 'rdf/n3/reader/meta' - autoload :Parser, 'rdf/n3/reader/parser' - autoload :Reader, 'rdf/n3/reader' - autoload :VERSION, 'rdf/n3/version' - autoload :Writer, 'rdf/n3/writer' + autoload :Meta, 'rdf/n3/reader/meta' + autoload :Parser, 'rdf/n3/reader/parser' + autoload :Reader, 'rdf/n3/reader' + autoload :Reasoner, 'rdf/n3/reasoner' + autoload :VERSION, 'rdf/n3/version' + autoload :Writer, 'rdf/n3/writer' end end \ No newline at end of file diff --git a/lib/rdf/n3/algebra.rb b/lib/rdf/n3/algebra.rb new file mode 100644 index 0000000..8b4300c --- /dev/null +++ b/lib/rdf/n3/algebra.rb @@ -0,0 +1,125 @@ +$:.unshift(File.expand_path("../..", __FILE__)) +require 'sparql/algebra' +require 'sxp' + +module RDF::N3 + # Based on the SPARQL Algebra, operators for executing a patch + # + # @author [Gregg Kellogg](http://greggkellogg.net/) + module Algebra + autoload :Formula, 'rdf/n3/algebra/formula' + + module List + autoload :Append, 'rdf/n3/algebra/list/append' + autoload :In, 'rdf/n3/algebra/list/in' + autoload :Last, 'rdf/n3/algebra/list/last' + autoload :Member, 'rdf/n3/algebra/list/member' + end + + module Log + autoload :Conclusion, 'rdf/n3/algebra/log/conclusion' + autoload :Conjunction, 'rdf/n3/algebra/log/conjunction' + autoload :EqualTo, 'rdf/n3/algebra/log/equalTo' + autoload :Implies, 'rdf/n3/algebra/log/implies' + autoload :Includes, 'rdf/n3/algebra/log/includes' + autoload :NotEqualTo, 'rdf/n3/algebra/log/notEqualTo' + autoload :NotIncludes, 'rdf/n3/algebra/log/notIncludes' + autoload :OutputString, 'rdf/n3/algebra/log/outputString' + end + + module Math + autoload :AbsoluteValue, 'rdf/n3/algebra/math/absoluteValue' + autoload :Difference, 'rdf/n3/algebra/math/difference' + autoload :EqualTo, 'rdf/n3/algebra/math/equalTo' + autoload :Exponentiation, 'rdf/n3/algebra/math/exponentiation' + autoload :GreaterThan, 'rdf/n3/algebra/math/greaterThan' + autoload :IntegerQuotient, 'rdf/n3/algebra/math/integerQuotient' + autoload :LessThan, 'rdf/n3/algebra/math/lessThan' + autoload :MemberCount, 'rdf/n3/algebra/math/memberCount' + autoload :Negation, 'rdf/n3/algebra/math/negation' + autoload :NotEqualTo, 'rdf/n3/algebra/math/notEqualTo' + autoload :NotGreaterThan, 'rdf/n3/algebra/math/notGreaterThan' + autoload :NotLessThan, 'rdf/n3/algebra/math/notLessThan' + autoload :Product, 'rdf/n3/algebra/math/product' + autoload :Quotient, 'rdf/n3/algebra/math/quotient' + autoload :Remainder, 'rdf/n3/algebra/math/remainder' + autoload :Rounded, 'rdf/n3/algebra/math/rounded' + autoload :Sum, 'rdf/n3/algebra/math/sum' + end + + module Str + autoload :Concatenation, 'rdf/n3/algebra/str/concatenation' + autoload :Contains, 'rdf/n3/algebra/str/contains' + autoload :ContainsIgnoringCase, 'rdf/n3/algebra/str/containsIgnoringCase' + autoload :EndsWith, 'rdf/n3/algebra/str/endsWith' + autoload :EqualIgnoringCase, 'rdf/n3/algebra/str/equalIgnoringCase' + autoload :Format, 'rdf/n3/algebra/str/format' + autoload :GreaterThan, 'rdf/n3/algebra/str/greaterThan' + autoload :LessThan, 'rdf/n3/algebra/str/lessThan' + autoload :Matches, 'rdf/n3/algebra/str/matches' + autoload :NotEqualIgnoringCase, 'rdf/n3/algebra/str/notEqualIgnoringCase' + autoload :NotGreaterThan, 'rdf/n3/algebra/str/notGreaterThan' + autoload :NotLessThan, 'rdf/n3/algebra/str/notLessThan' + autoload :NotMatches, 'rdf/n3/algebra/str/notMatches' + autoload :Replace, 'rdf/n3/algebra/str/replace' + autoload :Scrape, 'rdf/n3/algebra/str/scrape' + autoload :StartsWith, 'rdf/n3/algebra/str/startsWith' + end + + def for(uri) + { + RDF::N3::List.append => List::Append, + RDF::N3::List.in => List::In, + RDF::N3::List.last => List::Last, + RDF::N3::List.member => List::Member, + + RDF::N3::Log.conclusion => Log::Conclusion, + RDF::N3::Log.conjunction => Log::Conjunction, + RDF::N3::Log.equalTo => Log::EqualTo, + RDF::N3::Log.implies => Log::Implies, + RDF::N3::Log.includes => Log::Includes, + RDF::N3::Log.notEqualTo => Log::NotEqualTo, + RDF::N3::Log.notIncludes => Log::NotIncludes, + RDF::N3::Log.outputString => Log::OutputString, + + RDF::N3::Math.absoluteValue => Math::AbsoluteValue, + RDF::N3::Math.difference => Math::Difference, + RDF::N3::Math.equalTo => Math::EqualTo, + RDF::N3::Math.exponentiation => Math::Exponentiation, + RDF::N3::Math.greaterThan => Math::GreaterThan, + RDF::N3::Math.integerQuotient => Math::IntegerQuotient, + RDF::N3::Math.lessThan => Math::LessThan, + RDF::N3::Math.memberCount => Math::MemberCount, + RDF::N3::Math.negation => Math::Negation, + RDF::N3::Math.notEqualTo => Math::NotEqualTo, + RDF::N3::Math.notGreaterThan => Math::NotGreaterThan, + RDF::N3::Math.notLessThan => Math::NotLessThan, + RDF::N3::Math.product => Math::Product, + RDF::N3::Math.quotient => Math::Quotient, + RDF::N3::Math.remainder => Math::Remainder, + RDF::N3::Math.rounded => Math::Rounded, + RDF::N3::Math.sum => Math::Sum, + + RDF::N3::Str.concatenation => Str::Concatenation, + RDF::N3::Str.contains => Str::Contains, + RDF::N3::Str.containsIgnoringCase => Str::ContainsIgnoringCase, + RDF::N3::Str.endsWith => Str::EndsWith, + RDF::N3::Str.equalIgnoringCase => Str::EqualIgnoringCase, + RDF::N3::Str.format => Str::Format, + RDF::N3::Str.greaterThan => Str::GreaterThan, + RDF::N3::Str.lessThan => Str::LessThan, + RDF::N3::Str.matches => Str::Matches, + RDF::N3::Str.notEqualIgnoringCase => Str::NotEqualIgnoringCase, + RDF::N3::Str.notGreaterThan => Str::NotGreaterThan, + RDF::N3::Str.notLessThan => Str::NotLessThan, + RDF::N3::Str.notMatches => Str::NotMatches, + RDF::N3::Str.replace => Str::Replace, + RDF::N3::Str.scrape => Str::Scrape, + RDF::N3::Str.startsWith => Str::StartsWith, + }[uri] + end + module_function :for + end +end + + diff --git a/lib/rdf/n3/algebra/formula.rb b/lib/rdf/n3/algebra/formula.rb new file mode 100644 index 0000000..03b44f1 --- /dev/null +++ b/lib/rdf/n3/algebra/formula.rb @@ -0,0 +1,185 @@ +require 'rdf' + +module RDF::N3::Algebra + # + # A Notation3 Formula combines a graph with a BGP query. + class Formula < SPARQL::Algebra::Operator + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + attr_accessor :query + + NAME = [:formula] + + ## + # Yields solutions from patterns and other operands. Solutions are created by evaluating each pattern and other sub-operand against `queryable`. + # + # When executing, blank nodes are turned into non-distinguished existential variables, noted with `$$`. These variables are removed from the returned solutions, as they can't be bound outside of the formula. + # + # @param [RDF::Queryable] queryable + # the graph or repository to query + # @param [Hash{Symbol => Object}] options + # 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_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}"} + + # 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 + # Reject solutions which include variables as values + @solutions = @solutions + .merge(options[: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) + 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?('$$')} + variable_names.empty? ? @solutions : @solutions.dup.project(*variable_names) + end + + ## + # Yields each statement from this formula bound to previously determined solutions. + # + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + def each(&block) + @solutions ||= begin + # 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}"} + + # 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)) + end + + # Yield patterns by binding variables + # FIXME: do we need to do something with non-bound non-distinguished extistential variables? + @solutions.each do |solution| + # Bind blank nodes to the solution when it doesn't contain a solution for an existential variable + existential_vars.each do |var| + solution[var.name] ||= RDF::Node.intern(var.name.to_s.sub(/^\$+/, '')) + end + + log_debug {"(formula apply) #{solution.to_sxp} to BGP"} + # Yield each variable statement which is constant after applying solution + patterns.each do |pattern| + terms = {} + [:subject, :predicate, :object].each do |r| + terms[r] = case o = pattern.send(r) + when RDF::Query::Variable then solution[o] + else o + end + end + + statement = RDF::Statement.from(terms) + + # Sanity checking on statement + if statement.variable? || + statement.predicate.literal? || + statement.subject.is_a?(SPARQL::Algebra::Operator) || + statement.object.is_a?(SPARQL::Algebra::Operator) + log_debug {"(formula skip) #{statement.to_sxp}"} + next + end + + log_debug {"(formula add) #{statement.to_sxp}"} + block.call(statement) + end + end + + # statements from sub-operands + log_depth {sub_ops.each {|op| op.each(&block)}} + end + + # Set solutions + # @param [RDF::Query::Solutions] solutions + def solutions=(solutions) + @solutions = solutions + end + + # Graph name associated with this formula + # @return [RDF::Resource] + def graph_name; @options[:graph_name]; end + + ## + # 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 + end + end + + RDF::Query::Pattern.from(terms) + else + RDF::Query::Pattern.from(pattern) + end + end + end + + ## + # Constants memoizer + def constants + # BNodes in statements are existential variables + @constants ||= statements.select(&:constant?) + end + + ## + # Patterns memoizer + def patterns + # BNodes in statements are existential variables + @patterns ||= statements.reject(&:constant?) + end + + ## + # Non-statement operands memoizer + def sub_ops + # operands that aren't statements, ordered by their graph_name + @sub_ops ||= operands.reject {|op| op.is_a?(RDF::Statement)} + end + + ## + # Existential vars in this formula + def existential_vars + @existentials ||= patterns.vars.select(&:existential?) + end + + def to_sxp_bin + [:formula, graph_name].compact + + operands.map(&:to_sxp_bin) + end + end +end diff --git a/lib/rdf/n3/algebra/list/append.rb b/lib/rdf/n3/algebra/list/append.rb new file mode 100644 index 0000000..d9be709 --- /dev/null +++ b/lib/rdf/n3/algebra/list/append.rb @@ -0,0 +1,13 @@ +module RDF::N3::Algebra::List + ## + # Iff the subject is a list of lists and the concatenation of all those lists is the object, then this is true. + # @example + # ( (1 2) (3 4) ) list:append (1 2 3 4). + # + # The object can be calculated as a function of the subject. + class Append < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :listAppend + end +end diff --git a/lib/rdf/n3/algebra/list/in.rb b/lib/rdf/n3/algebra/list/in.rb new file mode 100644 index 0000000..10aed00 --- /dev/null +++ b/lib/rdf/n3/algebra/list/in.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::List + ## + # Iff the object is a list and the subject is in that list, then this is true. + class In < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :listIn + end +end diff --git a/lib/rdf/n3/algebra/list/last.rb b/lib/rdf/n3/algebra/list/last.rb new file mode 100644 index 0000000..301f655 --- /dev/null +++ b/lib/rdf/n3/algebra/list/last.rb @@ -0,0 +1,11 @@ +module RDF::N3::Algebra::List + ## + # Iff the suject is a list and the obbject is the last thing that list, then this is true. + # + # The object can be calculated as a function of the list. + class Last < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :listLast + end +end diff --git a/lib/rdf/n3/algebra/list/member.rb b/lib/rdf/n3/algebra/list/member.rb new file mode 100644 index 0000000..9633743 --- /dev/null +++ b/lib/rdf/n3/algebra/list/member.rb @@ -0,0 +1,7 @@ +module RDF::N3::Algebra::List + ## + # Iff the subject is a list and the obbject is in that list, then this is true. + class Member < SPARQL::Algebra::Operator::Binary + NAME = :listMember + end +end diff --git a/lib/rdf/n3/algebra/log/conclusion.rb b/lib/rdf/n3/algebra/log/conclusion.rb new file mode 100644 index 0000000..569fe4e --- /dev/null +++ b/lib/rdf/n3/algebra/log/conclusion.rb @@ -0,0 +1,9 @@ +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 + NAME = :logConclusion + end +end diff --git a/lib/rdf/n3/algebra/log/conjunction.rb b/lib/rdf/n3/algebra/log/conjunction.rb new file mode 100644 index 0000000..55bc59c --- /dev/null +++ b/lib/rdf/n3/algebra/log/conjunction.rb @@ -0,0 +1,9 @@ +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 + NAME = :logConjunction + end +end diff --git a/lib/rdf/n3/algebra/log/equalTo.rb b/lib/rdf/n3/algebra/log/equalTo.rb new file mode 100644 index 0000000..e93a7e5 --- /dev/null +++ b/lib/rdf/n3/algebra/log/equalTo.rb @@ -0,0 +1,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 < SPARQL::Algebra::Operator::Binary + NAME = :logEqualTo + end +end diff --git a/lib/rdf/n3/algebra/log/implies.rb b/lib/rdf/n3/algebra/log/implies.rb new file mode 100644 index 0000000..4935f5c --- /dev/null +++ b/lib/rdf/n3/algebra/log/implies.rb @@ -0,0 +1,77 @@ +module RDF::N3::Algebra::Log + ## + # Logical implication. + # + # This is the relation between the antecedent (subject) and conclusion (object) of a rule. The application of a rule to a knowledge-base is as follows. For every substitution which, applied to the antecedent, gives a formula which is a subset of the knowledge-base, then the result of applying that same substitution to the conclusion may be added to the knowledge-base. + # + # related: See log:conclusion. + class Implies < SPARQL::Algebra::Operator::Binary + include SPARQL::Algebra::Query + include SPARQL::Algebra::Update + include RDF::Enumerable + include RDF::Util::Logger + + NAME = :logImplies + + # Yields solutions from subject. Solutions are created by evaluating subject against `queryable`. + # + # @param [RDF::Queryable] queryable + # the graph or repository to query + # @param [Hash{Symbol => Object}] options + # any additional keyword options + # @option options [RDF::Query::Solutions] solutions + # optional initial solutions for chained queries + # @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 {"(logImplies solutions) #{@solutions.to_sxp}"} + + # Return original solutions, without bindings + solutions + end + + ## + # Yields statements from the object based on solutions determined from the subject. Each solution formed by querying `queryable` from the subject is used to create a graph, which must be a subgraph of `queryable`. If so, that solution is used to generate triples from the object formula which are yielded. + # + # @yield [statement] + # each matching statement + # @yieldparam [RDF::Statement] solution + # @yieldreturn [void] ignored + def each(&block) + @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 + + # 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 + end + else + log_debug("(logImplies implication false)") + end + 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/algebra/log/includes.rb b/lib/rdf/n3/algebra/log/includes.rb new file mode 100644 index 0000000..ee3aa3d --- /dev/null +++ b/lib/rdf/n3/algebra/log/includes.rb @@ -0,0 +1,13 @@ +module RDF::N3::Algebra::Log + ## + # The subject formula includes the object formula. + # + # Formula A includes formula B if there exists some substitution which when applied to B creates a formula B' such that for every statement in B' is also in A, every variable universally (or existentially) quantified in B' is quantified in the same way in A. + # + # Variable substitution is applied recursively to nested compound terms such as formulae, lists and sets. + # + # (Understood natively by cwm when in in the antecedent of a rule. You can use this to peer inside nested formulae.) + class Includes < SPARQL::Algebra::Operator::Binary + NAME = :logIncludes + end +end diff --git a/lib/rdf/n3/algebra/log/notEqualTo.rb b/lib/rdf/n3/algebra/log/notEqualTo.rb new file mode 100644 index 0000000..8346b6c --- /dev/null +++ b/lib/rdf/n3/algebra/log/notEqualTo.rb @@ -0,0 +1,7 @@ +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 + NAME = :logNotEqualTo + end +end diff --git a/lib/rdf/n3/algebra/log/notIncludes.rb b/lib/rdf/n3/algebra/log/notIncludes.rb new file mode 100644 index 0000000..d73513d --- /dev/null +++ b/lib/rdf/n3/algebra/log/notIncludes.rb @@ -0,0 +1,12 @@ +module RDF::N3::Algebra::Log + ## + # The object formula is NOT a subset of subject. True iff log:includes is false. The converse of log:includes. + # (Understood natively by cwm. The subject formula may contain variables.) + # + # (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 + NAME = :logNotIncludes + end +end diff --git a/lib/rdf/n3/algebra/log/outputString.rb b/lib/rdf/n3/algebra/log/outputString.rb new file mode 100644 index 0000000..0b525e5 --- /dev/null +++ b/lib/rdf/n3/algebra/log/outputString.rb @@ -0,0 +1,7 @@ +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 + NAME = :logOutputString + end +end diff --git a/lib/rdf/n3/algebra/math/absoluteValue.rb b/lib/rdf/n3/algebra/math/absoluteValue.rb new file mode 100644 index 0000000..f7287fe --- /dev/null +++ b/lib/rdf/n3/algebra/math/absoluteValue.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the absolute value of the subject. + class AbsoluteValue < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathAbsoluteValue + end +end diff --git a/lib/rdf/n3/algebra/math/difference.rb b/lib/rdf/n3/algebra/math/difference.rb new file mode 100644 index 0000000..15f89c9 --- /dev/null +++ b/lib/rdf/n3/algebra/math/difference.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a pair of numbers. The object is calculated by subtracting the second number of the pair from the first. + class Difference < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathDifference + end +end diff --git a/lib/rdf/n3/algebra/math/equalTo.rb b/lib/rdf/n3/algebra/math/equalTo.rb new file mode 100644 index 0000000..0505047 --- /dev/null +++ b/lib/rdf/n3/algebra/math/equalTo.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is EQUAL TO a number of which the object is a string representation. + class EqualTo < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathEqualTo + end +end diff --git a/lib/rdf/n3/algebra/math/exponentiation.rb b/lib/rdf/n3/algebra/math/exponentiation.rb new file mode 100644 index 0000000..9eaf9bd --- /dev/null +++ b/lib/rdf/n3/algebra/math/exponentiation.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a pair of numbers. The object is calculated by raising the first number of the power of the second. + class Exponentiation < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathExponentiation + end +end diff --git a/lib/rdf/n3/algebra/math/greaterThan.rb b/lib/rdf/n3/algebra/math/greaterThan.rb new file mode 100644 index 0000000..890675a --- /dev/null +++ b/lib/rdf/n3/algebra/math/greaterThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is greater than the number of which the object is a string representation. + class GreaterThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathGreaterThan + end +end diff --git a/lib/rdf/n3/algebra/math/integerQuotient.rb b/lib/rdf/n3/algebra/math/integerQuotient.rb new file mode 100644 index 0000000..5d1287b --- /dev/null +++ b/lib/rdf/n3/algebra/math/integerQuotient.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a pair of integer numbers. The object is calculated by dividing the first number of the pair by the second, ignoring remainder. + class IntegerQuotient < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathIntegerQuotient + end +end diff --git a/lib/rdf/n3/algebra/math/lessThan.rb b/lib/rdf/n3/algebra/math/lessThan.rb new file mode 100644 index 0000000..c37aac0 --- /dev/null +++ b/lib/rdf/n3/algebra/math/lessThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is LESS than a number of which the object is a string representation. + class LessThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathLessThan + end +end diff --git a/lib/rdf/n3/algebra/math/memberCount.rb b/lib/rdf/n3/algebra/math/memberCount.rb new file mode 100644 index 0000000..8e26fae --- /dev/null +++ b/lib/rdf/n3/algebra/math/memberCount.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The number of items in a list. The subject is a list, the object is calculated as the number of members. + class MemberCount < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathMemberCount + end +end diff --git a/lib/rdf/n3/algebra/math/negation.rb b/lib/rdf/n3/algebra/math/negation.rb new file mode 100644 index 0000000..9538f42 --- /dev/null +++ b/lib/rdf/n3/algebra/math/negation.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject or object is calculated to be the negation of the other. + class Negation < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathNegation + end +end diff --git a/lib/rdf/n3/algebra/math/notEqualTo.rb b/lib/rdf/n3/algebra/math/notEqualTo.rb new file mode 100644 index 0000000..e97c3f3 --- /dev/null +++ b/lib/rdf/n3/algebra/math/notEqualTo.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is NOT EQUAL to a number of which the object is a string representation. + class NotEqualTo < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathNotEqualTo + end +end diff --git a/lib/rdf/n3/algebra/math/notGreaterThan.rb b/lib/rdf/n3/algebra/math/notGreaterThan.rb new file mode 100644 index 0000000..fa46ec1 --- /dev/null +++ b/lib/rdf/n3/algebra/math/notGreaterThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is NOT greater than the number of which the object is a string representation. + class NotGreaterThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathNotGreaterThan + end +end diff --git a/lib/rdf/n3/algebra/math/notLessThan.rb b/lib/rdf/n3/algebra/math/notLessThan.rb new file mode 100644 index 0000000..8bd3774 --- /dev/null +++ b/lib/rdf/n3/algebra/math/notLessThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # True iff the subject is a string representation of a number which is NOT LESS than a number of which the object is a string representation. + class NotLessThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathNotLessThan + end +end diff --git a/lib/rdf/n3/algebra/math/product.rb b/lib/rdf/n3/algebra/math/product.rb new file mode 100644 index 0000000..6634b29 --- /dev/null +++ b/lib/rdf/n3/algebra/math/product.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a list of numbers. The object is calculated as the arithmentic product of those numbers. + class Product < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathProduct + end +end diff --git a/lib/rdf/n3/algebra/math/quotient.rb b/lib/rdf/n3/algebra/math/quotient.rb new file mode 100644 index 0000000..5050c7e --- /dev/null +++ b/lib/rdf/n3/algebra/math/quotient.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a pair of numbers. The object is calculated by dividing the first number of the pair by the second. + class Quotient < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathQuotient + end +end diff --git a/lib/rdf/n3/algebra/math/remainder.rb b/lib/rdf/n3/algebra/math/remainder.rb new file mode 100644 index 0000000..4d26d01 --- /dev/null +++ b/lib/rdf/n3/algebra/math/remainder.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a pair of integers. The object is calculated by dividing the first number of the pair by the second and taking the remainder. + class Remainder < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathRemainder + end +end diff --git a/lib/rdf/n3/algebra/math/rounded.rb b/lib/rdf/n3/algebra/math/rounded.rb new file mode 100644 index 0000000..1c9ab50 --- /dev/null +++ b/lib/rdf/n3/algebra/math/rounded.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The object is calulated as the subject rounded to the nearest integer. + class Rounded < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathRounded + end +end diff --git a/lib/rdf/n3/algebra/math/sum.rb b/lib/rdf/n3/algebra/math/sum.rb new file mode 100644 index 0000000..f8b0fcb --- /dev/null +++ b/lib/rdf/n3/algebra/math/sum.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Math + ## + # The subject is a list of numbers. The object is calculated as the arithmentic sum of those numbers. + class Sum < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :mathSum + end +end diff --git a/lib/rdf/n3/algebra/str/concatenation.rb b/lib/rdf/n3/algebra/str/concatenation.rb new file mode 100644 index 0000000..ac8b500 --- /dev/null +++ b/lib/rdf/n3/algebra/str/concatenation.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # The subject is a list of strings. The object is calculated as a concatenation of those strings. + class Concatenation < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strConcatenation + end +end diff --git a/lib/rdf/n3/algebra/str/contains.rb b/lib/rdf/n3/algebra/str/contains.rb new file mode 100644 index 0000000..6f38c1e --- /dev/null +++ b/lib/rdf/n3/algebra/str/contains.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the subject string contains the object string. + class Contains < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strContains + end +end diff --git a/lib/rdf/n3/algebra/str/containsIgnoringCase.rb b/lib/rdf/n3/algebra/str/containsIgnoringCase.rb new file mode 100644 index 0000000..01c00ce --- /dev/null +++ b/lib/rdf/n3/algebra/str/containsIgnoringCase.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the subject string contains the object string, with the comparison done ignoring the difference between upper case and lower case characters. + class ContainsIgnoringCase < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strContainsIgnoringCase + end +end diff --git a/lib/rdf/n3/algebra/str/endsWith.rb b/lib/rdf/n3/algebra/str/endsWith.rb new file mode 100644 index 0000000..f7ec877 --- /dev/null +++ b/lib/rdf/n3/algebra/str/endsWith.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the subject string ends with the object string. + class EndsWith < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strEndsWith + end +end diff --git a/lib/rdf/n3/algebra/str/equalIgnoringCase.rb b/lib/rdf/n3/algebra/str/equalIgnoringCase.rb new file mode 100644 index 0000000..fe51875 --- /dev/null +++ b/lib/rdf/n3/algebra/str/equalIgnoringCase.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the subject string is the same as object string ignoring differences between upper and lower case. + class EqualIgnoringCase < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strEqualIgnoringCase + end +end diff --git a/lib/rdf/n3/algebra/str/format.rb b/lib/rdf/n3/algebra/str/format.rb new file mode 100644 index 0000000..61724bf --- /dev/null +++ b/lib/rdf/n3/algebra/str/format.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # The subject is a list, whose first member is a format string, and whose remaining members are arguments to the format string. The formating string is in the style of python's % operator, very similar to C's sprintf(). The object is calculated from the subject. + class Format < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strFormat + end +end diff --git a/lib/rdf/n3/algebra/str/greaterThan.rb b/lib/rdf/n3/algebra/str/greaterThan.rb new file mode 100644 index 0000000..488606a --- /dev/null +++ b/lib/rdf/n3/algebra/str/greaterThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the string is greater than the object when ordered according to Unicode(tm) code order + class GreaterThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strGreaterThan + end +end diff --git a/lib/rdf/n3/algebra/str/lessThan.rb b/lib/rdf/n3/algebra/str/lessThan.rb new file mode 100644 index 0000000..fabbb90 --- /dev/null +++ b/lib/rdf/n3/algebra/str/lessThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the string is less than the object when ordered according to Unicode(tm) code order. + class LessThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strLessThan + end +end diff --git a/lib/rdf/n3/algebra/str/matches.rb b/lib/rdf/n3/algebra/str/matches.rb new file mode 100644 index 0000000..cc5b6e7 --- /dev/null +++ b/lib/rdf/n3/algebra/str/matches.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # The subject is a string; the object is is a regular expression in the perl, python style. It is true iff the string matches the regexp. + class Matches < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strMatches + end +end diff --git a/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb b/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb new file mode 100644 index 0000000..7795f6c --- /dev/null +++ b/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the subject string is the NOT same as object string ignoring differences between upper and lower case. + class NotEqualIgnoringCase < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strNotEqualIgnoringCase + end +end diff --git a/lib/rdf/n3/algebra/str/notGreaterThan.rb b/lib/rdf/n3/algebra/str/notGreaterThan.rb new file mode 100644 index 0000000..fba338c --- /dev/null +++ b/lib/rdf/n3/algebra/str/notGreaterThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the string is NOT greater than the object when ordered according to Unicode(tm) code order. + class NotGreaterThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strNotGreaterThan + end +end diff --git a/lib/rdf/n3/algebra/str/notLessThan.rb b/lib/rdf/n3/algebra/str/notLessThan.rb new file mode 100644 index 0000000..81ca196 --- /dev/null +++ b/lib/rdf/n3/algebra/str/notLessThan.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # True iff the string is NOT less than the object when ordered according to Unicode(tm) code order. + class NotLessThan < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strNotLessThan + end +end diff --git a/lib/rdf/n3/algebra/str/notMatches.rb b/lib/rdf/n3/algebra/str/notMatches.rb new file mode 100644 index 0000000..64ca466 --- /dev/null +++ b/lib/rdf/n3/algebra/str/notMatches.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # The subject string; the object is is a regular expression in the perl, python style. It is true iff the string does NOT match the regexp. + class NotMatches < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strNotMatches + end +end diff --git a/lib/rdf/n3/algebra/str/replace.rb b/lib/rdf/n3/algebra/str/replace.rb new file mode 100644 index 0000000..bc2ec47 --- /dev/null +++ b/lib/rdf/n3/algebra/str/replace.rb @@ -0,0 +1,12 @@ +module RDF::N3::Algebra::Str + ## + # A built-in for replacing characters or sub. takes a list of 3 strings; the first is the input data, the second the old and the third the new string. The object is calculated as the rplaced string. + # + # @example + # ("fofof bar", "of", "baz") string:replace "fbazbaz bar" + class Replace < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strReplace + end +end diff --git a/lib/rdf/n3/algebra/str/scrape.rb b/lib/rdf/n3/algebra/str/scrape.rb new file mode 100644 index 0000000..a941651 --- /dev/null +++ b/lib/rdf/n3/algebra/str/scrape.rb @@ -0,0 +1,9 @@ +module RDF::N3::Algebra::Str + ## + # The subject is a list of two strings. The second string is a regular expression in the perl, python style. It must contain one group (a part in parentheses). If the first string in the list matches the regular expression, then the object is calculated as being thepart of the first string which matches the group. + class Scrape < SPARQL::Algebra::Operator::Binary + include RDF::Util::Logger + + NAME = :strScrape + end +end diff --git a/lib/rdf/n3/algebra/str/startsWith.rb b/lib/rdf/n3/algebra/str/startsWith.rb new file mode 100644 index 0000000..cda654c --- /dev/null +++ b/lib/rdf/n3/algebra/str/startsWith.rb @@ -0,0 +1,56 @@ +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 new file mode 100644 index 0000000..ea366c4 --- /dev/null +++ b/lib/rdf/n3/extensions.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true +require 'rdf' + +# Monkey-patch RDF::Enumerable to add `:existentials` and `:univerals` accessors +module RDF + module Enumerable + # Existential quantifiers defined on this enumerable + # @return [Array] + attr_accessor :existentials + + # Universal quantifiers defined on this enumerable + # @return [Array] + attr_accessor :universals + + ## + # An enumerable contains another enumerable if every statement in other is a statement in self + # + # @param [RDF::Enumerable] other + # @return [Boolean] + def contain?(other) + other.all? {|statement| has_statement?(statement)} + end + end + + class Statement + # Override variable? + def variable? + to_a.any? {|term| !term.is_a?(RDF::Term) || term.variable?} || graph_name && graph_name.variable? + end + + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + [(variable? ? :pattern : :triple), subject, predicate, object, graph_name].compact + end + + ## + # Returns an S-Expression (SXP) representation + # + # @return [String] + def to_sxp + to_sxp_bin.to_sxp + end + end + + class Query::Solution + + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + [:solution] + bindings.map {|k, v| Query::Variable.new(k, v).to_sxp_bin} + end + + ## + # Returns an S-Expression (SXP) representation + # + # @return [String] + def to_sxp + to_sxp_bin.to_sxp + end + end + + class Query::Variable + + # Transform Statement into an SXP + # @return [Array] + def to_sxp_bin + [:var, name, (value.to_sxp_bin if value)].compact + end + + ## + # Returns an S-Expression (SXP) representation + # + # @return [String] + def to_sxp + to_sxp_bin.to_sxp + end + end +end diff --git a/lib/rdf/n3/format.rb b/lib/rdf/n3/format.rb index b62183c..2d1ec6f 100644 --- a/lib/rdf/n3/format.rb +++ b/lib/rdf/n3/format.rb @@ -15,7 +15,7 @@ module RDF::N3 # @example Obtaining serialization format file extension mappings # RDF::Format.file_extensions #=> {n3: "text/n3"} # - # @see http://www.w3.org/TR/rdf-testcases/#ntriples + # @see https://www.w3.org/TR/rdf-testcases/#ntriples class Format < RDF::Format content_type 'text/n3', extension: :n3, aliases: %w(text/rdf+n3;q=0.2 application/rdf+n3;q=0.2) content_encoding 'utf-8' diff --git a/lib/rdf/n3/reader.rb b/lib/rdf/n3/reader.rb index 1a3c2fd..6ee65f9 100644 --- a/lib/rdf/n3/reader.rb +++ b/lib/rdf/n3/reader.rb @@ -9,6 +9,8 @@ module RDF::N3 # # Separate pass to create branch_table from n3-selectors.n3 # + # This implementation uses distinguished variables for both universal and explicit existential variables (defined with `@forSome`). Variables created from blank nodes are non-distinguished. Distinguished existential variables are tracked using `$`, internally, as the RDF `query_pattern` logic looses details of the variable definition in solutions, where the variable is represented using a symbol. + # # @todo # * Formulae as RDF::Query representations # * Formula expansion similar to SPARQL Construct @@ -20,9 +22,13 @@ class Reader < RDF::Reader include RDF::Util::Logger include Meta include Parser - + N3_KEYWORDS = %w(a is of has keywords prefix base true false forSome forAny) + # The Blank nodes allocated for formula + # @return [Array] + attr_reader :formulae + ## # Initializes the N3 reader instance. # @@ -50,27 +56,40 @@ def initialize(input = $stdin, options = {}, &block) @input = input.respond_to?(:read) ? input : StringIO.new(input.to_s) @lineno = 0 readline # Prime the pump - + @memo = {} @keyword_mode = false - @keywords = %w(a is of this has) + @keywords = %w(a is of this has).map(&:freeze).freeze @productions = [] @prod_data = [] @branches = BRANCHES # Get from meta class @regexps = REGEXPS # Get from meta class - @formulae = [] # Nodes used as Formluae graph names + @formulae = [] # Nodes used as Formulae graph names @formulae_nodes = {} - @variables = {} # variable definitions along with defining formula + @label_uniquifier ||= "#{Random.new_seed}_000000" + @bnodes = {} # allocated bnodes by formula + @variables = {} # allocated variables by formula if options[:base_uri] - log_debug("@uri") { base_uri.inspect} + log_info("@uri") { base_uri.inspect} namespace(nil, uri("#{base_uri}#")) end - log_debug("validate") {validate?.inspect} - log_debug("canonicalize") {canonicalize?.inspect} - log_debug("intern") {intern?.inspect} + + # Prepopulate operator namespaces unless validating + unless validate? + namespace(:crypto, RDF::N3::Crypto) + namespace(:list, RDF::N3::List) + namespace(:log, RDF::N3::Log) + namespace(:math, RDF::N3::Math) + namespace(:rei, RDF::N3::Rei) + #namespace(:string, RDF::N3::String) + namespace(:time, RDF::N3::Time) + end + log_info("validate") {validate?.inspect} + log_info("canonicalize") {canonicalize?.inspect} + log_info("intern") {intern?.inspect} if block_given? case block.arity @@ -87,7 +106,6 @@ def inspect ## # Iterates the given block for each RDF statement in the input. - # # @yield [statement] # @yieldparam [RDF::Statement] statement # @return [void] @@ -101,9 +119,9 @@ def each_statement(&block) raise RDF::ReaderError, "Errors found during processing" end end - enum_for(:each_triple) + enum_for(:each_statement) end - + ## # Iterates the given block for each RDF triple in the input. # @@ -112,29 +130,30 @@ def each_statement(&block) # @yieldparam [RDF::URI] predicate # @yieldparam [RDF::Value] object # @return [void] - def each_triple(&block) + def each_triple if block_given? each_statement do |statement| - block.call(*statement.to_triple) + yield(*statement.to_triple) end end enum_for(:each_triple) end - + protected # Start of production def onStart(prod) handler = "#{prod}Start".to_sym - log_debug("#{handler}(#{respond_to?(handler, true)})", prod) + log_info("#{handler}(#{respond_to?(handler, true)})", prod, depth: depth) @productions << prod send(handler, prod) if respond_to?(handler, true) + end # End of production def onFinish prod = @productions.pop() handler = "#{prod}Finish".to_sym - log_debug("#{handler}(#{respond_to?(handler, true)})") {"#{prod}: #{@prod_data.last.inspect}"} + log_info("#{handler}(#{respond_to?(handler, true)})", depth: depth) {"#{prod}: #{@prod_data.last.inspect}"} send(handler) if respond_to?(handler, true) end @@ -143,22 +162,22 @@ def onToken(prod, tok) unless @productions.empty? parentProd = @productions.last handler = "#{parentProd}Token".to_sym - log_debug("#{handler}(#{respond_to?(handler, true)})") {"#{prod}, #{tok}: #{@prod_data.last.inspect}"} + log_info("#{handler}(#{respond_to?(handler, true)})", depth: depth) {"#{prod}, #{tok}: #{@prod_data.last.inspect}"} send(handler, prod, tok) if respond_to?(handler, true) else error("Token has no parent production") end end - + def booleanToken(prod, tok) lit = RDF::Literal.new(tok.delete("@"), datatype: RDF::XSD.boolean, validate: validate?, canonicalize: canonicalize?) add_prod_data(:literal, lit) end - + def declarationStart(prod) @prod_data << {} end - + def declarationToken(prod, tok) case prod when "@prefix", "@base", "@keywords" @@ -182,17 +201,17 @@ def declarationFinish # Base, set or update document URI uri = decl[:explicituri] options[:base_uri] = process_uri(uri) - + # The empty prefix "" is by default , bound to "#" -- the local namespace of the file. # The parser behaves as though there were a # @prefix : <#>. # just before the file. # This means that <#foo> can be written :foo and using @keywords one can reduce that to foo. - + namespace(nil, uri.match(/[\/\#]$/) ? base_uri : process_uri("#{uri}#")) - log_debug("declarationFinish[@base]") {"@base=#{base_uri}"} + log_debug("declarationFinish[@base]", depth: depth) {"@base=#{base_uri}"} when "@keywords" - log_debug("declarationFinish[@keywords]") {@keywords.inspect} + log_debug("declarationFinish[@keywords]", depth: depth) {@keywords.inspect} # Keywords are handled in tokenizer and maintained in @keywords array if (@keywords & N3_KEYWORDS) != @keywords error("Undefined keywords used: #{(@keywords - N3_KEYWORDS).to_sentence}") if validate? @@ -202,17 +221,17 @@ def declarationFinish error("declarationFinish: FIXME #{decl.inspect}") end end - + # Document start, instantiate def documentStart(prod) @formulae.push(nil) @prod_data << {} end - + def dtlangToken(prod, tok) add_prod_data(:langcode, tok) if prod == "langcode" end - + def existentialStart(prod) @prod_data << {} end @@ -227,22 +246,23 @@ def existentialFinish pd = @prod_data.pop forSome = Array(pd[:symbol]) forSome.each do |term| - @variables[term.to_s] = {formula: @formulae.last, var: RDF::Node.new(term.to_s.split(/[\/#]/).last)} + var = univar(term, existential: true) + add_var_to_formula(@formulae.last, term, var) end end - + def expressionStart(prod) @prod_data << {} end - + # Process path items, and push on the last object for parent processing def expressionFinish expression = @prod_data.pop - + # If we're in teh middle of a pathtail, append if @prod_data.last[:pathtail] && expression[:pathitem] && expression[:pathtail] path_list = [expression[:pathitem]] + expression[:pathtail] - log_debug("expressionFinish(pathtail)") {"set pathtail to #{path_list.inspect}"} + log_debug("expressionFinish(pathtail)", depth: depth) {"set pathtail to #{path_list.inspect}"} @prod_data.last[:pathtail] = path_list dir_list = [expression[:direction]] if expression[:direction] @@ -256,31 +276,31 @@ def expressionFinish error("expressionFinish: FIXME #{expression.inspect}") end end - + def literalStart(prod) @prod_data << {} end - + def literalToken(prod, tok) tok = tok[0, 3] == '"""' ? tok[3..-4] : tok[1..-2] add_prod_data(:string, tok) end - + def literalFinish lit = @prod_data.pop content = RDF::NTriples.unescape(lit[:string]) language = lit[:langcode] if lit[:langcode] language = language.downcase if language && canonicalize? datatype = lit[:symbol] - + lit = RDF::Literal.new(content, language: language, datatype: datatype, validate: validate?, canonicalize: canonicalize?) add_prod_data(:literal, lit) end - + def objectStart(prod) @prod_data << {} end - + def objectFinish object = @prod_data.pop if object[:expression] @@ -289,11 +309,11 @@ def objectFinish error("objectFinish: FIXME #{object.inspect}") end end - + def pathitemStart(prod) @prod_data << {} end - + def pathitemToken(prod, tok) case prod when "numericliteral" @@ -303,15 +323,19 @@ def pathitemToken(prod, tok) when /\./ then RDF::XSD.decimal else RDF::XSD.integer end - + lit = RDF::Literal.new(nl, datatype: datatype, validate: validate?, canonicalize: canonicalize?) add_prod_data(:literal, lit) when "quickvariable" # There is a also a shorthand syntax ?x which is the same as :x except that it implies that x is # universally quantified not in the formula but in its parent formula uri = process_qname(tok.sub('?', ':')) - @variables[uri.to_s] = { formula: @formulae[-2], var: univar(uri) } - add_prod_data(:symbol, 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) + + 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) @@ -324,13 +348,18 @@ def pathitemToken(prod, tok) add_prod_data(:symbol, symbol) when "{" # A new formula, push on a node as a named graph - node = RDF::Node.new + node = RDF::Node.new(".form_#{unique_label}") @formulae << node @formulae_nodes[node] = true + + # Promote variables defined on the earlier formula to this formula + @variables[node] = {} + @variables[@formulae[-2]].each do |name, var| + @variables[node][name] = var + end when "}" - # Pop off the formula, and remove any variables defined in this graph + # Pop off the formula formula = @formulae.pop - @variables.delete_if {|k, v| v[:formula] == formula} add_prod_data(:symbol, formula) else error("pathitemToken(#{prod}, #{tok}): FIXME") @@ -349,11 +378,11 @@ def pathitemFinish error("pathitemFinish: FIXME #{pathitem.inspect}") end end - + def pathlistStart(prod) @prod_data << {pathlist: []} end - + def pathlistFinish pathlist = @prod_data.pop # Flatten propertylist into an array @@ -361,11 +390,11 @@ def pathlistFinish add_prod_data(:pathlist, expr) if expr add_prod_data(:pathlist, pathlist[:pathlist]) if pathlist[:pathlist] end - + def pathtailStart(prod) @prod_data << {pathtail: []} end - + def pathtailToken(prod, tok) case tok when "!", "." @@ -374,46 +403,46 @@ def pathtailToken(prod, tok) add_prod_data(:direction, :reverse) end end - + def pathtailFinish pathtail = @prod_data.pop add_prod_data(:pathtail, pathtail[:pathtail]) add_prod_data(:direction, pathtail[:direction]) if pathtail[:direction] add_prod_data(:directiontail, pathtail[:directiontail]) if pathtail[:directiontail] end - + def propertylistStart(prod) @prod_data << {} end - + def propertylistFinish propertylist = @prod_data.pop # Flatten propertylist into an array ary = [propertylist, propertylist.delete(:propertylist)].flatten.compact @prod_data.last[:propertylist] = ary end - + def simpleStatementStart(prod) @prod_data << {} end - + # Completion of Simple Statement, all productions include :subject, and :propertyList def simpleStatementFinish statement = @prod_data.pop - + subject = statement[:subject] properties = Array(statement[:propertylist]) properties.each do |p| predicate = p[:verb] next unless predicate - log_debug("simpleStatementFinish(pred)") {predicate.to_s} + log_debug("simpleStatementFinish(pred)", depth: depth) {predicate.to_s} error(%(Illegal statment: "#{predicate}" missing object)) unless p.has_key?(:object) objects = Array(p[:object]) objects.each do |object| if p[:invert] - add_triple("simpleStatementFinish", object, predicate, subject) + add_statement("simpleStatementFinish", object, predicate, subject) else - add_triple("simpleStatementFinish", subject, predicate, object) + add_statement("simpleStatementFinish", subject, predicate, object) end end end @@ -422,17 +451,17 @@ def simpleStatementFinish def subjectStart(prod) @prod_data << {} end - + def subjectFinish subject = @prod_data.pop - + if subject[:expression] add_prod_data(:subject, subject[:expression]) else error("unknown expression type") end end - + def symbolToken(prod, tok) term = case prod when 'explicituri' @@ -442,7 +471,7 @@ def symbolToken(prod, tok) else error("symbolToken(#{prod}, #{tok}): FIXME #{term.inspect}") end - + add_prod_data(:symbol, term) end @@ -460,21 +489,21 @@ def universalFinish pd = @prod_data.pop forAll = Array(pd[:symbol]) forAll.each do |term| - @variables[term.to_s] = { formula: @formulae.last, var: univar(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::LOG.implies) + add_prod_data(:expression, RDF::N3::Log.implies) add_prod_data(:invert, true) when '=>' - add_prod_data(:expression, RDF::LOG.implies) + add_prod_data(:expression, RDF::N3::Log.implies) when '=' add_prod_data(:expression, RDF::OWL.sameAs) when '@a' @@ -486,7 +515,7 @@ def verbToken(prod, tok) else error("verbToken(#{prod}, #{tok}): FIXME #{term.inspect}") end - + add_prod_data(:symbol, term) end @@ -501,32 +530,39 @@ def verbFinish error("verbFinish: FIXME #{verb.inspect}") end end - + private - + ################### # Utility Functions ################### def process_anonnode(anonnode) - log_debug("process_anonnode") {anonnode.inspect} - + log_debug("process_anonnode", depth: depth) {anonnode.inspect} + if anonnode[:propertylist] properties = anonnode[:propertylist] - bnode = RDF::Node.new + bnode = bnode() properties.each do |p| predicate = p[:verb] - log_debug("process_anonnode(verb)") {predicate.inspect} + log_debug("process_anonnode(verb)", depth: depth) {predicate.inspect} objects = Array(p[:object]) - objects.each { |object| add_triple("anonnode", bnode, predicate, object) } + 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_triple("anonnode(list)", statement.subject, statement.predicate, statement.object) + add_statement("anonnode(list)", statement.subject, statement.predicate, statement.object) end list.subject end @@ -539,20 +575,20 @@ def process_anonnode(anonnode) # # Create triple and return property used for next iteration def process_path(expression) - log_debug("process_path") {expression.inspect} + log_debug("process_path", depth: depth) {expression.inspect} pathitem = expression[:pathitem] pathtail = expression[:pathtail] - + direction_list = [expression[:direction], expression[:directiontail]].flatten.compact pathtail.each do |pred| direction = direction_list.shift bnode = RDF::Node.new if direction == :reverse - add_triple("process_path(reverse)", bnode, pred, pathitem) + add_statement("process_path(reverse)", bnode, pred, pathitem) else - add_triple("process_path(forward)", pathitem, pred, bnode) + add_statement("process_path(forward)", pathitem, pred, bnode) end pathitem = bnode end @@ -562,7 +598,7 @@ def process_path(expression) def process_uri(uri) uri(base_uri, RDF::NTriples.unescape(uri)) end - + def process_qname(tok) if tok.include?(":") prefix, name = tok.split(":") @@ -579,24 +615,26 @@ def process_qname(tok) # old parser encountering true or false naked or in a @keywords return RDF::Literal.new(tok, datatype: RDF::XSD.boolean) else - error("Set user @keywords to use barenames.") + error("Set user @keywords to use barenames (#{tok}).") end uri = if prefix(prefix) - log_debug('process_qname(ns)') {"#{prefix(prefix)}, #{name}"} + log_debug('process_qname(ns)', depth: depth) {"#{prefix(prefix)}, #{name}"} ns(prefix, name) elsif prefix == '_' - log_debug('process_qname(bnode)', name) + 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) + log_debug('process_qname(default_ns)', name, depth: depth) namespace(nil, uri("#{base_uri}#")) unless prefix(nil) ns(nil, name) end - log_debug('process_qname') {uri.inspect} + log_debug('process_qname', depth: depth) {uri.inspect} uri end - + # Add values to production data, values aranged as an array def add_prod_data(sym, value) case @prod_data.last[sym] @@ -609,33 +647,38 @@ def add_prod_data(sym, value) end end - # Keep track of allocated BNodes - def bnode(value = nil) - @bnode_cache ||= {} - @bnode_cache[value.to_s] ||= RDF::Node.new(value) + # Keep track of allocated BNodes. Blank nodes are allocated to the formula. + def bnode(label = nil) + if label + value = "#{label}_#{unique_label}" + (@bnodes[@formulae.last] ||= {})[label.to_s] ||= RDF::Node.new(value) + else + RDF::Node.new + end end - def univar(label) - unless label - @unnamed_label ||= "var0" - label = @unnamed_label = @unnamed_label.succ - end - RDF::Query::Variable.new(label.to_s) + 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) end - # add a statement, object can be literal or URI or bnode + # add a pattern or statement # # @param [any] node string for showing graph_name - # @param [URI, Node] subject the subject of the statement - # @param [URI] predicate the predicate of the statement - # @param [URI, Node, Literal] object the object of the statement + # @param [RDF::Term] subject the subject of the statement + # @param [RDF::URI] predicate the predicate of the statement + # @param [RDF::Term] object the object of the statement # @return [Statement] Added statement # @raise [RDF::ReaderError] Checks parameter types and raises if they are incorrect if parsing mode is _validate_. - def add_triple(node, subject, predicate, object) - graph_name_opts = @formulae.last ? {graph_name: @formulae.last} : {} - - statement = RDF::Statement(subject, predicate, object, graph_name_opts) - log_debug(node) {statement.to_s} + def add_statement(node, subject, predicate, object) + statement = if @formulae.last + # It's a pattern in a formula + RDF::Query::Pattern.new(subject, predicate, object, graph_name: @formulae.last) + else + RDF::Statement(subject, predicate, object) + end + log_debug("statement(#{node})", depth: depth) {statement.to_s} @callback.call(statement) end @@ -644,7 +687,7 @@ def namespace(prefix, uri) if uri == '#' uri = prefix(nil).to_s + '#' end - log_debug("namespace") {"'#{prefix}' <#{uri}>"} + log_debug("namespace", depth: depth) {"'#{prefix}' <#{uri}>"} prefix(prefix, uri(uri)) end @@ -654,7 +697,7 @@ def keyword_check(kw) raise RDF::ReaderError, "unqualified keyword '#{kw}' used without @keyword directive" if validate? end end - + # Create URIs def uri(value, append = nil) value = RDF::URI(value) @@ -662,20 +705,42 @@ def uri(value, append = nil) value.validate! if validate? && value.respond_to?(:validate) value.canonicalize! if canonicalize? value = RDF::URI.intern(value, {}) if intern? - - # Variable substitution for in-scope variables. Variables are in scope if they are defined in anthing other than - # the current formula - var = @variables[value.to_s] - value = var[:var] if var + + # Variable substitution for in-scope variables. Variables are in scope if they are defined in anthing other than the current formula + var = find_var(@formulae.last, value) + value = var if var value end - + def ns(prefix, suffix) base = prefix(prefix).to_s suffix = suffix.to_s.sub(/^\#/, "") if base.index("#") - log_debug("ns") {"base: '#{base}', suffix: '#{suffix}'"} + log_debug("ns", depth: depth) {"base: '#{base}', suffix: '#{suffix}'"} uri(base + suffix.to_s) end + + # Returns a unique label + def unique_label + label, @label_uniquifier = @label_uniquifier, @label_uniquifier.succ + label + end + + # Find any variable that may be defined in the formula identified by `bn` + # @param [RDF::Node] bn name of formula + # @param [#to_s] name + # @return [RDF::Query::Variable] + def find_var(sym, name) + (@variables[sym] ||= {})[name.to_s] + end + + # Add a variable to the formula identified by `bn`, returning the variable. Useful as an LRU for variable name lookups + # @param [RDF::Node] bn name of formula + # @param [#to_s] name of variable for lookup + # @param [RDF::Query::Variable] var + # @return [RDF::Query::Variable] + def add_var_to_formula(bn, name, var) + (@variables[bn] ||= {})[name.to_s] = var + end end end diff --git a/lib/rdf/n3/reader/n3.n3 b/lib/rdf/n3/reader/n3.n3 index 14b5811..ff013b5 100644 --- a/lib/rdf/n3/reader/n3.n3 +++ b/lib/rdf/n3/reader/n3.n3 @@ -235,7 +235,7 @@ quickvariable cfg:matches "\\?[A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff # Whitespace is not allowed # was: "[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9]+)?"; -langcode cfg:matches "[a-z]+(-[a-z0-9]+)*"; # http://www.w3.org/TR/rdf-testcases/#language +langcode cfg:matches "[a-z]+(-[a-z0-9]+)*"; # https://www.w3.org/TR/rdf-testcases/#language cfg:canStartWith "a". diff --git a/lib/rdf/n3/reader/parser.rb b/lib/rdf/n3/reader/parser.rb index 14fc805..903ea32 100644 --- a/lib/rdf/n3/reader/parser.rb +++ b/lib/rdf/n3/reader/parser.rb @@ -8,11 +8,11 @@ module Parser SINGLE_CHARACTER_SELECTORS = %{\t\r\n !\"#$\%&'()*.,+/;<=>?[\\]^`{|}~} NOT_QNAME_CHARS = SINGLE_CHARACTER_SELECTORS + "@" NOT_NAME_CHARS = NOT_QNAME_CHARS + ":" - + def error(str) log_error(str, lineno: @lineno, exception: RDF::ReaderError) end - + def parse(prod) todo_stack = [{prod: prod, terms: nil}] while !todo_stack.empty? @@ -20,12 +20,12 @@ def parse(prod) if todo_stack.last[:terms].nil? todo_stack.last[:terms] = [] tok = self.token - #log_debug("parse tok: '#{tok}'") {"prod #{todo_stack.last[:prod]}"} - + log_debug("parse tok: '#{tok}'", depth: depth) {"prod #{todo_stack.last[:prod]}"} + # Got an opened production onStart(abbr(todo_stack.last[:prod])) break if tok.nil? - + cur_prod = todo_stack.last[:prod] prod_branch = @branches[cur_prod] error("No branches found for '#{abbr(cur_prod)}'") if prod_branch.nil? @@ -35,15 +35,15 @@ def parse(prod) expected = prod_branch.values.uniq.map {|u| u.map {|v| abbr(v).inspect}.join(",")} error("Found '#{tok}' when parsing a #{abbr(cur_prod)}. expected #{expected.join(' | ')}") end - #log_debug("sequence") {sequence.inspect} + #log_debug("sequence", depth: depth) {sequence.inspect} todo_stack.last[:terms] += sequence end - - #log_debug("parse") {todo_stack.last.inspect} + + #log_debug("parse", depth: depth) {todo_stack.last.inspect} while !todo_stack.last[:terms].to_a.empty? term = todo_stack.last[:terms].shift if term.is_a?(String) - log_debug("parse term(string)") {term.to_s} + log_debug("parse term(string)", depth: depth) {term.to_s} word = buffer[0, term.length] if word == term onToken(term, word) @@ -60,7 +60,7 @@ def parse(prod) string = '"""' consume(3) next_line = buffer - #log_debug("ml-str(start)") {next_line.dump} + #log_debug("ml-str(start)", depth: depth) {next_line.dump} until md = R_MLSTRING.match(next_line) begin string += next_line @@ -72,23 +72,23 @@ def parse(prod) string += md[0].to_s consume(md[0].to_s.length) onToken('string', string) - #log_debug("ml-str now") {buffer.dump} + #log_debug("ml-str now", depth: depth) {buffer.dump} else md = regexp.match(buffer) error("Token(#{abbr(term)}) '#{buffer[0, 10]}...' should match #{regexp}") unless md - log_debug("parse") {"term(#{abbr(term)}:regexp): #{term}, #{regexp}.match('#{buffer[0, 10]}...') => '#{md.inspect.force_encoding(Encoding::UTF_8)}'"} + log_debug("parse", depth: depth) {"term(#{abbr(term)}:regexp): #{term}, #{regexp}.match('#{buffer[0, 10]}...') => '#{md.inspect.force_encoding(Encoding::UTF_8)}'"} onToken(abbr(term), md.to_s) consume(md[0].length) end else - log_debug("parse term(push)") {term} + log_debug("parse term(push)", depth: depth) {term} todo_stack << {prod: term, terms: nil} pushed = true break end self.token end - + while !pushed && todo_stack.last[:terms].to_a.empty? todo_stack.pop self.onFinish @@ -105,26 +105,26 @@ def token unless @memo.has_key?(@pos) tok = self.get_token @memo[@pos] = tok - log_debug("token") {"'#{tok}'('#{buffer[0, 10]}...')"} if buffer + log_debug("token", depth: depth) {"'#{tok}'('#{buffer[0, 10]}...')"} if buffer end @memo[@pos] end def get_token whitespace - + return nil if buffer.nil? - + ch2 = buffer[0, 2] return ch2 if %w(=> <= ^^).include?(ch2) - + ch = buffer[0, 1] @keyword_mode = false if ch == '.' && @keyword_mode - + return ch if SINGLE_CHARACTER_SELECTORS.include?(ch) return ":" if ch == ":" return "0" if "+-0123456789".include?(ch) - + if ch == '@' return '@' if @pos > 0 && @line[@pos-1, 1] == '"' @@ -154,43 +154,45 @@ def get_token 'a' end - + def whitespace while buffer && md = R_WHITESPACE.match(buffer) return unless md[0].length > 0 consume(md[0].length) - #log_debug("ws") {"'#{md[0]}', pos=#{@pos}"} + #log_debug("ws", depth: depth) {"'#{md[0]}', pos=#{@pos}"} end end - + def readline @line = @input.readline @lineno += 1 @line.force_encoding(Encoding::UTF_8) - log_debug("readline[#{@lineno}]") {@line.dump} + log_debug("readline[#{@lineno}]", depth: depth) {@line.dump} @pos = 0 @line rescue EOFError @line, @pos = nil, 0 end - + # Return data from current off set to end of line def buffer @line[@pos, @line.length - @pos] unless @line.nil? end - + # Cause n characters of line to be consumed. Read new line while line is empty or until eof def consume(n) @memo = {} @pos += n readline while @line && @line.length <= @pos - #log_debug("consume[#{n}]") {buffer} + #log_debug("consume[#{n}]", depth: depth) {buffer} end - + def abbr(prodURI) prodURI.to_s.split('#').last end - + + def depth; (@productions || []).length; end + def onStart(prod) $stdout.puts ' ' * @productions.length + prod @productions << prod @@ -204,7 +206,7 @@ def onFinish def onToken(prod, tok) $stdout.puts ' ' * @productions.length + "#{prod}(#{tok})" end - + def dump_stack(stack) STDERR.puts "\nstack trace:" stack.reverse.each do |se| @@ -216,14 +218,14 @@ def dump_stack(stack) end end end - + def test(input, branches, regexps) # FIXME: for now, read in entire doc, eventually, process as stream @input = input.respond_to?(:read) ? (input.rewind; input) : StringIO.new(input.to_s) @lineno = 0 readline # Prime the pump $stdout ||= STDOUT - + @memo = {} @keyword_mode = false @keywords = %w(a is of this has) diff --git a/lib/rdf/n3/reasoner.rb b/lib/rdf/n3/reasoner.rb new file mode 100644 index 0000000..c050cb8 --- /dev/null +++ b/lib/rdf/n3/reasoner.rb @@ -0,0 +1,293 @@ +# coding: utf-8 +module RDF::N3 + ## + # A Notation-3/Turtle reasoner in Ruby + # + # Takes either a parsed formula or an `RDF::Queryable` and updates it by reasoning over formula defined within the queryable. + # + # @author [Gregg Kellogg](http://greggkellogg.net/) + class Reasoner + include RDF::Enumerable + include RDF::Mutable + include RDF::Util::Logger + + # The top-level parsed formula + # @return [RDF::N3::Algebra::Formula] + attr_reader :formula + + # Opens a Notation-3 file, and parses it to initialize the reasoner + # + # @param [String, #to_s] filename + # @yield [reasoner] `self` + # @yieldparam [RDF::N3::Reasoner] reasoner + # @yieldreturn [void] ignored + # @return [RDF::N3::Reasoner] + def self.open(file) + RDF::N3::Reader.open(file, **options) do |reader| + RDF::N3::Reasoner.new(reader, **options, &block) + end + end + + ## + # Initializes a new reasoner. If input is an IO or string, it is taken as n3 source and parsed first. Otherwise, it is a parsed formula. + # + # It returns the evaluated formula, or yields triples. + # + # @example Initializing from a reader + # reader = RDF::N3::Reader.new(":a :b :c .") + # reasoner = RDF::N3::Reasoner.new(reader) + # reasoner.each_triple {} + # + # @example Initializing as a mutable + # reasoner = RDF::N3::Reasoner.new do |r| + # r << RDF::N3::Reader.new(":a :b :c .") + # end + # reasoner.each_triple {} + # + # @example Initializing with multiple inputs + # reasoner = RDF::N3::Reasoner.new + # RDF::NTriples::Reader.open("example.nt") {|r| reasoner << r} + # RDF::N3::Reader.open("rules.n3") {|r| reasoner << r} + # reasoner.each_triple {} + # + # @param [RDF::Enumerable] input (nil) + # @param [Hash{Symbol => Object}] options + # @option options [#to_s] :base_uri (nil) + # the base URI to use when resolving relative URIs (for acessing intermediate parser productions) + # @yield [reasoner] `self` + # @yieldparam [RDF::N3::Reasoner] reasoner + # @yieldreturn [void] ignored + # @return [RDF::N3::Reasoner] + 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 + end + + log_debug("reasoner: expression", options) {SXP::Generator.string(formula.to_sxp_bin)} + + if block_given? + case block.arity + when 0 then instance_eval(&block) + else block.call(self) + end + end + end + + ## + # Returns a copy of this reasoner + def dup + repo = RDF::Repository.new {|r| r << @mutable} + self.class.new(repo) do |reasoner| + reasoner.instance_variable_set(:@options, @options.dup) + reasoner.instance_variable_set(:@formula, @formula.dup) if @formula + end + end + + ## + # Inserts an RDF statement the datastore, resets `formula`. + # + # @param [RDF::Statement] statement + # @return [void] + def insert_statement(statement) + @mutable.insert_statement(statement) + end + + ## + # Updates the datastore by reasoning over the formula, optionally yielding each conclusion; uses triples from the graph associated with this formula as the dataset over which to reason. + # + # @param [Hash{Symbol => Object}] options + # @option options [Boolean] :apply + # @option options [Boolean] :think + # @yield [statement] + # @yieldparam [RDF::Statement] statement + # @return [RDF::N3::Reasoner] `self` + def execute(**options, &block) + @options[:logger] = options[:logger] if options.has_key?(:logger) + + # If thinking, continuously execute until results stop growing + if options[:think] + count = 0 + log_info("reasoner: think start") { "count: #{count}"} + while @mutable.count > count + count = @mutable.count + dataset = RDF::Graph.new << @mutable.project_graph(nil) + log_depth {formula.execute(dataset, **options)} + @mutable << formula + end + log_info("reasoner: think end") { "count: #{count}"} + else + # Run one iteration + log_info("reasoner: apply start") { "count: #{count}"} + dataset = RDF::Graph.new << @mutable.project_graph(nil) + log_depth {formula.execute(dataset, **options)} + @mutable << formula + log_info("reasoner: apply end") { "count: #{count}"} + end + + log_debug("reasoner: datastore") {@mutable.to_sxp} + + conclusions(&block) if block_given? + self + end + alias_method :reason!, :execute + + ## + # Reason with results in a duplicate datastore + # + # @see {execute} + def reason(**options, &block) + self.dup.reason!(**options, &block) + end + + ## + # Yields each statement in the datastore + # + # @yieldparam [RDF::Statement] statement + # @yieldreturn [void] ignored + # @return [void] + def each(&block) + @mutable.each(&block) + end + + ## + # Yields data, excluding formulae or variables and statements referencing formulae or variables + # + # @overload data + # @yield [statement] + # each statement + # @yieldparam [RDF::Statement] statement + # @yieldreturn [void] ignored + # @return [void] + # + # @overload data + # @return [Enumerator] + # @return [RDF::Enumerator] + # @yield [statement] + # @yieldparam [RDF::Statement] statement + def data(&block) + if block_given? + project_graph(nil) do |statement| + block.call(statement) unless statement.variable? || + has_graph?(statement.subject) || + has_graph?(statement.object) + end + end + enum_data + end + alias_method :each_datum, :data + + ## + # Returns an enumerator for {#conclusions}. + # FIXME: enum_for doesn't seem to be working properly + # in JRuby 1.7, so specs are marked pending + # + # @return [Enumerator] + # @see #each_statement + def enum_data + # Ensure that statements are queryable, countable and enumerable + this = self + RDF::Queryable::Enumerator.new do |yielder| + this.send(:each_datum) {|y| yielder << y} + end + end + + ## + # Yields conclusions, excluding formulae and those statements in the original dataset, or returns an enumerator over the conclusions + # + # @overload conclusions + # @yield [statement] + # each statement + # @yieldparam [RDF::Statement] statement + # @yieldreturn [void] ignored + # @return [void] + # + # @overload conclusions + # @return [Enumerator] + # @return [RDF::Enumerator] + # @yield [statement] + # @yieldparam [RDF::Statement] statement + def conclusions(&block) + if block_given? + # Invoke {#each} in the containing class: + each_statement {|s| block.call(s) if s.inferred?} + end + enum_conclusions + end + alias_method :each_conclusion, :conclusions + + ## + # Returns an enumerator for {#conclusions}. + # FIXME: enum_for doesn't seem to be working properly + # in JRuby 1.7, so specs are marked pending + # + # @return [Enumerator] + # @see #each_statement + def enum_conclusions + # Ensure that statements are queryable, countable and enumerable + this = self + RDF::Queryable::Enumerator.new do |yielder| + this.send(:each_conclusion) {|y| yielder << y} + end + end + + ## + # Returns the top-level formula for this file + # + # @return [RDF::N3::Algebra::Formula] + def formula + # SPARQL used for SSE and algebra functionality + require 'sparql' unless defined?(:SPARQL) + + @formula ||= begin + # 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 + + # Add patterns to appropiate formula based on graph_name, + # and replace subject and object bnodes which identify + # named graphs with those formula + @mutable.each_statement do |statement| + pattern = statement.variable? ? RDF::Query::Pattern.from(statement) : statement + + # A graph name indicates a formula. + form = formulae[pattern.graph_name] + + # Formulae may be the subject or object of a known operator + if klass = Algebra.for(pattern.predicate) + fs = formulae.fetch(pattern.subject, pattern.subject) + fo = formulae.fetch(pattern.object, pattern.object) + form.operands << klass.new(fs, fo, parent: form, **@options) + else + # Add formulae as direct operators + if formulae.has_key?(pattern.subject) + form.operands << formulae[pattern.subject] + end + if formulae.has_key?(pattern.object) + form.operands << formulae[pattern.object] + end + pattern.graph_name = nil + form.operands << pattern + end + end + + # Formula is that without a graph name + formulae[nil] + end + end + + ## + # Returns the SPARQL S-Expression (SSE) representation of the parsed formula. + # Formulae are represented as subjects and objects in the containing graph, along with their universals and existentials + # + # @return [Array] `self` + # @see http://openjena.org/wiki/SSE + def to_sxp_bin + formula.to_sxp_bin + end + end +end + diff --git a/lib/rdf/n3/vocab.rb b/lib/rdf/n3/vocab.rb index a89fccb..bf0e009 100644 --- a/lib/rdf/n3/vocab.rb +++ b/lib/rdf/n3/vocab.rb @@ -1,4 +1,9 @@ -module RDF - class LOG < Vocabulary("http://www.w3.org/2000/10/swap/log#"); end - class REI < Vocabulary("http://www.w3.org/2004/06/rei#"); end +module RDF::N3 + class Crypto < RDF::Vocabulary("http://www.w3.org/2000/10/swap/crypto#"); end + class List < RDF::Vocabulary("http://www.w3.org/2000/10/swap/list#"); end + class Log < RDF::Vocabulary("http://www.w3.org/2000/10/swap/log#"); end + class Math < RDF::Vocabulary("http://www.w3.org/2000/10/swap/math#"); end + class Rei < RDF::Vocabulary("http://www.w3.org/2000/10/swap/reify#"); end + class Str < RDF::Vocabulary("http://www.w3.org/2000/10/swap/string#"); end + class Time < RDF::Vocabulary("http://www.w3.org/2000/10/swap/time#"); end end diff --git a/lib/rdf/n3/writer.rb b/lib/rdf/n3/writer.rb index b90c694..4942f92 100644 --- a/lib/rdf/n3/writer.rb +++ b/lib/rdf/n3/writer.rb @@ -1,42 +1,39 @@ # coding: utf-8 module RDF::N3 ## - # A Turtle serialiser in Ruby + # A Notation-3 serialiser in Ruby # # Note that the natural interface is to write a whole graph at a time. # Writing statements or Triples will create a graph to add them to # and then serialize the graph. # - # @example Obtaining a Turtle writer class + # @example Obtaining a N3 writer class # RDF::Writer.for(:n3) #=> RDF::N3::Writer # RDF::Writer.for("etc/test.n3") - # RDF::Writer.for("etc/test.ttl") # RDF::Writer.for(file_name: "etc/test.n3") - # RDF::Writer.for(file_name: "etc/test.ttl") # RDF::Writer.for(file_extension: "n3") - # RDF::Writer.for(file_extension: "ttl") # RDF::Writer.for(content_type: "text/n3") # - # @example Serializing RDF graph into an Turtle file + # @example Serializing RDF graph into an N3 file # RDF::N3::Writer.open("etc/test.n3") do |writer| # writer << graph # end # - # @example Serializing RDF statements into an Turtle file + # @example Serializing RDF statements into an N3 file # RDF::N3::Writer.open("etc/test.n3") do |writer| # graph.each_statement do |statement| # writer << statement # end # end # - # @example Serializing RDF statements into an Turtle string + # @example Serializing RDF statements into an N3 string # RDF::N3::Writer.buffer do |writer| # graph.each_statement do |statement| # writer << statement # end # end # - # The writer will add prefix definitions, and use them for creating @prefix definitions, and minting QNames + # The writer will add prefix definitions, and use them for creating @prefix definitions, and minting pnames # # @example Creating @base and @prefix definitions in output # RDF::N3::Writer.buffer(base_uri: "http://example.com/", prefixes: { @@ -54,10 +51,11 @@ class Writer < RDF::Writer include RDF::Util::Logger QNAME = Meta::REGEXPS[:"http://www.w3.org/2000/10/swap/grammar/n3#qname"] - # @return [Graph] Graph of statements serialized + # @return [RDF::Repository] Repository of statements serialized + attr_accessor :repo + + # @return [RDF::Graph] Graph being serialized attr_accessor :graph - # @return [URI] Base URI used for relativizing URIs - attr_accessor :base_uri ## # N3 Writer options @@ -78,7 +76,7 @@ def self.options end ## - # Initializes the Turtle writer instance. + # Initializes the N3 writer instance. # # @param [IO, File] output # the output stream @@ -106,10 +104,11 @@ def self.options # @yield [writer] # @yieldparam [RDF::Writer] writer def initialize(output = $stdout, options = {}, &block) + @repo = RDF::Repository.new + @uri_to_pname = {} + @uri_to_prefix = {} super do - @graph = RDF::Graph.new - @uri_to_qname = {} - @uri_to_prefix = {} + reset if block_given? case block.arity when 0 then instance_eval(&block) @@ -128,7 +127,19 @@ def initialize(output = $stdout, options = {}, &block) # @raise [NotImplementedError] unless implemented in subclass # @abstract def write_triple(subject, predicate, object) - @graph.insert(RDF::Statement(subject, predicate, object)) + repo.insert(RDF::Statement(subject, predicate, object)) + end + + ## + # Adds a quad to be serialized + # @param [RDF::Resource] subject + # @param [RDF::URI] predicate + # @param [RDF::Value] object + # @param [RDF::Resource] graph_name + # @return [void] + def write_quad(subject, predicate, object, graph_name) + statement = RDF::Statement.new(subject, predicate, object, graph_name: graph_name) + repo.insert(statement) end ## @@ -138,28 +149,46 @@ def write_triple(subject, predicate, object) # @see #write_triple def write_epilogue @max_depth = @options[:max_depth] || 3 - @base_uri = RDF::URI(@options[:base_uri]) self.reset - log_debug {"\nserialize: graph: #{@graph.size}"} + log_debug {"\nserialize: repo: #{repo.size}"} preprocess + start_document - order_subjects.each do |subject| - unless is_done?(subject) - statement(subject) + with_graph(nil) do + count = 0 + order_subjects.each do |subject| + unless is_done?(subject) + statement(subject, count) + count += 1 + end + end + + # Output any formulae not already serialized using owl:sameAs + repo.graph_names.each do |graph_name| + next if graph_done?(graph_name) + + log_debug {"named graph(#{graph_name})"} + @output.write("\n#{indent}") + p_term(graph_name, :subject) + @output.write(" ") + predicate(RDF::OWL.sameAs) + @output.write(" ") + formula(graph_name, :graph_name) + @output.write(" .\n") end end super end - - # Return a QName for the URI, or nil. Adds namespace of QName to defined prefixes + + # Return a pname for the URI, or nil. Adds namespace of pname to defined prefixes # @param [RDF::Resource] resource # @return [String, nil] value to use to identify URI - def get_qname(resource) + def get_pname(resource) case resource when RDF::Node return options[:unique_bnodes] ? resource.to_unique_base : resource.to_base @@ -169,37 +198,39 @@ def get_qname(resource) return nil end - log_debug {"get_qname(#{resource}), std?}"} - qname = case - when @uri_to_qname.has_key?(uri) - return @uri_to_qname[uri] + #log_debug {"get_pname(#{resource}), std?}"} + pname = case + when @uri_to_pname.has_key?(uri) + return @uri_to_pname[uri] when u = @uri_to_prefix.keys.detect {|u| uri.index(u.to_s) == 0} # Use a defined prefix prefix = @uri_to_prefix[u] - prefix(prefix, u) unless u.to_s.empty? # Define for output - log_debug {"get_qname: add prefix #{prefix.inspect} => #{u}"} - uri.sub(u.to_s, "#{prefix}:") + unless u.to_s.empty? + prefix(prefix, u) unless u.to_s.empty? + #log_debug("get_pname") {"add prefix #{prefix.inspect} => #{u}"} + uri.sub(u.to_s, "#{prefix}:") + end when @options[:standard_prefixes] && vocab = RDF::Vocabulary.each.to_a.detect {|v| uri.index(v.to_uri.to_s) == 0} prefix = vocab.__name__.to_s.split('::').last.downcase @uri_to_prefix[vocab.to_uri.to_s] = prefix prefix(prefix, vocab.to_uri) # Define for output - log_debug {"get_qname: add standard prefix #{prefix.inspect} => #{vocab.to_uri}"} + #log_debug {"get_pname: add standard prefix #{prefix.inspect} => #{vocab.to_uri}"} uri.sub(vocab.to_uri.to_s, "#{prefix}:") else nil end - - # Make sure qname is a valid qname - if qname - md = QNAME.match(qname) - qname = nil unless md.to_s.length == qname.length + + # Make sure pname is a valid pname + if pname + md = QNAME.match(pname) + pname = nil unless md.to_s.length == pname.length end - @uri_to_qname[uri] = qname + @uri_to_pname[uri] = pname rescue Addressable::URI::InvalidURIError => e raise RDF::WriterError, "Invalid URI #{resource.inspect}: #{e.message}" end - + # Take a hash from predicate uris to lists of values. # Sort the lists of values. Return a sorted list of properties. # @param [Hash{String => Array}] properties A hash of Property to Resource mappings @@ -207,17 +238,17 @@ def get_qname(resource) def sort_properties(properties) # Make sorted list of properties prop_list = [] - + predicate_order.each do |prop| next unless properties[prop.to_s] prop_list << prop.to_s end - + properties.keys.sort.each do |prop| next if prop_list.include?(prop.to_s) prop_list << prop.to_s end - + log_debug {"sort_properties: #{prop_list.join(', ')}"} prop_list end @@ -232,11 +263,11 @@ def format_literal(literal, options = {}) literal = literal.dup.canonicalize! if @options[:canonicalize] case literal when RDF::Literal - case literal.datatype + case literal.valid? ? literal.datatype : false when RDF::XSD.boolean, RDF::XSD.integer, RDF::XSD.decimal - literal.to_s + literal.canonicalize.to_s when RDF::XSD.double - literal.to_s.sub('E', 'e') # Favor lower case exponent + literal.canonicalize.to_s.sub('E', 'e') # Favor lower case exponent else text = quoted(literal.value) text << "@#{literal.language}" if literal.has_language? @@ -247,21 +278,21 @@ def format_literal(literal, options = {}) quoted(literal.to_s) end end - + ## - # Returns the Turtle/N3 representation of a URI reference. + # Returns the N3 representation of a URI reference. # # @param [RDF::URI] uri # @param [Hash{Symbol => Object}] options # @return [String] def format_uri(uri, options = {}) - md = relativize(uri) - log_debug {"relativize(#{uri.inspect}) => #{md.inspect}"} if md != uri.to_s - md != uri.to_s ? "<#{md}>" : (get_qname(uri) || "<#{uri}>") + md = uri.relativize(base_uri) + log_debug("relativize") {"#{uri.to_sxp} => #{md.inspect}"} if md != uri.to_s + md != uri.to_s ? "<#{md}>" : (get_pname(uri) || "<#{uri}>") end - + ## - # Returns the Turtle/N3 representation of a blank node. + # Returns the N3 representation of a blank node. # # @param [RDF::Node] node # @param [Hash{Symbol => Object}] options @@ -269,24 +300,28 @@ def format_uri(uri, options = {}) def format_node(node, options = {}) options[:unique_bnodes] ? node.to_unique_base : node.to_base end - + protected # Output @base and @prefix definitions def start_document - @output.write("#{indent}@base <#{base_uri}> .\n") unless base_uri.to_s.empty? - - log_debug {"start_document: #{prefixes.inspect}"} + @output.write("@base <#{base_uri}> .\n") unless base_uri.to_s.empty? + + log_debug {"start_document: prefixes #{prefixes.inspect}"} prefixes.keys.sort_by(&:to_s).each do |prefix| - @output.write("#{indent}@prefix #{prefix}: <#{prefixes[prefix]}> .\n") + @output.write("@prefix #{prefix}: <#{prefixes[prefix]}> .\n") + end + + unless @universals.empty? + log_debug {"start_document: universals #{@universals.inspect}"} + terms = @universals.map {|v| format_uri(RDF::URI(v.name.to_s))} + @output.write("@forAll #{terms.join(', ')} .\n") + end + + unless @existentials.empty? + log_debug {"start_document: universals #{@existentials.inspect}"} + terms = @existentials.map {|v| format_uri(RDF::URI(v.name.to_s))} + @output.write("@forSome #{terms.join(', ')} .\n") end - end - - # If base_uri is defined, use it to try to make uri relative - # @param [#to_s] uri - # @return [String] - def relativize(uri) - uri = uri.to_s - base_uri ? uri.sub(base_uri.to_s, "") : uri end # Defines rdf:type of subjects to be emitted at the beginning of the graph. Defaults to rdfs:Class @@ -297,7 +332,7 @@ def top_classes; [RDF::RDFS.Class]; end # [rdf:type, rdfs:label, dc:title] # @return [Array] def predicate_order; [RDF.type, RDF::RDFS.label, RDF::URI("http://purl.org/dc/terms/title")]; end - + # Order subjects for output. Override this to output subjects in another order. # # Uses #top_classes and #base_uri. @@ -305,46 +340,44 @@ def predicate_order; [RDF.type, RDF::RDFS.label, RDF::URI("http://purl.org/dc/te def order_subjects seen = {} subjects = [] - + # Start with base_uri if base_uri && @subjects.keys.include?(base_uri) subjects << base_uri seen[base_uri] = true end - log_debug {"subjects1: #{subjects.inspect}"} - + # Add distinguished classes top_classes.each do |class_uri| - graph.query(predicate: RDF.type, object: class_uri). + graph.query(predicate: RDF.type, object: class_uri). map {|st| st.subject}. sort. uniq. each do |subject| - log_debug("order_subjects") {subject.to_ntriples} + log_debug("order_subjects") {subject.to_sxp} subjects << subject seen[subject] = true end end - log_debug {"subjects2: #{subjects.inspect}"} # Mark as seen lists that are part of another list @lists.values.map(&:statements). flatten.each do |st| - seen[st.object] if @lists.has_key?(st.object) + seen[st.object] = true if @lists.has_key?(st.object) end + # List elements which are bnodes should not be targets for top-level serialization + list_elements = @lists.values.map(&:to_a).flatten.select(&:node?).compact + # Sort subjects by resources over bnodes, ref_counts and the subject URI itself - recursable = @subjects.keys. + recursable = (@subjects.keys - list_elements). select {|s| !seen.include?(s)}. map {|r| [r.node? ? 1 : 0, ref_count(r), r]}. sort - - log_debug {"subjects3: #{subjects.inspect}"} + subjects += recursable.map{|r| r.last} - log_debug {"subjects4: #{subjects.inspect}"} - subjects end - + # Perform any preprocessing of statements required def preprocess # Load defined prefixes @@ -355,57 +388,62 @@ def preprocess prefix(nil, @options[:default_namespace]) if @options[:default_namespace] - @graph.each {|statement| preprocess_statement(statement)} + @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)} + @universals = vars.reject(&:existential?) + @existentials = vars - @universals end - + # Perform any statement preprocessing required. This is used to perform reference counts and determine required # prefixes. # @param [Statement] statement def preprocess_statement(statement) #log_debug {"preprocess: #{statement.inspect}"} - references = ref_count(statement.object) + 1 - @references[statement.object] = references - @subjects[statement.subject] = true + + # Pre-fetch pnames, to fill prefixes + get_pname(statement.subject) + get_pname(statement.predicate) + get_pname(statement.object) + get_pname(statement.object.datatype) if statement.object.literal? && statement.object.datatype + end + + # Perform graph-specific preprocessing + # @param [Statement] + def preprocess_graph_statement(statement) + bump_reference(statement.object) + # Count properties of this subject + @subjects[statement.subject] ||= {} + @subjects[statement.subject][statement.predicate] ||= 0 + @subjects[statement.subject][statement.predicate] += 1 # Collect lists if statement.predicate == RDF.first - @lists[statement.subject] = RDF::List.new(subject: statement.subject, graph: graph) + l = RDF::List.new(subject: statement.subject, graph: graph) + @lists[statement.subject] = l if l.valid? end if statement.object == RDF.nil || statement.subject == RDF.nil # Add an entry for the list tail @lists[RDF.nil] ||= RDF::List[] end - - # Pre-fetch qnames, to fill prefixes - get_qname(statement.subject) - get_qname(statement.predicate) - get_qname(statement.object) - get_qname(statement.object.datatype) if statement.object.literal? && statement.object.datatype - - @references[statement.predicate] = ref_count(statement.predicate) + 1 - end - - # Return the number of times this node has been referenced in the object position - # @return [Integer] - def ref_count(node) - @references.fetch(node, 0) end # Returns indent string multiplied by the depth # @param [Integer] modifier Increase depth by specified amount # @return [String] A number of spaces, depending on current depth def indent(modifier = 0) - " " * (@depth + modifier) + " " * (@options.fetch(:log_depth, log_depth) * 2 + modifier) end # Reset internal helper instance variables def reset - @depth = 0 + @universals, @existentials = [], [] @lists = {} - @references = {} @serialized = {} + @graphs = {} @subjects = {} end @@ -416,7 +454,7 @@ def reset # @return [String] def quoted(string) if string.to_s.match(/[\t\n\r]/) - string = string.gsub('\\', '\\\\\\\\').gsub('"""', '\\"""') + string = string.gsub('\\', '\\\\\\\\').gsub('"""', '\\"\\"\\"') %("""#{string}""") else "\"#{escaped(string)}\"" @@ -424,154 +462,276 @@ def quoted(string) end private - + # Checks if l is a valid RDF list, i.e. no nodes have other properties. - def is_valid_list(l) - #log_debug {"is_valid_list: #{l.inspect}"} + def is_valid_list?(l) + #log_debug("is_valid_list?") {l.inspect} return @lists[l] && @lists[l].valid? end - - def do_list(l) + + def do_list(l, position) list = @lists[l] - log_debug {"do_list: #{list.inspect}"} - position = :subject + log_debug("do_list") {list.inspect} + subject_done(RDF.nil) + 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 - - def p_list(node, position) - return false if !is_valid_list(node) - #log_debug {"p_list: #{node.inspect}, #{position}"} - @output.write(position == :subject ? "(" : " (") - @depth += 2 - do_list(node) - @depth -= 2 + 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}"} + + @output.write("(") + log_depth {do_list(node, position)} @output.write(')') end - - def p_squared?(node, position) - node.node? && - !@serialized.has_key?(node) && - ref_count(node) <= 1 - end - - def p_squared(node, position) - return false unless p_squared?(node, position) - - #log_debug {"p_squared: #{node.inspect}, #{position}"} - subject_done(node) - @output.write(position == :subject ? '[' : ' [') - @depth += 2 - predicate_list(node) - @depth -= 2 - @output.write(']') - - true - end - - def p_default(node, position) - #log_debug {"p_default: #{node.inspect}, #{position}"} - l = (position == :subject ? "" : " ") + format_term(node, options) + + # Default singular resource representation. + def p_term(resource, position) + #log_debug("p_term") {"#{resource.to_sxp}, #{position}"} + l = if resource.is_a?(RDF::Query::Variable) + format_term(RDF::URI(resource.name.to_s.sub(/^\$/, ''))) + elsif resource == RDF.nil + "()" + else + format_term(resource, options) + end @output.write(l) end - - def path(node, position) - log_debug do - "path: #{node.inspect}, " + + + # Represent a resource in subject, predicate or object position. + # Use either collection, blankNodePropertyList or singular resource notation. + def path(resource, position) + log_debug("path") do + "#{resource.to_sxp}, " + "pos: #{position}, " + - "[]: #{is_valid_list(node)}, " + - "p2?: #{p_squared?(node, position)}, " + - "rc: #{ref_count(node)}" + "{}?: #{formula?(resource, position)}, " + + "()?: #{is_valid_list?(resource)}, " + + "[]?: #{blankNodePropertyList?(resource, position)}, " + + "rc: #{ref_count(resource)}" end - raise RDF::WriterError, "Cannot serialize node '#{node}'" unless p_list(node, position) || p_squared(node, position) || p_default(node, position) + raise RDF::WriterError, "Cannot serialize resource '#{resource}'" unless + formula(resource, position) || + collection(resource, position) || + blankNodePropertyList(resource, position) || + p_term(resource, position) end - - def verb(node) - log_debug {"verb: #{node.inspect}"} - if node == RDF.type - @output.write(" a") + + def predicate(resource) + log_debug("predicate") {resource.to_sxp} + case resource + when RDF.type + @output.write("a") + when RDF::OWL.sameAs + @output.write("=") + when RDF::N3::Log.implies + @output.write("=>") else - path(node, :predicate) + path(resource, :predicate) end end - - def object_list(objects) - log_debug {"object_list: #{objects.inspect}"} + + # Render an objectList having a common subject and predicate + def objectList(objects) + log_debug("objectList") {objects.inspect} return if objects.empty? objects.each_with_index do |obj, i| - @output.write(",\n#{indent(4)}") if i > 0 + if i > 0 && (formula?(obj, :object) || blankNodePropertyList?(obj, :object)) + @output.write ", " + elsif i > 0 + @output.write ",\n#{indent(4)}" + end path(obj, :object) end end - - def predicate_list(subject) + + # Render a predicateObjectList having a common subject. + # @return [Integer] the number of properties serialized + def predicateObjectList(subject, from_bpl = false) properties = {} - @graph.query(subject: subject) do |st| - properties[st.predicate.to_s] ||= [] - properties[st.predicate.to_s] << st.object + if subject.variable? + # Can't query on variable + @graph.enum_statement.select {|s| s.subject.equal?(subject)}.each do |st| + (properties[st.predicate.to_s] ||= []) << st.object + end + else + @graph.query(subject: subject) do |st| + (properties[st.predicate.to_s] ||= []) << st.object + end end prop_list = sort_properties(properties) - prop_list -= [RDF.first.to_s, RDF.rest.to_s] if subject.node? - log_debug {"predicate_list: #{prop_list.inspect}"} - return if prop_list.empty? + prop_list -= [RDF.first.to_s, RDF.rest.to_s] if @lists.include?(subject) + log_debug("predicateObjectList") {prop_list.inspect} + return 0 if prop_list.empty? + @output.write("\n#{indent(2)}") if properties.keys.length > 1 && from_bpl prop_list.each_with_index do |prop, i| begin @output.write(";\n#{indent(2)}") if i > 0 - verb(prop[0, 2] == "_:" ? RDF::Node.intern(prop.split(':').last) : RDF::URI.intern(prop)) - object_list(properties[prop]) - rescue Addressable::URI::InvalidURIError => e - log_debug {"Predicate #{prop.inspect} is an invalid URI: #{e.message}"} + predicate(RDF::URI.intern(prop)) + @output.write(" ") + objectList(properties[prop]) end end + properties.keys.length + end + + # Can subject be represented as a blankNodePropertyList? + def blankNodePropertyList?(resource, position) + resource.node? && + !formula?(resource, position) && + !is_valid_list?(resource) && + (!is_done?(resource) || position == :subject) && + ref_count(resource) == (position == :object ? 1 : 0) && + resource_in_single_graph?(resource) && + !repo.has_graph?(resource) end - - def s_squared?(subject) - ref_count(subject) == 0 && subject.node? && !is_valid_list(subject) - end - - def s_squared(subject) - return false unless s_squared?(subject) - - log_debug {"s_squared: #{subject.inspect}"} - @output.write("\n#{indent} [") - @depth += 1 - predicate_list(subject) - @depth -= 1 - @output.write("] .") + + def blankNodePropertyList(resource, position) + return false unless blankNodePropertyList?(resource, position) + + log_debug("blankNodePropertyList") {resource.to_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 == :object ? ']' : '] .')) true end - - def s_default(subject) + + # 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) + end + + def formula(resource, position) + return false unless formula?(resource, position) + + log_debug("formula") {resource.to_sxp} + subject_done(resource) + @output.write('{') + log_depth do + with_graph(resource) do + count = 0 + order_subjects.each do |subject| + unless is_done?(subject) + statement(subject, count) + count += 1 + end + end + end + end + @output.write((graph.count > 1 ? "\n#{indent}" : "") + '}') + true + end + + # Render triples having the same subject using an explicit subject + def triples(subject) @output.write("\n#{indent}") path(subject, :subject) - predicate_list(subject) - @output.write(" .") + @output.write(" ") + num_props = predicateObjectList(subject) + @output.write("#{num_props > 0 ? ' ' : ''}.") true end - - def statement(subject) - log_debug {"statement: #{subject.inspect}, s2?: #{s_squared?(subject)}"} + + def statement(subject, count) + log_debug("statement") {"#{subject.to_sxp}, bnodePL?: #{blankNodePropertyList?(subject, :subject)}"} subject_done(subject) - s_squared(subject) || s_default(subject) - @output.write("\n") + blankNodePropertyList(subject, :subject) || triples(subject) + @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) + @references.fetch(node, 0) + end + + # Increase the reference count of this resource + # @param [RDF::Resource] resource + # @return [Integer] resulting reference count + def bump_reference(resource) + @references[resource] = ref_count(resource) + 1 + end + def is_done?(subject) @serialized.include?(subject) end - + # Mark a subject as done. def subject_done(subject) @serialized[subject] = true end + + def graph_done?(subject) + @graphs.include?(subject) + end + + # Mark a graph as done. + def graph_done(graph_name) + @graphs[graph_name] = true + end + + def resource_in_single_graph?(resource) + if resource.variable? + graph_names = @repo. + enum_statement. + select {|st| st.subject.equal?(resource) || st.object.equal?(resource)}. + map(&:graph_name) + else + graph_names = @repo.query(subject: resource).map(&:graph_name) + graph_names += @repo.query(object: resource).map(&:graph_name) + end + graph_names.uniq.length <= 1 + end + + # Process a graph projection + def with_graph(graph_name) + old_lists, @lists = @lists, {} + old_references, @references = @references, {} + old_serialized, @serialized = @serialized, {} + old_subjects, @subjects = @subjects, {} + old_graph, @graph = @graph, repo.project_graph(graph_name) + + graph_done(graph_name) + + graph.each {|statement| preprocess_graph_statement(statement)} + + # Remove lists that are referenced and have non-list properties; + # these are legal, but can't be serialized as lists + @lists.reject! do |node, list| + ref_count(node) > 0 && prop_count(node) > 0 || + list.subjects.any? {|elt| !resource_in_single_graph?(elt)} + end + + yield + ensure + @graph, @lists, @references, @serialized, @subjects = old_graph, old_lists, old_references, old_serialized, old_subjects + end end end diff --git a/rdf-n3.gemspec b/rdf-n3.gemspec index 3fb657e..d08bac6 100755 --- a/rdf-n3.gemspec +++ b/rdf-n3.gemspec @@ -6,10 +6,10 @@ Gem::Specification.new do |gem| gem.date = File.mtime('VERSION').strftime('%Y-%m-%d') gem.name = %q{rdf-n3} - gem.homepage = %q{http://ruby-rdf.github.com/rdf-n3} + gem.homepage = %q{https://ruby-rdf.github.com/rdf-n3} gem.license = 'Unlicense' - gem.summary = %q{Notation3 reader/writer for RDF.rb.} - gem.description = %q{RDF::N3 is an Notation-3 reader/writer for the RDF.rb library suite.} + gem.summary = %q{Notation3 reader/writer and reasoner for RDF.rb.} + gem.description = %q{RDF::N3 is an Notation-3 reader/writer and reasoner for the RDF.rb library suite.} gem.authors = %w(Gregg Kellogg) gem.email = 'public-rdf-ruby@w3.org' @@ -22,14 +22,17 @@ Gem::Specification.new do |gem| gem.requirements = [] gem.add_dependency 'rdf', '~> 3.0' - gem.add_development_dependency 'open-uri-cached', '~> 0.0', '>= 0.0.5' - #gem.add_development_dependency 'json-ld', '~> 3.0' - gem.add_development_dependency 'json-ld', '>= 2.2', '< 4.0' - gem.add_development_dependency 'rspec', '~> 3.7' + gem.add_dependency 'sparql', '~> 3.0' + gem.add_runtime_dependency 'sxp', '~> 1.0' + + gem.add_development_dependency 'json-ld', '~> 3.0' + gem.add_development_dependency 'rspec', '~> 3.8' gem.add_development_dependency 'rspec-its', '~> 1.2' gem.add_development_dependency 'rdf-spec', '~> 3.0' gem.add_development_dependency 'rdf-isomorphic', '~> 3.0' - gem.add_development_dependency 'yard' , '~> 0.9.12' + gem.add_development_dependency 'rdf-trig', '~> 3.0' + gem.add_development_dependency 'rdf-vocab', '~> 3.0' + gem.add_development_dependency 'yard' , '~> 0.9.16' gem.post_install_message = nil end diff --git a/script/parse b/script/parse index 7736049..053ad09 100755 --- a/script/parse +++ b/script/parse @@ -5,28 +5,34 @@ require "bundler/setup" require 'logger' require 'rdf/n3' require 'rdf/ntriples' +require 'rdf/trig' require 'getoptlong' require 'open-uri' def run(input, options) + require 'profiler' if options[:profile] + reader_class = RDF::Reader.for(options[:input_format].to_sym) raise "Reader not found for #{options[:input_format]}" unless reader_class - prefixes = {} start = Time.new num = 0 - if options[:parse_only] - include RDF::N3::Meta - include RDF::N3::Parser - puts "\nparse #{input.read}---\n\n" unless options[:quiet] - input.rewind - if options[:quiet] - $stdout = StringIO.new - end - test(input, BRANCHES, REGEXPS) - if options[:quiet] - $stdout = STDOUT - print "." + Profiler__::start_profile if options[:profile] + if options[:think] + # Parse into a new reasoner and evaluate + reader_class.new(input, options[:parser_options].merge(logger: nil)) do |reader| + reasoner = RDF::N3::Reasoner.new(reader, options[:parser_options]) + reasoner.reason!(options) + repo = RDF::Repository.new + if options[:conclusions] + repo << reasoner.conclusions + elsif options[:data] + repo << reasoner.data + else + repo << reasoner + end + num = repo.count + options[:output].puts repo.dump(options[:output_format], prefixes: reader.prefixes, standard_prefixes: true, logger: options[:logger]) end elsif options[:output_format] == :ntriples || options[:quiet] reader_class.new(input, options[:parser_options]).each do |statement| @@ -39,16 +45,25 @@ def run(input, options) options[:output].puts statement.to_ntriples end end + elsif options[:output_format] == :sxp + reader_class.new(input, options[:parser_options]) do |reader| + 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| num += 1 options[:output].puts statement.inspect end else - r = reader_class.new(input, options[:parser_options]) - g = RDF::Graph.new << r - num = g.count - options[:output].puts g.dump(options[:output_format], base_uri: options[:base_uri], prefixes: r.prefixes, standard_prefixes: true) + 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]) + end + if options[:profile] + Profiler__::stop_profile + Profiler__::print_profile($stderr) end puts secs = Time.new - start @@ -72,41 +87,77 @@ parser_options = { options = { parser_options: parser_options, + logger: logger, output: STDOUT, - output_format: :ntriples, + output_format: :n3, input_format: :n3, } input = nil -opts = GetoptLong.new( - ["--dbg", GetoptLong::NO_ARGUMENT], - ["--errors", GetoptLong::NO_ARGUMENT], - ["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT], - ["--canonicalize", GetoptLong::NO_ARGUMENT], - ["--format", GetoptLong::REQUIRED_ARGUMENT], - ["--input-format", GetoptLong::REQUIRED_ARGUMENT], - ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT], - ["--profile", GetoptLong::NO_ARGUMENT], - ["--progress", GetoptLong::NO_ARGUMENT], - ["--quiet", GetoptLong::NO_ARGUMENT], - ["--uri", GetoptLong::REQUIRED_ARGUMENT], - ["--validate", GetoptLong::NO_ARGUMENT], - ["--verbose", GetoptLong::NO_ARGUMENT] -) +OPT_ARGS = [ + ["--apply", GetoptLong::REQUIRED_ARGUMENT, "Apply rules from specified file"], + ["--conclusions", GetoptLong::NO_ARGUMENT, "Remove all except conclusions"], + ["--canonicalize", GetoptLong::NO_ARGUMENT, "Canonize all terms"], + ["--data", GetoptLong::NO_ARGUMENT, "Remove all except plain RDF triples (formulae, forAll, etc)v"], + ["--debug", GetoptLong::NO_ARGUMENT, "Debugging output"], + ["--errors", GetoptLong::NO_ARGUMENT, "Display invalid statements"], + ["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT, "Run against source in argument"], + ["--format", GetoptLong::REQUIRED_ARGUMENT, "Output format, any RDF format symbol, sxp, or inspect"], + ["--help", "-?", GetoptLong::NO_ARGUMENT, "print this message"], + ["--input-format", GetoptLong::REQUIRED_ARGUMENT, "Format of the input file, defaults to n3"], + ["--info", GetoptLong::NO_ARGUMENT, "Show progress on execution"], + ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT, "Save output to file"], + ["--profile", GetoptLong::NO_ARGUMENT, "Show an execution profile"], + ["--quiet", GetoptLong::NO_ARGUMENT, "Do not show parser output"], + ["--rules", GetoptLong::NO_ARGUMENT, "Run rules adding to the store"], + ["--think", GetoptLong::NO_ARGUMENT, "Run rules until until no more triples generated"], + ["--uri", GetoptLong::REQUIRED_ARGUMENT, "Default base URI"], + ["--validate", GetoptLong::NO_ARGUMENT, "Run parser in strict validation mode"], + #["--verbose", GetoptLong::NO_ARGUMENT, "Verbose output"], +] + +def usage + STDERR.puts %{ + n3 version #{RDF::N3::VERSION} + Exersize N3 parser/reasoner + + Usage: #{$0} [options] file ... + }.gsub(/^ /, '') + width = OPT_ARGS.map do |o| + l = o.first.length + l += o[1].length + 2 if o[1].is_a?(String) + l + end.max + OPT_ARGS.each do |o| + s = " %-*s " % [width, (o[1].is_a?(String) ? "#{o[0,2].join(', ')}" : o[0])] + s += o.last + STDERR.puts s + end + exit(1) +end + +opts = GetoptLong.new(*OPT_ARGS.map {|o| o[0..-2]}) + opts.each do |opt, arg| case opt - when '--dbg' then logger.level = Logger::DEBUG + when '--apply' then # Read rules + when '--conclusions' then options[:conclusions] = true when '--canonicalize' then parser_options[:canonicalize] = true + when "--data" then options[:data] = true + when '--debug' then logger.level = Logger::DEBUG when '--errors' then options[:errors] = true when '--execute' then input = arg when '--format' then options[:output_format] = arg.to_sym + when "--help" then usage() + when '--info' then logger.level = Logger::INFO when '--input-format' then options[:input_format] = arg.to_sym when '--output' then options[:output] = File.open(arg, "w") when '--profile' then options[:profile] = true - when '--progress' then logger.level = Logger::INFO when '--quiet' options[:quiet] = true logger.level = Logger::FATAL + when '--rules' then options[:rules] = true + when '--think' then options[:think] = true when '--uri' then parser_options[:base_uri] = arg when '--validate' then parser_options[:debug] ||= 1 when '--verbose' then $verbose = true diff --git a/spec/format_spec.rb b/spec/format_spec.rb index a142891..e008b9d 100644 --- a/spec/format_spec.rb +++ b/spec/format_spec.rb @@ -1,5 +1,4 @@ -$:.unshift "." -require File.join(File.dirname(__FILE__), 'spec_helper') +require_relative 'spec_helper' require 'rdf/spec/format' describe RDF::N3::Format do diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 2c3d3dc..78be377 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -1,7 +1,7 @@ # coding: utf-8 -$:.unshift "." -require File.join(File.dirname(__FILE__), 'spec_helper') +require_relative 'spec_helper' require 'rdf/spec/reader' +require 'rdf/trig' describe "RDF::N3::Reader" do let!(:doap) {File.expand_path("../../etc/doap.n3", __FILE__)} @@ -66,7 +66,7 @@ #_:abcd a po:Episode. EOF end - + it "should yield reader" do inner = double("inner") expect(inner).to receive(:called).with(RDF::N3::Reader) @@ -74,11 +74,11 @@ inner.called(reader.class) end end - + it "should return reader" do expect(RDF::N3::Reader.new(@sampledoc)).to be_a(RDF::N3::Reader) end - + it "should yield statements" do inner = double("inner") expect(inner).to receive(:called).with(RDF::Statement).exactly(15) @@ -86,7 +86,7 @@ inner.called(statement.class) end end - + it "should yield triples" do inner = double("inner") expect(inner).to receive(:called).exactly(15) @@ -103,11 +103,11 @@ @graph = parse(n3_string) @statement = @graph.statements.first end - + it "should have a single triple" do expect(@graph.size).to eq 1 end - + it "should have subject" do expect(@statement.subject.to_s).to eq "http://example.org/" end @@ -118,7 +118,7 @@ expect(@statement.object.to_s).to eq "Gregg Kellogg" end end - + # NTriple tests from http://www.w3.org/2000/10/rdf-tests/rdfcore/ntriples/test.nt describe "with blank lines" do { @@ -153,7 +153,7 @@ expect(statement.object.value).to eq contents end end - + { 'Dürst' => ' "Dürst" .', "é" => ' "é" .', @@ -167,13 +167,13 @@ expect(statement.object.value).to eq contents end end - + it "should parse long literal with escape" do n3 = %(@prefix : . :a :b "\\U00015678another" .) statement = parse(n3).statements.first expect(statement.object.value).to eq "\u{15678}another" end - + context "string3 literals" do { "simple" => %q(foo), @@ -255,16 +255,16 @@ "XML Literals as Datatyped Literals (7)" => ' "a c"^^ .', "XML Literals as Datatyped Literals (8)" => ' "a\n\nc"^^ .', "XML Literals as Datatyped Literals (9)" => ' "chat"^^ .', - + "Plain literals with languages (1)" => ' "chat"@fr .', "Plain literals with languages (2)" => ' "chat"@en .', - + "Typed Literals" => ' "abc"^^ .', }.each_pair do |name, statement| specify "test #{name}" do graph = parse([statement].flatten.first) g2 = RDF::NTriples::Reader.new([statement].flatten.last) - expect(graph).to be_equivalent_graph(g2, about: "http://a/b", logger: logger) + expect(graph).to be_equivalent_graph(g2, about: "http://a/b", logger: logger, format: :n3) end end @@ -295,7 +295,7 @@ %(<#D%C3%BCrst> a "URI percent ^encoded as C3, BC".) => %( "URI percent ^encoded as C3, BC" .), }.each_pair do |n3, nt| it "for '#{n3}'" do - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end @@ -306,7 +306,7 @@ %(:alice :resumé "Alice's normalized resumé".) => ' "Alice\'s normalized resumé" .', }.each_pair do |n3, nt| it "for '#{n3}'" do - expect(parse(n3, base_uri: "http://a/b", logger: false)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b", logger: false)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end @@ -315,11 +315,11 @@ #%(:a :related :ひらがな .) => %( .), }.each_pair do |n3, nt| it "for '#{n3}'" do - expect(parse(n3, base_uri: "http://a/b", logger: false)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b", logger: false)).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end end - + it "should create URIs" do n3doc = " ." statement = parse(n3doc).statements.first @@ -333,7 +333,7 @@ expect(statement.object).to be_literal end end - + describe "with n3 grammar" do describe "syntactic expressions" do it "should create typed literals with qname" do @@ -352,80 +352,96 @@ nt = %( . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should use <#> as a prefix and as a triple node" do n3 = %(@prefix : <#> . <#> a :a.) nt = %( . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should generate rdf:type for 'a'" do n3 = %(@prefix a: . a:b a .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should generate rdf:type for '@a'" do n3 = %(@prefix a: . a:b @a .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should generate inverse predicate for 'is xxx of'" do n3 = %("value" is :prop of :b . :b :prop "value" .) nt = %( "value" .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should generate inverse predicate for '@is xxx @of'" do n3 = %("value" @is :prop @of :b . :b :prop "value" .) nt = %( "value" .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should generate inverse predicate for 'is xxx of' with object list" do n3 = %("value" is :prop of :b, :c . ) nt = %( "value" . "value" . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) + end + + it "should generate inverse predicate for 'is xxx of' with blankNodePropertyList" do + n3 = %([ is :prop of :George]) + nt = %( + _:bn . + ) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) + end + + it "should generate inverse predicate for 'is xxx of' with bnode" do + n3 = %(_:bn is :prop of :George.) + nt = %( + _:bn . + ) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should generate predicate for 'has xxx'" do n3 = %(@prefix a: . a:b has :pred a:c .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should generate predicate for '@has xxx'" do n3 = %(@prefix a: . a:b @has :pred a:c .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should create log:implies predicate for '=>'" do n3 = %(@prefix a: . _:a => a:something .) nt = %(_:a .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should create log:implies inverse predicate for '<='" do n3 = %(@prefix a: . _:a <= a:something .) nt = %( _:a .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should create owl:sameAs predicate for '='" do n3 = %(@prefix a: . _:a = a:something .) nt = %(_:a .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + { %(:a :b @true) => %( "true"^^ .), %(:a :b @false) => %( "false"^^ .), @@ -440,22 +456,24 @@ }.each_pair do |n3, nt| it "should create typed literal for '#{n3}'" do expected = RDF::NTriples::Reader.new(nt) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end end - + it "should accept empty localname" do n3 = %(: : : .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should accept prefix with empty local name" do n3 = %(@prefix foo: . foo: foo: foo: .) nt = %( .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + end + + context "patterns" do it "substitutes variable for URI with @forAll" do n3 = %(@forAll :x . :x :y :z .) g = parse(n3, base_uri: "http://a/b") @@ -477,35 +495,35 @@ expect(statement.predicate).not_to equal statement.object end - it "substitutes node for URI with @forEach" do + it "substitutes node for URI with @forSome" do n3 = %(@forSome :x . :x :y :z .) g = parse(n3, base_uri: "http://a/b") statement = g.statements.first - expect(statement.subject).to be_node + expect(statement.subject).to be_variable, logger.to_s expect(statement.predicate.to_s).to eq "http://a/b#y" expect(statement.object.to_s).to eq "http://a/b#z" end - it "substitutes node for URIs with @forEach" do + it "substitutes node for URIs with @forSome" do n3 = %(@forSome :x, :y, :z . :x :y :z .) g = parse(n3, base_uri: "http://a/b") statement = g.statements.first - expect(statement.subject).to be_node - expect(statement.predicate).to be_node - expect(statement.object).to be_node + expect(statement.subject).to be_variable + expect(statement.predicate).to be_variable + expect(statement.object).to be_variable expect(statement.subject).not_to equal statement.predicate expect(statement.object).not_to equal statement.predicate expect(statement.predicate).not_to equal statement.object end end - + describe "prefixes" do it "should not append # for http://foo/bar" 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) + 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 @@ -513,7 +531,7 @@ nt = %( . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should not append # for http://foo/bar#" do @@ -521,7 +539,7 @@ nt = %( . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should set absolute base" do @@ -530,27 +548,27 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should set absolute base (trailing /)" do 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) + 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 = %( . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should set relative base" do n3 = %( @base . @@ -568,9 +586,9 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "returns defined prefixes" do n3 = %( @prefix rdf: . @@ -580,7 +598,7 @@ :bar :d :c. :a :d :c. ) - reader = RDF::N3::Reader.new(n3) + reader = RDF::N3::Reader.new(n3, validate: true) reader.each {|statement|} expect(reader.prefixes).to eq({ rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", @@ -589,7 +607,7 @@ }) end end - + describe "keywords" do [ %(base <>.), @@ -605,7 +623,7 @@ end.to raise_error(RDF::ReaderError) end end - + [ %(prefix :<>.), ].each do |n3| @@ -624,7 +642,7 @@ %(:c :a t) => %( .), }.each_pair do |n3, nt| it "should use default_ns for '#{n3}'" do - expect(parse("@keywords . #{n3}", base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse("@keywords . #{n3}", base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end @@ -638,10 +656,10 @@ } .each_pair do |n3, nt| it "should use keyword for '#{n3}'" do expected = RDF::NTriples::Reader.new(nt) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end end - + it "should raise error if unknown keyword set" do n3 = %(@keywords foo.) expect do @@ -649,7 +667,7 @@ end.to raise_error(RDF::ReaderError, /Undefined keywords used: foo/) end end - + describe "declaration ordering" do it "should process _ namespace binding after an initial use as a BNode" do n3 = %( @@ -661,9 +679,9 @@ . _:a . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should allow a prefix to be redefined" do n3 = %( @prefix a: . @@ -676,7 +694,7 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should process sequential @base declarations (swap base.n3)" do @@ -690,50 +708,50 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end - + describe "BNodes" do it "should create BNode for identifier with '_' prefix" do n3 = %(@prefix a: . _:a a:p a:v .) nt = %(_:bnode0 .) g = parse(n3, base_uri: "http://a/b") - expect(g).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(g).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should create BNode for [] as subject" do n3 = %(@prefix a: . [] a:p a:v .) nt = %(_:bnode0 .) g = parse(n3, base_uri: "http://a/b") - expect(g).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(g).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should create BNode for [] as predicate" do n3 = %(@prefix a: . a:s [] a:o .) g = parse(n3, base_uri: "http://a/b") st = g.statements.first expect(st.predicate).to be_a_node end - + it "should create BNode for [] as object" do n3 = %(@prefix a: . a:s a:p [] .) nt = %( _:bnode0 .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should create BNode for [] as statement" do n3 = %([:a :b] .) nt = %(_:bnode0 .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should create statements with BNode subjects using [ pref obj]" do n3 = %(@prefix a: . [ a:p a:v ] .) nt = %(_:bnode0 .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should create BNode as a single object" do n3 = %(@prefix a: . a:b a:oneRef [ a:pp "1" ; a:qq "2" ] .) nt = %( @@ -741,9 +759,9 @@ _:bnode0 "2" . _:bnode0 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should create a shared BNode" do n3 = %( @prefix a: . @@ -761,9 +779,9 @@ _:a :pred _:bnode0 . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end - + it "should create nested BNodes" do n3 = %( @prefix a: . @@ -778,7 +796,7 @@ _:bnode1 "v4" . _:bnode1 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end describe "from paths" do @@ -786,16 +804,16 @@ n3 = %(:x2!:y2 :p2 "3" .) nt = %(:x2 :y2 _:bnode0 ._:bnode0 :p2 "3" .) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end - + it "should create bnode for path x^p" do n3 = %(:x2^:y2 :p2 "3" .) nt = %(_:bnode0 :y2 :x2 . _:bnode0 :p2 "3" .) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end - + it "should decode :joe!fam:mother!loc:office!loc:zip as Joe's mother's office's zipcode" do n3 = %( @prefix fam: . @@ -809,7 +827,7 @@ _:bnode1 _:bnode2 . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end it "should decode :joe!fam:mother^fam:mother Anyone whose mother is Joe's mother." do @@ -824,7 +842,7 @@ _:bnode1 _:bnode0 . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end it "should decode path with property list." do @@ -840,7 +858,7 @@ _:bnode1 :q2 "5" . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end it "should decode path as object(1)" do @@ -850,7 +868,7 @@ _:bnode :c "lit" . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end it "should decode path as object(2)" do @@ -861,30 +879,65 @@ :r :p _:bnode1 . ) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end end end - + describe "formulae" do before(:each) { @repo = RDF::Repository.new } - it "creates an RDF::Graph instance for formula" do + it "creates an RDF::Node instance for formula" do n3 = %(:a :b {}) - parse(n3, graph: @repo, base_uri: "http://a/b") - statement = @repo.statements.first - expect(statement.object).to be_node + nq = %(:a :b _:c .) + result = parse(n3, repo: @repo, base_uri: "http://a/b") + expected = parse(nq, repo: @repo, base_uri: "http://a/b") + expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) end - it "adds statements with graph_name" + it "adds statements with graph_name" do + n3 = %(:a :b {[:c :d]}) + trig = %(<#a> <#b> _:c . _:c {[<#c> <#d>] .}) + result = parse(n3, repo: @repo, base_uri: "http://a/b") + expected = RDF::Repository.new {|r| r << RDF::TriG::Reader.new(trig, base_uri: "http://a/b")} + expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) + end + + it "creates quads for patterns when added to a repository" do + n3 = %( + @prefix x: . + @prefix log: . + @prefix dc: . + { [ x:firstname "Ora" ] dc:wrote [ dc:title "Moby Dick" ] } a log:falsehood . + ) + trig = %( + @prefix x: . + @prefix log: . + @prefix dc: . + _:f a log:falsehood . + _:f { + [ x:firstname "Ora" ] dc:wrote [ dc:title "Moby Dick" ] . + } + ) + result = parse(n3, repo: @repo, base_uri: "http://a/b") + expected = RDF::Repository.new {|r| r << RDF::TriG::Reader.new(trig, base_uri: "http://a/b")} + expect(result).to be_equivalent_graph(expected, logger: logger, format: :n3) + end + + it "creates unique bnodes within different formula" do + n3 = %( + _:a a :Thing . + {_:a a :Thing} => {_:a a :Thing} + ) + result = parse(n3, repo: @repo, base_uri: "http://a/b") + expect(result.statements.uniq.length).to eq 4 + end - it "creates variables with ?" - context "contexts" do before(:each) do n3 = %( # Test data in notation3 http://www.w3.org/DesignIssues/Notation3.html - # + # @prefix u: . @prefix : <#> . @@ -901,7 +954,7 @@ # ENDS ) @repo = RDF::Repository.new - parse(n3, graph: @repo, base_uri: "http://a/b") + parse(n3, repo: @repo, base_uri: "http://a/b") end it "assumption graph has 2 statements" do @@ -926,16 +979,16 @@ end end - + describe "object lists" do it "should create 2 statements for simple list" do n3 = %(:a :b :c, :d) nt = %( . .) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end end - + describe "property lists" do it "should parse property list" do n3 = %( @@ -950,18 +1003,18 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end - + 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) + 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 = %( @@ -969,9 +1022,9 @@ _:bnode0 . _:bnode0 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should parse list with multiple elements" do n3 = %(@prefix :. :gregg :name ("Gregg" "Barnum" "Kellogg").) nt = %( @@ -983,9 +1036,9 @@ _:bnode2 . _:bnode0 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should parse unattached lists" do n3 = %( @prefix a: . @@ -1002,20 +1055,20 @@ _:bnode2 "3" . _:bnode2 . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end - + it "should add property to nil list" do n3 = %(@prefix a: . () a:prop "nilProp" .) nt = %( "nilProp" .) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end it "should parse with compound items" do n3 = %( @prefix a: . a:a a:p ( - [ a:p2 "v1" ] + [ a:p2 "v1" ] ("inner list") @@ -1048,9 +1101,9 @@ expect(seq.third).to eq RDF::URI.new("http://resource2") expect(seq.fourth).to be_node end - + end - + # n3p tests taken from http://inamidst.com/n3p/test/ describe "with real data tests" do dirs = %w(misc lcsh rdflib n3p) @@ -1080,10 +1133,10 @@ . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end - + describe "with blank clause" do it "should have 4 namespaces" do n3 = %( @@ -1101,10 +1154,10 @@ _:g2160128180 . . ) - expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b")).to be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) end end - + describe "with empty subject" do before(:each) do @graph = parse(%( @@ -1115,14 +1168,14 @@ <> a log:N3Document. ), base_uri: "http://test/") end - + it "should have 4 namespaces" do nt = %( . ) - expect(@graph).to be_equivalent_graph(nt, about: "http://test/", logger: logger) + expect(@graph).to be_equivalent_graph(nt, about: "http://test/", logger: logger, format: :n3) end - + it "should have default subject" do expect(@graph.size).to eq 1 expect(@graph.statements.first.subject.to_s).to eq "http://test/" @@ -1166,7 +1219,7 @@ # it "returns subject #{result} given #{input}" do # n3 = %(#{input} a :b) # nt = %(#{result} .) -# parse(n3, base_uri: "http://a/b", canonicalize: true).should be_equivalent_graph(nt, about: "http://a/b", logger: logger) +# parse(n3, base_uri: "http://a/b", canonicalize: true).should be_equivalent_graph(nt, about: "http://a/b", logger: logger, format: :n3) # end # end @@ -1180,11 +1233,11 @@ n3 = %(@prefix xsd: . :a :b #{input} .) nt = %( #{result} .) expected = RDF::Graph.new {|g| g << RDF::N3::Reader.new(nt, base_uri: "http://a/b")} - expect(parse(n3, base_uri: "http://a/b", canonicalize: true)).to be_equivalent_graph(expected, about: "http://a/b", logger: logger) + expect(parse(n3, base_uri: "http://a/b", canonicalize: true)).to be_equivalent_graph(expected, about: "http://a/b", logger: logger, format: :n3) end end end - + describe "validation" do { %(:y :p1 "xyz"^^xsd:integer .) => %r("xyz" is not a valid .*), @@ -1203,7 +1256,7 @@ end end end - + it "should parse rdf_core testcase" do sampledoc = <<-EOF; . @@ -1219,21 +1272,22 @@ expect(graph).to be_equivalent_graph(sampledoc, about: "http://www.w3.org/2000/10/rdf-tests/rdfcore/amp-in-url/Manifest.rdf", - logger: logger + logger: logger, + format: :n3 ) end - - def parse(input, options = {}) + + def parse(input, **options) options = { logger: logger, validate: false, canonicalize: false, }.merge(options) - graph = options[:graph] || RDF::Graph.new - RDF::N3::Reader.new(input, options).each do |statement| - graph << statement + repo = options[:repo] || RDF::Repository.new + RDF::N3::Reader.new(input, **options).each_statement do |statement| + repo << statement end - graph + repo end def test_file(filepath) @@ -1243,6 +1297,7 @@ def test_file(filepath) nt_string = File.read(filepath.sub('.n3', '.nt')) expect(@graph).to be_equivalent_graph(nt_string, about: "file:#{filepath}", - logger: logger) + logger: logger, + format: :n3) end end diff --git a/spec/reasoner_spec.rb b/spec/reasoner_spec.rb new file mode 100644 index 0000000..e9626e6 --- /dev/null +++ b/spec/reasoner_spec.rb @@ -0,0 +1,135 @@ +# coding: utf-8 +require_relative 'spec_helper' +require 'rdf/trig' + +describe "RDF::N3::Reasoner" do + let(:logger) {RDF::Spec.logger} + + context "n3:log" do + context "log:implies" do + { + "r1" => { + input: %( + @forAll :a, :b, :c, :x, :y, :z. + ( "one" "two" ) a :whatever. + { (:a :b) a :whatever } log:implies { :a a :SUCCESS. :b a :SUCCESS }. + ), + expect: %( + ( "one" "two" ) a :whatever. + "one" a :SUCCESS. + "two" a :SUCCESS. + ) + }, + "unify2" => { + input: %( + ( 17 ) a :TestCase. + { ( ?x ) a :TestCase} => { ?x a :RESULT }. + ), + expect: %( + ( 17 ) a :TestCase. + 17 a :RESULT. + ) + }, + "unify3" => { + input: %( + ( ( 17 ) ) a :TestCase. + { ( ( ?x ) ) a :TestCase} => { ?x a :RESULT }. + { 17 a :RESULT } => { :THIS_TEST a :SUCCESS }. + ), + expect: %( + ( ( 17 ) ) a :TestCase. + 17 a :RESULT. + :THIS_TEST a :SUCCESS. + ) + }, + "double" => { + input: %( + @keywords is, of, a. + dan a Man; home []. + { ?WHO home ?WHERE. ?WHERE in ?REGION } => { ?WHO homeRegion ?REGION }. + { dan home ?WHERE} => {?WHERE in Texas} . + ), + expect: %( + :dan a :Man; + :home [:in :Texas ]; + :homeRegion :Texas . + ), + }, + "single_gen" => { + input: %( + :a :b "A noun", 3.14159265359 . + {:a :b ?X} => { [ a :Thing ] } . + ), + expect: %( + :a :b "A noun", 3.14159265359 . + [ a :Thing] + ) + }, + "uses variables bound in parent" => { + input: %( + :a :b :c. + ?x :b :c. # ?x bound to :a + {:a :b :c} => {?x :d :e}. + ), + expect: %( + :a :b :c; :d :e. + ) + } + }.each do |name, options| + it name do + result = parse(options[:expect]) + expect(reason(options[:input])).to be_equivalent_graph(result, logger: logger) + end + end + end + end + + context "n3:string" do + context "string:startsWith" do + { + "literal starts with literal" => { + input: %( + @prefix string: . + :a :b :c . + {"abc" string:startsWith "a"} => {:test a :Success}. + ), + expect: %( + :a :b :c . + :test a :Success. + ) + } + }.each do |name, options| + it name do + result = parse(options[:expect]) + expect(reason(options[:input])).to be_equivalent_graph(result, logger: logger) + end + end + end + end + + # Parse N3 input into a repository + def parse(input, **options) + repo = options[:repo] || RDF::Repository.new + RDF::N3::Reader.new(input, **options).each_statement do |statement| + repo << statement + end + repo + end + + # Reason over input, returning a repo + def reason(input, base_uri: 'http://example.com/', filter: false, data: true, think: true, **options) + input = parse(input, **options) if input.is_a?(String) + reasoner = RDF::N3::Reasoner.new(input, base_uri: base_uri) + repo = RDF::Repository.new + + reasoner.execute(logger: logger, think: think) + if filter + repo << reasoner.conclusions + elsif data + repo << reasoner.data + else + repo << reasoner + end + repo + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fb11ee6..1df5aa0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,20 +3,13 @@ require "bundler/setup" require 'rspec' -require 'bigdecimal' # XXX Remove Me +require 'matchers' +require 'rdf/isomorphic' require 'rdf/n3' require 'rdf/ntriples' require 'rdf/spec' require 'rdf/spec/matchers' -require 'matchers' -require 'rdf/isomorphic' require 'yaml' # XXX should be in open-uri/cached -require 'open-uri/cached' - -# Create and maintain a cache of downloaded URIs -URI_CACHE = File.expand_path(File.join(File.dirname(__FILE__), "uri-cache")) -Dir.mkdir(URI_CACHE) unless File.directory?(URI_CACHE) -OpenURI::Cache.class_eval { @cache_path = URI_CACHE } ::RSpec.configure do |c| c.filter_run focus: true @@ -26,6 +19,12 @@ } end +module RDF + module Isomorphic + alias_method :==, :isomorphic_with? + end +end + # Heuristically detect the input stream def detect_format(stream) # Got to look into the file to see diff --git a/spec/suite_helper.rb b/spec/suite_helper.rb index 9a8d799..18c826b 100644 --- a/spec/suite_helper.rb +++ b/spec/suite_helper.rb @@ -1,48 +1,144 @@ require 'rdf/n3' require 'json/ld' +# For now, override RDF::Utils::File.open_file to look for the file locally before attempting to retrieve it +module RDF::Util + module File + REMOTE_PATH = "https://w3c.github.io/n3/" + LOCAL_PATH = ::File.expand_path("../w3c-n3", __FILE__) + '/' + + class << self + alias_method :original_open_file, :open_file + end + + ## + # Override to use Patron for http and https, Kernel.open otherwise. + # + # @param [String] filename_or_url to open + # @param [Hash{Symbol => Object}] options + # @option options [Array, String] :headers + # HTTP Request headers. + # @return [IO] File stream + # @yield [IO] File stream + def self.open_file(filename_or_url, options = {}, &block) + case + when filename_or_url.to_s =~ /^file:/ + path = filename_or_url[5..-1] + Kernel.open(path.to_s, options, &block) + when (filename_or_url.to_s =~ %r{^#{REMOTE_PATH}} && Dir.exist?(LOCAL_PATH)) + #puts "attempt to open #{filename_or_url} locally" + localpath = filename_or_url.to_s.sub(REMOTE_PATH, LOCAL_PATH) + response = begin + ::File.open(localpath) + rescue Errno::ENOENT => e + raise IOError, e.message + end + document_options = { + base_uri: RDF::URI(filename_or_url), + charset: Encoding::UTF_8, + code: 200, + headers: {} + } + #puts "use #{filename_or_url} locally" + document_options[:headers][:content_type] = case filename_or_url.to_s + when /\.ttl$/ then 'text/turtle' + when /\.n3$/ then 'text/n3' + when /\.nt$/ then 'application/n-triples' + when /\.jsonld$/ then 'application/ld+json' + else 'unknown' + end + + document_options[:headers][:content_type] = response.content_type if response.respond_to?(:content_type) + # For overriding content type from test data + document_options[:headers][:content_type] = options[:contentType] if options[:contentType] + + remote_document = RDF::Util::File::RemoteDocument.new(response.read, document_options) + if block_given? + yield remote_document + else + remote_document + end + else + original_open_file(filename_or_url, options, &block) + end + end + end +end + module Fixtures module SuiteTest - BASE = "http://www.w3.org/2000/10/swap/test/" - CONTEXT = JSON.parse(%q({ - "@vocab": "http://www.w3.org/2004/11/n3test#", - - "inputDocument": {"@type": "@id"}, - "outputDocument": {"@type": "@id"} + FRAME = JSON.parse(%q({ + "@context": { + "xsd": "http://www.w3.org/2001/XMLSchema#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "mf": "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#", + "mq": "http://www.w3.org/2001/sw/DataAccess/tests/test-query#", + "rdft": "http://www.w3.org/ns/rdftest#", + "test": "https://w3c.github.io/n3/tests/test.n3#", + "comment": "rdfs:comment", + "entries": {"@id": "mf:entries", "@container": "@list"}, + "name": "mf:name", + "action": {"@id": "mf:action", "@type": "@id"}, + "result": {"@id": "mf:result", "@type": "@id"}, + "options": {"@id": "test:options", "@type": "@id"}, + "data": {"@id": "test:data", "@type": "xsd:boolean"}, + "think": {"@id": "test:think", "@type": "xsd:boolean"}, + "filter": {"@id": "test:filter", "@type": "xsd:boolean"}, + "rules": {"@id": "test:rules", "@type": "xsd:boolean"} + }, + "@type": "mf:Manifest", + "entries": { + "@type": [ + "test:TestN3Reason", + "test:TestN3Eval", + "test:TestN3PositiveSyntax", + "test:TestN3NegativeSyntax" + ] + } })) - - class Entry < JSON::LD::Resource + + class Manifest < JSON::LD::Resource def self.open(file) #puts "open: #{file}" - prefixes = {} - g = RDF::Repository.load(file, format: :n3) + g = RDF::Repository.load(file, format: :n3) JSON::LD::API.fromRDF(g) do |expanded| - JSON::LD::API.compact(expanded, CONTEXT) do |doc| - doc['@graph'].each {|r| yield Entry.new(r) if r['@type']} + JSON::LD::API.frame(expanded, FRAME) do |framed| + yield Manifest.new(framed['@graph'].first) end end end + + def entries + # Map entries to resources + attributes['entries'].map {|e| Entry.new(e)} + end + end + + class Entry < JSON::LD::Resource attr_accessor :logger + # For debug output formatting + def format; :n3; end + def base - inputDocument + action end def name - base.to_s.split('/').last.sub('.n3', '') + id.to_s.split('#').last end # Alias data and query def input - RDF::Util::File.open_file(inputDocument) + @input ||= RDF::Util::File.open_file(action) {|f| f.read} end def expected - RDF::Util::File.open_file(outputDocument) + @expected ||= RDF::Util::File.open_file(result) {|f| f.read} end def positive_test? - !attributes['@type'].match(/Negative/) + !attributes['@type'].to_s.match(/Negative/) end def negative_test? @@ -50,16 +146,27 @@ def negative_test? end def evaluate? - !syntax? + !!attributes['@type'].to_s.match(/Eval/) + end + + def reason? + !!attributes['@type'].to_s.match(/Reason/) end def syntax? - !outputDocument + !!attributes['@type'].to_s.match(/Syntax/) + end + + def reason? + !!attributes['@type'].to_s.match(/Reason/) end def inspect super.sub('>', "\n" + " positive?: #{positive_test?.inspect}\n" + + " syntax?: #{syntax?.inspect}\n" + + " eval?: #{evaluate?.inspect}\n" + + " reason?: #{reason?.inspect}\n" + ">" ) end diff --git a/spec/suite_parser_spec.rb b/spec/suite_parser_spec.rb new file mode 100644 index 0000000..d3402f2 --- /dev/null +++ b/spec/suite_parser_spec.rb @@ -0,0 +1,68 @@ +require_relative 'spec_helper' +require 'rdf/trig' # For formatting error descriptions + +describe RDF::N3::Reader do + # W3C N3 Test suite from http://www.w3.org/2000/10/swap/test/n3parser.tests + describe "w3c n3 tests" do + require_relative 'suite_helper' + + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-parser.n3") 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") + end + + t.logger = RDF::Spec.logger + t.logger.info t.inspect + t.logger.info "source:\n#{t.input}" + + reader = RDF::N3::Reader.new(t.input, + base_uri: t.base, + canonicalize: false, + validate: true, + logger: t.logger) + + repo = RDF::Repository.new + + 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.logger) + end + end + + if t.positive_test? + begin + repo << reader + rescue Exception => e + expect(e.message).to produce("Not exception #{e.inspect}", t.logger) + end + + if t.evaluate? + expect(repo).to be_equivalent_graph(output_repo, t) + else + expect(repo).to be_enumerable + end + elsif t.syntax? + expect { + repo << reader + repo.dump(:nquads).should produce("not this", t.logger) + }.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_reasoner_spec.rb b/spec/suite_reasoner_spec.rb new file mode 100644 index 0000000..c4ac34e --- /dev/null +++ b/spec/suite_reasoner_spec.rb @@ -0,0 +1,76 @@ +require_relative 'spec_helper' +require 'rdf/trig' # For formatting error descriptions + +describe RDF::N3::Reader do + # W3C N3 Test suite from http://www.w3.org/2000/10/swap/test/n3parser.tests + describe "w3c n3 tests" do + require_relative 'suite_helper' + + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-reasoner.n3") do |m| + describe m.label do + m.entries.each do |t| + specify "#{t.name}: #{t.comment}" do + case t.id.split('#').last + when *%w{listin bnode concat t2006} + pending "support for lists" + when *%w{t1018b2 t103 t104 t105 concat} + pending "support for string" + when *%w{t06proof} + 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" + end + + t.logger = RDF::Spec.logger + t.logger.info t.inspect + t.logger.info "source:\n#{t.input}" + + reader = RDF::N3::Reader.new(t.input, + base_uri: t.base, + canonicalize: false, + validate: false) + + reasoner = RDF::N3::Reasoner.new(reader, + base_uri: t.base, + logger: t.logger) + + repo = RDF::Repository.new + + if t.positive_test? + begin + reasoner.execute(logger: t.logger, think: !!t.options['think']) + if t.options["filter"] + repo << reasoner.conclusions + elsif t.options["data"] + repo << reasoner.data + else + repo << reasoner + end + rescue Exception => e + expect(e.message).to produce("Not exception #{e.inspect}: #{e.backtrace.join("\n")}", t) + end + if t.evaluate? || t.reason? + output_repo = RDF::Repository.load(t.result, format: :n3, base_uri: t.base) + expect(repo).to be_equivalent_graph(output_repo, t) + else + end + else + expect { + graph << reader + expect(graph.dump(:nquads)).to produce("not this", t) + }.to raise_error(RDF::ReaderError) + end + end + end + end + end + end +end unless ENV['CI'] \ No newline at end of file diff --git a/spec/swap b/spec/swap new file mode 120000 index 0000000..3978bda --- /dev/null +++ b/spec/swap @@ -0,0 +1 @@ +../../swap/ \ No newline at end of file diff --git a/spec/swap_spec.rb b/spec/swap_spec.rb deleted file mode 100644 index 079b9af..0000000 --- a/spec/swap_spec.rb +++ /dev/null @@ -1,69 +0,0 @@ -$:.unshift "." -require File.join(File.dirname(__FILE__), 'spec_helper') - -describe RDF::N3::Reader do - # W3C N3 Test suite from http://www.w3.org/2000/10/swap/test/n3parser.tests - describe "w3c swap tests" do - require 'suite_helper' - - %w(n3parser.tests).each do |man| - Fixtures::SuiteTest::Entry.open(Fixtures::SuiteTest::BASE + man) do |t| - #next unless t.subject.to_s =~ /rdfms-rdf-names-use/ - #next unless t.name =~ /11/ - #puts t.inspect - next if %w(keywords1 keywords2 n3parser.tests contexts strquot - numbers qvars1 qvars2 lists too-nested equals1).include?(t.name) - specify "#{t.name}: #{t.description}" do - case t.name - when 'n3_10012' - pending("Skip long input file") - when *%w(n3_10004 n3_10007 n3_10014 n3_10015 n3_10017) - pending("Formulae inferrence not supported") - when *%w(n3_10003 n3_10006 n3_10009) - pending("Verified test results are incorrect") - when *%w(n3_10008 n3_10013) - pending("Isomorphic compare issue") - else - t.logger = RDF::Spec.logger - t.logger.info t.inspect - t.logger.info "source:\n#{t.input}" - - 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.logger) - end - - if t.evaluate? - output_graph = begin - format = detect_format(t.outputDocument) - RDF::Repository.load(t.outputDocument, format: format, base_uri: t.inputDocument) - rescue Exception => e - expect(e.message).to produce("Not exception #{e.inspect}", t.logger) - end - - expect(graph).to be_equivalent_graph(output_graph, t) - else - expect(graph).to be_enumerable - end - else - expect { - graph << reader - graph.dump(:ntriples).should produce("not this", t.logger) - }.to raise_error(RDF::ReaderError) - end - end - end - end - end - end -end unless ENV['CI'] \ No newline at end of file diff --git a/spec/w3c-n3 b/spec/w3c-n3 new file mode 120000 index 0000000..149ed6a --- /dev/null +++ b/spec/w3c-n3 @@ -0,0 +1 @@ +../../w3c-n3 \ No newline at end of file diff --git a/spec/writer_spec.rb b/spec/writer_spec.rb index 302297a..270e2a2 100644 --- a/spec/writer_spec.rb +++ b/spec/writer_spec.rb @@ -1,7 +1,8 @@ # coding: utf-8 -$:.unshift "." -require File.join(File.dirname(__FILE__), 'spec_helper') +require_relative 'spec_helper' require 'rdf/spec/writer' +require 'rdf/vocab' +require 'rdf/trig' describe RDF::N3::Writer do let(:logger) {RDF::Spec.logger} @@ -27,301 +28,286 @@ end describe "simple tests" do - it "should use full URIs without base" do - input = %( .) - serialize(input, nil, [%r(^ \.$)]) - end - - it "should use relative URIs with base" do - input = %( .) - serialize(input, "http://a/", - [ %r(^@base \.$), - %r(^ \.$)] - ) - end - - it "should use qname URIs with prefix" do - input = %( .) - serialize(input, nil, - [%r(^@prefix foaf: \.$), - %r(^foaf:b foaf:c foaf:d \.$)], - prefixes: { foaf: "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should use qname URIs with empty prefix" do - input = %( .) - serialize(input, nil, - [%r(^@prefix : \.$), - %r(^:b :c :d \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - # see example-files/arnau-registered-vocab.rb - it "should use qname URIs with empty suffix" do - input = %( .) - serialize(input, nil, - [%r(^@prefix foaf: \.$), - %r(^foaf: foaf: foaf: \.$)], + { + "full URIs without base" => { + input: %( .), + regexp: [%r(^ \.$)], + }, + "relative URIs with base" => { + input: %( .), + regexp: [ %r(^@base \.$), %r(^ \.$)], + base_uri: "http://a/" + }, + "qname URIs with prefix" => { + input: %( .), + regexp: [ + %r(^@prefix ex: \.$), + %r(^ex:b ex:c ex:d \.$) + ], + prefixes: {ex: "http://example.com/"} + }, + "qname URIs with empty prefix" => { + input: %( .), + regexp: [ + %r(^@prefix : \.$), + %r(^:b :c :d \.$) + ], + prefixes: {"" => "http://example.com/"} + }, + # see example-files/arnau-registered-vocab.rb + "qname URIs with empty suffix" => { + input: %( .), + regexp: [ + %r(^@prefix foaf: \.$), + %r(^foaf: foaf: foaf: \.$) + ], prefixes: { "foaf" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should not use qname with illegal local part" do - input = %( - @prefix db: . - @prefix dbo: . - db:Michael_Jackson dbo:artistOf . - ) - - serialize(input, nil, - [%r(^@prefix db: \.$), - %r(^db:Michael_Jackson dbo:artistOf \.$)], + }, + "order properties" => { + input: %( + @prefix ex: . + @prefix dc: . + @prefix rdfs: . + ex:b ex:c ex:d . + ex:b dc:title "title" . + ex:b a ex:class . + ex:b rdfs:label "label" . + ), + regexp: [ + %r(^ex:b a ex:class;$), + %r(ex:class;\s+rdfs:label "label")m, + %r("label";\s+ex:c ex:d)m, + %r(ex:d;\s+dc:title "title" \.$)m + ], + }, + "object list" => { + input: %(@prefix ex: . ex:b ex:c ex:d, ex:e .), + regexp: [ + %r(^@prefix ex: \.$), + %r(^ex:b ex:c ex:[de],\s+ex:[de] \.$)m, + ], + }, + "property list" => { + input: %(@prefix ex: . ex:b ex:c ex:d; ex:e ex:f .), + regexp: [ + %r(^@prefix ex: \.$), + %r(^ex:b ex:c ex:d;$), + %r(^\s+ex:e ex:f \.$) + ], + }, + "bare anon" => { + input: %(@prefix ex: . [ex:a ex:b] .), + regexp: [%r(^\s*\[ex:a ex:b\] \.$)], + }, + "anon as subject" => { + input: %(@prefix ex: . [ex:a ex:b] ex:c ex:d .), + regexp: [ + %r(^\s*\[\s*ex:a ex:b;$)m, + %r(^\s+ex:c ex:d\s*\] \.$)m + ], + }, + "anon as object" => { + input: %(@prefix ex: . ex:a ex:b [ex:c ex:d] .), + regexp: [%r(^ex:a ex:b \[ex:c ex:d\] \.$)], + }, + "reuses BNode labels by default" => { + input: %(@prefix ex: . _:a ex:b _:a .), + regexp: [%r(^\s*_:a(_\d+_\d+) ex:b _:a\1 \.$)] + }, + "standard prefixes" => { + input: %( + a ; + "Person" . + ), + regexp: [ + %r(^@prefix foaf: \.$), + %r(^@prefix dc: \.$), + %r(^ a foaf:Person;$), + %r(dc:title "Person" \.$), + ], + standard_prefixes: true, prefixes: {} + }, + "should not use qname with illegal local part" => { + input: %( + @prefix db: . + @prefix dbo: . + db:Michael_Jackson dbo:artistOf . + ), + regexp: [ + %r(^@prefix db: \.$), + %r(^db:Michael_Jackson dbo:artistOf \.$) + ], prefixes: { "db" => RDF::URI("http://dbpedia.org/resource/"), "dbo" => RDF::URI("http://dbpedia.org/ontology/")} - ) - end - - it "should order properties" do - input = %( - @prefix : . - @prefix dc: . - @prefix rdfs: . - :b :c :d . - :b dc:title "title" . - :b a :class . - :b rdfs:label "label" . - ) - serialize(input, nil, - [ - %r(^:b a :class;$), - %r(:class;\s+rdfs:label "label")m, - %r("label";\s+dc:title "title")m, - %r("title";\s+:c :d \.$)m - ], - prefixes: { "" => "http://xmlns.com/foaf/0.1/", dc: "http://purl.org/dc/elements/1.1/", rdfs: RDF::RDFS} - ) - end - - it "should generate object list" do - input = %(@prefix : . :b :c :d, :e .) - serialize(input, nil, - [%r(^@prefix : \.$), - %r(^:b :c :[de],\s+:[de] \.$)m], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate property list" do - input = %(@prefix : . :b :c :d; :e :f .) - serialize(input, nil, - [%r(^@prefix : \.$), - %r(^:b :[ce] :[df];\s+:[ce] :[df] \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - end - - describe "anons" do - it "should generate bare anon" do - input = %(@prefix : . [:a :b] .) - serialize(input, nil, - [%r(^\s*\[ :a :b\] \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate anon as subject" do - input = %(@prefix : . [:a :b] :c :d .) - serialize(input, nil, - [%r(^\s*\[ :a :b;$), - %r(^\s+:c :d\] \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate anon as object" do - input = %(@prefix : . :a :b [:c :d] .) - serialize(input, nil, - [%r(^\s*\:a :b \[ :c :d\] \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - end - - describe "BNodes" do - let(:input) {%(@prefix : . _:a :b _:a .)} - it "reuses BNode labels by default" do - serialize(input, nil, - [%r(^\s*_:a :b _:a \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - it "uses generated BNodes with :unique_bnodes" do - serialize(input, nil, - [%r(^\s*_:g\w+ :b _:g\w+ \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"}, - unique_bnodes: true - ) + } + }.each do |name, params| + it name do + serialize(params[:input], params[:regexp], params) + end end end describe "lists" do - it "should generate bare list" do - input = %(@prefix : . (:a :b) .) - serialize(input, nil, - [%r(^\(:a :b\) \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate literal list" do - input = %(@prefix : . :a :b ( "apple" "banana" ) .) - serialize(input, nil, - [%r(^:a :b \("apple" "banana"\) \.$)], + { + "bare list": { + input: %(@prefix ex: . (ex:a ex:b) .), + regexp: [%r(^\(\s*ex:a ex:b\s*\) \.$)] + }, + "literal list": { + input: %(@prefix ex: . ex:a ex:b ( "apple" "banana" ) .), + regexp: [%r(^ex:a ex:b \(\s*"apple" "banana"\s*\) \.$)] + }, + "empty list": { + input: %(@prefix ex: . ex:a ex:b () .), + regexp: [%r(^ex:a ex:b \(\s*\) \.$)], + prefixes: { "" => RDF::Vocab::FOAF} + }, + "should generate empty list(2)" => { + input: %(@prefix : . :emptyList = () .), + regexp: [%r(^:emptyList (<.*sameAs>|owl:sameAs|=) \(\) \.$)], prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate empty list" do - input = %(@prefix : . :a :b () .) - serialize(input, nil, - [%r(^:a :b \(\) \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate empty list(2)" do - input = %(@prefix : . :emptyList = () .) - serialize(input, nil, - [%r(^:emptyList (<.*sameAs>|owl:sameAs) \(\) \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate empty list as subject" do - input = %(@prefix : . () :a :b .) - serialize(input, nil, - [%r(^\(\) :a :b \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate list as subject" do - input = %(@prefix : . (:a) :b :c .) - serialize(input, nil, - [%r(^\(:a\) :b :c \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate list of empties" do - input = %(@prefix : . :listOf2Empties = (() ()) .) - serialize(input, nil, - [%r(^:listOf2Empties (<.*sameAs>|owl:sameAs) \(\(\) \(\)\) \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate list anon" do - input = %(@prefix : . :twoAnons = ([a :mother] [a :father]) .) - serialize(input, nil, - [%r(^:twoAnons (<.*sameAs>|owl:sameAs) \(\[\s*a :(mother|father)\] \[\s*a :(mother|father)\]\) \.$)], - prefixes: { "" => "http://xmlns.com/foaf/0.1/"} - ) - end - - it "should generate owl:unionOf list" do - input = %( - @prefix : . - @prefix owl: . - @prefix rdf: . - @prefix rdfs: . - :a rdfs:domain [ - a owl:Class; - owl:unionOf [ + }, + "empty list as subject": { + input: %(@prefix ex: . () ex:a ex:b .), + regexp: [%r(^\(\s*\) ex:a ex:b \.$)] + }, + "list as subject": { + input: %(@prefix ex: . (ex:a) ex:b ex:c .), + regexp: [%r(^\(\s*ex:a\s*\) ex:b ex:c \.$)] + }, + "list of empties": { + input: %(@prefix ex: . [ex:listOf2Empties (() ())] .), + regexp: [%r(\[\s*ex:listOf2Empties \(\s*\(\s*\) \(\s*\)\s*\)\s*\] \.$)] + }, + "list anon": { + input: %(@prefix ex: . [ex:twoAnons ([a ex:mother] [a ex:father])] .), + regexp: [%r(\[\s*ex:twoAnons \(\s*\[\s*a ex:mother\s*\] \[\s*a ex:father\s*\]\)\] \.$)] + }, + "list subjects": { + input: %(@prefix ex: . (ex:a ex:b) . ex:a a ex:Thing . ex:b a ex:Thing .), + regexp: [ + %r(\(ex:a ex:b\) \.), + %r(ex:a a ex:Thing \.), + %r(ex:b a ex:Thing \.), + ] + }, + "owl:unionOf list": { + input: %( + @prefix ex: . + @prefix owl: . + @prefix rdf: . + @prefix rdfs: . + ex:a rdfs:domain [ a owl:Class; - rdf:first :b; - rdf:rest [ + owl:unionOf [ a owl:Class; - rdf:first :c; - rdf:rest rdf:nil + rdf:first ex:b; + rdf:rest [ + a owl:Class; + rdf:first ex:c; + rdf:rest rdf:nil + ] ] - ] - ] . - ) - #$verbose = true - serialize(input, nil, - [ - %r(:a rdfs:domain \[\s*a owl:Class;\s+owl:unionOf\s+\(:b\s+:c\)\]\s*\.$)m, - %r(@prefix : \.), + ] . + ), + regexp: [ + %r(ex:a rdfs:domain \[\s*a owl:Class;\s+owl:unionOf\s+\(\s*ex:b\s+ex:c\s*\)\s*\]\s*\.$)m, + %r(@prefix ex: \.), %r(@prefix rdf: \.), - ], - prefixes: { "" => "http://xmlns.com/foaf/0.1/", dfs: RDF::RDFS, owl: RDF::OWL, rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#"} - ) - #$verbose = false - end - - it "should generate list with first subject a URI" do - input = %( - "1"^^ . - _:g47006741228480 . - _:g47006741228480 "2"^^ . - _:g47006741228480 _:g47006737917560 . - _:g47006737917560 "3"^^ . - _:g47006737917560 . - ) - #$verbose = true - serialize(input, nil, - [ + ] + }, + "list with first subject a URI": { + input: %( + "1"^^ . + _:g47006741228480 . + _:g47006741228480 "2"^^ . + _:g47006741228480 _:g47006737917560 . + _:g47006737917560 "3"^^ . + _:g47006737917560 . + ), + regexp: [ %r(@prefix rdf: \.), %r( rdf:first 1;), - %r(rdf:rest \(2 3\) \.), + %r(rdf:rest \(\s*2 3\s*\) \.), + ], + standard_prefixes: true + }, + "list pattern without rdf:nil": { + input: %( + _:a . + _:a "a" . + _:a _:b . + _:b "b" . + _:b _:c . + _:c "c" . + ), + regexp: [%r( \[), + %r(rdf:first "a";), + %r(rdf:rest \[), + %r(rdf:first "b";), + %r(rdf:rest \[\s*rdf:first "c"\s*\]), + ], + standard_prefixes: true + }, + "list pattern with extra properties": { + input: %( + _:a . + _:a "a" . + _:a _:b . + _:b "b" . + _:a "This list node has also properties other than rdf:first and rdf:rest" . + _:b _:c . + _:c "c" . + _:c . + ), + regexp: [%r( \[), + %r( "This list node has also properties other than rdf:first and rdf:rest";), + %r(rdf:first "a";), + %r(rdf:rest \(\s*"b" "c"\s*\)), + ], + standard_prefixes: true + }, + "list with empty list": { + input: %( + _:l1 . + _:l1 . + _:l1 . + ), + regexp: [ + %r( \(\s*\(\)\) .) + ], + standard_prefixes: true + }, + "list with multiple lists": { + input: %( + _:l1 . + _:a "a" . + _:a . + _:b "b" . + _:b . + _:l1 _:a . + _:l1 _:l2 . + _:l2 _:b . + _:l2 . + ), + regexp: [ + %r( \(\s*\(\s*"a"\) \(\s*"b"\)\) .) ], standard_prefixes: true - ) - #$verbose = false + }, + }.each do |name, params| + it name do + serialize(params[:input], params[:regexp], params) + end end end describe "literals" do - describe "plain" do - it "encodes embedded \"\"\"" do - n3 = %(:a :b """testing string parsing in N3. - """ .) - serialize(n3, nil, [/testing string parsing in N3.\n/]) - end - - it "encodes embedded \"" do - n3 = %(:a :b """string with " escaped quote marks""" .) - serialize(n3, nil, [/string with \\" escaped quote mark/]) - end - - it "encodes embedded \\" do - n3 = %(:a :b """string with \\\\ escaped quote marks""" .) - serialize(n3, nil, [/string with \\\\ escaped quote mark/]) - end - - it "encodes embedded \\ multi-line" do - n3 = %(:a :b """string with \\\\ escaped quote marks - """ .) - serialize(n3, nil, [/string with \\\\ escaped quote mark/]) - end - end - - describe "with language" do - it "specifies language for literal with language" do - ttl = %q(:a :b "string"@en .) - serialize(ttl, nil, [%r("string"@en)]) - end - end - describe "xsd:anyURI" do it "uses xsd namespace for datatype" do - ttl = %q(@prefix xsd: . :a :b "http://foo/"^^xsd:anyURI .) - serialize(ttl, nil, [ + ttl = %q(@prefix xsd: . "http://foo/"^^xsd:anyURI .) + serialize(ttl, [ %r(@prefix xsd: \.), %r("http://foo/"\^\^xsd:anyURI \.), ]) @@ -340,13 +326,29 @@ [%q(false), /false ./], ].each do |(l,r)| it "uses token for #{l.inspect}" do - ttl = %(@prefix : . @prefix xsd: . :a :b #{l} .) - serialize(ttl, nil, [ + ttl = %(@prefix xsd: . #{l} .) + serialize(ttl, [ %r(@prefix xsd: \.), r, ], canonicalize: true) end end + + [ + [true, "true"], + [false, "false"], + [1, "true"], + [0, "false"], + ["true", "true"], + ["false", "false"], + ["1", "true"], + ["0", "false"], + ["string", %{"string"^^}], + ].each do |(l,r)| + it "serializes #{l.inspect} to #{r.inspect}" do + expect(subject.format_literal(RDF::Literal::Boolean.new(l))).to eql r + end + end end describe "xsd:integer" do @@ -359,13 +361,27 @@ [%q(10), /10 ./], ].each do |(l,r)| it "uses token for #{l.inspect}" do - ttl = %(@prefix : . @prefix xsd: . :a :b #{l} .) - serialize(ttl, nil, [ + ttl = %(@prefix xsd: . #{l} .) + serialize(ttl, [ %r(@prefix xsd: \.), r, ], canonicalize: true) end end + + [ + [0, "0"], + [10, "10"], + [-1, "-1"], + ["0", "0"], + ["true", %{"true"^^}], + ["false", %{"false"^^}], + ["string", %{"string"^^}], + ].each do |(l,r)| + it "serializes #{l.inspect} to #{r.inspect}" do + expect(subject.format_literal(RDF::Literal::Integer.new(l))).to eql r + end + end end describe "xsd:int" do @@ -375,8 +391,8 @@ [%q("10"^^xsd:int), /"10"\^\^xsd:int ./], ].each do |(l,r)| it "uses token for #{l.inspect}" do - ttl = %(@prefix : . @prefix xsd: . :a :b #{l} .) - serialize(ttl, nil, [ + ttl = %(@prefix xsd: . #{l} .) + serialize(ttl, [ %r(@prefix xsd: \.), r, ], canonicalize: true) @@ -394,13 +410,32 @@ [%q(10.02), /10.02 ./], ].each do |(l,r)| it "uses token for #{l.inspect}" do - ttl = %(@prefix : . @prefix xsd: . :a :b #{l} .) - serialize(ttl, nil, [ + ttl = %(@prefix xsd: . #{l} .) + serialize(ttl, [ %r(@prefix xsd: \.), r, ], canonicalize: true) end end + + [ + [0, "0.0"], + [10, "10.0"], + [-1, "-1.0"], + ["0", "0.0"], + ["10", "10.0"], + ["-1", "-1.0"], + ["1.0", "1.0"], + ["0.1", "0.1"], + ["10.01", "10.01"], + ["true", %{"true"^^}], + ["false", %{"false"^^}], + ["string", %{"string"^^}], + ].each do |(l,r)| + it "serializes #{l.inspect} to #{r.inspect}" do + expect(subject.format_literal(RDF::Literal::Decimal.new(l))).to eql r + end + end end describe "xsd:double" do @@ -411,31 +446,220 @@ [%q(0.1e1), /1.0e0 ./], [%q("10.02e1"^^xsd:double), /1.002e2 ./], [%q(10.02e1), /1.002e2 ./], + [%q("14"^^xsd:double), /1.4e1 ./], ].each do |(l,r)| it "uses token for #{l.inspect}" do - ttl = %(@prefix : . @prefix xsd: . :a :b #{l} .) - serialize(ttl, nil, [ + ttl = %(@prefix xsd: . #{l} .) + serialize(ttl, [ %r(@prefix xsd: \.), r, ], canonicalize: true) end end + + [ + [0, "0.0e0"], + [10, "1.0e1"], + [-1, "-1.0e0"], + ["0", "0.0e0"], + ["10", "1.0e1"], + ["-1", "-1.0e0"], + ["1.0", "1.0e0"], + ["0.1", "1.0e-1"], + ["10.01", "1.001e1"], + ["true", %{"true"^^}], + ["false", %{"false"^^}], + ["string", %{"string"^^}], + ].each do |(l,r)| + it "serializes #{l.inspect} to #{r.inspect}" do + expect(subject.format_literal(RDF::Literal::Double.new(l))).to eql r + end + end end end - def parse(input, options = {}) - graph = RDF::Graph.new - RDF::N3::Reader.new(input, options).each do |statement| - graph << statement + describe "formulae" do + { + "empty subject" => { + input: %({} .), + regexp: [ + %r(\[ \] \.) + ] + }, + "empty object" => { + input: %( {} .), + regexp: [ + %r( \[\] \.) + ] + }, + "as subject with constant content" => { + input: %({ } .), + regexp: [ + %r({\s+ \.\s+} \.)m + ] + }, + "as object with constant content" => { + input: %( { } .), + regexp: [ + %r( {\s+ \.\s+} \.)m + ] + }, + "implies" => { + input: %({ _:x :is _:x } => {_:x :is _:x } .), + regexp: [ + %r({\s+_:x(_\d+_\d+) :is _:x\1 \.\s+} => {\s+_:x(_\d+_\d+) :is _:x\2 \.\s+} \.)m + ] + }, + "formula simple" => { + input: %(<> :about { :c :d :e }.), + regexp: [ + %r(<> :about {\s+:c :d :e \.\s+} \.) + ] + }, + "nested" => { + input: %( + @prefix doc: . + @prefix ex: . + @prefix contact: . + [] + doc:creator [contact:email ]; + ex:says { + [] doc:title "Huckleberry Finn"; + doc:creator [contact:knownAs "Mark Twain"] + }. + ), + regexp: [ + %r(\[\s+ex:says {\s+\[)m, + %r(doc:creator \[contact:knownAs "Mark Twain"\];), + %r(doc:title "Huckleberry Finn"), + %r(\] \.\s+};)m, + %r(doc:creator \[contact:email ) + ] + }, + "named with URI" => { + input: %q( + . + { .} + ), + regexp: [ + %r( \.), + %r( = {), + %r( \.), + %r(} \.), + ], + input_format: :trig + }, + "named with BNode" => { + input: %q( + . + _:C { .} + ), + regexp: [ + %r( \.), + %r(_:C = {), + %r( \.), + %r(} \.), + ], + input_format: :trig + } + }.each do |name, params| + it name do + serialize(params[:input], params[:regexp], params) + end + end + end + + describe "variables" do + { + "@forAll": { + input: %(@forAll :o. :s :p :o .), + regexp: [ + %r(@forAll :o_\d+_\d+ \.), + %r(:s :p :o_\d+_\d+ \.), + ] + }, + "@forSome": { + input: %(@forSome :o. :s :p :o .), + regexp: [ + %r(@forSome :o_\d+_\d+ \.), + %r(:s :p :o_\d+_\d+ \.), + ] + }, + "?o": { + input: %(:s :p ?o .), + regexp: [ + %r(@forAll :o_\d+_\d+ \.), + %r(:s :p :o_\d+_\d+ \.), + ] + }, + }.each do |name, params| + it name do + serialize(params[:input], params[:regexp], params) + end end - graph + end + + # W3C TriG Test suite + describe "w3c n3 parser tests" do + require_relative 'suite_helper' + + Fixtures::SuiteTest::Manifest.open("https://w3c.github.io/n3/tests/manifest-parser.n3") do |m| + describe m.comment do + m.entries.each do |t| + next unless t.positive_test? && t.evaluate? + specify "#{t.name}: #{t.comment} (action)" do + case t.name + when *%w(n3_10003 n3_10004 n3_10008) + skip "Blank Node predicates" + when *%w(n3_10012 n3_10017) + pending "Investigate" + when *%w(n3_10013) + pending "Number syntax" + end + logger.info t.inspect + logger.info "source: #{t.input}" + repo = parse(t.input, base_uri: t.base) + n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true) + logger.info "serialized: #{n3}" + g2 = parse(n3, base_uri: t.base) + expect(g2).to be_equivalent_graph(repo, logger: logger) + end + + specify "#{t.name}: #{t.comment} (result)" do + case t.name + when *%w(n3_10003 n3_10004 n3_10008) + skip "Blank Node predicates" + when *%w(n3_10012 n3_10017) + pending "Investigate" + when *%w(n3_10013) + pending "Number syntax" + end + logger.info t.inspect + logger.info "source: #{t.expected}" + format = detect_format(t.expected) + repo = parse(t.expected, base_uri: t.base, format: format) + n3 = serialize(repo, [], base_uri: t.base, standard_prefixes: true) + logger.info "serialized: #{n3}" + g2 = parse(n3, base_uri: t.base) + expect(g2).to be_equivalent_graph(repo, logger: logger) + end + end + end + end + end unless ENV['CI'] + + def parse(input, format: :n3, **options) + repo = RDF::Repository.new + reader = RDF::Reader.for(format) + repo << reader.new(input, options) + repo end # Serialize ntstr to a string and compare against regexps - def serialize(ntstr, base = nil, regexps = [], options = {}) + def serialize(ntstr, regexps = [], base_uri: nil, **options) prefixes = options[:prefixes] || {} - g = ntstr.is_a?(RDF::Enumerable) ? ntstr : parse(ntstr, base_uri: base, prefixes: prefixes, validate: false, logger: []) - result = RDF::N3::Writer.buffer(options.merge(logger: logger, base_uri: base, prefixes: prefixes)) do |writer| + g = ntstr.is_a?(RDF::Enumerable) ? ntstr : parse(ntstr, base_uri: base_uri, prefixes: prefixes, validate: false, logger: [], 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 if $verbose @@ -446,7 +670,7 @@ def serialize(ntstr, base = nil, regexps = [], options = {}) logger.info "result: #{result}" regexps.each do |re| logger.info "match: #{re.inspect}" - expect(result).to match_re(re, about: base, logger: logger, input: ntstr), logger.to_s + expect(result).to match_re(re, about: base_uri, logger: logger, input: ntstr), logger.to_s end result