Skip to content

Commit

Permalink
Deprecates URIs for quantified variables and allow '$' variables.
Browse files Browse the repository at this point in the history
  • Loading branch information
gkellogg committed Nov 18, 2020
1 parent 044743f commit dc4ee25
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 55 deletions.
30 changes: 18 additions & 12 deletions etc/n3.ebnf
Expand Up @@ -44,6 +44,7 @@

[18] pathItem ::= iri
| blankNode
| quantifiedVar
| quickVar
| collection
| blankNodePropertyList
Expand All @@ -69,7 +70,7 @@

[26] iri ::= IRIREF | prefixedName

[27] iriList ::= iri ( ',' iri )*
[27] varList ::= (iri | quantifiedVar) ( ',' (iri | quantifiedVar) )*

[28] prefixedName ::= PNAME_LN | PNAME_NS
# PNAME_NS will be matched for ':' (i.e., "empty") prefixedNames
Expand All @@ -78,19 +79,23 @@
[29] blankNode ::= BLANK_NODE_LABEL | ANON

[30] quickVar ::= QUICK_VAR_NAME
[30] quantifiedVar ::= QUANTIFIED_VAR_NAME

[31] quickVar ::= QUICK_VAR_NAME
# only made this a parser rule for consistency
# (all other path-items are also parser rules)

[31] existential ::= '@forSome' iriList

[32] universal ::= '@forAll' iriList
[32] existential ::= '@forSome' varList
# iriList is deprecated

[33] universal ::= '@forAll' varList
# iriList is deprecated

@terminals

[33] BOOLEAN_LITERAL ::= 'true' | 'false'
[34] BOOLEAN_LITERAL ::= 'true' | 'false'

[34] STRING ::= STRING_LITERAL_LONG_SINGLE_QUOTE
[35] STRING ::= STRING_LITERAL_LONG_SINGLE_QUOTE
| STRING_LITERAL_LONG_QUOTE
| STRING_LITERAL_QUOTE
| STRING_LITERAL_SINGLE_QUOTE
Expand All @@ -110,12 +115,13 @@
[157s] STRING_LITERAL_SINGLE_QUOTE ::= "'" ( [^#x27#x5C#xA#xD] | ECHAR | UCHAR )* "'"
[158s] STRING_LITERAL_LONG_SINGLE_QUOTE ::= "'''" ( ( "'" | "''" )? ( [^'\] | ECHAR | UCHAR ) )* "'''"
[159s] STRING_LITERAL_LONG_QUOTE ::= '"""' ( ( '"' | '""' )? ( [^"\] | ECHAR | UCHAR ) )* '"""'
[35] UCHAR ::= ( "\u" HEX HEX HEX HEX ) | ( "\U" HEX HEX HEX HEX HEX HEX HEX HEX )
[36] UCHAR ::= ( "\u" HEX HEX HEX HEX ) | ( "\U" HEX HEX HEX HEX HEX HEX HEX HEX )
[160s] ECHAR ::= "\" [tbnrf\"']
[162s] WS ::= #x20 | #x9 | #xD | #xA
[163s] ANON ::= '[' WS* ']'
[36] QUICK_VAR_NAME ::= "?" PN_LOCAL
[37] QUICK_VAR_NAME ::= "?" PN_LOCAL
/* Allows fuller character set */
[38] QUANTIFIED_VAR_NAME ::= "$" PN_LOCAL
[164s] PN_CHARS_BASE ::= [A-Z] | [a-z] | [#x00C0-#x00D6]
| [#x00D8-#x00F6] | [#x00F8-#x02FF] | [#x0370-#x037D]
| [#x037F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F]
Expand All @@ -124,16 +130,16 @@
[165s] PN_CHARS_U ::= PN_CHARS_BASE | '_'
[167s] PN_CHARS ::= PN_CHARS_U | "-" | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040]
/* BASE and PREFIX must be case-insensitive, hence these monstrosities */
[37] BASE ::= ('B'|'b') ('A'|'a') ('S'|'s') ('E'|'e')
[38] PREFIX ::= ('P'|'p') ('R'|'r') ('E'|'e') ('F'|'f') ('I'|'i') ('X'|'x')
[39] BASE ::= ('B'|'b') ('A'|'a') ('S'|'s') ('E'|'e')
[40] PREFIX ::= ('P'|'p') ('R'|'r') ('E'|'e') ('F'|'f') ('I'|'i') ('X'|'x')
[168s] PN_PREFIX ::= PN_CHARS_BASE ( ( PN_CHARS | "." )* PN_CHARS )?
[169s] PN_LOCAL ::= ( PN_CHARS_U | ':' | [0-9] | PLX ) ( ( PN_CHARS | '.' | ':' | PLX )* ( PN_CHARS | ':' | PLX ) ) ?
[170s] PLX ::= PERCENT | PN_LOCAL_ESC
[171s] PERCENT ::= '%' HEX HEX
[172s] HEX ::= [0-9] | [A-F] | [a-f]
[173s] PN_LOCAL_ESC ::= '\' ( '_' | '~' | '.' | '-' | '!' | '$' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | ';' | '='
| '/' | '?' | '#' | '@' | '%' )
[39] COMMENT ::= ('#' - '#x') [^#xA#xC#xD]*
[41] COMMENT ::= ('#' - '#x') [^#xA#xC#xD]*

# Ignore all whitespace and comments between non-terminals
@pass ( WS | COMMENT )*
Expand Down
24 changes: 13 additions & 11 deletions etc/n3.sxp
Expand Up @@ -34,15 +34,16 @@
(rule numericLiteral "24" (alt DOUBLE DECIMAL INTEGER))
(rule rdfLiteral "25" (seq STRING (opt (alt LANGTAG (seq "^^" iri)))))
(rule iri "26" (alt IRIREF prefixedName))
(rule iriList "27" (seq iri (star (seq "," iri))))
(rule varList "27" (seq (alt iri quantifiedVar) (star (seq "," (alt iri quantifiedVar)))))
(rule prefixedName "28" (alt PNAME_LN PNAME_NS))
(rule blankNode "29" (alt BLANK_NODE_LABEL ANON))
(rule quickVar "30" (seq QUICK_VAR_NAME))
(rule existential "31" (seq "@forSome" iriList))
(rule universal "32" (seq "@forAll" iriList))
(rule quantifiedVar "30" (seq QUANTIFIED_VAR_NAME))
(rule quickVar "31" (seq QUICK_VAR_NAME))
(rule existential "32" (seq "@forSome" varList))
(rule universal "33" (seq "@forAll" varList))
(terminals _terminals (seq))
(terminal BOOLEAN_LITERAL "33" (alt "true" "false"))
(terminal STRING "34"
(terminal BOOLEAN_LITERAL "34" (alt "true" "false"))
(terminal STRING "35"
(alt STRING_LITERAL_LONG_SINGLE_QUOTE STRING_LITERAL_LONG_QUOTE
STRING_LITERAL_QUOTE STRING_LITERAL_SINGLE_QUOTE ))
(terminal IRIREF "139s"
Expand Down Expand Up @@ -70,12 +71,13 @@
(seq "'''" (star (seq (opt (alt "'" "''")) (alt (range "^'\\") ECHAR UCHAR))) "'''"))
(terminal STRING_LITERAL_LONG_QUOTE "159s"
(seq "\"\"\"" (star (seq (opt (alt "\"" "\"\"")) (alt (range "^\"\\") ECHAR UCHAR))) "\"\"\""))
(terminal UCHAR "35"
(terminal UCHAR "36"
(alt (seq "\\u" HEX HEX HEX HEX) (seq "\\U" HEX HEX HEX HEX HEX HEX HEX HEX)))
(terminal ECHAR "160s" (seq "\\" (range "tbnrf\\\"'")))
(terminal WS "162s" (alt (hex "#x20") (hex "#x9") (hex "#xD") (hex "#xA")))
(terminal ANON "163s" (seq "[" (star WS) "]"))
(terminal QUICK_VAR_NAME "36" (seq "?" PN_LOCAL))
(terminal QUICK_VAR_NAME "37" (seq "?" PN_LOCAL))
(terminal QUANTIFIED_VAR_NAME "38" (seq "$" PN_LOCAL))
(terminal PN_CHARS_BASE "164s"
(alt
(range "A-Z")
Expand All @@ -99,8 +101,8 @@
(hex "#x00B7")
(range "#x0300-#x036F")
(range "#x203F-#x2040")) )
(terminal BASE "37" (seq (alt "B" "b") (alt "A" "a") (alt "S" "s") (alt "E" "e")))
(terminal PREFIX "38"
(terminal BASE "39" (seq (alt "B" "b") (alt "A" "a") (alt "S" "s") (alt "E" "e")))
(terminal PREFIX "40"
(seq (alt "P" "p") (alt "R" "r") (alt "E" "e") (alt "F" "f") (alt "I" "i") (alt "X" "x")))
(terminal PN_PREFIX "168s"
(seq PN_CHARS_BASE (opt (seq (star (alt PN_CHARS ".")) PN_CHARS))))
Expand All @@ -115,5 +117,5 @@
(seq "\\"
(alt "_" "~" "." "-" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" "/" "?" "#"
"@" "%" )) )
(terminal COMMENT "39" (seq (diff "#" "#x") (star (range "^#xA#xC#xD"))))
(terminal COMMENT "41" (seq (diff "#" "#x") (star (range "^#xA#xC#xD"))))
(pass _pass (star (alt WS COMMENT))))
57 changes: 41 additions & 16 deletions lib/rdf/n3/reader.rb
Expand Up @@ -192,6 +192,7 @@ def each_triple
terminal(:BASE, BASE)
terminal(:LANGTAG, LANGTAG)
terminal(:QUICK_VAR_NAME, QUICK_VAR_NAME, unescape: true)
terminal(:QUANTIFIED_VAR_NAME, QUANTIFIED_VAR_NAME, unescape: true)

private
##
Expand Down Expand Up @@ -419,6 +420,7 @@ def read_path
pathtail[:pathitem] = prod(:pathItem) do
read_iri ||
read_blankNode ||
read_quantifiedVar ||
read_quickVar ||
read_collection ||
read_blankNodePropertyList ||
Expand Down Expand Up @@ -638,17 +640,35 @@ def read_blankNode
end
end

##
# Read a quantifiedVar.
#
# [30] quantifiedVar ::= QUANTIFIED_VAR_NAME
#
# @param [Boolean] existential. Set if called for either `@forSome` or `@forAll`. Otherwise, it will have been cached.
# @return [RDF::Query::Variable]
def read_quantifiedVar(existential: false)
if @lexer.first.type == :QUANTIFIED_VAR_NAME
prod(:quantifiedVar) do
token = @lexer.shift
value = token.value[1..-1]
iri = ns(nil, "#{value}_quant")
variables[formulae.last][iri] ||= univar(iri, scope: formulae.last, existential: existential)
end
end
end

##
# Read a quickVar, having global scope.
#
# [30] quickVar ::= QUICK_VAR_NAME
# [31] quickVar ::= QUICK_VAR_NAME
#
# @return [RDF::Query::Variable]
def read_quickVar
if @lexer.first.type == :QUICK_VAR_NAME
prod(:quickVar) do
token = @lexer.shift
value = token.value.sub('?', '')
value = token.value[1..-1]
iri = ns(nil, "#{value}_quick")
variables[nil][iri] ||= univar(iri, scope: nil)
end
Expand All @@ -658,19 +678,20 @@ def read_quickVar
##
# Read a list of IRIs
#
# [27] iriList ::= iri ( ',' iri )*
# [27] varList ::= (iri | quantifiedVar) ( ',' (iri | quantifiedVar) )*
#
# @param [Boolean] existential. Set if called for either `@forSome` or `@forAll`. Otherwise, it will have been cached.
# @return [Array<RDF::URI>] the list of IRIs
def read_irilist
iris = []
prod(:iriList, %{,}) do
while iri = read_iri
iris << iri
def read_varList(existential: false)
vars = []
prod(:varlist, %{,}) do
while var = read_quantifiedVar(existential: existential) || read_iri
vars << var
break unless @lexer.first === ','
@lexer.shift while @lexer.first === ','
end
end
iris
vars
end

##
Expand All @@ -690,12 +711,16 @@ def read_irilist
def read_uniext
if %w(@forSome @forAll).include?(@lexer.first.value)
token = @lexer.shift
prod(token === '@forAll' ? :universal : :existential) do
iri_list = read_irilist
iri_list.each do |iri|
uniext = token === '@forAll' ? :universal : :existential
prod(uniext) do
var_list = read_varList(existential: uniext == :existential)
var_list.each do |label|
log_warn(
"[DEPRECATION] The use of IRIs as quantified variables is deprecated; use the '$var' form instead."
) if label.iri?
# Note, this might re-create an equivalent variable already defined in this formula, and replaces an equivalent variable that may have been defined in the parent formula.
var = univar(iri, scope: formulae.last, existential: token === '@forSome')
add_var_to_formula(formulae.last, iri, var)
var = univar(label, scope: formulae.last, existential: uniext == :existential)
add_var_to_formula(formulae.last, label, var)
end
end
end
Expand Down Expand Up @@ -772,8 +797,8 @@ def bnode(label = nil)

# If not in ground formula, note scope, and if existential
def univar(label, scope:, existential: false)
value = existential ? "#{label}_ext" : label
value = "#{value}#{scope.id}" if scope
return label if label.is_a?(RDF::Query::Variable)
value = scope ? "#{label}#{scope.id}" : label
RDF::Query::Variable.new(value, existential: existential)
end

Expand Down
1 change: 1 addition & 0 deletions lib/rdf/n3/terminals.rb
Expand Up @@ -69,6 +69,7 @@ module Terminals
# 29t
BASE = /@?base/ui.freeze
QUICK_VAR_NAME = /\?#{PN_LOCAL}/.freeze
QUANTIFIED_VAR_NAME = /\$#{PN_LOCAL}/.freeze

# 161s
WS = /(?:\s|(?:#[^\n\r]*))+/um.freeze
Expand Down
18 changes: 16 additions & 2 deletions lib/rdf/n3/writer.rb
Expand Up @@ -335,13 +335,25 @@ def start_document
# Universals and extentials at top-level
unless @universals.empty?
log_debug("start_document: universals") { @universals.inspect}
terms = @universals.map {|v| format_uri(RDF::URI(v.name.to_s))}
terms = @universals.map do |v|
if v.to_s.include?('_quant')
'$' + RDF::URI(v.name).fragment.sub(/_quant$/, '').sub(/_ext$/, '')
else
format_uri(RDF::URI(v.name.to_s.sub(/_ext$/, '')))
end
end
@output.write("@forAll #{terms.join(', ')} .\n")
end

unless @existentials.empty?
log_debug("start_document: existentials") { @existentials.inspect}
terms = @existentials.map {|v| format_uri(RDF::URI(v.name.to_s.sub(/_ext$/, '')))}
terms = @existentials.map do |v|
if v.to_s.include?('_quant')
'$' + RDF::URI(v.name).fragment.sub(/_quant$/, '').sub(/_ext$/, '')
else
format_uri(RDF::URI(v.name.to_s.sub(/_ext$/, '')))
end
end
@output.write("@forSome #{terms.join(', ')} .\n")
end
end
Expand Down Expand Up @@ -523,6 +535,8 @@ def p_term(resource, position)
l = if resource.is_a?(RDF::Query::Variable)
if resource.to_s.end_with?('_quick')
'?' + RDF::URI(resource.name).fragment.sub(/_quick$/, '')
elsif resource.to_s.include?('_quant')
'$' + RDF::URI(resource.name).fragment.sub(/_quant$/, '')
else
format_term(RDF::URI(resource.name.to_s.sub(/_ext$/, '')))
end
Expand Down
32 changes: 26 additions & 6 deletions spec/reader_spec.rb
Expand Up @@ -490,17 +490,27 @@
end

context "patterns" do
it "substitutes variable for URI with @forAll" do
it "warns of deprecation on @forAll with URIs" do
n3 = %(@forAll :x . :x :y :z .)
g = parse(n3, base_uri: "http://a/b")
statement = g.statements.first
expect(statement.subject).to be_variable
expect(statement.predicate.to_s).to eq "http://a/b#y"
expect(statement.object.to_s).to eq "http://a/b#z"
expect(logger.to_s).to include("[DEPRECATION]")
end

it "substitutes variable for URIs with @forAll" do
n3 = %(@forAll :x, :y, :z . :x :y :z .)
it "substitutes variable for variable with @forAll" do
n3 = %(@forAll $x . $x :y :z .)
g = parse(n3, base_uri: "http://a/b")
statement = g.statements.first
expect(statement.subject).to be_variable
expect(statement.predicate.to_s).to eq "http://a/b#y"
expect(statement.object.to_s).to eq "http://a/b#z"
end

it "substitutes variable for variables with @forAll" do
n3 = %(@forAll $x, $y, $z . $x $y $z .)
g = parse(n3, base_uri: "http://a/b")
statement = g.statements.first
expect(statement.subject).to be_variable
Expand All @@ -522,17 +532,27 @@
expect(statement.subject).not_to equal statement.object
end

it "substitutes node for URI with @forSome" do
it "warns of deprecation on @forSome with URIs" do
n3 = %(@forSome :x . :x :y :z .)
g = parse(n3, base_uri: "http://a/b")
statement = g.statements.first
expect(statement.subject).to be_variable
expect(statement.predicate.to_s).to eq "http://a/b#y"
expect(statement.object.to_s).to eq "http://a/b#z"
expect(logger.to_s).to include("[DEPRECATION]")
end

it "substitutes node for variable 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_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 @forSome" do
n3 = %(@forSome :x, :y, :z . :x :y :z .)
it "substitutes node for variables 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_variable
Expand Down

0 comments on commit dc4ee25

Please sign in to comment.