rdf-n3 3.1.1 → 3.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +148 -69
- data/UNLICENSE +1 -1
- data/VERSION +1 -1
- data/lib/rdf/n3.rb +8 -8
- data/lib/rdf/n3/algebra.rb +147 -68
- data/lib/rdf/n3/algebra/builtin.rb +79 -0
- data/lib/rdf/n3/algebra/formula.rb +355 -94
- data/lib/rdf/n3/algebra/list/append.rb +33 -4
- data/lib/rdf/n3/algebra/list/first.rb +24 -0
- data/lib/rdf/n3/algebra/list/in.rb +42 -3
- data/lib/rdf/n3/algebra/list/last.rb +17 -4
- data/lib/rdf/n3/algebra/list/length.rb +24 -0
- data/lib/rdf/n3/algebra/list/member.rb +39 -2
- data/lib/rdf/n3/algebra/list_operator.rb +83 -0
- data/lib/rdf/n3/algebra/log/conclusion.rb +57 -1
- data/lib/rdf/n3/algebra/log/conjunction.rb +28 -1
- data/lib/rdf/n3/algebra/log/content.rb +34 -0
- data/lib/rdf/n3/algebra/log/equal_to.rb +34 -0
- data/lib/rdf/n3/algebra/log/implies.rb +55 -30
- data/lib/rdf/n3/algebra/log/includes.rb +58 -1
- data/lib/rdf/n3/algebra/log/n3_string.rb +34 -0
- data/lib/rdf/n3/algebra/log/not_equal_to.rb +23 -0
- data/lib/rdf/n3/algebra/log/not_includes.rb +27 -0
- data/lib/rdf/n3/algebra/log/output_string.rb +40 -0
- data/lib/rdf/n3/algebra/log/parsed_as_n3.rb +36 -0
- data/lib/rdf/n3/algebra/log/semantics.rb +40 -0
- data/lib/rdf/n3/algebra/math/absolute_value.rb +36 -0
- data/lib/rdf/n3/algebra/math/acos.rb +26 -0
- data/lib/rdf/n3/algebra/math/acosh.rb +26 -0
- data/lib/rdf/n3/algebra/math/asin.rb +26 -0
- data/lib/rdf/n3/algebra/math/asinh.rb +26 -0
- data/lib/rdf/n3/algebra/math/atan.rb +26 -0
- data/lib/rdf/n3/algebra/math/atanh.rb +26 -0
- data/lib/rdf/n3/algebra/math/ceiling.rb +28 -0
- data/lib/rdf/n3/algebra/math/cos.rb +40 -0
- data/lib/rdf/n3/algebra/math/cosh.rb +38 -0
- data/lib/rdf/n3/algebra/math/difference.rb +34 -3
- data/lib/rdf/n3/algebra/math/equal_to.rb +54 -0
- data/lib/rdf/n3/algebra/math/exponentiation.rb +29 -3
- data/lib/rdf/n3/algebra/math/floor.rb +28 -0
- data/lib/rdf/n3/algebra/math/greater_than.rb +41 -0
- data/lib/rdf/n3/algebra/math/less_than.rb +41 -0
- data/lib/rdf/n3/algebra/math/negation.rb +31 -2
- data/lib/rdf/n3/algebra/math/not_equal_to.rb +25 -0
- data/lib/rdf/n3/algebra/math/not_greater_than.rb +25 -0
- data/lib/rdf/n3/algebra/math/not_less_than.rb +25 -0
- data/lib/rdf/n3/algebra/math/product.rb +14 -3
- data/lib/rdf/n3/algebra/math/quotient.rb +30 -3
- data/lib/rdf/n3/algebra/math/remainder.rb +29 -3
- data/lib/rdf/n3/algebra/math/rounded.rb +20 -3
- data/lib/rdf/n3/algebra/math/sin.rb +40 -0
- data/lib/rdf/n3/algebra/math/sinh.rb +38 -0
- data/lib/rdf/n3/algebra/math/sum.rb +35 -4
- data/lib/rdf/n3/algebra/math/tan.rb +40 -0
- data/lib/rdf/n3/algebra/math/tanh.rb +38 -0
- data/lib/rdf/n3/algebra/not_implemented.rb +13 -0
- data/lib/rdf/n3/algebra/resource_operator.rb +123 -0
- data/lib/rdf/n3/algebra/str/concatenation.rb +21 -3
- data/lib/rdf/n3/algebra/str/contains.rb +28 -4
- data/lib/rdf/n3/algebra/str/contains_ignoring_case.rb +33 -0
- data/lib/rdf/n3/algebra/str/ends_with.rb +33 -0
- data/lib/rdf/n3/algebra/str/equal_ignoring_case.rb +34 -0
- data/lib/rdf/n3/algebra/str/format.rb +12 -4
- data/lib/rdf/n3/algebra/str/greater_than.rb +38 -0
- data/lib/rdf/n3/algebra/str/less_than.rb +33 -0
- data/lib/rdf/n3/algebra/str/matches.rb +33 -5
- data/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb +17 -0
- data/lib/rdf/n3/algebra/str/not_greater_than.rb +17 -0
- data/lib/rdf/n3/algebra/str/not_less_than.rb +17 -0
- data/lib/rdf/n3/algebra/str/not_matches.rb +18 -0
- data/lib/rdf/n3/algebra/str/replace.rb +28 -5
- data/lib/rdf/n3/algebra/str/scrape.rb +31 -5
- data/lib/rdf/n3/algebra/str/starts_with.rb +33 -0
- data/lib/rdf/n3/algebra/time/day.rb +35 -0
- data/lib/rdf/n3/algebra/time/day_of_week.rb +27 -0
- data/lib/rdf/n3/algebra/time/gm_time.rb +29 -0
- data/lib/rdf/n3/algebra/time/hour.rb +35 -0
- data/lib/rdf/n3/algebra/time/in_seconds.rb +59 -0
- data/lib/rdf/n3/algebra/time/local_time.rb +29 -0
- data/lib/rdf/n3/algebra/time/minute.rb +35 -0
- data/lib/rdf/n3/algebra/time/month.rb +35 -0
- data/lib/rdf/n3/algebra/time/second.rb +35 -0
- data/lib/rdf/n3/algebra/time/timezone.rb +36 -0
- data/lib/rdf/n3/algebra/time/year.rb +29 -0
- data/lib/rdf/n3/extensions.rb +180 -21
- data/lib/rdf/n3/format.rb +65 -0
- data/lib/rdf/n3/list.rb +630 -0
- data/lib/rdf/n3/reader.rb +762 -485
- data/lib/rdf/n3/reasoner.rb +57 -68
- data/lib/rdf/n3/refinements.rb +178 -0
- data/lib/rdf/n3/repository.rb +332 -0
- data/lib/rdf/n3/terminals.rb +80 -0
- data/lib/rdf/n3/vocab.rb +35 -7
- data/lib/rdf/n3/writer.rb +208 -148
- metadata +110 -52
- data/AUTHORS +0 -1
- data/History.markdown +0 -99
- data/lib/rdf/n3/algebra/log/equalTo.rb +0 -7
- data/lib/rdf/n3/algebra/log/notEqualTo.rb +0 -7
- data/lib/rdf/n3/algebra/log/notIncludes.rb +0 -12
- data/lib/rdf/n3/algebra/log/outputString.rb +0 -7
- data/lib/rdf/n3/algebra/math/absoluteValue.rb +0 -9
- data/lib/rdf/n3/algebra/math/equalTo.rb +0 -9
- data/lib/rdf/n3/algebra/math/greaterThan.rb +0 -9
- data/lib/rdf/n3/algebra/math/integerQuotient.rb +0 -9
- data/lib/rdf/n3/algebra/math/lessThan.rb +0 -9
- data/lib/rdf/n3/algebra/math/memberCount.rb +0 -9
- data/lib/rdf/n3/algebra/math/notEqualTo.rb +0 -9
- data/lib/rdf/n3/algebra/math/notGreaterThan.rb +0 -9
- data/lib/rdf/n3/algebra/math/notLessThan.rb +0 -9
- data/lib/rdf/n3/algebra/str/containsIgnoringCase.rb +0 -9
- data/lib/rdf/n3/algebra/str/endsWith.rb +0 -9
- data/lib/rdf/n3/algebra/str/equalIgnoringCase.rb +0 -9
- data/lib/rdf/n3/algebra/str/greaterThan.rb +0 -9
- data/lib/rdf/n3/algebra/str/lessThan.rb +0 -9
- data/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb +0 -9
- data/lib/rdf/n3/algebra/str/notGreaterThan.rb +0 -9
- data/lib/rdf/n3/algebra/str/notLessThan.rb +0 -9
- data/lib/rdf/n3/algebra/str/notMatches.rb +0 -9
- data/lib/rdf/n3/algebra/str/startsWith.rb +0 -56
- data/lib/rdf/n3/patches/array_hacks.rb +0 -53
- data/lib/rdf/n3/reader/meta.rb +0 -641
- data/lib/rdf/n3/reader/parser.rb +0 -239
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'rdf/n3'
|
2
|
+
|
3
|
+
module RDF::N3::Algebra
|
4
|
+
##
|
5
|
+
# Behavior for N3 builtin operators
|
6
|
+
module Builtin
|
7
|
+
include RDF::Enumerable
|
8
|
+
include RDF::Util::Logger
|
9
|
+
|
10
|
+
##
|
11
|
+
# Determine ordering for running built-in operator considering if subject or object is varaible and considered an input or an output. Accepts a solution set to determine if variable inputs are bound.
|
12
|
+
#
|
13
|
+
# @param [RDF::Query::Solutions] solutions
|
14
|
+
# @return [Integer] rake for ordering, lower numbers have fewer unbound output variables.
|
15
|
+
def rank(solutions)
|
16
|
+
vars = input_operand.vars - solutions.variable_names
|
17
|
+
# The rank is the remaining unbound variables
|
18
|
+
vars.count
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Return subject or object operand, or both, depending on which is considered an input.
|
23
|
+
#
|
24
|
+
# @return [RDF::Term]
|
25
|
+
def input_operand
|
26
|
+
# By default, return the merger of input and output operands
|
27
|
+
RDF::N3::List.new(values: operands)
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Evaluates the builtin using the given variable `bindings` by cloning the builtin replacing variables with their bindings recursively.
|
32
|
+
#
|
33
|
+
# @param [Hash{Symbol => RDF::Term}] bindings
|
34
|
+
# a query solution containing zero or more variable bindings
|
35
|
+
# @param [Hash{Symbol => Object}] options ({})
|
36
|
+
# options passed from query
|
37
|
+
# @return [RDF::N3::Algebra::Builtin]
|
38
|
+
# Returns a new builtin with bound values.
|
39
|
+
# @see SPARQL::Algebra::Expression.evaluate
|
40
|
+
def evaluate(bindings, formulae:, **options)
|
41
|
+
args = operands.map { |operand| operand.evaluate(bindings, formulae: formulae, **options) }
|
42
|
+
# Replace operands with bound operands
|
43
|
+
self.class.new(*args, formulae: formulae, **options)
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# By default, operators yield themselves and the operands, recursively.
|
48
|
+
#
|
49
|
+
# Pass in solutions to have quantifiers resolved to those solutions.
|
50
|
+
def each(solutions: RDF::Query::Solutions(), &block)
|
51
|
+
log_debug("(#{self.class.const_get(:NAME)} each)")
|
52
|
+
log_depth do
|
53
|
+
subject, object = operands.map {|op| op.formula? ? op.graph_name : op}
|
54
|
+
block.call(RDF::Statement(subject, self.to_uri, object))
|
55
|
+
operands.each do |op|
|
56
|
+
next unless op.is_a?(Builtin)
|
57
|
+
op.each(solutions: solutions) do |st|
|
58
|
+
# Maintain formula graph name for formula operands
|
59
|
+
st.graph_name ||= op.graph_name if op.formula?
|
60
|
+
block.call(st)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# The builtin hash is the hash of it's operands and NAME.
|
68
|
+
#
|
69
|
+
# @see RDF::Value#hash
|
70
|
+
def hash
|
71
|
+
([self.class.const_get(:NAME)] + operands).hash
|
72
|
+
end
|
73
|
+
|
74
|
+
# The URI of this operator.
|
75
|
+
def to_uri
|
76
|
+
self.class.const_get(:URI)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -1,17 +1,110 @@
|
|
1
|
-
require 'rdf'
|
1
|
+
require 'rdf/n3'
|
2
2
|
|
3
3
|
module RDF::N3::Algebra
|
4
4
|
#
|
5
5
|
# A Notation3 Formula combines a graph with a BGP query.
|
6
6
|
class Formula < SPARQL::Algebra::Operator
|
7
|
+
include RDF::Term
|
8
|
+
include RDF::Enumerable
|
7
9
|
include SPARQL::Algebra::Query
|
8
10
|
include SPARQL::Algebra::Update
|
9
|
-
include RDF::
|
10
|
-
include RDF::Util::Logger
|
11
|
+
include RDF::N3::Algebra::Builtin
|
11
12
|
|
13
|
+
##
|
14
|
+
# Query to run against a queryable to determine if the formula matches the queryable.
|
15
|
+
#
|
16
|
+
# @return [RDF::Query]
|
12
17
|
attr_accessor :query
|
13
18
|
|
14
|
-
NAME =
|
19
|
+
NAME = :formula
|
20
|
+
|
21
|
+
##
|
22
|
+
# Create a formula from an RDF::Enumerable (such as RDF::N3::Repository)
|
23
|
+
#
|
24
|
+
# @param [RDF::Enumerable] enumerable
|
25
|
+
# @param [Hash{Symbol => Object}] options
|
26
|
+
# any additional keyword options
|
27
|
+
# @return [RDF::N3::Algebra::Formula]
|
28
|
+
def self.from_enumerable(enumerable, **options)
|
29
|
+
# SPARQL used for SSE and algebra functionality
|
30
|
+
require 'sparql' unless defined?(:SPARQL)
|
31
|
+
|
32
|
+
# Create formulae from statement graph_names
|
33
|
+
formulae = {}
|
34
|
+
enumerable.graph_names.unshift(nil).each do |graph_name|
|
35
|
+
formulae[graph_name] = Formula.new(graph_name: graph_name, formulae: formulae, **options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Add patterns to appropiate formula based on graph_name,
|
39
|
+
# and replace subject and object bnodes which identify
|
40
|
+
# named graphs with those formula
|
41
|
+
enumerable.each_statement do |statement|
|
42
|
+
# A graph name indicates a formula.
|
43
|
+
graph_name = statement.graph_name
|
44
|
+
form = formulae[graph_name]
|
45
|
+
|
46
|
+
# Map statement components to formulae, if necessary.
|
47
|
+
statement = RDF::Statement.from(statement.to_a.map do |term|
|
48
|
+
case term
|
49
|
+
when RDF::Node
|
50
|
+
term = if formulae[term]
|
51
|
+
# Transform blank nodes denoting formulae into those formulae
|
52
|
+
formulae[term]
|
53
|
+
elsif graph_name
|
54
|
+
# If we're in a quoted graph, transform blank nodes into undistinguished existential variables.
|
55
|
+
term.to_ndvar(graph_name)
|
56
|
+
else
|
57
|
+
term
|
58
|
+
end
|
59
|
+
when RDF::N3::List
|
60
|
+
# Transform blank nodes denoting formulae into those formulae
|
61
|
+
term = term.transform {|t| t.node? ? formulae.fetch(t, t) : t}
|
62
|
+
|
63
|
+
# If we're in a quoted graph, transform blank node components into existential variables
|
64
|
+
if graph_name && term.has_nodes?
|
65
|
+
term = term.to_ndvar(graph_name)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
term
|
69
|
+
end)
|
70
|
+
|
71
|
+
pattern = statement.variable? ? RDF::Query::Pattern.from(statement) : statement
|
72
|
+
|
73
|
+
# Formulae may be the subject or object of a known operator
|
74
|
+
if klass = RDF::N3::Algebra.for(pattern.predicate)
|
75
|
+
form.operands << klass.new(pattern.subject,
|
76
|
+
pattern.object,
|
77
|
+
formulae: formulae,
|
78
|
+
parent: form,
|
79
|
+
predicate: pattern.predicate,
|
80
|
+
**options)
|
81
|
+
else
|
82
|
+
pattern.graph_name = nil
|
83
|
+
form.operands << pattern
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Formula is that without a graph name
|
88
|
+
this = formulae[nil]
|
89
|
+
|
90
|
+
# If assigned a graph name, add it here
|
91
|
+
this.graph_name = options[:graph_name] if options[:graph_name]
|
92
|
+
this
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Duplicate this formula, recursively, renaming graph names using hash function.
|
97
|
+
#
|
98
|
+
# @return [RDF::N3::Algebra::Formula]
|
99
|
+
def deep_dup
|
100
|
+
#new_ops = operands.map(&:dup)
|
101
|
+
new_ops = operands.map do |op|
|
102
|
+
op.deep_dup
|
103
|
+
end
|
104
|
+
graph_name = RDF::Node.intern(new_ops.hash)
|
105
|
+
log_debug("formula") {"dup: #{self.graph_name} to #{graph_name}"}
|
106
|
+
self.class.new(*new_ops, **@options.merge(graph_name: graph_name, formulae: formulae))
|
107
|
+
end
|
15
108
|
|
16
109
|
##
|
17
110
|
# Yields solutions from patterns and other operands. Solutions are created by evaluating each pattern and other sub-operand against `queryable`.
|
@@ -20,40 +113,117 @@ module RDF::N3::Algebra
|
|
20
113
|
#
|
21
114
|
# @param [RDF::Queryable] queryable
|
22
115
|
# the graph or repository to query
|
116
|
+
# @param [RDF::Query::Solutions] solutions
|
117
|
+
# initial solutions for chained queries (RDF::Query::Solutions(RDF::Query::Solution.new))
|
23
118
|
# @param [Hash{Symbol => Object}] options
|
24
119
|
# any additional keyword options
|
25
|
-
# @option options [RDF::Query::Solutions] solutions
|
26
|
-
# optional initial solutions for chained queries
|
27
120
|
# @return [RDF::Solutions] distinct solutions
|
28
121
|
def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options)
|
29
|
-
|
122
|
+
log_info("formula #{graph_name}") {SXP::Generator.string operands.to_sxp_bin}
|
123
|
+
log_debug("(formula bindings)") { SXP::Generator.string solutions.to_sxp_bin}
|
30
124
|
|
31
|
-
# If we were passed solutions in options, extract bindings to use for query
|
32
|
-
bindings = solutions.bindings
|
33
|
-
log_debug {"(formula bindings) #{bindings.map {|k,v| RDF::Query::Variable.new(k,v)}.to_sxp}"}
|
34
|
-
|
35
|
-
# Only query as patterns if this is an embedded formula
|
36
125
|
@query ||= RDF::Query.new(patterns).optimize!
|
37
|
-
|
126
|
+
log_info("(formula query)") { SXP::Generator.string(@query.to_sxp_bin)}
|
127
|
+
|
128
|
+
solutions = if @query.empty?
|
129
|
+
solutions
|
130
|
+
else
|
131
|
+
these_solutions = queryable.query(@query, solutions: solutions, **options)
|
132
|
+
if these_solutions.empty?
|
133
|
+
# Pattern doesn't match, so there can be no solutions
|
134
|
+
log_debug("(formula query solutions)") { SXP::Generator.string([].to_sxp_bin)}
|
135
|
+
RDF::Query::Solutions.new
|
136
|
+
else
|
137
|
+
these_solutions.map! do |solution|
|
138
|
+
RDF::Query::Solution.new(solution.to_h.inject({}) do |memo, (name, value)|
|
139
|
+
# Replace blank node bindings with lists and formula references with formula, where those blank nodes are associated with lists.
|
140
|
+
value = formulae.fetch(value, value) if value.node?
|
141
|
+
l = RDF::N3::List.try_list(value, queryable)
|
142
|
+
value = l if l.constant?
|
143
|
+
memo.merge(name => value)
|
144
|
+
end)
|
145
|
+
end
|
146
|
+
log_debug("(formula query solutions)") { SXP::Generator.string(these_solutions.to_sxp_bin)}
|
147
|
+
solutions.merge(these_solutions)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
return solutions if solutions.empty?
|
38
152
|
|
39
|
-
# Merge solution sets
|
40
153
|
# Reject solutions which include variables as values
|
41
|
-
|
42
|
-
.merge(options[:solutions])
|
43
|
-
.filter {|s| s.enum_value.none?(&:variable?)}
|
154
|
+
solutions.filter! {|s| s.enum_value.none?(&:variable?)}
|
44
155
|
|
45
156
|
# Use our solutions for sub-ops
|
46
157
|
# Join solutions from other operands
|
158
|
+
#
|
159
|
+
# * Order operands by those having inputs which are constant or bound.
|
160
|
+
# * Run built-ins with indeterminant inputs (two-way) until any produces non-empty solutions, and then run remaining built-ins until exhasted or finished.
|
161
|
+
# * Re-calculate inputs with bound inputs after each built-in is run.
|
47
162
|
log_depth do
|
48
|
-
sub_ops
|
49
|
-
|
163
|
+
# Iterate over sub_ops using evaluation heuristic
|
164
|
+
ops = sub_ops.sort_by {|op| op.rank(solutions)}
|
165
|
+
while !ops.empty?
|
166
|
+
last_op = nil
|
167
|
+
ops.each do |op|
|
168
|
+
log_debug("(formula built-in)") {SXP::Generator.string op.to_sxp_bin}
|
169
|
+
these_solutions = op.execute(queryable, solutions: solutions)
|
170
|
+
# If there are no solutions, try the next one, until we either run out of operations, or we have solutions
|
171
|
+
next if these_solutions.empty?
|
172
|
+
last_op = op
|
173
|
+
solutions = RDF::Query::Solutions(these_solutions)
|
174
|
+
break
|
175
|
+
end
|
176
|
+
|
177
|
+
# If there is no last_op, there are no solutions.
|
178
|
+
unless last_op
|
179
|
+
solutions = RDF::Query::Solutions.new
|
180
|
+
break
|
181
|
+
end
|
182
|
+
|
183
|
+
# Remove op from list, and re-order remaining ops.
|
184
|
+
ops = (ops - [last_op]).sort_by {|op| op.rank(solutions)}
|
50
185
|
end
|
51
186
|
end
|
52
|
-
|
187
|
+
log_info("(formula sub-op solutions)") {SXP::Generator.string solutions.to_sxp_bin}
|
188
|
+
solutions
|
189
|
+
end
|
190
|
+
|
191
|
+
##
|
192
|
+
# Evaluates the formula using the given variable `bindings` by cloning the formula replacing variables with their bindings recursively.
|
193
|
+
#
|
194
|
+
# @param [Hash{Symbol => RDF::Term}] bindings
|
195
|
+
# a query solution containing zero or more variable bindings
|
196
|
+
# @param [Hash{Symbol => Object}] options ({})
|
197
|
+
# options passed from query
|
198
|
+
# @return [RDF::N3::List]
|
199
|
+
# @see SPARQL::Algebra::Expression.evaluate
|
200
|
+
def evaluate(bindings, formulae:, **options)
|
201
|
+
return self if bindings.empty?
|
202
|
+
this = dup
|
203
|
+
# Maintain formula relationships
|
204
|
+
formulae {|k, v| this.formulae[k] ||= v}
|
205
|
+
|
206
|
+
# Replace operands with bound operands
|
207
|
+
this.operands = operands.map do |op|
|
208
|
+
op.evaluate(bindings, formulae: formulae, **options)
|
209
|
+
end
|
210
|
+
this
|
211
|
+
end
|
212
|
+
|
213
|
+
##
|
214
|
+
# Returns `true` if `self` is a {RDF::N3::Algebra::Formula}.
|
215
|
+
#
|
216
|
+
# @return [Boolean]
|
217
|
+
def formula?
|
218
|
+
true
|
219
|
+
end
|
53
220
|
|
54
|
-
|
55
|
-
|
56
|
-
|
221
|
+
##
|
222
|
+
# The formula hash is the hash of it's operands and graph_name.
|
223
|
+
#
|
224
|
+
# @see RDF::Value#hash
|
225
|
+
def hash
|
226
|
+
([graph_name] + operands).hash
|
57
227
|
end
|
58
228
|
|
59
229
|
##
|
@@ -61,64 +231,88 @@ module RDF::N3::Algebra
|
|
61
231
|
#
|
62
232
|
# @yield [statement]
|
63
233
|
# each matching statement
|
64
|
-
# @yieldparam [RDF::Statement]
|
234
|
+
# @yieldparam [RDF::Statement] statement
|
65
235
|
# @yieldreturn [void] ignored
|
66
|
-
def each(&block)
|
67
|
-
|
68
|
-
# If there are no solutions, create a single solution
|
69
|
-
RDF::Query::Solutions(RDF::Query::Solution.new)
|
70
|
-
end
|
71
|
-
log_debug {"formula #{graph_name} each #{@solutions.to_sxp}"}
|
236
|
+
def each(solutions: RDF::Query::Solutions(RDF::Query::Solution.new), &block)
|
237
|
+
log_debug("(formula each)") {SXP::Generator.string([self, solutions].to_sxp_bin)}
|
72
238
|
|
73
|
-
# Yield
|
74
|
-
|
75
|
-
log_debug {"(formula constant) #{pattern.to_sxp}"}
|
76
|
-
block.call(RDF::Statement.from(pattern, graph_name: graph_name))
|
77
|
-
end
|
78
|
-
|
79
|
-
# Yield patterns by binding variables
|
80
|
-
# FIXME: do we need to do something with non-bound non-distinguished extistential variables?
|
81
|
-
@solutions.each do |solution|
|
239
|
+
# Yield statements by binding variables
|
240
|
+
solutions.each do |solution|
|
82
241
|
# Bind blank nodes to the solution when it doesn't contain a solution for an existential variable
|
83
242
|
existential_vars.each do |var|
|
84
243
|
solution[var.name] ||= RDF::Node.intern(var.name.to_s.sub(/^\$+/, ''))
|
85
244
|
end
|
86
245
|
|
87
|
-
log_debug
|
246
|
+
log_debug("(formula apply)") {solution.to_sxp}
|
88
247
|
# Yield each variable statement which is constant after applying solution
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
248
|
+
log_depth do
|
249
|
+
n3statements.each do |statement|
|
250
|
+
terms = {}
|
251
|
+
[:subject, :predicate, :object].each do |part|
|
252
|
+
terms[part] = case o = statement.send(part)
|
253
|
+
when RDF::Query::Variable
|
254
|
+
if solution[o] && solution[o].formula?
|
255
|
+
log_info("(formula from var form)") {solution[o].graph_name.to_sxp}
|
256
|
+
form_statements(solution[o], solution: solution, &block)
|
257
|
+
else
|
258
|
+
solution[o] || o
|
259
|
+
end
|
260
|
+
when RDF::N3::List
|
261
|
+
o.variable? ? o.evaluate(solution.bindings, formulae: formulae) : o
|
262
|
+
when RDF::N3::Algebra::Formula
|
263
|
+
# uses the graph_name of the formula, and yields statements from the formula. No solutions are passed in.
|
264
|
+
log_info("(formula from form)") {o.graph_name.to_sxp}
|
265
|
+
form_statements(o, solution: solution, &block)
|
266
|
+
else
|
267
|
+
o
|
268
|
+
end
|
95
269
|
end
|
96
|
-
end
|
97
270
|
|
98
|
-
|
271
|
+
statement = RDF::Statement.from(terms)
|
272
|
+
log_debug("(formula add)") {statement.to_sxp}
|
99
273
|
|
100
|
-
|
101
|
-
if statement.variable? ||
|
102
|
-
statement.predicate.literal? ||
|
103
|
-
statement.subject.is_a?(SPARQL::Algebra::Operator) ||
|
104
|
-
statement.object.is_a?(SPARQL::Algebra::Operator)
|
105
|
-
log_debug {"(formula skip) #{statement.to_sxp}"}
|
106
|
-
next
|
274
|
+
block.call(statement)
|
107
275
|
end
|
108
276
|
|
109
|
-
|
110
|
-
|
277
|
+
# statements from sub-operands
|
278
|
+
sub_ops.each do |op|
|
279
|
+
log_debug("(formula sub_op)") {SXP::Generator.string [op, solution].to_sxp_bin}
|
280
|
+
op.each(solutions: RDF::Query::Solutions(solution)) do |stmt|
|
281
|
+
log_debug("(formula add from sub_op)") {stmt.to_sxp}
|
282
|
+
block.call(stmt)
|
283
|
+
# Add statements for any term which is a formula
|
284
|
+
stmt.to_a.select(&:node?).map {|n| formulae[n]}.compact.each do |ef|
|
285
|
+
log_debug("(formula from form)") {ef.graph_name.to_sxp}
|
286
|
+
form_statements(ef, solution: solution, &block)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
111
290
|
end
|
112
291
|
end
|
113
|
-
|
114
|
-
# statements from sub-operands
|
115
|
-
log_depth {sub_ops.each {|op| op.each(&block)}}
|
116
292
|
end
|
117
293
|
|
118
|
-
|
119
|
-
#
|
120
|
-
|
121
|
-
|
294
|
+
##
|
295
|
+
# Yields each pattern which is not a builtin
|
296
|
+
#
|
297
|
+
# @yield [pattern]
|
298
|
+
# each matching pattern
|
299
|
+
# @yieldparam [RDF::Query::Pattern] pattern
|
300
|
+
# @yieldreturn [void] ignored
|
301
|
+
def each_pattern(&block)
|
302
|
+
n3statements.each do |statement|
|
303
|
+
terms = {}
|
304
|
+
[:subject, :predicate, :object].each do |part|
|
305
|
+
terms[part] = case o = statement.send(part)
|
306
|
+
when RDF::N3::Algebra::Formula
|
307
|
+
form_statements(o, solution: RDF::Query::Solution.new(), &block)
|
308
|
+
else
|
309
|
+
o
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
pattern = RDF::Query::Pattern.from(terms)
|
314
|
+
block.call(pattern)
|
315
|
+
end
|
122
316
|
end
|
123
317
|
|
124
318
|
# Graph name associated with this formula
|
@@ -126,60 +320,127 @@ module RDF::N3::Algebra
|
|
126
320
|
def graph_name; @options[:graph_name]; end
|
127
321
|
|
128
322
|
##
|
129
|
-
#
|
130
|
-
|
131
|
-
|
132
|
-
@statements ||= operands.
|
133
|
-
select {|op| op.is_a?(RDF::Statement)}.
|
134
|
-
map do |pattern|
|
135
|
-
|
136
|
-
# Map nodes to non-distinguished existential variables (except when in top-level formula)
|
137
|
-
if graph_name
|
138
|
-
terms = {}
|
139
|
-
[:subject, :predicate, :object].each do |r|
|
140
|
-
terms[r] = case o = pattern.send(r)
|
141
|
-
when RDF::Node then RDF::Query::Variable.new(o.id, existential: true, distinguished: false)
|
142
|
-
else o
|
143
|
-
end
|
144
|
-
end
|
323
|
+
# The URI of a formula is its graph name
|
324
|
+
# @return [RDF::URI]
|
325
|
+
alias_method :to_uri, :graph_name
|
145
326
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
327
|
+
# Assign a graph name to this formula
|
328
|
+
# @param [RDF::Resource] name
|
329
|
+
# @return [RDF::Resource]
|
330
|
+
def graph_name=(name)
|
331
|
+
formulae[name] = self
|
332
|
+
@options[:graph_name] = name
|
151
333
|
end
|
152
334
|
|
153
335
|
##
|
154
|
-
#
|
155
|
-
|
156
|
-
|
157
|
-
|
336
|
+
# Statements memoizer, from the operands which are statements.
|
337
|
+
#
|
338
|
+
# Statements may include embedded formulae.
|
339
|
+
def n3statements
|
340
|
+
# BNodes in statements are existential variables.
|
341
|
+
@n3statements ||= begin
|
342
|
+
# Operations/Builtins are not statements.
|
343
|
+
operands.
|
344
|
+
select {|op| op.is_a?(RDF::Statement)}
|
345
|
+
end
|
158
346
|
end
|
159
347
|
|
160
348
|
##
|
161
|
-
# Patterns memoizer
|
349
|
+
# Patterns memoizer, from the operands which are statements and not builtins.
|
350
|
+
#
|
351
|
+
# Expands statements containing formulae into their statements.
|
162
352
|
def patterns
|
163
|
-
# BNodes in statements are existential variables
|
164
|
-
@patterns ||=
|
353
|
+
# BNodes in statements are existential variables.
|
354
|
+
@patterns ||= enum_for(:each_pattern).to_a
|
165
355
|
end
|
166
356
|
|
167
357
|
##
|
168
358
|
# Non-statement operands memoizer
|
169
359
|
def sub_ops
|
170
360
|
# operands that aren't statements, ordered by their graph_name
|
171
|
-
@sub_ops ||= operands.reject {|op| op.is_a?(RDF::Statement)}
|
361
|
+
@sub_ops ||= operands.reject {|op| op.is_a?(RDF::Statement)}.map do |op|
|
362
|
+
# Substitute nodes for existential variables in operator operands
|
363
|
+
op.operands.map! do |o|
|
364
|
+
case o
|
365
|
+
when RDF::N3::List
|
366
|
+
# Substitute blank node members with existential variables, recusively.
|
367
|
+
graph_name && o.has_nodes? ? o.to_ndvar(graph_name) : o
|
368
|
+
when RDF::Node
|
369
|
+
graph_name ? o.to_ndvar(graph_name) : o
|
370
|
+
else
|
371
|
+
o
|
372
|
+
end
|
373
|
+
end
|
374
|
+
op
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
##
|
379
|
+
# Return the variables contained within this formula
|
380
|
+
# @return [Array<RDF::Query::Variable>]
|
381
|
+
def vars
|
382
|
+
operands.vars.flatten.compact
|
383
|
+
end
|
384
|
+
|
385
|
+
##
|
386
|
+
# Universal vars in this formula and sub-formulae
|
387
|
+
# @return [Array<RDF::Query::Variable]
|
388
|
+
def universal_vars
|
389
|
+
@universals ||= vars.reject(&:existential?).uniq
|
172
390
|
end
|
173
391
|
|
174
392
|
##
|
175
393
|
# Existential vars in this formula
|
394
|
+
# @return [Array<RDF::Query::Variable]
|
176
395
|
def existential_vars
|
177
|
-
@existentials ||=
|
396
|
+
@existentials ||= vars.select(&:existential?)
|
397
|
+
end
|
398
|
+
|
399
|
+
##
|
400
|
+
# Distinguished vars in this formula
|
401
|
+
# @return [Array<RDF::Query::Variable]
|
402
|
+
def distinguished_vars
|
403
|
+
@distinguished ||= vars.vars.select(&:distinguished?)
|
404
|
+
end
|
405
|
+
|
406
|
+
##
|
407
|
+
# Undistinguished vars in this formula
|
408
|
+
# @return [Array<RDF::Query::Variable]
|
409
|
+
def undistinguished_vars
|
410
|
+
@undistinguished ||= vars.vars.reject(&:distinguished?)
|
411
|
+
end
|
412
|
+
|
413
|
+
def to_s
|
414
|
+
to_sxp
|
178
415
|
end
|
179
416
|
|
180
417
|
def to_sxp_bin
|
181
418
|
[:formula, graph_name].compact +
|
182
419
|
operands.map(&:to_sxp_bin)
|
183
420
|
end
|
421
|
+
|
422
|
+
def to_base
|
423
|
+
inspect
|
424
|
+
end
|
425
|
+
|
426
|
+
def inspect
|
427
|
+
sprintf("#<%s:%s(%d)>", self.class.name, self.graph_name, self.operands.count)
|
428
|
+
end
|
429
|
+
|
430
|
+
private
|
431
|
+
# Get statements from a sub-form
|
432
|
+
# @return [RDF::Resource] graph name of form
|
433
|
+
def form_statements(form, solution:, &block)
|
434
|
+
# uses the graph_name of the formula, and yields statements from the formula
|
435
|
+
log_depth do
|
436
|
+
form.each(solutions: RDF::Query::Solutions(solution)) do |stmt|
|
437
|
+
stmt.graph_name ||= form.graph_name
|
438
|
+
log_debug("(form statements add)") {stmt.to_sxp}
|
439
|
+
block.call(stmt)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
form.graph_name
|
444
|
+
end
|
184
445
|
end
|
185
446
|
end
|