sparql 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/AUTHORS +3 -0
  2. data/CREDITS +0 -0
  3. data/README.markdown +103 -53
  4. data/UNLICENSE +24 -0
  5. data/VERSION +1 -0
  6. data/bin/sparql +87 -0
  7. data/lib/sparql.rb +105 -22
  8. data/lib/sparql/algebra.rb +369 -0
  9. data/lib/sparql/algebra/evaluatable.rb +37 -0
  10. data/lib/sparql/algebra/expression.rb +284 -0
  11. data/lib/sparql/algebra/extensions.rb +159 -0
  12. data/lib/sparql/algebra/operator.rb +492 -0
  13. data/lib/sparql/algebra/operator/add.rb +34 -0
  14. data/lib/sparql/algebra/operator/and.rb +65 -0
  15. data/lib/sparql/algebra/operator/asc.rb +29 -0
  16. data/lib/sparql/algebra/operator/ask.rb +46 -0
  17. data/lib/sparql/algebra/operator/base.rb +46 -0
  18. data/lib/sparql/algebra/operator/bgp.rb +26 -0
  19. data/lib/sparql/algebra/operator/bound.rb +48 -0
  20. data/lib/sparql/algebra/operator/compare.rb +84 -0
  21. data/lib/sparql/algebra/operator/construct.rb +85 -0
  22. data/lib/sparql/algebra/operator/dataset.rb +77 -0
  23. data/lib/sparql/algebra/operator/datatype.rb +42 -0
  24. data/lib/sparql/algebra/operator/desc.rb +17 -0
  25. data/lib/sparql/algebra/operator/describe.rb +71 -0
  26. data/lib/sparql/algebra/operator/distinct.rb +50 -0
  27. data/lib/sparql/algebra/operator/divide.rb +43 -0
  28. data/lib/sparql/algebra/operator/equal.rb +32 -0
  29. data/lib/sparql/algebra/operator/exprlist.rb +52 -0
  30. data/lib/sparql/algebra/operator/filter.rb +71 -0
  31. data/lib/sparql/algebra/operator/graph.rb +28 -0
  32. data/lib/sparql/algebra/operator/greater_than.rb +32 -0
  33. data/lib/sparql/algebra/operator/greater_than_or_equal.rb +33 -0
  34. data/lib/sparql/algebra/operator/is_blank.rb +35 -0
  35. data/lib/sparql/algebra/operator/is_iri.rb +37 -0
  36. data/lib/sparql/algebra/operator/is_literal.rb +36 -0
  37. data/lib/sparql/algebra/operator/join.rb +67 -0
  38. data/lib/sparql/algebra/operator/lang.rb +29 -0
  39. data/lib/sparql/algebra/operator/lang_matches.rb +53 -0
  40. data/lib/sparql/algebra/operator/left_join.rb +95 -0
  41. data/lib/sparql/algebra/operator/less_than.rb +32 -0
  42. data/lib/sparql/algebra/operator/less_than_or_equal.rb +32 -0
  43. data/lib/sparql/algebra/operator/minus.rb +31 -0
  44. data/lib/sparql/algebra/operator/multiply.rb +34 -0
  45. data/lib/sparql/algebra/operator/not.rb +35 -0
  46. data/lib/sparql/algebra/operator/not_equal.rb +26 -0
  47. data/lib/sparql/algebra/operator/or.rb +65 -0
  48. data/lib/sparql/algebra/operator/order.rb +69 -0
  49. data/lib/sparql/algebra/operator/plus.rb +31 -0
  50. data/lib/sparql/algebra/operator/prefix.rb +45 -0
  51. data/lib/sparql/algebra/operator/project.rb +46 -0
  52. data/lib/sparql/algebra/operator/reduced.rb +47 -0
  53. data/lib/sparql/algebra/operator/regex.rb +70 -0
  54. data/lib/sparql/algebra/operator/same_term.rb +46 -0
  55. data/lib/sparql/algebra/operator/slice.rb +60 -0
  56. data/lib/sparql/algebra/operator/str.rb +35 -0
  57. data/lib/sparql/algebra/operator/subtract.rb +32 -0
  58. data/lib/sparql/algebra/operator/union.rb +55 -0
  59. data/lib/sparql/algebra/query.rb +99 -0
  60. data/lib/sparql/algebra/sxp_extensions.rb +35 -0
  61. data/lib/sparql/algebra/version.rb +20 -0
  62. data/lib/sparql/extensions.rb +102 -0
  63. data/lib/sparql/grammar.rb +298 -0
  64. data/lib/sparql/grammar/lexer.rb +609 -0
  65. data/lib/sparql/grammar/parser.rb +1383 -0
  66. data/lib/sparql/grammar/parser/meta.rb +1801 -0
  67. data/lib/sparql/results.rb +220 -0
  68. data/lib/sparql/version.rb +20 -0
  69. metadata +232 -62
  70. data/Rakefile +0 -22
  71. data/coverage/index.html +0 -252
  72. data/coverage/lib-sparql-execute_sparql_rb.html +0 -621
  73. data/coverage/lib-sparql_rb.html +0 -622
  74. data/lib/sparql/execute_sparql.rb +0 -27
  75. data/lib/sparql/sparql.treetop +0 -159
  76. data/sparql.gemspec +0 -16
  77. data/spec/spec.opts +0 -2
  78. data/spec/spec_helper.rb +0 -24
  79. data/spec/unit/graph_parsing_spec.rb +0 -76
  80. data/spec/unit/iri_parsing_spec.rb +0 -46
  81. data/spec/unit/prefixed_names_parsing_spec.rb +0 -40
  82. data/spec/unit/primitives_parsing_spec.rb +0 -26
  83. data/spec/unit/sparql_parsing_spec.rb +0 -72
  84. data/spec/unit/variables_parsing_spec.rb +0 -36
@@ -0,0 +1,159 @@
1
+ require 'json'
2
+
3
+ ##
4
+ # Extensions for Ruby's `Object` class.
5
+ class Object
6
+ ##
7
+ # Returns the SXP representation of this object, defaults to `self'.
8
+ #
9
+ # @return [String]
10
+ def to_sse
11
+ self
12
+ end
13
+ end
14
+
15
+ ##
16
+ # Extensions for Ruby's `Object` class.
17
+ class Array
18
+ ##
19
+ # Returns the SXP representation of this object, defaults to `self'.
20
+ #
21
+ # @return [String]
22
+ def to_sse
23
+ map {|x| x.to_sse}
24
+ end
25
+
26
+ ##
27
+ # Evaluates the array using the given variable `bindings`.
28
+ #
29
+ # In this case, the Array has two elements, the first of which is
30
+ # an XSD datatype, and the second is the expression to be evaluated.
31
+ # The result is cast as a literal of the appropriate type
32
+ #
33
+ # @param [RDF::Query::Solution, #[]] bindings
34
+ # a query solution containing zero or more variable bindings
35
+ # @return [RDF::Term]
36
+ def evaluate(bindings)
37
+ dt, val = self.map {|o| o.evaluate(bindings)}
38
+ SPARQL::Algebra::Expression.cast(*self.map {|o| o.evaluate(bindings)})
39
+ end
40
+ end
41
+
42
+ ##
43
+ # Extensions for `RDF::Term`.
44
+ module RDF::Term
45
+ include SPARQL::Algebra::Expression
46
+
47
+ def evaluate(bindings)
48
+ self
49
+ end
50
+ end # RDF::Term
51
+
52
+ # Override RDF::Queryable to execute against SPARQL::Algebra::Query elements as well as RDF::Query and RDF::Pattern
53
+ module RDF::Queryable
54
+ alias_method :query_without_sparql, :query
55
+ ##
56
+ # Queries `self` for RDF statements matching the given `pattern`.
57
+ #
58
+ # Monkey patch to RDF::Queryable#query to execute a {SPARQL::Algebra::Operator}
59
+ # in addition to an {RDF::Query} object.
60
+ #
61
+ # @example
62
+ # queryable.query([nil, RDF::DOAP.developer, nil])
63
+ # queryable.query(:predicate => RDF::DOAP.developer)
64
+ #
65
+ # op = SPARQL::Algebra::Expression.parse(%q((bgp (triple ?a doap:developer ?b))))
66
+ # queryable.query(op)
67
+ #
68
+ # @param [RDF::Query, RDF::Statement, Array(RDF::Term), Hash, SPARQL::Operator] pattern
69
+ # @yield [statement]
70
+ # each matching statement
71
+ # @yieldparam [RDF::Statement] statement
72
+ # @yieldreturn [void] ignored
73
+ # @return [Enumerator]
74
+ # @see RDF::Queryable#query_pattern
75
+ def query(pattern, &block)
76
+ raise TypeError, "#{self} is not readable" if respond_to?(:readable?) && !readable?
77
+
78
+ if pattern.is_a?(SPARQL::Algebra::Operator) && pattern.respond_to?(:execute)
79
+ before_query(pattern) if respond_to?(:before_query)
80
+ query_execute(pattern, &block)
81
+ after_query(pattern) if respond_to?(:after_query)
82
+ enum_for(:query_execute, pattern)
83
+ else
84
+ query_without_sparql(pattern, &block)
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ class RDF::Query
91
+ # Equivalence for Queries:
92
+ # Same Patterns
93
+ # Same Context
94
+ # @return [Boolean]
95
+ def ==(other)
96
+ other.is_a?(RDF::Query) && patterns == other.patterns && context == context
97
+ end
98
+
99
+ # Transform Query into an Array form of an SSE
100
+ #
101
+ # If Query is named, it's treated as a GroupGraphPattern, otherwise, a BGP
102
+ #
103
+ # @return [Array]
104
+ def to_sse
105
+ res = [:bgp] + patterns.map(&:to_sse)
106
+ (context ? [:graph, context, res] : res)
107
+ end
108
+ end
109
+
110
+ class RDF::Query::Pattern
111
+ # Transform Query Pattern into an SXP
112
+ # @return [Array]
113
+ def to_sse
114
+ [:triple, subject, predicate, object]
115
+ end
116
+ end
117
+
118
+ ##
119
+ # Extensions for `RDF::Query::Variable`.
120
+ class RDF::Query::Variable
121
+ include SPARQL::Algebra::Expression
122
+
123
+ ##
124
+ # Returns the value of this variable in the given `bindings`.
125
+ #
126
+ # @param [RDF::Query::Solution, #[]] bindings
127
+ # @return [RDF::Term] the value of this variable
128
+ def evaluate(bindings = {})
129
+ bindings[name.to_sym]
130
+ end
131
+ end # RDF::Query::Variable
132
+
133
+ ##
134
+ # Extensions for `RDF::Query::Solutions`.
135
+ class RDF::Query::Solutions
136
+ alias_method :filter_without_expression, :filter
137
+
138
+ ##
139
+ # Filters this solution sequence by the given `criteria`.
140
+ #
141
+ # @param [SPARQL::Algebra::Expression] expression
142
+ # @yield [solution]
143
+ # each solution
144
+ # @yieldparam [RDF::Query::Solution] solution
145
+ # @yieldreturn [Boolean]
146
+ # @return [void] `self`
147
+ def filter(expression = {}, &block)
148
+ case expression
149
+ when SPARQL::Algebra::Expression
150
+ filter_without_expression do |solution|
151
+ expression.evaluate(solution).true?
152
+ end
153
+ filter_without_expression(&block) if block_given?
154
+ self
155
+ else filter_without_expression(expression, &block)
156
+ end
157
+ end
158
+ alias_method :filter!, :filter
159
+ end # RDF::Query::Solutions
@@ -0,0 +1,492 @@
1
+ module SPARQL; module Algebra
2
+ ##
3
+ # A SPARQL operator.
4
+ #
5
+ # @abstract
6
+ class Operator
7
+ include Expression
8
+
9
+ # Unary operators
10
+ autoload :Not, 'sparql/algebra/operator/not'
11
+ autoload :Plus, 'sparql/algebra/operator/plus'
12
+ autoload :Minus, 'sparql/algebra/operator/minus'
13
+ autoload :Bound, 'sparql/algebra/operator/bound'
14
+ autoload :IsBlank, 'sparql/algebra/operator/is_blank'
15
+ autoload :IsIRI, 'sparql/algebra/operator/is_iri'
16
+ autoload :IsURI, 'sparql/algebra/operator/is_iri'
17
+ autoload :IsLiteral, 'sparql/algebra/operator/is_literal'
18
+ autoload :Str, 'sparql/algebra/operator/str'
19
+ autoload :Lang, 'sparql/algebra/operator/lang'
20
+ autoload :Datatype, 'sparql/algebra/operator/datatype'
21
+
22
+ # Binary operators
23
+ autoload :Or, 'sparql/algebra/operator/or'
24
+ autoload :And, 'sparql/algebra/operator/and'
25
+ autoload :Compare, 'sparql/algebra/operator/compare'
26
+ autoload :Equal, 'sparql/algebra/operator/equal'
27
+ autoload :NotEqual, 'sparql/algebra/operator/not_equal'
28
+ autoload :LessThan, 'sparql/algebra/operator/less_than'
29
+ autoload :GreaterThan, 'sparql/algebra/operator/greater_than'
30
+ autoload :LessThanOrEqual, 'sparql/algebra/operator/less_than_or_equal'
31
+ autoload :GreaterThanOrEqual, 'sparql/algebra/operator/greater_than_or_equal'
32
+ autoload :Multiply, 'sparql/algebra/operator/multiply'
33
+ autoload :Divide, 'sparql/algebra/operator/divide'
34
+ autoload :Add, 'sparql/algebra/operator/add'
35
+ autoload :Subtract, 'sparql/algebra/operator/subtract'
36
+ autoload :SameTerm, 'sparql/algebra/operator/same_term'
37
+ autoload :LangMatches, 'sparql/algebra/operator/lang_matches'
38
+ autoload :Regex, 'sparql/algebra/operator/regex'
39
+
40
+ # Miscellaneous
41
+ autoload :Asc, 'sparql/algebra/operator/asc'
42
+ autoload :Desc, 'sparql/algebra/operator/desc'
43
+ autoload :Exprlist, 'sparql/algebra/operator/exprlist'
44
+
45
+ # Query operators
46
+ autoload :Ask, 'sparql/algebra/operator/ask'
47
+ autoload :Base, 'sparql/algebra/operator/base'
48
+ autoload :BGP, 'sparql/algebra/operator/bgp'
49
+ autoload :Construct, 'sparql/algebra/operator/construct'
50
+ autoload :Dataset, 'sparql/algebra/operator/dataset'
51
+ autoload :Describe, 'sparql/algebra/operator/describe'
52
+ autoload :Distinct, 'sparql/algebra/operator/distinct'
53
+ autoload :Filter, 'sparql/algebra/operator/filter'
54
+ autoload :Graph, 'sparql/algebra/operator/graph'
55
+ autoload :Join, 'sparql/algebra/operator/join'
56
+ autoload :LeftJoin, 'sparql/algebra/operator/left_join'
57
+ autoload :Order, 'sparql/algebra/operator/order'
58
+ autoload :Prefix, 'sparql/algebra/operator/prefix'
59
+ autoload :Project, 'sparql/algebra/operator/project'
60
+ autoload :Reduced, 'sparql/algebra/operator/reduced'
61
+ autoload :Slice, 'sparql/algebra/operator/slice'
62
+ autoload :Union, 'sparql/algebra/operator/union'
63
+
64
+ ##
65
+ # Returns an operator class for the given operator `name`.
66
+ #
67
+ # @param [Symbol, #to_s] name
68
+ # @param [Integer] arity
69
+ # @return [Class] an operator class, or `nil` if an operator was not found
70
+ def self.for(name, arity = nil)
71
+ # TODO: refactor this to dynamically introspect loaded operator classes.
72
+ case (name.to_s.downcase.to_sym rescue nil)
73
+ when :<=> then Compare # non-standard
74
+ when :'=' then Equal
75
+ when :'!=' then NotEqual
76
+ when :< then LessThan
77
+ when :> then GreaterThan
78
+ when :<= then LessThanOrEqual
79
+ when :>= then GreaterThanOrEqual
80
+ when :* then Multiply
81
+ when :'/' then Divide
82
+ when :+ then arity.eql?(1) ? Plus : Add
83
+ when :- then arity.eql?(1) ? Minus : Subtract
84
+ when :not, :'!' then Not
85
+ when :plus then Plus
86
+ when :minus then Minus
87
+ when :bound then Bound
88
+ when :isblank then IsBlank
89
+ when :isiri then IsIRI
90
+ when :isuri then IsIRI # alias
91
+ when :isliteral then IsLiteral
92
+ when :str then Str
93
+ when :lang then Lang
94
+ when :datatype then Datatype
95
+ when :or, :'||' then Or
96
+ when :and, :'&&' then And
97
+ when :multiply then Multiply
98
+ when :divide then Divide
99
+ when :add then Add
100
+ when :subtract then Subtract
101
+ when :sameterm then SameTerm
102
+ when :langmatches then LangMatches
103
+ when :regex then Regex
104
+
105
+ # Miscellaneous
106
+ when :asc then Asc
107
+ when :desc then Desc
108
+ when :exprlist then Exprlist
109
+
110
+ # Datasets
111
+ when :dataset then Dataset
112
+
113
+ # Query forms
114
+ when :ask then Ask
115
+ when :base then Base
116
+ when :bgp then BGP
117
+ when :construct then Construct
118
+ when :describe then Describe
119
+ when :distinct then Distinct
120
+ when :filter then Filter
121
+ when :graph then Graph
122
+ when :join then Join
123
+ when :leftjoin then LeftJoin
124
+ when :order then Order
125
+ when :prefix then Prefix
126
+ when :project then Project
127
+ when :reduced then Reduced
128
+ when :slice then Slice
129
+ when :triple then RDF::Query::Pattern
130
+ when :union then Union
131
+ else nil # not found
132
+ end
133
+ end
134
+
135
+ ##
136
+ # @param [Array<RDF::Term>] operands
137
+ # @return [RDF::Term]
138
+ # @see Operator#evaluate
139
+ def self.evaluate(*operands)
140
+ self.new(*operands).evaluate(RDF::Query::Solution.new)
141
+ end
142
+
143
+ ##
144
+ # Returns the arity of this operator class.
145
+ #
146
+ # @example
147
+ # Operator.arity #=> -1
148
+ # Operator::Nullary.arity #=> 0
149
+ # Operator::Unary.arity #=> 1
150
+ # Operator::Binary.arity #=> 2
151
+ # Operator::Ternary.arity #=> 3
152
+ #
153
+ # @return [Integer] an integer in the range `(-1..3)`
154
+ def self.arity
155
+ self.const_get(:ARITY)
156
+ end
157
+
158
+ ARITY = -1 # variable arity
159
+
160
+ ##
161
+ # Initializes a new operator instance.
162
+ #
163
+ # @param [Array<RDF::Term>] operands
164
+ # @param [Hash{Symbol => Object}] options
165
+ # any additional options
166
+ # @option options [Boolean] :memoize (false)
167
+ # whether to memoize results for particular operands
168
+ # @raise [TypeError] if any operand is invalid
169
+ def initialize(*operands)
170
+ @options = operands.last.is_a?(Hash) ? operands.pop.dup : {}
171
+ @operands = operands.map! do |operand|
172
+ case operand
173
+ when Operator, Variable, RDF::Term, RDF::Query, RDF::Query::Pattern, Array, Symbol
174
+ operand
175
+ when TrueClass, FalseClass, Numeric, String, DateTime, Date, Time
176
+ RDF::Literal(operand)
177
+ else raise TypeError, "invalid SPARQL::Algebra::Operator operand: #{operand.inspect}"
178
+ end
179
+ end
180
+ end
181
+
182
+ ##
183
+ # Base URI used for reading data sources with relative URIs
184
+ #
185
+ # @return [RDF::URI]
186
+ def base_uri
187
+ Operator.base_uri
188
+ end
189
+
190
+ ##
191
+ # Base URI used for reading data sources with relative URIs
192
+ #
193
+ # @return [RDF::URI]
194
+ def self.base_uri
195
+ @base_uri
196
+ end
197
+
198
+ ##
199
+ # Set Base URI associated with SPARQL document, typically done
200
+ # when reading SPARQL from a URI
201
+ #
202
+ # @param [RDF::URI] base
203
+ # @return [RDF::URI]
204
+ def self.base_uri=(uri)
205
+ @base_uri = RDF::URI(uri)
206
+ end
207
+
208
+ ##
209
+ # Prefixes useful for future serialization
210
+ #
211
+ # @return [Hash{Symbol => RDF::URI}]
212
+ # Prefix definitions
213
+ def prefixes
214
+ Operator.prefixes
215
+ end
216
+
217
+ ##
218
+ # Prefixes useful for future serialization
219
+ #
220
+ # @return [Hash{Symbol => RDF::URI}]
221
+ # Prefix definitions
222
+ def self.prefixes
223
+ @prefixes
224
+ end
225
+
226
+ ##
227
+ # Prefixes useful for future serialization
228
+ #
229
+ # @param [Hash{Symbol => RDF::URI}] hash
230
+ # Prefix definitions
231
+ # @return [Hash{Symbol => RDF::URI}]
232
+ def self.prefixes=(hash)
233
+ @prefixes = hash
234
+ end
235
+
236
+ ##
237
+ # Any additional options for this operator.
238
+ #
239
+ # @return [Hash]
240
+ attr_reader :options
241
+
242
+ ##
243
+ # The operands to this operator.
244
+ #
245
+ # @return [Array]
246
+ attr_reader :operands
247
+
248
+ ##
249
+ # Returns the operand at the given `index`.
250
+ #
251
+ # @param [Integer] index
252
+ # an operand index in the range `(0...(operands.count))`
253
+ # @return [RDF::Term]
254
+ def operand(index = 0)
255
+ operands[index]
256
+ end
257
+
258
+ ##
259
+ # Returns `true` if any of the operands are variables, `false`
260
+ # otherwise.
261
+ #
262
+ # @return [Boolean] `true` or `false`
263
+ # @see #constant?
264
+ def variable?
265
+ operands.any? do |operand|
266
+ operand.is_a?(Variable) ||
267
+ (operand.respond_to?(:variable?) && operand.variable?)
268
+ end
269
+ end
270
+
271
+ ##
272
+ # Returns `true` if this is evaluatable (i.e., returns values for a binding), `false`
273
+ # otherwise.
274
+ #
275
+ # @return [Boolean] `true` or `false`
276
+ def evaluatable?
277
+ respond_to?(:evaluate)
278
+ end
279
+
280
+ ##
281
+ # Returns `true` if this is executable (i.e., contains a graph patterns), `false`
282
+ # otherwise.
283
+ #
284
+ # @return [Boolean] `true` or `false`
285
+ def executable?
286
+ respond_to?(:execute)
287
+ end
288
+
289
+ ##
290
+ # Returns `true` if none of the operands are variables, `false`
291
+ # otherwise.
292
+ #
293
+ # @return [Boolean] `true` or `false`
294
+ # @see #variable?
295
+ def constant?
296
+ !(variable?)
297
+ end
298
+
299
+ ##
300
+ # Returns an optimized version of this expression.
301
+ #
302
+ # For constant expressions containing no variables, returns the result
303
+ # of evaluating the expression with empty bindings; otherwise returns
304
+ # `self`.
305
+ #
306
+ # Optimization is not possible if the expression raises an exception,
307
+ # such as a `TypeError` or `ZeroDivisionError`, which must be conserved
308
+ # at runtime.
309
+ #
310
+ # @return [SPARQL::Algebra::Expression]
311
+ def optimize
312
+ if constant?
313
+ # Note that if evaluation results in a `TypeError` or other error,
314
+ # we must return `self` so that the error is conserved at runtime:
315
+ evaluate rescue self
316
+ else
317
+ super # returns `self`
318
+ end
319
+ end
320
+
321
+ ##
322
+ # Returns the SPARQL S-Expression (SSE) representation of this operator.
323
+ #
324
+ # @return [Array]
325
+ # @see http://openjena.org/wiki/SSE
326
+ def to_sse
327
+ operator = [self.class.const_get(:NAME)].flatten.first
328
+ [operator, *(operands || []).map(&:to_sse)]
329
+ end
330
+
331
+ ##
332
+ # Returns an S-Expression (SXP) representation of this operator
333
+ #
334
+ # @return [String]
335
+ def to_sxp
336
+ begin
337
+ require 'sxp' # @see http://rubygems.org/gems/sxp
338
+ rescue LoadError
339
+ abort "SPARQL::Algebra::Operator#to_sxp requires the SXP gem (hint: `gem install sxp')."
340
+ end
341
+ require 'sparql/algebra/sxp_extensions'
342
+
343
+ to_sse.to_sxp
344
+ end
345
+
346
+ ##
347
+ # Returns a developer-friendly representation of this operator.
348
+ #
349
+ # @return [String]
350
+ def inspect
351
+ sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, operands.map(&:inspect).join(', '))
352
+ end
353
+
354
+ ##
355
+ # @param [Statement] other
356
+ # @return [Boolean]
357
+ def eql?(other)
358
+ other.class == self.class && other.operands == self.operands
359
+ end
360
+ alias_method :==, :eql?
361
+ protected
362
+
363
+ ##
364
+ # Returns the effective boolean value (EBV) of the given `literal`.
365
+ #
366
+ # @param [RDF::Literal] literal
367
+ # @return [RDF::Literal::Boolean] `true` or `false`
368
+ # @raise [TypeError] if the literal could not be coerced to an `RDF::Literal::Boolean`
369
+ # @see http://www.w3.org/TR/rdf-sparql-query/#ebv
370
+ def boolean(literal)
371
+ case literal
372
+ when FalseClass then RDF::Literal::FALSE
373
+ when TrueClass then RDF::Literal::TRUE
374
+ # If the argument is a typed literal with a datatype of
375
+ # `xsd:boolean`, the EBV is the value of that argument.
376
+ # However, the EBV of any literal whose type is `xsd:boolean` is
377
+ # false if the lexical form is not valid for that datatype.
378
+ when RDF::Literal::Boolean
379
+ RDF::Literal(literal.valid? && literal.true?)
380
+ # If the argument is a numeric type or a typed literal with a
381
+ # datatype derived from a numeric type, the EBV is false if the
382
+ # operand value is NaN or is numerically equal to zero; otherwise
383
+ # the EBV is true.
384
+ # However, the EBV of any literal whose type is numeric is
385
+ # false if the lexical form is not valid for that datatype.
386
+ when RDF::Literal::Numeric
387
+ RDF::Literal(literal.valid? && !(literal.zero?) && !(literal.respond_to?(:nan?) && literal.nan?))
388
+ # If the argument is a plain literal or a typed literal with a
389
+ # datatype of `xsd:string`, the EBV is false if the operand value
390
+ # has zero length; otherwise the EBV is true.
391
+ else case
392
+ when literal.is_a?(RDF::Literal) && (literal.plain? || literal.datatype.eql?(RDF::XSD.string))
393
+ RDF::Literal(!(literal.value.empty?))
394
+ # All other arguments, including unbound arguments, produce a type error.
395
+ else raise TypeError, "could not coerce #{literal.inspect} to an RDF::Literal::Boolean"
396
+ end
397
+ end
398
+ end
399
+
400
+ private
401
+
402
+ @@subclasses = [] # @private
403
+
404
+ ##
405
+ # @private
406
+ # @return [void]
407
+ def self.inherited(child)
408
+ @@subclasses << child unless child.superclass.equal?(Operator) # grandchildren only
409
+ super
410
+ end
411
+
412
+ ##
413
+ # A SPARQL nullary operator.
414
+ #
415
+ # Operators of this kind take no operands.
416
+ #
417
+ # @abstract
418
+ class Nullary < Operator
419
+ ARITY = 0
420
+
421
+ ##
422
+ # @param [Hash{Symbol => Object}] options
423
+ # any additional options (see {Operator#initialize})
424
+ def initialize(options = {})
425
+ super
426
+ end
427
+ end # Nullary
428
+
429
+ ##
430
+ # A SPARQL unary operator.
431
+ #
432
+ # Operators of this kind take one operand.
433
+ #
434
+ # @abstract
435
+ class Unary < Operator
436
+ ARITY = 1
437
+
438
+ ##
439
+ # @param [RDF::Term] arg
440
+ # the operand
441
+ # @param [Hash{Symbol => Object}] options
442
+ # any additional options (see {Operator#initialize})
443
+ def initialize(arg, options = {})
444
+ super
445
+ end
446
+ end # Unary
447
+
448
+ ##
449
+ # A SPARQL binary operator.
450
+ #
451
+ # Operators of this kind take two operands.
452
+ #
453
+ # @abstract
454
+ class Binary < Operator
455
+ ARITY = 2
456
+
457
+ ##
458
+ # @param [RDF::Term] arg1
459
+ # the first operand
460
+ # @param [RDF::Term] arg2
461
+ # the second operand
462
+ # @param [Hash{Symbol => Object}] options
463
+ # any additional options (see {Operator#initialize})
464
+ def initialize(arg1, arg2, options = {})
465
+ super
466
+ end
467
+ end # Binary
468
+
469
+ ##
470
+ # A SPARQL ternary operator.
471
+ #
472
+ # Operators of this kind take three operands.
473
+ #
474
+ # @abstract
475
+ class Ternary < Operator
476
+ ARITY = 3
477
+
478
+ ##
479
+ # @param [RDF::Term] arg1
480
+ # the first operand
481
+ # @param [RDF::Term] arg2
482
+ # the second operand
483
+ # @param [RDF::Term] arg3
484
+ # the third operand
485
+ # @param [Hash{Symbol => Object}] options
486
+ # any additional options (see {Operator#initialize})
487
+ def initialize(arg1, arg2, arg3, options = {})
488
+ super
489
+ end
490
+ end # Ternary
491
+ end # Operator
492
+ end; end # SPARQL::Algebra