rdf-n3 3.0.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +198 -76
- data/UNLICENSE +1 -1
- data/VERSION +1 -1
- data/lib/rdf/n3/algebra/builtin.rb +79 -0
- data/lib/rdf/n3/algebra/formula.rb +446 -0
- data/lib/rdf/n3/algebra/list/append.rb +42 -0
- data/lib/rdf/n3/algebra/list/first.rb +24 -0
- data/lib/rdf/n3/algebra/list/in.rb +48 -0
- data/lib/rdf/n3/algebra/list/iterate.rb +96 -0
- data/lib/rdf/n3/algebra/list/last.rb +24 -0
- data/lib/rdf/n3/algebra/list/length.rb +24 -0
- data/lib/rdf/n3/algebra/list/member.rb +44 -0
- data/lib/rdf/n3/algebra/list_operator.rb +96 -0
- data/lib/rdf/n3/algebra/log/conclusion.rb +65 -0
- data/lib/rdf/n3/algebra/log/conjunction.rb +36 -0
- data/lib/rdf/n3/algebra/log/content.rb +34 -0
- data/lib/rdf/n3/algebra/log/dtlit.rb +41 -0
- data/lib/rdf/n3/algebra/log/equal_to.rb +34 -0
- data/lib/rdf/n3/algebra/log/implies.rb +102 -0
- data/lib/rdf/n3/algebra/log/includes.rb +70 -0
- data/lib/rdf/n3/algebra/log/langlit.rb +41 -0
- 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 +40 -0
- data/lib/rdf/n3/algebra/math/equal_to.rb +54 -0
- data/lib/rdf/n3/algebra/math/exponentiation.rb +35 -0
- 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 +38 -0
- 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 +20 -0
- data/lib/rdf/n3/algebra/math/quotient.rb +36 -0
- data/lib/rdf/n3/algebra/math/remainder.rb +35 -0
- data/lib/rdf/n3/algebra/math/rounded.rb +26 -0
- 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 +40 -0
- 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 +122 -0
- data/lib/rdf/n3/algebra/str/concatenation.rb +27 -0
- data/lib/rdf/n3/algebra/str/contains.rb +33 -0
- 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 +17 -0
- 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 +37 -0
- 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 +35 -0
- data/lib/rdf/n3/algebra/str/scrape.rb +35 -0
- 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/algebra.rb +210 -0
- data/lib/rdf/n3/extensions.rb +221 -0
- data/lib/rdf/n3/format.rb +66 -1
- data/lib/rdf/n3/list.rb +630 -0
- data/lib/rdf/n3/reader.rb +774 -497
- data/lib/rdf/n3/reasoner.rb +282 -0
- data/lib/rdf/n3/refinements.rb +178 -0
- data/lib/rdf/n3/repository.rb +332 -0
- data/lib/rdf/n3/terminals.rb +78 -0
- data/lib/rdf/n3/vocab.rb +36 -3
- data/lib/rdf/n3/writer.rb +461 -250
- data/lib/rdf/n3.rb +11 -8
- metadata +177 -49
- data/AUTHORS +0 -1
- data/History.markdown +0 -99
- 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 -237
@@ -0,0 +1,446 @@
|
|
1
|
+
require 'rdf/n3'
|
2
|
+
|
3
|
+
module RDF::N3::Algebra
|
4
|
+
#
|
5
|
+
# A Notation3 Formula combines a graph with a BGP query.
|
6
|
+
class Formula < SPARQL::Algebra::Operator
|
7
|
+
include RDF::Term
|
8
|
+
include RDF::Enumerable
|
9
|
+
include SPARQL::Algebra::Query
|
10
|
+
include SPARQL::Algebra::Update
|
11
|
+
include RDF::N3::Algebra::Builtin
|
12
|
+
|
13
|
+
##
|
14
|
+
# Query to run against a queryable to determine if the formula matches the queryable.
|
15
|
+
#
|
16
|
+
# @return [RDF::Query]
|
17
|
+
attr_accessor :query
|
18
|
+
|
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
|
108
|
+
|
109
|
+
##
|
110
|
+
# Yields solutions from patterns and other operands. Solutions are created by evaluating each pattern and other sub-operand against `queryable`.
|
111
|
+
#
|
112
|
+
# 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.
|
113
|
+
#
|
114
|
+
# @param [RDF::Queryable] queryable
|
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))
|
118
|
+
# @param [Hash{Symbol => Object}] options
|
119
|
+
# any additional keyword options
|
120
|
+
# @return [RDF::Solutions] distinct solutions
|
121
|
+
def execute(queryable, solutions: RDF::Query::Solutions(RDF::Query::Solution.new), **options)
|
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}
|
124
|
+
|
125
|
+
@query ||= RDF::Query.new(patterns).optimize!
|
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?
|
152
|
+
|
153
|
+
# Reject solutions which include variables as values
|
154
|
+
solutions.filter! {|s| s.enum_value.none?(&:variable?)}
|
155
|
+
|
156
|
+
# Use our solutions for sub-ops
|
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.
|
162
|
+
log_depth do
|
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)}
|
185
|
+
end
|
186
|
+
end
|
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
|
220
|
+
|
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
|
227
|
+
end
|
228
|
+
|
229
|
+
##
|
230
|
+
# Yields each statement from this formula bound to previously determined solutions.
|
231
|
+
#
|
232
|
+
# @yield [statement]
|
233
|
+
# each matching statement
|
234
|
+
# @yieldparam [RDF::Statement] statement
|
235
|
+
# @yieldreturn [void] ignored
|
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)}
|
238
|
+
|
239
|
+
# Yield statements by binding variables
|
240
|
+
solutions.each do |solution|
|
241
|
+
# Bind blank nodes to the solution when it doesn't contain a solution for an existential variable
|
242
|
+
existential_vars.each do |var|
|
243
|
+
solution[var.name] ||= RDF::Node.intern(var.name.to_s.sub(/^\$+/, ''))
|
244
|
+
end
|
245
|
+
|
246
|
+
log_debug("(formula apply)") {solution.to_sxp}
|
247
|
+
# Yield each variable statement which is constant after applying solution
|
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
|
269
|
+
end
|
270
|
+
|
271
|
+
statement = RDF::Statement.from(terms)
|
272
|
+
log_debug("(formula add)") {statement.to_sxp}
|
273
|
+
|
274
|
+
block.call(statement)
|
275
|
+
end
|
276
|
+
|
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
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
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
|
316
|
+
end
|
317
|
+
|
318
|
+
# Graph name associated with this formula
|
319
|
+
# @return [RDF::Resource]
|
320
|
+
def graph_name; @options[:graph_name]; end
|
321
|
+
|
322
|
+
##
|
323
|
+
# The URI of a formula is its graph name
|
324
|
+
# @return [RDF::URI]
|
325
|
+
alias_method :to_uri, :graph_name
|
326
|
+
|
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
|
333
|
+
end
|
334
|
+
|
335
|
+
##
|
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
|
346
|
+
end
|
347
|
+
|
348
|
+
##
|
349
|
+
# Patterns memoizer, from the operands which are statements and not builtins.
|
350
|
+
#
|
351
|
+
# Expands statements containing formulae into their statements.
|
352
|
+
def patterns
|
353
|
+
# BNodes in statements are existential variables.
|
354
|
+
@patterns ||= enum_for(:each_pattern).to_a
|
355
|
+
end
|
356
|
+
|
357
|
+
##
|
358
|
+
# Non-statement operands memoizer
|
359
|
+
def sub_ops
|
360
|
+
# operands that aren't statements, ordered by their graph_name
|
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
|
390
|
+
end
|
391
|
+
|
392
|
+
##
|
393
|
+
# Existential vars in this formula
|
394
|
+
# @return [Array<RDF::Query::Variable]
|
395
|
+
def existential_vars
|
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
|
415
|
+
end
|
416
|
+
|
417
|
+
def to_sxp_bin
|
418
|
+
[:formula, graph_name].compact +
|
419
|
+
operands.map(&:to_sxp_bin)
|
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
|
445
|
+
end
|
446
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RDF::N3::Algebra::List
|
2
|
+
##
|
3
|
+
# Iff the subject is a list of lists and the concatenation of all those lists is the object, then this is true. The object can be calculated as a function of the subject.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# ( (1 2) (3 4) ) list:append (1 2 3 4).
|
7
|
+
#
|
8
|
+
# The object can be calculated as a function of the subject.
|
9
|
+
class Append < RDF::N3::Algebra::ListOperator
|
10
|
+
NAME = :listAppend
|
11
|
+
URI = RDF::N3::List.append
|
12
|
+
|
13
|
+
##
|
14
|
+
# Resolves this operator using the given variable `bindings`.
|
15
|
+
# If the last operand is a variable, it creates a solution for each element in the list.
|
16
|
+
#
|
17
|
+
# @param [RDF::N3::List] list
|
18
|
+
# @return [RDF::Term]
|
19
|
+
# @see RDF::N3::ListOperator#evaluate
|
20
|
+
def resolve(list)
|
21
|
+
flattened = list.to_a.map(&:to_a).flatten
|
22
|
+
# Bind a new list based on the values, whos subject use made up from original list subjects
|
23
|
+
subj = RDF::Node.intern(list.map(&:subject).hash)
|
24
|
+
RDF::N3::List.new(subject: subj, values: flattened)
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# The list argument must be a pair of literals.
|
29
|
+
#
|
30
|
+
# @param [RDF::N3::List] list
|
31
|
+
# @return [Boolean]
|
32
|
+
# @see RDF::N3::ListOperator#validate
|
33
|
+
def validate(list)
|
34
|
+
if super && list.to_a.all? {|li| li.list?}
|
35
|
+
true
|
36
|
+
else
|
37
|
+
log_error(NAME) {"operand is not a list of lists: #{list.to_sxp}"}
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RDF::N3::Algebra::List
|
2
|
+
##
|
3
|
+
# Iff the subject is a list and the object is the first thing that list, then this is true. The object can be calculated as a function of the list.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# { ( 1 2 3 4 5 6 ) list:first 1 } => { :test1 a :SUCCESS }.
|
7
|
+
#
|
8
|
+
# The object can be calculated as a function of the list.
|
9
|
+
class First < RDF::N3::Algebra::ListOperator
|
10
|
+
NAME = :listFirst
|
11
|
+
URI = RDF::N3::List.first
|
12
|
+
|
13
|
+
##
|
14
|
+
# Resolves this operator using the given variable `bindings`.
|
15
|
+
# If the last operand is a variable, it creates a solution for each element in the list.
|
16
|
+
#
|
17
|
+
# @param [RDF::N3::List] list
|
18
|
+
# @return [RDF::Term]
|
19
|
+
# @see RDF::N3::ListOperator#evaluate
|
20
|
+
def resolve(list)
|
21
|
+
list.first
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RDF::N3::Algebra::List
|
2
|
+
##
|
3
|
+
# Iff the object is a list and the subject is in that list, then this is true.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# { 1 list:in ( 1 2 3 4 5 ) } => { :test4a a :SUCCESS }.
|
7
|
+
class In < RDF::N3::Algebra::ListOperator
|
8
|
+
NAME = :listIn
|
9
|
+
URI = RDF::N3::List.in
|
10
|
+
|
11
|
+
##
|
12
|
+
# Evaluates this operator using the given variable `bindings`.
|
13
|
+
# If the first operand is a variable, it creates a solution for each element in the list.
|
14
|
+
#
|
15
|
+
# @param [RDF::Queryable] queryable
|
16
|
+
# the graph or repository to query
|
17
|
+
# @param [RDF::Query::Solutions] solutions
|
18
|
+
# solutions for chained queries
|
19
|
+
# @return [RDF::Query::Solutions]
|
20
|
+
def execute(queryable, solutions:, **options)
|
21
|
+
RDF::Query::Solutions(solutions.map do |solution|
|
22
|
+
subject = operand(0).evaluate(solution.bindings, formulae: formulae) || operand(0)
|
23
|
+
# Might be a variable or node evaluating to a list in queryable, or might be a list with variables
|
24
|
+
list = operand(1).evaluate(solution.bindings, formulae: formulae)
|
25
|
+
next unless list
|
26
|
+
# If it evaluated to a BNode, re-expand as a list
|
27
|
+
list = RDF::N3::List.try_list(list, queryable).evaluate(solution.bindings, formulae: formulae)
|
28
|
+
|
29
|
+
log_debug(NAME) {"subject: #{subject.to_sxp}, list: #{list.to_sxp}"}
|
30
|
+
unless list.list? && list.valid?
|
31
|
+
log_error(NAME) {"operand is not a list: #{list.to_sxp}"}
|
32
|
+
next
|
33
|
+
end
|
34
|
+
|
35
|
+
if subject.variable?
|
36
|
+
# Bind all list entries to this solution, creates an array of solutions
|
37
|
+
list.to_a.map do |term|
|
38
|
+
solution.merge(subject.to_sym => term)
|
39
|
+
end
|
40
|
+
elsif list.to_a.include?(subject)
|
41
|
+
solution
|
42
|
+
else
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end.flatten.compact.uniq)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module RDF::N3::Algebra::List
|
2
|
+
##
|
3
|
+
# Generates a list of lists when each constituent list is composed of the index and value of each element in the subject.
|
4
|
+
#
|
5
|
+
# Binds variables in the object list.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# { (1 2 3) list:iterate ((0 1) (1 2) (2 3)) } => { :test4a a :SUCCESS }.
|
9
|
+
class Iterate < RDF::N3::Algebra::ListOperator
|
10
|
+
NAME = :listIterate
|
11
|
+
URI = RDF::N3::List.iterate
|
12
|
+
|
13
|
+
##
|
14
|
+
# Evaluates this operator using the given variable `bindings`.
|
15
|
+
# The subject MUST evaluate to a list and the object to a list composed of two components: index and value.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# {(1 2 3) list:iterate (?x ?y)} => {:solution :is (?x ?y)} .
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# {(1 2 3) list:iterate ?L} => {:solution :is ?L} .
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# {(1 2 3) list:iterate (1 ?y)} => {:value :is ?y} .
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# {(1 2 3) list:iterate (?x 2)} => {:index :is ?x} .
|
28
|
+
#
|
29
|
+
# @param [RDF::Queryable] queryable
|
30
|
+
# the graph or repository to query
|
31
|
+
# @param [RDF::Query::Solutions] solutions
|
32
|
+
# solutions for chained queries
|
33
|
+
# @return [RDF::Query::Solutions]
|
34
|
+
def execute(queryable, solutions:, **options)
|
35
|
+
RDF::Query::Solutions(solutions.map do |solution|
|
36
|
+
subject = operand(0).evaluate(solution.bindings, formulae: formulae) || operand(0)
|
37
|
+
# Might be a variable or node evaluating to a list in queryable, or might be a list with variables
|
38
|
+
# If subject evaluated to a BNode, re-expand as a list
|
39
|
+
subject = RDF::N3::List.try_list(subject, queryable).evaluate(solution.bindings, formulae: formulae)
|
40
|
+
next unless validate(subject)
|
41
|
+
|
42
|
+
object = operand(1).evaluate(solution.bindings, formulae: formulae) || operand(1)
|
43
|
+
next unless object
|
44
|
+
# If object evaluated to a BNode, re-expand as a list
|
45
|
+
object = RDF::N3::List.try_list(object, queryable).evaluate(solution.bindings, formulae: formulae) || object
|
46
|
+
|
47
|
+
if object.list? && object.variable?
|
48
|
+
# Create a solution for those entries in subject that match object
|
49
|
+
if object.length != 2
|
50
|
+
log_error(NAME) {"object is not a list with two entries: #{object.to_sxp}"}
|
51
|
+
next
|
52
|
+
end
|
53
|
+
if object.first.variable? && object.last.variable?
|
54
|
+
solutions = RDF::Query::Solutions.new
|
55
|
+
subject.each_with_index do |r, i|
|
56
|
+
s = solution.merge(object.first.to_sym => RDF::Literal(i), object.last.to_sym => r)
|
57
|
+
log_debug(self.class.const_get(:NAME), "result: #{s.to_sxp}")
|
58
|
+
solutions << s
|
59
|
+
end
|
60
|
+
solutions
|
61
|
+
elsif object.first.variable?
|
62
|
+
# Solution binds indexes to all matching values
|
63
|
+
solutions = RDF::Query::Solutions.new
|
64
|
+
subject.each_with_index do |r, i|
|
65
|
+
next unless r == object.last
|
66
|
+
s = solution.merge(object.first.to_sym => RDF::Literal(i))
|
67
|
+
log_debug(self.class.const_get(:NAME), "result: #{s.to_sxp}")
|
68
|
+
solutions << s
|
69
|
+
end
|
70
|
+
solutions
|
71
|
+
elsif object.last.variable?
|
72
|
+
# Solution binds value at specified index
|
73
|
+
next unless v = subject.at(object.first)
|
74
|
+
s = solution.merge(object.last.to_sym => v)
|
75
|
+
log_debug(self.class.const_get(:NAME), "result: #{s.to_sxp}")
|
76
|
+
s
|
77
|
+
end
|
78
|
+
elsif object.variable?
|
79
|
+
# Create a solution for each index/value pair in subject
|
80
|
+
solutions = RDF::Query::Solutions.new
|
81
|
+
subject.each_with_index do |r, i|
|
82
|
+
s = solution.merge(object.to_sym => RDF::N3::List[RDF::Literal(i), r])
|
83
|
+
log_debug(self.class.const_get(:NAME), "result: #{s.to_sxp}")
|
84
|
+
solutions << s
|
85
|
+
end
|
86
|
+
solutions
|
87
|
+
else
|
88
|
+
# Evaluates to true if the subject has a matching entry
|
89
|
+
same = subject.at(object.first) == object.last
|
90
|
+
log_debug(self.class.const_get(:NAME), "result: #{same.inspect}")
|
91
|
+
solution if same
|
92
|
+
end
|
93
|
+
end.flatten.compact.uniq)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RDF::N3::Algebra::List
|
2
|
+
##
|
3
|
+
# Iff the subject is a list and the object is the last thing that list, then this is true. The object can be calculated as a function of the list.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# { ( 1 2 3 4 5 6 ) list:last 6 } => { :test1 a :SUCCESS }.
|
7
|
+
#
|
8
|
+
# The object can be calculated as a function of the list.
|
9
|
+
class Last < RDF::N3::Algebra::ListOperator
|
10
|
+
NAME = :listLast
|
11
|
+
URI = RDF::N3::List.last
|
12
|
+
|
13
|
+
##
|
14
|
+
# Resolves this operator using the given variable `bindings`.
|
15
|
+
# If the last operand is a variable, it creates a solution for each element in the list.
|
16
|
+
#
|
17
|
+
# @param [RDF::N3::List] list
|
18
|
+
# @return [RDF::Term]
|
19
|
+
# @see RDF::N3::ListOperator#evaluate
|
20
|
+
def resolve(list)
|
21
|
+
list.last
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|