shex 0.1.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 +7 -0
- data/AUTHORS +1 -0
- data/CREDITS +0 -0
- data/LICENSE +24 -0
- data/README.md +124 -0
- data/VERSION +1 -0
- data/etc/doap.ttl +33 -0
- data/lib/shex.rb +133 -0
- data/lib/shex/algebra.rb +39 -0
- data/lib/shex/algebra/and.rb +32 -0
- data/lib/shex/algebra/annotation.rb +6 -0
- data/lib/shex/algebra/base.rb +6 -0
- data/lib/shex/algebra/each_of.rb +53 -0
- data/lib/shex/algebra/external.rb +24 -0
- data/lib/shex/algebra/inclusion.rb +59 -0
- data/lib/shex/algebra/node_constraint.rb +148 -0
- data/lib/shex/algebra/not.rb +19 -0
- data/lib/shex/algebra/one_of.rb +50 -0
- data/lib/shex/algebra/operator.rb +274 -0
- data/lib/shex/algebra/or.rb +37 -0
- data/lib/shex/algebra/prefix.rb +6 -0
- data/lib/shex/algebra/satisfiable.rb +44 -0
- data/lib/shex/algebra/schema.rb +125 -0
- data/lib/shex/algebra/semact.rb +38 -0
- data/lib/shex/algebra/shape.rb +93 -0
- data/lib/shex/algebra/shape_ref.rb +46 -0
- data/lib/shex/algebra/start.rb +20 -0
- data/lib/shex/algebra/stem.rb +20 -0
- data/lib/shex/algebra/stem_range.rb +42 -0
- data/lib/shex/algebra/triple_constraint.rb +72 -0
- data/lib/shex/algebra/triple_expression.rb +46 -0
- data/lib/shex/algebra/unary_shape.rb +6 -0
- data/lib/shex/algebra/value.rb +28 -0
- data/lib/shex/meta.rb +7914 -0
- data/lib/shex/parser.rb +801 -0
- data/lib/shex/terminals.rb +106 -0
- metadata +224 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module ShEx::Algebra
|
2
|
+
##
|
3
|
+
class External < Operator
|
4
|
+
include Satisfiable
|
5
|
+
NAME = :external
|
6
|
+
|
7
|
+
#
|
8
|
+
# S is a ShapeRef and the Schema's shapes maps reference to a shape expression se2 and satisfies(n, se2, G, m).
|
9
|
+
def satisfies?(n)
|
10
|
+
extern_shape = nil
|
11
|
+
|
12
|
+
# Find the label for this external
|
13
|
+
label = schema.shapes.key(self)
|
14
|
+
not_satisfied("Can't find label for this extern") unless label
|
15
|
+
|
16
|
+
schema.external_schemas.each do |schema|
|
17
|
+
extern_shape ||= schema.shapes[label]
|
18
|
+
end
|
19
|
+
|
20
|
+
not_satisfied("External not configured for this shape") unless extern_shape
|
21
|
+
extern_shape.satisfies?(n)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ShEx::Algebra
|
2
|
+
##
|
3
|
+
class Inclusion < Operator
|
4
|
+
include TripleExpression
|
5
|
+
NAME = :inclusion
|
6
|
+
|
7
|
+
def initialize(arg, **options)
|
8
|
+
raise ArgumentError, "Shape inclusion must be an IRI or BNode: #{arg}" unless arg.is_a?(RDF::Resource)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# In this case, we accept an array of statements, and match based on cardinality.
|
14
|
+
#
|
15
|
+
# @param [Array<RDF::Statement>] t
|
16
|
+
# @return [Array<RDF::Statement>]
|
17
|
+
# @raise NotMatched, ShEx::NotSatisfied
|
18
|
+
def matches(t)
|
19
|
+
status "referenced_shape: #{operands.first}"
|
20
|
+
expression = referenced_shape.triple_expressions.first
|
21
|
+
max = maximum
|
22
|
+
results = expression.matches(t)
|
23
|
+
|
24
|
+
# Max violations handled in Shape
|
25
|
+
not_matched "Minimum Cardinality Violation: #{results.length} < #{minimum}" if
|
26
|
+
results.length < minimum
|
27
|
+
|
28
|
+
results
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Returns the referenced shape
|
33
|
+
#
|
34
|
+
# @return [Operand]
|
35
|
+
def referenced_shape
|
36
|
+
schema.shapes[operands.first.to_s]
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# A Inclusion is valid if it's ancestor schema has any shape with a lable
|
41
|
+
# the same as it's reference.
|
42
|
+
#
|
43
|
+
# An Inclusion object's include property must appear in the schema's shapes map and the corresponding triple expression must be a Shape with a tripleExpr. The function dereference(include) returns the shape's tripleExpr.
|
44
|
+
def validate!
|
45
|
+
structure_error("Missing included shape: #{operands.first}") if referenced_shape.nil?
|
46
|
+
structure_error("Self included shape: #{operands.first}") if referenced_shape == first_ancestor(Shape)
|
47
|
+
|
48
|
+
triple_expressions = referenced_shape.triple_expressions
|
49
|
+
case triple_expressions.length
|
50
|
+
when 0
|
51
|
+
structure_error("Includes shape with no triple expressions")
|
52
|
+
when 1
|
53
|
+
else
|
54
|
+
structure_error("Includes shape with multiple triple expressions")
|
55
|
+
end
|
56
|
+
super
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module ShEx::Algebra
|
2
|
+
##
|
3
|
+
class NodeConstraint < Operator
|
4
|
+
include Satisfiable
|
5
|
+
NAME = :nodeConstraint
|
6
|
+
|
7
|
+
#
|
8
|
+
# S is a NodeConstraint and satisfies2(n, se) as described below in Node Constraints. Note that testing if a node satisfies a node constraint does not require a graph or shapeMap.
|
9
|
+
# @param [RDF::Resource] n
|
10
|
+
# @return [Boolean] `true` if satisfied, `false` if it does not apply
|
11
|
+
# @raise [ShEx::NotSatisfied] if not satisfied
|
12
|
+
def satisfies?(n)
|
13
|
+
status ""
|
14
|
+
satisfies_node_kind?(n) &&
|
15
|
+
satisfies_datatype?(n) &&
|
16
|
+
satisfies_string_facet?(n) &&
|
17
|
+
satisfies_numeric_facet?(n) &&
|
18
|
+
satisfies_values?(n)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
##
|
24
|
+
# Satisfies Node Kind Constraint
|
25
|
+
# @return [Boolean] `true` if satisfied, `false` if it does not apply
|
26
|
+
# @raise [ShEx::NotSatisfied] if not satisfied
|
27
|
+
def satisfies_node_kind?(value)
|
28
|
+
kind = case operands.first
|
29
|
+
when :iri then RDF::URI
|
30
|
+
when :bnode then RDF::Node
|
31
|
+
when :literal then RDF::Literal
|
32
|
+
when :nonliteral then RDF::Resource
|
33
|
+
else return true
|
34
|
+
end
|
35
|
+
|
36
|
+
not_satisfied "Node was #{value.inspect} expected kind #{kind}" unless
|
37
|
+
value.is_a?(kind)
|
38
|
+
status "right kind: #{value}: #{kind}"
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Datatype Constraint
|
44
|
+
# @return [Boolean] `true` if satisfied, `false` if it does not apply
|
45
|
+
# @raise [ShEx::NotSatisfied] if not satisfied
|
46
|
+
def satisfies_datatype?(value)
|
47
|
+
dt = operands[1] if operands.first == :datatype
|
48
|
+
return true unless dt
|
49
|
+
|
50
|
+
not_satisfied "Node was #{value.inspect}, expected datatype #{dt}" unless
|
51
|
+
value.is_a?(RDF::Literal) && value.datatype == RDF::URI(dt)
|
52
|
+
status "right datatype: #{value}: #{dt}"
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# String Facet Constraint
|
58
|
+
# Checks all length/minlength/maxlength/pattern facets against the string representation of the value.
|
59
|
+
# @return [Boolean] `true` if satisfied, `false` if it does not apply
|
60
|
+
# @raise [ShEx::NotSatisfied] if not satisfied
|
61
|
+
def satisfies_string_facet?(value)
|
62
|
+
length = op_fetch(:length)
|
63
|
+
minlength = op_fetch(:minlength)
|
64
|
+
maxlength = op_fetch(:maxlength)
|
65
|
+
pattern = op_fetch(:pattern)
|
66
|
+
|
67
|
+
return true if (length || minlength || maxlength || pattern).nil?
|
68
|
+
|
69
|
+
v_s = case value
|
70
|
+
when RDF::Node then value.id
|
71
|
+
else value.to_s
|
72
|
+
end
|
73
|
+
not_satisfied "Node #{v_s.inspect} length not #{length}" if
|
74
|
+
length && v_s.length != length.to_i
|
75
|
+
not_satisfied"Node #{v_s.inspect} length < #{minlength}" if
|
76
|
+
minlength && v_s.length < minlength.to_i
|
77
|
+
not_satisfied "Node #{v_s.inspect} length > #{maxlength}" if
|
78
|
+
maxlength && v_s.length > maxlength.to_i
|
79
|
+
not_satisfied "Node #{v_s.inspect} does not match #{pattern}" if
|
80
|
+
pattern && !Regexp.new(pattern).match(v_s)
|
81
|
+
status "right string facet: #{value}"
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Numeric Facet Constraint
|
87
|
+
# Checks all numeric facets against the value.
|
88
|
+
# @return [Boolean] `true` if satisfied, `false` if it does not apply
|
89
|
+
# @raise [ShEx::NotSatisfied] if not satisfied
|
90
|
+
def satisfies_numeric_facet?(value)
|
91
|
+
mininclusive = op_fetch(:mininclusive)
|
92
|
+
minexclusive = op_fetch(:minexclusive)
|
93
|
+
maxinclusive = op_fetch(:maxinclusive)
|
94
|
+
maxexclusive = op_fetch(:maxexclusive)
|
95
|
+
totaldigits = op_fetch(:totaldigits)
|
96
|
+
fractiondigits = op_fetch(:fractiondigits)
|
97
|
+
|
98
|
+
return true if (mininclusive || minexclusive || maxinclusive || maxexclusive || totaldigits || fractiondigits).nil?
|
99
|
+
|
100
|
+
not_satisfied "Node #{value.inspect} not numeric" unless
|
101
|
+
value.is_a?(RDF::Literal::Numeric)
|
102
|
+
|
103
|
+
not_satisfied "Node #{value.inspect} not decimal" if
|
104
|
+
(totaldigits || fractiondigits) && (!value.is_a?(RDF::Literal::Decimal) || value.invalid?)
|
105
|
+
|
106
|
+
numeric_value = value.object
|
107
|
+
case
|
108
|
+
when !mininclusive.nil? && numeric_value < mininclusive.object then not_satisfied("Node #{value.inspect} < #{mininclusive.object}")
|
109
|
+
when !minexclusive.nil? && numeric_value <= minexclusive.object then not_satisfied("Node #{value.inspect} not <= #{minexclusive.object}")
|
110
|
+
when !maxinclusive.nil? && numeric_value > maxinclusive.object then not_satisfied("Node #{value.inspect} > #{maxinclusive.object}")
|
111
|
+
when !maxexclusive.nil? && numeric_value >= maxexclusive.object then not_satisfied("Node #{value.inspect} >= #{maxexclusive.object}")
|
112
|
+
when !totaldigits.nil?
|
113
|
+
md = value.canonicalize.to_s.match(/([1-9]\d*|0)?(?:\.(\d+)(?!0))?/)
|
114
|
+
digits = md ? (md[1].to_s + md[2].to_s) : ""
|
115
|
+
if digits.length > totaldigits.to_i
|
116
|
+
not_satisfied "Node #{value.inspect} total digits != #{totaldigits}"
|
117
|
+
end
|
118
|
+
when !fractiondigits.nil?
|
119
|
+
md = value.canonicalize.to_s.match(/\.(\d+)(?!0)?/)
|
120
|
+
num = md ? md[1].to_s : ""
|
121
|
+
if num.length > fractiondigits.to_i
|
122
|
+
not_satisfied "Node #{value.inspect} fractional digits != #{fractiondigits}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
status "right numeric facet: #{value}"
|
126
|
+
true
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Value Constraint
|
131
|
+
# Checks all numeric facets against the value.
|
132
|
+
# @return [Boolean] `true` if satisfied, `false` if it does not apply
|
133
|
+
# @raise [ShEx::NotSatisfied] if not satisfied
|
134
|
+
def satisfies_values?(value)
|
135
|
+
values = operands.select {|op| op.is_a?(Value)}
|
136
|
+
return true if values.empty?
|
137
|
+
matched_value = values.detect {|v| v.match?(value)}
|
138
|
+
not_satisfied "Node #{value.inspect} not expected" unless matched_value
|
139
|
+
status "right value: #{value}"
|
140
|
+
true
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the value of a particular facet
|
144
|
+
def op_fetch(which)
|
145
|
+
(operands.detect {|op| op.is_a?(Array) && op[0] == which} || [])[1]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ShEx::Algebra
|
2
|
+
##
|
3
|
+
class Not < Operator::Unary
|
4
|
+
include Satisfiable
|
5
|
+
NAME = :not
|
6
|
+
|
7
|
+
#
|
8
|
+
# S is a ShapeNot and for the shape expression se2 at shapeExpr, notSatisfies(n, se2, G, m).
|
9
|
+
# @param [RDF::Resource] n
|
10
|
+
# @return [Boolean] `true` when satisfied, meaning that the operand was not satisfied
|
11
|
+
# @raise [ShEx::NotSatisfied] if not satisfied, meaning that the operand was satisfied
|
12
|
+
def satisfies?(n)
|
13
|
+
status ""
|
14
|
+
operands.last.not_satisfies?(n)
|
15
|
+
status "not satisfied"
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ShEx::Algebra
|
2
|
+
##
|
3
|
+
class OneOf < Operator
|
4
|
+
include TripleExpression
|
5
|
+
NAME = :oneOf
|
6
|
+
|
7
|
+
##
|
8
|
+
# `expr` is a OneOf and there is some shape expression `se2` in shapeExprs such that a `matches(T, se2, m)`...
|
9
|
+
#
|
10
|
+
# @param [Array<RDF::Statement>] t
|
11
|
+
# @return [Array<RDF::Statement]
|
12
|
+
def matches(t)
|
13
|
+
results = []
|
14
|
+
statements = t.dup
|
15
|
+
num_iters = 0
|
16
|
+
max = maximum
|
17
|
+
|
18
|
+
# OneOf is greedy, and consumes triples from every sub-expression, although only one is requred it succeed. Cardinality is somewhat complicated, as if two expressions match, this works for either a cardinality of one or two. Or two passes with just one match on each pass.
|
19
|
+
status ""
|
20
|
+
while num_iters < max
|
21
|
+
matched_something = operands.select {|o| o.is_a?(TripleExpression)}.any? do |op|
|
22
|
+
begin
|
23
|
+
matched = op.matches(statements)
|
24
|
+
results += matched
|
25
|
+
statements -= matched
|
26
|
+
status "matched #{t.first.to_sxp}"
|
27
|
+
rescue NotMatched => e
|
28
|
+
log_recover("oneOf: ignore error: #{e.message}", depth: options.fetch(:depth, 0))
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
break unless matched_something
|
33
|
+
num_iters += 1
|
34
|
+
status "matched #{results.length} statements after #{num_iters} iterations"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Max violations handled in Shape
|
38
|
+
not_matched "Minimum Cardinality Violation: #{num_iters} < #{minimum}" if
|
39
|
+
num_iters < minimum
|
40
|
+
|
41
|
+
# Last, evaluate semantic acts
|
42
|
+
semantic_actions.all? do |op|
|
43
|
+
op.satisfies?(results)
|
44
|
+
end unless results.empty?
|
45
|
+
|
46
|
+
status "one of satisfied"
|
47
|
+
results
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,274 @@
|
|
1
|
+
require 'sparql/algebra'
|
2
|
+
require 'sparql/extensions'
|
3
|
+
|
4
|
+
module ShEx::Algebra
|
5
|
+
|
6
|
+
##
|
7
|
+
# The ShEx operator.
|
8
|
+
#
|
9
|
+
# @abstract
|
10
|
+
class Operator
|
11
|
+
extend SPARQL::Algebra::Expression
|
12
|
+
include RDF::Util::Logger
|
13
|
+
|
14
|
+
# Location of schema including this operator
|
15
|
+
attr_accessor :schema
|
16
|
+
|
17
|
+
# Initialization options
|
18
|
+
attr_accessor :options
|
19
|
+
|
20
|
+
ARITY = -1 # variable arity
|
21
|
+
|
22
|
+
##
|
23
|
+
# Initializes a new operator instance.
|
24
|
+
#
|
25
|
+
# @overload initialize(*operands)
|
26
|
+
# @param [Array<RDF::Term>] operands
|
27
|
+
#
|
28
|
+
# @overload initialize(*operands, options)
|
29
|
+
# @param [Array<RDF::Term>] operands
|
30
|
+
# @param [Hash{Symbol => Object}] options
|
31
|
+
# any additional options
|
32
|
+
# @option options [Boolean] :memoize (false)
|
33
|
+
# whether to memoize results for particular operands
|
34
|
+
# @raise [TypeError] if any operand is invalid
|
35
|
+
def initialize(*operands)
|
36
|
+
@options = operands.last.is_a?(Hash) ? operands.pop.dup : {}
|
37
|
+
@operands = operands.map! do |operand|
|
38
|
+
case operand
|
39
|
+
when Array
|
40
|
+
operand.each do |op|
|
41
|
+
op.parent = self if op.respond_to?(:parent=)
|
42
|
+
end
|
43
|
+
operand
|
44
|
+
when Operator, RDF::Term, RDF::Query, RDF::Query::Pattern, Array, Symbol
|
45
|
+
operand.parent = self if operand.respond_to?(:parent=)
|
46
|
+
operand
|
47
|
+
when TrueClass, FalseClass, Numeric, String, DateTime, Date, Time
|
48
|
+
RDF::Literal(operand)
|
49
|
+
when NilClass
|
50
|
+
raise ArgumentError, "Found nil operand for #{self.class.name}"
|
51
|
+
else raise TypeError, "invalid ShEx::Algebra::Operator operand: #{operand.inspect}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if options[:logger]
|
56
|
+
options[:depth] = 0
|
57
|
+
each_descendant(1) do |depth, operand|
|
58
|
+
if operand.respond_to?(:options)
|
59
|
+
operand.options[:logger] = options[:logger]
|
60
|
+
operand.options[:depth] = depth
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Is this shape closed?
|
68
|
+
# @return [Boolean]
|
69
|
+
def closed?
|
70
|
+
operands.include?(:closed)
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Semantic Actions
|
75
|
+
# @return [Array<SemAct>]
|
76
|
+
def semantic_actions
|
77
|
+
operands.select {|o| o.is_a?(SemAct)}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Does this operator include Satisfiable?
|
81
|
+
def satisfiable?; false; end
|
82
|
+
|
83
|
+
# Does this operator include TripleExpression?
|
84
|
+
def triple_expression?; false; end
|
85
|
+
|
86
|
+
# Does this operator a SemAct?
|
87
|
+
def semact?; false; end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Exception handling
|
91
|
+
def not_matched(message, **opts)
|
92
|
+
expression = opts.fetch(:expression, self)
|
93
|
+
exception = opts.fetch(:exception, NotMatched)
|
94
|
+
log_error(message, depth: options.fetch(:depth, 0), exception: exception) {"expression: #{expression.to_sxp}"}
|
95
|
+
end
|
96
|
+
|
97
|
+
def not_satisfied(message, **opts)
|
98
|
+
expression = opts.fetch(:expression, self)
|
99
|
+
exception = opts.fetch(:exception, ShEx::NotSatisfied)
|
100
|
+
log_error(message, depth: options.fetch(:depth, 0), exception: exception) {"expression: #{expression.to_sxp}"}
|
101
|
+
end
|
102
|
+
|
103
|
+
def structure_error(message, **opts)
|
104
|
+
expression = opts.fetch(:expression, self)
|
105
|
+
exception = opts.fetch(:exception, ShEx::StructureError)
|
106
|
+
log_error(message, depth: options.fetch(:depth, 0), exception: exception) {"expression: #{expression.to_sxp}"}
|
107
|
+
end
|
108
|
+
|
109
|
+
def status(message, &block)
|
110
|
+
log_info(self.class.const_get(:NAME), message, depth: options.fetch(:depth, 0), &block)
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# The operands to this operator.
|
116
|
+
#
|
117
|
+
# @return [Array]
|
118
|
+
attr_reader :operands
|
119
|
+
|
120
|
+
##
|
121
|
+
# Returns the operand at the given `index`.
|
122
|
+
#
|
123
|
+
# @param [Integer] index
|
124
|
+
# an operand index in the range `(0...(operands.count))`
|
125
|
+
# @return [RDF::Term]
|
126
|
+
def operand(index = 0)
|
127
|
+
operands[index]
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Returns the SPARQL S-Expression (SSE) representation of this operator.
|
132
|
+
#
|
133
|
+
# @return [Array]
|
134
|
+
# @see http://openjena.org/wiki/SSE
|
135
|
+
def to_sxp_bin
|
136
|
+
operator = [self.class.const_get(:NAME)].flatten.first
|
137
|
+
[operator, *(operands || []).map(&:to_sxp_bin)]
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Returns an S-Expression (SXP) representation of this operator
|
142
|
+
#
|
143
|
+
# @return [String]
|
144
|
+
def to_sxp
|
145
|
+
begin
|
146
|
+
require 'sxp' # @see http://rubygems.org/gems/sxp
|
147
|
+
rescue LoadError
|
148
|
+
abort "SPARQL::Algebra::Operator#to_sxp requires the SXP gem (hint: `gem install sxp')."
|
149
|
+
end
|
150
|
+
require 'sparql/algebra/sxp_extensions'
|
151
|
+
|
152
|
+
to_sxp_bin.to_sxp
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# Returns a developer-friendly representation of this operator.
|
157
|
+
#
|
158
|
+
# @return [String]
|
159
|
+
def inspect
|
160
|
+
sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, operands.to_sse.gsub(/\s+/m, ' '))
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# @param [Statement] other
|
165
|
+
# @return [Boolean]
|
166
|
+
def eql?(other)
|
167
|
+
other.class == self.class && other.operands == self.operands
|
168
|
+
end
|
169
|
+
alias_method :==, :eql?
|
170
|
+
|
171
|
+
##
|
172
|
+
# Enumerate via depth-first recursive descent over operands, yielding each operator
|
173
|
+
# @param [Integer] depth incrementeded for each depth of operator, and provided to block if Arity is 2
|
174
|
+
# @yield operator
|
175
|
+
# @yieldparam [Object] operator
|
176
|
+
# @return [Enumerator]
|
177
|
+
def each_descendant(depth = 0, &block)
|
178
|
+
if block_given?
|
179
|
+
operands.each do |operand|
|
180
|
+
case operand
|
181
|
+
when Array
|
182
|
+
operand.each do |op|
|
183
|
+
op.each_descendant(depth + 1, &block) if op.respond_to?(:each_descendant)
|
184
|
+
end
|
185
|
+
else
|
186
|
+
operand.each_descendant(depth + 1, &block) if operand.respond_to?(:each_descendant)
|
187
|
+
end
|
188
|
+
|
189
|
+
case block.arity
|
190
|
+
when 1 then block.call(operand)
|
191
|
+
else block.call(depth, operand)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
enum_for(:each_descendant)
|
196
|
+
end
|
197
|
+
alias_method :descendants, :each_descendant
|
198
|
+
alias_method :each, :each_descendant
|
199
|
+
|
200
|
+
##
|
201
|
+
# Parent expression, if any
|
202
|
+
#
|
203
|
+
# @return [Operator]
|
204
|
+
def parent; @options[:parent]; end
|
205
|
+
|
206
|
+
##
|
207
|
+
# Parent operator, if any
|
208
|
+
#
|
209
|
+
# @return [Operator]
|
210
|
+
def parent=(operator)
|
211
|
+
@options[:parent]= operator
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# First ancestor operator of type `klass`
|
216
|
+
#
|
217
|
+
# @param [Class] klass
|
218
|
+
# @return [Operator]
|
219
|
+
def first_ancestor(klass)
|
220
|
+
parent.is_a?(klass) ? parent : parent.first_ancestor(klass) if parent
|
221
|
+
end
|
222
|
+
|
223
|
+
##
|
224
|
+
# Validate all operands, operator specific classes should override for operator-specific validation
|
225
|
+
# @return [SPARQL::Algebra::Expression] `self`
|
226
|
+
# @raise [ShEx::StructureError] if the value is invalid
|
227
|
+
def validate!
|
228
|
+
operands.each {|op| op.validate! if op.respond_to?(:validate!)}
|
229
|
+
self
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# A unary operator.
|
234
|
+
#
|
235
|
+
# Operators of this kind take one operand.
|
236
|
+
#
|
237
|
+
# @abstract
|
238
|
+
class Unary < Operator
|
239
|
+
ARITY = 1
|
240
|
+
|
241
|
+
##
|
242
|
+
# @param [RDF::Term] arg1
|
243
|
+
# the first operand
|
244
|
+
# @param [Hash{Symbol => Object}] options
|
245
|
+
# any additional options (see {Operator#initialize})
|
246
|
+
def initialize(arg1, options = {})
|
247
|
+
raise ArgumentError, "wrong number of arguments (given 2, expected 1)" unless options.is_a?(Hash)
|
248
|
+
super
|
249
|
+
end
|
250
|
+
end # Unary
|
251
|
+
|
252
|
+
##
|
253
|
+
# A binary operator.
|
254
|
+
#
|
255
|
+
# Operators of this kind take two operands.
|
256
|
+
#
|
257
|
+
# @abstract
|
258
|
+
class Binary < Operator
|
259
|
+
ARITY = 2
|
260
|
+
|
261
|
+
##
|
262
|
+
# @param [RDF::Term] arg1
|
263
|
+
# the first operand
|
264
|
+
# @param [RDF::Term] arg2
|
265
|
+
# the second operand
|
266
|
+
# @param [Hash{Symbol => Object}] options
|
267
|
+
# any additional options (see {Operator#initialize})
|
268
|
+
def initialize(arg1, arg2, options = {})
|
269
|
+
raise ArgumentError, "wrong number of arguments (given 3, expected 2)" unless options.is_a?(Hash)
|
270
|
+
super
|
271
|
+
end
|
272
|
+
end # Binary
|
273
|
+
end
|
274
|
+
end
|