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.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +148 -69
  3. data/UNLICENSE +1 -1
  4. data/VERSION +1 -1
  5. data/lib/rdf/n3.rb +8 -8
  6. data/lib/rdf/n3/algebra.rb +147 -68
  7. data/lib/rdf/n3/algebra/builtin.rb +79 -0
  8. data/lib/rdf/n3/algebra/formula.rb +355 -94
  9. data/lib/rdf/n3/algebra/list/append.rb +33 -4
  10. data/lib/rdf/n3/algebra/list/first.rb +24 -0
  11. data/lib/rdf/n3/algebra/list/in.rb +42 -3
  12. data/lib/rdf/n3/algebra/list/last.rb +17 -4
  13. data/lib/rdf/n3/algebra/list/length.rb +24 -0
  14. data/lib/rdf/n3/algebra/list/member.rb +39 -2
  15. data/lib/rdf/n3/algebra/list_operator.rb +83 -0
  16. data/lib/rdf/n3/algebra/log/conclusion.rb +57 -1
  17. data/lib/rdf/n3/algebra/log/conjunction.rb +28 -1
  18. data/lib/rdf/n3/algebra/log/content.rb +34 -0
  19. data/lib/rdf/n3/algebra/log/equal_to.rb +34 -0
  20. data/lib/rdf/n3/algebra/log/implies.rb +55 -30
  21. data/lib/rdf/n3/algebra/log/includes.rb +58 -1
  22. data/lib/rdf/n3/algebra/log/n3_string.rb +34 -0
  23. data/lib/rdf/n3/algebra/log/not_equal_to.rb +23 -0
  24. data/lib/rdf/n3/algebra/log/not_includes.rb +27 -0
  25. data/lib/rdf/n3/algebra/log/output_string.rb +40 -0
  26. data/lib/rdf/n3/algebra/log/parsed_as_n3.rb +36 -0
  27. data/lib/rdf/n3/algebra/log/semantics.rb +40 -0
  28. data/lib/rdf/n3/algebra/math/absolute_value.rb +36 -0
  29. data/lib/rdf/n3/algebra/math/acos.rb +26 -0
  30. data/lib/rdf/n3/algebra/math/acosh.rb +26 -0
  31. data/lib/rdf/n3/algebra/math/asin.rb +26 -0
  32. data/lib/rdf/n3/algebra/math/asinh.rb +26 -0
  33. data/lib/rdf/n3/algebra/math/atan.rb +26 -0
  34. data/lib/rdf/n3/algebra/math/atanh.rb +26 -0
  35. data/lib/rdf/n3/algebra/math/ceiling.rb +28 -0
  36. data/lib/rdf/n3/algebra/math/cos.rb +40 -0
  37. data/lib/rdf/n3/algebra/math/cosh.rb +38 -0
  38. data/lib/rdf/n3/algebra/math/difference.rb +34 -3
  39. data/lib/rdf/n3/algebra/math/equal_to.rb +54 -0
  40. data/lib/rdf/n3/algebra/math/exponentiation.rb +29 -3
  41. data/lib/rdf/n3/algebra/math/floor.rb +28 -0
  42. data/lib/rdf/n3/algebra/math/greater_than.rb +41 -0
  43. data/lib/rdf/n3/algebra/math/less_than.rb +41 -0
  44. data/lib/rdf/n3/algebra/math/negation.rb +31 -2
  45. data/lib/rdf/n3/algebra/math/not_equal_to.rb +25 -0
  46. data/lib/rdf/n3/algebra/math/not_greater_than.rb +25 -0
  47. data/lib/rdf/n3/algebra/math/not_less_than.rb +25 -0
  48. data/lib/rdf/n3/algebra/math/product.rb +14 -3
  49. data/lib/rdf/n3/algebra/math/quotient.rb +30 -3
  50. data/lib/rdf/n3/algebra/math/remainder.rb +29 -3
  51. data/lib/rdf/n3/algebra/math/rounded.rb +20 -3
  52. data/lib/rdf/n3/algebra/math/sin.rb +40 -0
  53. data/lib/rdf/n3/algebra/math/sinh.rb +38 -0
  54. data/lib/rdf/n3/algebra/math/sum.rb +35 -4
  55. data/lib/rdf/n3/algebra/math/tan.rb +40 -0
  56. data/lib/rdf/n3/algebra/math/tanh.rb +38 -0
  57. data/lib/rdf/n3/algebra/not_implemented.rb +13 -0
  58. data/lib/rdf/n3/algebra/resource_operator.rb +123 -0
  59. data/lib/rdf/n3/algebra/str/concatenation.rb +21 -3
  60. data/lib/rdf/n3/algebra/str/contains.rb +28 -4
  61. data/lib/rdf/n3/algebra/str/contains_ignoring_case.rb +33 -0
  62. data/lib/rdf/n3/algebra/str/ends_with.rb +33 -0
  63. data/lib/rdf/n3/algebra/str/equal_ignoring_case.rb +34 -0
  64. data/lib/rdf/n3/algebra/str/format.rb +12 -4
  65. data/lib/rdf/n3/algebra/str/greater_than.rb +38 -0
  66. data/lib/rdf/n3/algebra/str/less_than.rb +33 -0
  67. data/lib/rdf/n3/algebra/str/matches.rb +33 -5
  68. data/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb +17 -0
  69. data/lib/rdf/n3/algebra/str/not_greater_than.rb +17 -0
  70. data/lib/rdf/n3/algebra/str/not_less_than.rb +17 -0
  71. data/lib/rdf/n3/algebra/str/not_matches.rb +18 -0
  72. data/lib/rdf/n3/algebra/str/replace.rb +28 -5
  73. data/lib/rdf/n3/algebra/str/scrape.rb +31 -5
  74. data/lib/rdf/n3/algebra/str/starts_with.rb +33 -0
  75. data/lib/rdf/n3/algebra/time/day.rb +35 -0
  76. data/lib/rdf/n3/algebra/time/day_of_week.rb +27 -0
  77. data/lib/rdf/n3/algebra/time/gm_time.rb +29 -0
  78. data/lib/rdf/n3/algebra/time/hour.rb +35 -0
  79. data/lib/rdf/n3/algebra/time/in_seconds.rb +59 -0
  80. data/lib/rdf/n3/algebra/time/local_time.rb +29 -0
  81. data/lib/rdf/n3/algebra/time/minute.rb +35 -0
  82. data/lib/rdf/n3/algebra/time/month.rb +35 -0
  83. data/lib/rdf/n3/algebra/time/second.rb +35 -0
  84. data/lib/rdf/n3/algebra/time/timezone.rb +36 -0
  85. data/lib/rdf/n3/algebra/time/year.rb +29 -0
  86. data/lib/rdf/n3/extensions.rb +180 -21
  87. data/lib/rdf/n3/format.rb +65 -0
  88. data/lib/rdf/n3/list.rb +630 -0
  89. data/lib/rdf/n3/reader.rb +762 -485
  90. data/lib/rdf/n3/reasoner.rb +57 -68
  91. data/lib/rdf/n3/refinements.rb +178 -0
  92. data/lib/rdf/n3/repository.rb +332 -0
  93. data/lib/rdf/n3/terminals.rb +80 -0
  94. data/lib/rdf/n3/vocab.rb +35 -7
  95. data/lib/rdf/n3/writer.rb +208 -148
  96. metadata +110 -52
  97. data/AUTHORS +0 -1
  98. data/History.markdown +0 -99
  99. data/lib/rdf/n3/algebra/log/equalTo.rb +0 -7
  100. data/lib/rdf/n3/algebra/log/notEqualTo.rb +0 -7
  101. data/lib/rdf/n3/algebra/log/notIncludes.rb +0 -12
  102. data/lib/rdf/n3/algebra/log/outputString.rb +0 -7
  103. data/lib/rdf/n3/algebra/math/absoluteValue.rb +0 -9
  104. data/lib/rdf/n3/algebra/math/equalTo.rb +0 -9
  105. data/lib/rdf/n3/algebra/math/greaterThan.rb +0 -9
  106. data/lib/rdf/n3/algebra/math/integerQuotient.rb +0 -9
  107. data/lib/rdf/n3/algebra/math/lessThan.rb +0 -9
  108. data/lib/rdf/n3/algebra/math/memberCount.rb +0 -9
  109. data/lib/rdf/n3/algebra/math/notEqualTo.rb +0 -9
  110. data/lib/rdf/n3/algebra/math/notGreaterThan.rb +0 -9
  111. data/lib/rdf/n3/algebra/math/notLessThan.rb +0 -9
  112. data/lib/rdf/n3/algebra/str/containsIgnoringCase.rb +0 -9
  113. data/lib/rdf/n3/algebra/str/endsWith.rb +0 -9
  114. data/lib/rdf/n3/algebra/str/equalIgnoringCase.rb +0 -9
  115. data/lib/rdf/n3/algebra/str/greaterThan.rb +0 -9
  116. data/lib/rdf/n3/algebra/str/lessThan.rb +0 -9
  117. data/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb +0 -9
  118. data/lib/rdf/n3/algebra/str/notGreaterThan.rb +0 -9
  119. data/lib/rdf/n3/algebra/str/notLessThan.rb +0 -9
  120. data/lib/rdf/n3/algebra/str/notMatches.rb +0 -9
  121. data/lib/rdf/n3/algebra/str/startsWith.rb +0 -56
  122. data/lib/rdf/n3/patches/array_hacks.rb +0 -53
  123. data/lib/rdf/n3/reader/meta.rb +0 -641
  124. 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::Enumerable
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 = [:formula]
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
- log_debug {"formula #{graph_name} #{operands.to_sxp}"}
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
- @solutions = @query.patterns.empty? ? solutions : queryable.query(@query, solutions: solutions, bindings: bindings, **options)
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
- @solutions = @solutions
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.each do |op|
49
- @solutions = op.execute(queryable, solutions: @solutions)
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
- log_debug {"(formula solutions) #{@solutions.to_sxp}"}
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
- # Only return solutions with distinguished variables
55
- variable_names = @solutions.variable_names.reject {|v| v.to_s.start_with?('$$')}
56
- variable_names.empty? ? @solutions : @solutions.dup.project(*variable_names)
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] solution
234
+ # @yieldparam [RDF::Statement] statement
65
235
  # @yieldreturn [void] ignored
66
- def each(&block)
67
- @solutions ||= begin
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 constant statements/patterns
74
- constants.each do |pattern|
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 {"(formula apply) #{solution.to_sxp} to BGP"}
246
+ log_debug("(formula apply)") {solution.to_sxp}
88
247
  # Yield each variable statement which is constant after applying solution
89
- patterns.each do |pattern|
90
- terms = {}
91
- [:subject, :predicate, :object].each do |r|
92
- terms[r] = case o = pattern.send(r)
93
- when RDF::Query::Variable then solution[o]
94
- else o
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
- statement = RDF::Statement.from(terms)
271
+ statement = RDF::Statement.from(terms)
272
+ log_debug("(formula add)") {statement.to_sxp}
99
273
 
100
- # Sanity checking on statement
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
- log_debug {"(formula add) #{statement.to_sxp}"}
110
- block.call(statement)
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
- # Set solutions
119
- # @param [RDF::Query::Solutions] solutions
120
- def solutions=(solutions)
121
- @solutions = solutions
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
- # Statements memoizer
130
- def statements
131
- # BNodes in statements are non-distinguished existential variables
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
- RDF::Query::Pattern.from(terms)
147
- else
148
- RDF::Query::Pattern.from(pattern)
149
- end
150
- end
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
- # Constants memoizer
155
- def constants
156
- # BNodes in statements are existential variables
157
- @constants ||= statements.select(&:constant?)
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 ||= statements.reject(&:constant?)
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 ||= patterns.vars.select(&:existential?)
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