shex 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5d83ac4fdd795f4d27280e23fc431a42faa72dd7
4
- data.tar.gz: 61b80075c76f018481226ed563fa7ec44fe0c35f
3
+ metadata.gz: 96423cdff8bd77f2573ab32cd954d5217b2dfe20
4
+ data.tar.gz: 510d72ce00605e864b37af848f39c54f19de96b8
5
5
  SHA512:
6
- metadata.gz: 2a158d18cdfbe1c0ae951549bbafc0e8d2d312cc08d259eefe76729e192db380b60a63b0d4a525cc4c49eee8f3092b9fd82cee03fe174cf8ab334c57bb943a4f
7
- data.tar.gz: 5078624090859f18d37daf162baadcca943533c4bd35eb3e52299c737a18b3c7ab85e1d3e0d4819f3d7a12b9c97e308e580b7d408a43d866ee1264331bb01d73
6
+ metadata.gz: 7af06418271fa283bf833b837b546a10870af1e6f60fea90099d8a75d8dc4097f1fce6c7800b0874b50efcddbafe97abe5e30888c51f1d97f62e46298b2c5e86
7
+ data.tar.gz: 5c9a7e9c9f7373a64a4c0722edefb6d2c9c20e5a9aaad8920f539641ddca97d5499dec2d3c2f1aa719122405b381a2d52bf536f4bcabb71ea41702eafeb4aabc
data/README.md CHANGED
@@ -48,7 +48,7 @@ The ShEx gem implements a [ShEx][ShExSpec] Shape Expression engine.
48
48
  }
49
49
  schema.satisfies?("http://rubygems.org/gems/shex", graph, map)
50
50
  # => true
51
- ### Validating a node using ShExC
51
+ ### Validating a node using ShExJ
52
52
 
53
53
  require 'rubygems'
54
54
  require 'rdf/turtle'
@@ -174,10 +174,12 @@ The ShExC parser uses the [EBNF][] gem to generate first, follow and branch tabl
174
174
 
175
175
  The parser takes branch and follow tables generated from the [ShEx Grammar](file.shex.html) described in the [specification][ShExSpec]. Branch and Follow tables are specified in the generated {ShEx::Meta}.
176
176
 
177
+ The result of parsing either ShExC or ShExJ is the creation of a set of executable {ShEx::Algebra} Operators which are directly executed to perform shape validation.
178
+
177
179
  ## Dependencies
178
180
 
179
- * [Ruby](http://ruby-lang.org/) (>= 2.0)
180
- * [RDF.rb](http://rubygems.org/gems/rdf) (>= 2.1)
181
+ * [Ruby](http://ruby-lang.org/) (>= 2.2.2)
182
+ * [RDF.rb](http://rubygems.org/gems/rdf) (>= 2.2)
181
183
 
182
184
  ## Installation
183
185
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.4.0
@@ -35,10 +35,10 @@ module ShEx
35
35
  when 'shexc' then Parser.new(expression, options).parse
36
36
  when 'shexj'
37
37
  expression = expression.read if expression.respond_to?(:read)
38
- Algebra.from_shexj(JSON.parse expression)
38
+ Algebra.from_shexj(JSON.parse(expression), options)
39
39
  when 'sxp'
40
40
  expression = expression.read if expression.respond_to?(:read)
41
- Algebra.from_sxp(JSON.parse expression)
41
+ Algebra.from_sxp(expression, options)
42
42
  else raise "Unknown expression format: #{format.inspect}"
43
43
  end
44
44
  end
@@ -10,17 +10,15 @@ module ShEx
10
10
  autoload :And, 'shex/algebra/and'
11
11
  autoload :Annotation, 'shex/algebra/annotation'
12
12
  autoload :EachOf, 'shex/algebra/each_of'
13
- autoload :Inclusion, 'shex/algebra/inclusion'
13
+ autoload :External, 'shex/algebra/external'
14
14
  autoload :Not, 'shex/algebra/not'
15
15
  autoload :NodeConstraint, 'shex/algebra/node_constraint'
16
16
  autoload :OneOf, 'shex/algebra/one_of'
17
17
  autoload :Operator, 'shex/algebra/operator'
18
18
  autoload :Or, 'shex/algebra/or'
19
- autoload :Satisfiable, 'shex/algebra/satisfiable'
20
19
  autoload :Schema, 'shex/algebra/schema'
21
20
  autoload :SemAct, 'shex/algebra/semact'
22
- autoload :External, 'shex/algebra/external'
23
- autoload :ShapeRef, 'shex/algebra/shape_ref'
21
+ autoload :ShapeExpression, 'shex/algebra/shape_expression'
24
22
  autoload :Shape, 'shex/algebra/shape'
25
23
  autoload :Start, 'shex/algebra/start'
26
24
  autoload :Stem, 'shex/algebra/stem'
@@ -50,16 +48,15 @@ module ShEx
50
48
  klass = case operator['type']
51
49
  when 'Annotation' then Annotation
52
50
  when 'EachOf' then EachOf
53
- when 'Inclusion' then Inclusion
54
51
  when 'NodeConstraint' then NodeConstraint
55
52
  when 'OneOf' then OneOf
56
53
  when 'Schema' then Schema
57
54
  when 'SemAct' then SemAct
58
55
  when 'Shape' then Shape
59
56
  when 'ShapeAnd' then And
57
+ when 'ShapeExternal' then External
60
58
  when 'ShapeNot' then Not
61
59
  when 'ShapeOr' then Or
62
- when 'ShapeRef' then ShapeRef
63
60
  when 'Stem' then Stem
64
61
  when 'StemRange' then StemRange
65
62
  when 'TripleConstraint' then TripleConstraint
@@ -1,7 +1,7 @@
1
1
  module ShEx::Algebra
2
2
  ##
3
3
  class And < Operator
4
- include Satisfiable
4
+ include ShapeExpression
5
5
  NAME = :and
6
6
 
7
7
  def initialize(*args, **options)
@@ -10,8 +10,8 @@ module ShEx::Algebra
10
10
  raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 2..)"
11
11
  end
12
12
 
13
- # All arguments must be Satisfiable
14
- raise ArgumentError, "All operands must be Shape operands" unless args.all? {|o| o.is_a?(Satisfiable)}
13
+ # All arguments must be ShapeExpression
14
+ raise ArgumentError, "All operands must be Shape operands or resource" unless args.all? {|o| o.is_a?(ShapeExpression) || o.is_a?(RDF::Resource)}
15
15
  super
16
16
  end
17
17
 
@@ -27,18 +27,28 @@ module ShEx::Algebra
27
27
 
28
28
  #
29
29
  # S is a ShapeAnd and for every shape expression se2 in shapeExprs, satisfies(n, se2, G, m).
30
- # @param (see Satisfiable#satisfies?)
31
- # @return (see Satisfiable#satisfies?)
32
- # @raise (see Satisfiable#satisfies?)
30
+ # @param (see ShapeExpression#satisfies?)
31
+ # @return (see ShapeExpression#satisfies?)
32
+ # @raise (see ShapeExpression#satisfies?)
33
33
  def satisfies?(focus, depth: 0)
34
34
  status ""
35
- expressions = operands.select {|o| o.is_a?(Satisfiable)}
36
35
  satisfied = []
37
36
  unsatisfied = expressions.dup
38
37
 
39
38
  # Operand raises NotSatisfied, so no need to check here.
40
39
  expressions.each do |op|
41
- satisfied << op.satisfies?(focus, depth: depth)
40
+ satisfied << case op
41
+ when RDF::Resource
42
+ schema.enter_shape(op, focus) do |shape|
43
+ if shape
44
+ shape.satisfies?(focus, depth: depth + 1)
45
+ else
46
+ status "Satisfy as #{op} was re-entered for #{focus}", depth: depth
47
+ end
48
+ end
49
+ when ShapeExpression
50
+ op.satisfies?(focus, depth: depth + 1)
51
+ end
42
52
  unsatisfied.shift
43
53
  end
44
54
  satisfy focus: focus, satisfied: satisfied, depth: depth
@@ -50,6 +60,26 @@ module ShEx::Algebra
50
60
  depth: depth
51
61
  end
52
62
 
63
+ ##
64
+ # expressions must be ShapeExpressions
65
+ #
66
+ # @return [Operator] `self`
67
+ # @raise [ShEx::StructureError] if the value is invalid
68
+ def validate!
69
+ expressions.each do |op|
70
+ case op
71
+ when ShapeExpression
72
+ when RDF::Resource
73
+ ref = schema.find(op)
74
+ ref.is_a?(ShapeExpression) ||
75
+ structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
76
+ else
77
+ structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
78
+ end
79
+ end
80
+ super
81
+ end
82
+
53
83
  def json_type
54
84
  "ShapeAnd"
55
85
  end
@@ -17,7 +17,7 @@ module ShEx::Algebra
17
17
  def to_h
18
18
  {
19
19
  'type' => json_type,
20
- 'predicate' => operands.first.to_s,
20
+ 'predicate' => operands.first.last.to_s,
21
21
  'object' => serialize_value(operands.last)
22
22
  }
23
23
  end
@@ -31,8 +31,9 @@ module ShEx::Algebra
31
31
  while num_iters < max
32
32
  begin
33
33
  matched_this_iter = []
34
- operands.select {|o| o.is_a?(TripleExpression)}.all? do |op|
34
+ expressions.select {|o| o.is_a?(TripleExpression) || o.is_a?(RDF::Resource)}.all? do |op|
35
35
  begin
36
+ op = schema.find(op) if op.is_a?(RDF::Resource)
36
37
  matched_op = op.matches(arcs_in - matched_this_iter, arcs_out - matched_this_iter, depth: depth + 1)
37
38
  satisfied << matched_op
38
39
  matched_this_iter += matched_op.matched
@@ -70,5 +71,25 @@ module ShEx::Algebra
70
71
  ensure
71
72
  semantic_actions.each {|op| op.exit(matched: matched, depth: depth + 1)}
72
73
  end
74
+
75
+ ##
76
+ # expressions must be TripleExpressions
77
+ #
78
+ # @return [Operator] `self`
79
+ # @raise [ShEx::StructureError] if the value is invalid
80
+ def validate!
81
+ expressions.each do |op|
82
+ case op
83
+ when TripleExpression
84
+ when RDF::Resource
85
+ ref = schema.find(op)
86
+ ref.is_a?(TripleExpression) ||
87
+ structure_error("#{json_type} must reference a TripleExpression: #{ref}")
88
+ else
89
+ structure_error("#{json_type} must reference a TripleExpression: #{ref}")
90
+ end
91
+ end
92
+ super
93
+ end
73
94
  end
74
95
  end
@@ -1,7 +1,7 @@
1
1
  module ShEx::Algebra
2
2
  ##
3
3
  class External < Operator
4
- include Satisfiable
4
+ include ShapeExpression
5
5
  NAME = :external
6
6
 
7
7
  #
@@ -9,11 +9,11 @@ module ShEx::Algebra
9
9
  def satisfies?(focus, depth: 0)
10
10
  extern_shape = nil
11
11
 
12
- # Find the label for this external
13
- not_satisfied("Can't find label for this extern", depth: depth) unless label
12
+ # Find the id for this external
13
+ not_satisfied("Can't find id for this extern", depth: depth) unless id
14
14
 
15
15
  schema.external_schemas.each do |schema|
16
- extern_shape ||= schema.shapes.detect {|s| s.label == label}
16
+ extern_shape ||= schema.shapes.detect {|s| s.id == id}
17
17
  end
18
18
 
19
19
  not_satisfied("External not configured for this shape", depth: depth) unless extern_shape
@@ -1,7 +1,7 @@
1
1
  module ShEx::Algebra
2
2
  ##
3
3
  class NodeConstraint < Operator
4
- include Satisfiable
4
+ include ShapeExpression
5
5
  NAME = :nodeConstraint
6
6
 
7
7
  ##
@@ -15,9 +15,9 @@ module ShEx::Algebra
15
15
 
16
16
  #
17
17
  # S is a NodeConstraint and satisfies2(focus, se) as described below in Node Constraints. Note that testing if a node satisfies a node constraint does not require a graph or shapeMap.
18
- # @param (see Satisfiable#satisfies?)
19
- # @return (see Satisfiable#satisfies?)
20
- # @raise (see Satisfiable#satisfies?)
18
+ # @param (see ShapeExpression#satisfies?)
19
+ # @return (see ShapeExpression#satisfies?)
20
+ # @raise (see ShapeExpression#satisfies?)
21
21
  def satisfies?(focus, depth: 0)
22
22
  status "", depth: depth
23
23
  satisfies_node_kind?(focus, depth: depth + 1) &&
@@ -1,7 +1,7 @@
1
1
  module ShEx::Algebra
2
2
  ##
3
3
  class Not < Operator::Unary
4
- include Satisfiable
4
+ include ShapeExpression
5
5
  NAME = :not
6
6
 
7
7
  ##
@@ -16,14 +16,27 @@ module ShEx::Algebra
16
16
 
17
17
  #
18
18
  # S is a ShapeNot and for the shape expression se2 at shapeExpr, notSatisfies(n, se2, G, m).
19
- # @param (see Satisfiable#satisfies?)
20
- # @return (see Satisfiable#satisfies?)
21
- # @raise (see Satisfiable#satisfies?)
19
+ # @param (see ShapeExpression#satisfies?)
20
+ # @return (see ShapeExpression#satisfies?)
21
+ # @raise (see ShapeExpression#satisfies?)
22
22
  # @see [https://shexspec.github.io/spec/#shape-expression-semantics]
23
23
  def satisfies?(focus, depth: 0)
24
24
  status ""
25
+ op = expressions.last
25
26
  satisfied_op = begin
26
- operands.last.satisfies?(focus, depth: depth + 1)
27
+ case op
28
+ when RDF::Resource
29
+ schema.enter_shape(op, focus) do |shape|
30
+ if shape
31
+ shape.satisfies?(focus, depth: depth + 1)
32
+ else
33
+ status "Satisfy as #{op} was re-entered for #{focus}", depth: depth
34
+ shape
35
+ end
36
+ end
37
+ when ShapeExpression
38
+ op.satisfies?(focus, depth: depth + 1)
39
+ end
27
40
  rescue ShEx::NotSatisfied => e
28
41
  return satisfy focus: focus, satisfied: e.expression.unsatisfied, depth: depth
29
42
  end
@@ -31,6 +44,24 @@ module ShEx::Algebra
31
44
  focus: focus, unsatisfied: satisfied_op, depth: depth
32
45
  end
33
46
 
47
+ ##
48
+ # expression must be a ShapeExpression
49
+ #
50
+ # @return [Operator] `self`
51
+ # @raise [ShEx::StructureError] if the value is invalid
52
+ def validate!
53
+ case expression
54
+ when ShapeExpression
55
+ when RDF::Resource
56
+ ref = schema.find(expression)
57
+ ref.is_a?(ShapeExpression) ||
58
+ structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
59
+ else
60
+ structure_error("#{json_type} must reference a ShapeExpression: #{ref}")
61
+ end
62
+ super
63
+ end
64
+
34
65
  def json_type
35
66
  "ShapeNot"
36
67
  end
@@ -27,8 +27,9 @@ module ShEx::Algebra
27
27
  # 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.
28
28
  status ""
29
29
  while num_iters < max
30
- matched_something = operands.select {|o| o.is_a?(TripleExpression)}.any? do |op|
30
+ matched_something = expressions.select {|o| o.is_a?(TripleExpression) || o.is_a?(RDF::Resource)}.any? do |op|
31
31
  begin
32
+ op = schema.find(op) if op.is_a?(RDF::Resource)
32
33
  matched_op = op.matches(arcs_in, arcs_out, depth: depth + 1)
33
34
  satisfied << matched_op
34
35
  results += matched_op.matched
@@ -62,5 +63,25 @@ module ShEx::Algebra
62
63
  matched: results, unmatched: ((arcs_in + arcs_out).uniq - results),
63
64
  satisfied: satisfied, unsatisfied: unsatisfied, depth: depth
64
65
  end
66
+
67
+ ##
68
+ # expressions must be TripleExpressions
69
+ #
70
+ # @return [Operator] `self`
71
+ # @raise [ShEx::StructureError] if the value is invalid
72
+ def validate!
73
+ expressions.each do |op|
74
+ case op
75
+ when TripleExpression
76
+ when RDF::Resource
77
+ ref = schema.find(op)
78
+ ref.is_a?(TripleExpression) ||
79
+ structure_error("#{json_type} must reference a TripleExpression: #{ref}")
80
+ else
81
+ structure_error("#{json_type} must reference a TripleExpression: #{ref}")
82
+ end
83
+ end
84
+ super
85
+ end
65
86
  end
66
87
  end
@@ -32,7 +32,7 @@ module ShEx::Algebra
32
32
  # any additional options
33
33
  # @option options [Boolean] :memoize (false)
34
34
  # whether to memoize results for particular operands
35
- # @option options [RDF::Resource] :label
35
+ # @option options [RDF::Resource] :id
36
36
  # Identifier of the operator
37
37
  # @raise [TypeError] if any operand is invalid
38
38
  def initialize(*operands)
@@ -55,7 +55,7 @@ module ShEx::Algebra
55
55
  end
56
56
  end
57
57
 
58
- @label = options[:label]
58
+ @id = options[:id]
59
59
  end
60
60
 
61
61
  ##
@@ -72,9 +72,6 @@ module ShEx::Algebra
72
72
  operands.select {|o| o.is_a?(SemAct)}
73
73
  end
74
74
 
75
- # Does this operator include Satisfiable?
76
- def satisfiable?; false; end
77
-
78
75
  # Does this operator include TripleExpression?
79
76
  def triple_expression?; false; end
80
77
 
@@ -185,7 +182,7 @@ module ShEx::Algebra
185
182
  end
186
183
 
187
184
  def status(message, **opts, &block)
188
- log_debug(self.class.const_get(:NAME).to_s + (@label ? "(#{@label})" : ""), message, **opts, &block)
185
+ log_debug(self.class.const_get(:NAME).to_s + (@id ? "(#{@id})" : ""), message, **opts, &block)
189
186
  true
190
187
  end
191
188
 
@@ -196,9 +193,9 @@ module ShEx::Algebra
196
193
  attr_reader :operands
197
194
 
198
195
  ##
199
- # The label (or subject) of this operand
196
+ # The id (or subject) of this operand
200
197
  # @return [RDF::Resource]
201
- attr_accessor :label
198
+ attr_accessor :id
202
199
 
203
200
  ##
204
201
  # Logging support (reader is in RDF::Util::Logger)
@@ -215,6 +212,21 @@ module ShEx::Algebra
215
212
  operands[index]
216
213
  end
217
214
 
215
+ ##
216
+ # Expressions are all operands which are Operators or RDF::Resource
217
+ # @return [RDF::Resource, Operand]
218
+ def expressions
219
+ @expressions = operands.
220
+ select {|op| op.is_a?(RDF::Resource) || op.is_a?(ShapeExpression) || op.is_a?(TripleExpression)}
221
+ end
222
+
223
+ ##
224
+ # The optional TripleExpression for this Shape.
225
+ # @return [TripleExpression]
226
+ def expression
227
+ expressions.first
228
+ end
229
+
218
230
  ##
219
231
  # Returns the binary S-Expression (SXP) representation of this operator.
220
232
  #
@@ -222,7 +234,7 @@ module ShEx::Algebra
222
234
  # @see https://en.wikipedia.org/wiki/S-expression
223
235
  def to_sxp_bin
224
236
  [self.class.const_get(:NAME)] +
225
- (label ? [[:label, label]] : []) +
237
+ (id ? [[:id, id]] : []) +
226
238
  (operands || []).map(&:to_sxp_bin)
227
239
  end
228
240
 
@@ -251,19 +263,25 @@ module ShEx::Algebra
251
263
  def self.from_shexj(operator, options = {})
252
264
  options[:context] ||= JSON::LD::Context.parse(ShEx::CONTEXT)
253
265
  operands = []
254
- label = nil
266
+ id = nil
255
267
 
256
268
  operator.each do |k, v|
257
269
  case k
258
- when /length|pattern|clusive/ then operands << [k.to_sym, v]
259
- when 'label' then label = iri(v, options)
260
- when 'min', 'max', 'inverse', 'closed' then operands << [k.to_sym, v]
270
+ when /length|pattern|clusive|digits/ then operands << [k.to_sym, RDF::Literal(v)]
271
+ when 'id' then id = iri(v, options)
272
+ when 'min', 'max' then operands << [k.to_sym, v]
273
+ when 'inverse', 'closed' then operands << k.to_sym
261
274
  when 'nodeKind' then operands << v.to_sym
262
275
  when 'object' then operands << value(v, options)
263
- when 'start' then operands << Start.new(ShEx::Algebra.from_shexj(v, options))
276
+ when 'start'
277
+ if v.is_a?(String)
278
+ operands << Start.new(iri(v, options))
279
+ else
280
+ operands << Start.new(ShEx::Algebra.from_shexj(v, options))
281
+ end
264
282
  when '@context' then
265
283
  options[:context] = JSON::LD::Context.parse(v)
266
- options[:base_uri] = options[:context].base
284
+ options[:base_uri] ||= options[:context].base
267
285
  when 'shapes'
268
286
  operands << case v
269
287
  when Array
@@ -271,10 +289,10 @@ module ShEx::Algebra
271
289
  else
272
290
  raise "Expected value of shapes #{v.inspect}"
273
291
  end
274
- when 'reference', 'include', 'stem', 'name'
292
+ when 'stem', 'name'
275
293
  # Value may be :wildcard for stem
276
294
  operands << (v.is_a?(Symbol) ? v : iri(v, options))
277
- when 'predicate' then operands << iri(v, options)
295
+ when 'predicate' then operands << [:predicate, iri(v, options)]
278
296
  when 'extra', 'datatype'
279
297
  v = [v] unless v.is_a?(Array)
280
298
  operands << (v.map {|op| iri(op, options)}).unshift(k.to_sym)
@@ -285,11 +303,15 @@ module ShEx::Algebra
285
303
  ShEx::Algebra.from_shexj(op, options) :
286
304
  value(op, options)
287
305
  end.unshift(:exclusions)
288
- when 'min', 'max', 'inverse', 'closed', 'valueExpr', 'semActs',
289
- 'shapeExpr', 'shapeExprs', 'startActs', 'expression',
290
- 'expressions', 'annotations'
306
+ when 'semActs', 'startActs', 'annotations'
291
307
  v = [v] unless v.is_a?(Array)
292
308
  operands += v.map {|op| ShEx::Algebra.from_shexj(op, options)}
309
+ when 'expression', 'expressions', 'shapeExpr', 'shapeExprs', 'valueExpr'
310
+ v = [v] unless v.is_a?(Array)
311
+ operands += v.map do |op|
312
+ # It's a URI reference to a Shape
313
+ op.is_a?(String) ? iri(op, options) : ShEx::Algebra.from_shexj(op, options)
314
+ end
293
315
  when 'code'
294
316
  operands << v
295
317
  when 'values'
@@ -300,7 +322,7 @@ module ShEx::Algebra
300
322
  end
301
323
  end
302
324
 
303
- new(*operands, label: label)
325
+ new(*operands, options.merge(id: id))
304
326
  end
305
327
 
306
328
  def json_type
@@ -315,10 +337,9 @@ module ShEx::Algebra
315
337
  # Create a hash version of the operator, suitable for turning into JSON.
316
338
  # @return [Hash]
317
339
  def to_h
318
- obj = json_type == 'Schema' ?
319
- {'@context' => ShEx::CONTEXT, 'type' => json_type} :
320
- {'type' => json_type}
321
- obj['label'] = label.to_s if label
340
+ obj = json_type == 'Schema' ? {'@context' => ShEx::CONTEXT} : {}
341
+ obj['id'] = id.to_s if id
342
+ obj['type'] = json_type
322
343
  operands.each do |op|
323
344
  case op
324
345
  when Array
@@ -340,6 +361,7 @@ module ShEx::Algebra
340
361
  :totaldigits,
341
362
  :fractiondigits then obj[op.first.to_s] = op.last.object
342
363
  when :min, :max then obj[op.first.to_s] = op.last
364
+ when :predicate then obj[op.first.to_s] = op.last.to_s
343
365
  when :base, :prefix
344
366
  # Ignore base and prefix
345
367
  when Symbol then obj[sym.to_s] = Array(op[1..-1]).map(&:to_h)
@@ -349,14 +371,20 @@ module ShEx::Algebra
349
371
  when :wildcard then obj['stem'] = {'type' => 'Wildcard'}
350
372
  when Annotation then (obj['annotations'] ||= []) << op.to_h
351
373
  when SemAct then (obj[is_a?(Schema) ? 'startActs' : 'semActs'] ||= []) << op.to_h
352
- when Start then obj['start'] = op.operands.first.to_h
374
+ when Start
375
+ obj['start'] = case op.operands.first
376
+ when RDF::Resource then op.operands.first.to_s
377
+ else op.operands.first.to_h
378
+ end
353
379
  when RDF::Value
354
380
  case self
355
- when TripleConstraint then obj['predicate'] = op.to_s
356
381
  when Stem, StemRange then obj['stem'] = op.to_s
357
- when Inclusion then obj['include'] = op.to_s
358
- when ShapeRef then obj['reference'] = op.to_s
359
382
  when SemAct then obj[op.is_a?(RDF::URI) ? 'name' : 'code'] = op.to_s
383
+ when TripleConstraint then obj['valueExpr'] = op.to_s
384
+ when Shape then obj['expression'] = op.to_s
385
+ when EachOf, OneOf then (obj['expressions'] ||= []) << op.to_s
386
+ when And, Or then (obj['shapeExprs'] ||= []) << op.to_s
387
+ when Not then obj['shapeExpr'] = op.to_s
360
388
  else
361
389
  raise "How to serialize Value #{op.inspect} to json for #{self}"
362
390
  end
@@ -368,7 +396,7 @@ module ShEx::Algebra
368
396
  else
369
397
  raise "How to serialize Symbol #{op.inspect} to json for #{self}"
370
398
  end
371
- when TripleConstraint, EachOf, OneOf, Inclusion
399
+ when TripleConstraint, EachOf, OneOf
372
400
  case self
373
401
  when EachOf, OneOf
374
402
  (obj['expressions'] ||= []) << op.to_h
@@ -382,7 +410,7 @@ module ShEx::Algebra
382
410
  else
383
411
  obj['valueExpr'] = op.to_h
384
412
  end
385
- when And, Or, Shape, Not, ShapeRef
413
+ when And, Or, Shape, Not
386
414
  case self
387
415
  when And, Or
388
416
  (obj['shapeExprs'] ||= []) << op.to_h
@@ -462,11 +490,7 @@ module ShEx::Algebra
462
490
  RDF::URI(value)
463
491
  end
464
492
  else
465
- if options[:context]
466
- options[:context].expand_iri(value, document: true)
467
- elsif base_uri
468
- base_uri.join(value)
469
- elsif base_uri
493
+ if base_uri
470
494
  base_uri.join(value)
471
495
  else
472
496
  RDF::URI(value)
@@ -526,14 +550,17 @@ module ShEx::Algebra
526
550
  #
527
551
  # @return [String]
528
552
  def inspect
529
- sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, operands.to_sse.gsub(/\s+/m, ' '))
553
+ sprintf("#<%s:%#0x(%s%s)>", self.class.name, __id__, ("id: #{id} " if id), operands.inspect)
530
554
  end
531
555
 
532
556
  ##
557
+ # Comparison does not consider operand order
533
558
  # @param [Statement] other
534
559
  # @return [Boolean]
535
560
  def eql?(other)
536
- other.class == self.class && other.operands == self.operands
561
+ other.class == self.class &&
562
+ other.id == self.id &&
563
+ other.operands.sort_by(&:to_s) == self.operands.sort_by(&:to_s)
537
564
  end
538
565
  alias_method :==, :eql?
539
566
 
@@ -543,22 +570,19 @@ module ShEx::Algebra
543
570
  # @yield operator
544
571
  # @yieldparam [Object] operator
545
572
  # @return [Enumerator]
546
- def each_descendant(depth = 0, &block)
573
+ def each_descendant(&block)
547
574
  if block_given?
548
575
 
549
- case block.arity
550
- when 1 then block.call(self)
551
- else block.call(depth, self)
552
- end
576
+ block.call(self)
553
577
 
554
578
  operands.each do |operand|
555
579
  case operand
556
580
  when Array
557
581
  operand.each do |op|
558
- op.each_descendant(depth + 1, &block) if op.respond_to?(:each_descendant)
582
+ op.each_descendant(&block) if op.respond_to?(:each_descendant)
559
583
  end
560
584
  else
561
- operand.each_descendant(depth + 1, &block) if operand.respond_to?(:each_descendant)
585
+ operand.each_descendant(&block) if operand.respond_to?(:each_descendant)
562
586
  end
563
587
  end
564
588
  end
@@ -582,34 +606,38 @@ module ShEx::Algebra
582
606
  end
583
607
 
584
608
  ##
585
- # First ancestor operator of type `klass`
586
- #
587
- # @param [Class] klass
588
- # @return [Operator]
589
- def first_ancestor(klass)
590
- parent.is_a?(klass) ? parent : parent.first_ancestor(klass) if parent
591
- end
609
+ # Ancestors of this Operator
610
+ # @return [Array<Operator>]
611
+ #def ancestors
612
+ # parent.is_a?(Operator) ? ([parent] + parent.ancestors) : []
613
+ #end
592
614
 
593
615
  ##
594
- # Validate all operands, operator specific classes should override for operator-specific validation
616
+ # Validate all operands, operator specific classes should override for operator-specific validation.
617
+ #
618
+ # A schema **must not** contain any shape expression `S` with negated references, either directly or transitively, to `S`.
619
+ #
595
620
  # @return [SPARQL::Algebra::Expression] `self`
596
621
  # @raise [ShEx::StructureError] if the value is invalid
597
622
  def validate!
598
- operands.each {|op| op.validate! if op.respond_to?(:validate!)}
623
+ operands.each do |op|
624
+ op.validate! if op.respond_to?(:validate!)
625
+ if op.is_a?(RDF::Resource) && (is_a?(ShapeExpression) || is_a?(TripleExpression))
626
+ ref = schema.find(op)
627
+ structure_error("Missing reference: #{op}") if ref.nil?
628
+ #if ancestors.unshift(self).include?(ref)
629
+ # structure_error("Self-recursive reference to #{op}")
630
+ #end
631
+ structure_error("Self referencing shape: #{operands.first}") if ref == self
632
+ end
633
+ end
599
634
  self
600
635
  end
601
636
 
602
637
  protected
603
638
  def dup
604
639
  operands = @operands.map {|o| o.dup rescue o}
605
- self.class.new(*operands, label: @label)
606
- end
607
-
608
- ##
609
- # Implement `to_hash` only if accessed; otherwise, it becomes an _Implicit Accessor_ which will cause problems with splat arguments, which causes the last to be turned into a hash for extracting keyword aruments.
610
- def method_missing(method, *args)
611
- return to_h(*args) if method == :hash
612
- super
640
+ self.class.new(*operands, id: @id)
613
641
  end
614
642
 
615
643
  ##