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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +198 -76
  3. data/UNLICENSE +1 -1
  4. data/VERSION +1 -1
  5. data/lib/rdf/n3/algebra/builtin.rb +79 -0
  6. data/lib/rdf/n3/algebra/formula.rb +446 -0
  7. data/lib/rdf/n3/algebra/list/append.rb +42 -0
  8. data/lib/rdf/n3/algebra/list/first.rb +24 -0
  9. data/lib/rdf/n3/algebra/list/in.rb +48 -0
  10. data/lib/rdf/n3/algebra/list/iterate.rb +96 -0
  11. data/lib/rdf/n3/algebra/list/last.rb +24 -0
  12. data/lib/rdf/n3/algebra/list/length.rb +24 -0
  13. data/lib/rdf/n3/algebra/list/member.rb +44 -0
  14. data/lib/rdf/n3/algebra/list_operator.rb +96 -0
  15. data/lib/rdf/n3/algebra/log/conclusion.rb +65 -0
  16. data/lib/rdf/n3/algebra/log/conjunction.rb +36 -0
  17. data/lib/rdf/n3/algebra/log/content.rb +34 -0
  18. data/lib/rdf/n3/algebra/log/dtlit.rb +41 -0
  19. data/lib/rdf/n3/algebra/log/equal_to.rb +34 -0
  20. data/lib/rdf/n3/algebra/log/implies.rb +102 -0
  21. data/lib/rdf/n3/algebra/log/includes.rb +70 -0
  22. data/lib/rdf/n3/algebra/log/langlit.rb +41 -0
  23. data/lib/rdf/n3/algebra/log/n3_string.rb +34 -0
  24. data/lib/rdf/n3/algebra/log/not_equal_to.rb +23 -0
  25. data/lib/rdf/n3/algebra/log/not_includes.rb +27 -0
  26. data/lib/rdf/n3/algebra/log/output_string.rb +40 -0
  27. data/lib/rdf/n3/algebra/log/parsed_as_n3.rb +36 -0
  28. data/lib/rdf/n3/algebra/log/semantics.rb +40 -0
  29. data/lib/rdf/n3/algebra/math/absolute_value.rb +36 -0
  30. data/lib/rdf/n3/algebra/math/acos.rb +26 -0
  31. data/lib/rdf/n3/algebra/math/acosh.rb +26 -0
  32. data/lib/rdf/n3/algebra/math/asin.rb +26 -0
  33. data/lib/rdf/n3/algebra/math/asinh.rb +26 -0
  34. data/lib/rdf/n3/algebra/math/atan.rb +26 -0
  35. data/lib/rdf/n3/algebra/math/atanh.rb +26 -0
  36. data/lib/rdf/n3/algebra/math/ceiling.rb +28 -0
  37. data/lib/rdf/n3/algebra/math/cos.rb +40 -0
  38. data/lib/rdf/n3/algebra/math/cosh.rb +38 -0
  39. data/lib/rdf/n3/algebra/math/difference.rb +40 -0
  40. data/lib/rdf/n3/algebra/math/equal_to.rb +54 -0
  41. data/lib/rdf/n3/algebra/math/exponentiation.rb +35 -0
  42. data/lib/rdf/n3/algebra/math/floor.rb +28 -0
  43. data/lib/rdf/n3/algebra/math/greater_than.rb +41 -0
  44. data/lib/rdf/n3/algebra/math/less_than.rb +41 -0
  45. data/lib/rdf/n3/algebra/math/negation.rb +38 -0
  46. data/lib/rdf/n3/algebra/math/not_equal_to.rb +25 -0
  47. data/lib/rdf/n3/algebra/math/not_greater_than.rb +25 -0
  48. data/lib/rdf/n3/algebra/math/not_less_than.rb +25 -0
  49. data/lib/rdf/n3/algebra/math/product.rb +20 -0
  50. data/lib/rdf/n3/algebra/math/quotient.rb +36 -0
  51. data/lib/rdf/n3/algebra/math/remainder.rb +35 -0
  52. data/lib/rdf/n3/algebra/math/rounded.rb +26 -0
  53. data/lib/rdf/n3/algebra/math/sin.rb +40 -0
  54. data/lib/rdf/n3/algebra/math/sinh.rb +38 -0
  55. data/lib/rdf/n3/algebra/math/sum.rb +40 -0
  56. data/lib/rdf/n3/algebra/math/tan.rb +40 -0
  57. data/lib/rdf/n3/algebra/math/tanh.rb +38 -0
  58. data/lib/rdf/n3/algebra/not_implemented.rb +13 -0
  59. data/lib/rdf/n3/algebra/resource_operator.rb +122 -0
  60. data/lib/rdf/n3/algebra/str/concatenation.rb +27 -0
  61. data/lib/rdf/n3/algebra/str/contains.rb +33 -0
  62. data/lib/rdf/n3/algebra/str/contains_ignoring_case.rb +33 -0
  63. data/lib/rdf/n3/algebra/str/ends_with.rb +33 -0
  64. data/lib/rdf/n3/algebra/str/equal_ignoring_case.rb +34 -0
  65. data/lib/rdf/n3/algebra/str/format.rb +17 -0
  66. data/lib/rdf/n3/algebra/str/greater_than.rb +38 -0
  67. data/lib/rdf/n3/algebra/str/less_than.rb +33 -0
  68. data/lib/rdf/n3/algebra/str/matches.rb +37 -0
  69. data/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb +17 -0
  70. data/lib/rdf/n3/algebra/str/not_greater_than.rb +17 -0
  71. data/lib/rdf/n3/algebra/str/not_less_than.rb +17 -0
  72. data/lib/rdf/n3/algebra/str/not_matches.rb +18 -0
  73. data/lib/rdf/n3/algebra/str/replace.rb +35 -0
  74. data/lib/rdf/n3/algebra/str/scrape.rb +35 -0
  75. data/lib/rdf/n3/algebra/str/starts_with.rb +33 -0
  76. data/lib/rdf/n3/algebra/time/day.rb +35 -0
  77. data/lib/rdf/n3/algebra/time/day_of_week.rb +27 -0
  78. data/lib/rdf/n3/algebra/time/gm_time.rb +29 -0
  79. data/lib/rdf/n3/algebra/time/hour.rb +35 -0
  80. data/lib/rdf/n3/algebra/time/in_seconds.rb +59 -0
  81. data/lib/rdf/n3/algebra/time/local_time.rb +29 -0
  82. data/lib/rdf/n3/algebra/time/minute.rb +35 -0
  83. data/lib/rdf/n3/algebra/time/month.rb +35 -0
  84. data/lib/rdf/n3/algebra/time/second.rb +35 -0
  85. data/lib/rdf/n3/algebra/time/timezone.rb +36 -0
  86. data/lib/rdf/n3/algebra/time/year.rb +29 -0
  87. data/lib/rdf/n3/algebra.rb +210 -0
  88. data/lib/rdf/n3/extensions.rb +221 -0
  89. data/lib/rdf/n3/format.rb +66 -1
  90. data/lib/rdf/n3/list.rb +630 -0
  91. data/lib/rdf/n3/reader.rb +774 -497
  92. data/lib/rdf/n3/reasoner.rb +282 -0
  93. data/lib/rdf/n3/refinements.rb +178 -0
  94. data/lib/rdf/n3/repository.rb +332 -0
  95. data/lib/rdf/n3/terminals.rb +78 -0
  96. data/lib/rdf/n3/vocab.rb +36 -3
  97. data/lib/rdf/n3/writer.rb +461 -250
  98. data/lib/rdf/n3.rb +11 -8
  99. metadata +177 -49
  100. data/AUTHORS +0 -1
  101. data/History.markdown +0 -99
  102. data/lib/rdf/n3/patches/array_hacks.rb +0 -53
  103. data/lib/rdf/n3/reader/meta.rb +0 -641
  104. 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