shex 0.1.0 → 0.2.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: a8b0ba57a643b21c61dc3ddb8a7b49323c39bc1f
4
- data.tar.gz: 82bdcc8715f967354d09e1990f801806778c39b6
3
+ metadata.gz: 3ba75ebd8c6632f82452b8cc6a19dbe69e86258d
4
+ data.tar.gz: a924845f0d0ffd7caa35926b98829345ee3d427a
5
5
  SHA512:
6
- metadata.gz: 6828f94a4e12c8d344ded59c69ec16e3c1f01990ea53110a1f21f1382eca27a2a1bdb53fa32e61e8c7095f2bdf0d74feb5c60f09e26b93b0626daad690171535
7
- data.tar.gz: 6ad0d4ae5c64c2d8076b270f0861749410a983ba2525db7fcabef7b405394af1432fcb63f08f625c75114e8bc8bd11da335e563560aed84290327a0cfb938e37
6
+ metadata.gz: 1e844351dfd63a5c158a088b6d83375e3f96382d1a56c3e7aaa8e037f4c3ae380bac4f04d1da9db3727053400563b96565d291cb7537974c920df636bf7d3614
7
+ data.tar.gz: 3797b22ce44300b723770608ee42a3fd03418f0c8497e4a370fdf960f47ad324c67b07ec061ca498a8fa2d683b97a4fab7c95ced34123840e2c968120e162418
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -1,5 +1,3 @@
1
- require 'sparql/extensions'
2
-
3
1
  ##
4
2
  # A ShEx runtime for RDF.rb.
5
3
  #
@@ -20,16 +18,16 @@ module ShEx
20
18
  # ).parse
21
19
  #
22
20
  # @param [IO, StringIO, String, #to_s] expression (ShExC or ShExJ)
23
- # @param ['shexc', 'shexj', 'sse'] format ('shexc')
21
+ # @param ['shexc', 'shexj', 'sxp'] format ('shexc')
24
22
  # @param [Hash{Symbol => Object}] options
25
- # @return [ShEx::Algebra::Schema] The executable parsed expression.
26
- # @raise [ShEx::ParseError] when a syntax error is detected
27
- # @raise [ShEx::StructureError, ArgumentError] on structural problems with schema
23
+ # @option (see ShEx::Parser#initialize)
24
+ # @return (see ShEx::Parser#parse)
25
+ # @raise (see ShEx::Parser#parse)
28
26
  def self.parse(expression, format: 'shexc', **options)
29
27
  case format
30
28
  when 'shexc' then Parser.new(expression, options).parse
31
29
  when 'shexj'
32
- when 'sse'
30
+ when 'sxp'
33
31
  else raise "Unknown expression format: #{format.inspect}"
34
32
  end
35
33
  end
@@ -41,15 +39,10 @@ module ShEx
41
39
  # schema = ShEx.parse('foo.shex').parse
42
40
  #
43
41
  # @param [String, #to_s] filename
44
- # @param ['shexc', 'shexj', 'sse'] format ('shexc')
45
- # @param [Hash{Symbol => Object}] options
46
- # any additional options (see `RDF::Reader#initialize` and `RDF::Format.for`)
47
- # @yield [ShEx::Algebra::Schema]
48
- # @yieldparam [RDF::Reader] reader
49
- # @yieldreturn [void] ignored
50
- # @return [ShEx::Algebra::Schema] The executable parsed expression.
51
- # @raise [ShEx::ParseError] when a syntax error is detected
52
- # @raise [ShEx::StructureError, ArgumentError] on structural problems with schema
42
+ # @param (see parse)
43
+ # @option (see ShEx::Parser#initialize)
44
+ # @return (see ShEx::Parser#parse)
45
+ # @raise (see ShEx::Parser#parse)
53
46
  def self.open(filename, format: 'shexc', **options, &block)
54
47
  RDF::Util::File.open_file(filename, options) do |file|
55
48
  self.parse(file, options.merge(format: format))
@@ -64,18 +57,31 @@ module ShEx
64
57
  # ShEx.execute('etc/doap.shex', graph, "http://rubygems.org/gems/shex", "")
65
58
  #
66
59
  # @param [IO, StringIO, String, #to_s] expression (ShExC or ShExJ)
67
- # @param [RDF::Resource] focus
68
- # @param [RDF::Resource] shape
69
- # @param ['shexc', 'shexj', 'sse'] format ('shexc')
70
- # @param [Hash{Symbol => Object}] options
71
- # @return [Boolean] `true` if satisfied, `false` if it does not apply
72
- # @raise [ShEx::NotSatisfied] if not satisfied
73
- # @raise [ShEx::ParseError] when a syntax error is detected
74
- # @raise [ShEx::StructureError, ArgumentError] on structural problems with schema
60
+ # @param (see ShEx::Algebra::Schema#execute)
61
+ # @return (see ShEx::Algebra::Schema#execute)
62
+ # @raise (see ShEx::Algebra::Schema#execute)
75
63
  def self.execute(expression, queryable, focus, shape, format: 'shexc', **options)
76
64
  shex = self.parse(expression, options.merge(format: format))
77
65
  queryable = queryable || RDF::Graph.new
78
66
 
67
+ shex.execute(focus, queryable, {focus => shape}, options)
68
+ end
69
+
70
+ ##
71
+ # Parse and validate the given ShEx `expression` string against `queriable`.
72
+ #
73
+ # @example executing a ShExC schema
74
+ # graph = RDF::Graph.load("etc/doap.ttl")
75
+ # ShEx.execute('etc/doap.shex', graph, "http://rubygems.org/gems/shex", "")
76
+ #
77
+ # @param [IO, StringIO, String, #to_s] expression (ShExC or ShExJ)
78
+ # @param (see ShEx::Algebra::Schema#satisfies?)
79
+ # @return (see ShEx::Algebra::Schema#satisfies?)
80
+ # @raise (see ShEx::Algebra::Schema#satisfies?)
81
+ def self.satisfies?(expression, queryable, focus, shape, format: 'shexc', **options)
82
+ shex = self.parse(expression, options.merge(format: format))
83
+ queryable = queryable || RDF::Graph.new
84
+
79
85
  shex.satisfies?(focus, queryable, {focus => shape}, options)
80
86
  end
81
87
 
@@ -100,7 +106,48 @@ module ShEx
100
106
  class StructureError < Error; end
101
107
 
102
108
  # Shape expectation not satisfied
103
- class NotSatisfied < Error; end
109
+ class NotSatisfied < Error
110
+ ##
111
+ # The expression which was not satified
112
+ # @return [ShEx::Satisfiable]
113
+ attr_reader :expression
114
+
115
+ ##
116
+ # Initializes a new parser error instance.
117
+ #
118
+ # @param [String, #to_s] message
119
+ # @param [Satisfiable] expression (self)
120
+ def initialize(message, expression: self)
121
+ @expression = expression
122
+ super(message.to_s)
123
+ end
124
+
125
+ def inspect
126
+ super + (expression ? SXP::Generator.string(expression.to_sxp_bin) : '')
127
+ end
128
+ end
129
+
130
+ # TripleExpression did not match
131
+ class NotMatched < ShEx::Error
132
+ ##
133
+ # The expression which was not satified
134
+ # @return [ShEx::Algebra::TripleExpression]
135
+ attr_reader :expression
136
+
137
+ ##
138
+ # Initializes a new parser error instance.
139
+ #
140
+ # @param [String, #to_s] message
141
+ # @param [Satisfiable] expression (self)
142
+ def initialize(message, expression: self)
143
+ @expression = expression
144
+ super(message.to_s)
145
+ end
146
+
147
+ def inspect
148
+ super + (expression ? SXP::Generator.string(expression.to_sxp_bin) : '')
149
+ end
150
+ end
104
151
 
105
152
  # Indicates bad syntax found in LD Patch document
106
153
  class ParseError < Error
@@ -117,17 +164,16 @@ module ShEx
117
164
  attr_reader :lineno
118
165
 
119
166
  ##
120
- # Initializes a new parser error instance.
167
+ # ParseError includes `token` and `lineno` associated with the expression.
121
168
  #
122
169
  # @param [String, #to_s] message
123
170
  # @param [Hash{Symbol => Object}] options
124
- # @option options [String] :token (nil)
125
- # @option options [Integer] :lineno (nil)
126
- # @option options [Integer] :code (400)
127
- def initialize(message, options = {})
128
- @token = options[:token]
129
- @lineno = options[:lineno] || (@token.lineno if @token.respond_to?(:lineno))
130
- super(message.to_s, code: options.fetch(:code, 400))
171
+ # @param [String] token (nil)
172
+ # @param [Integer] lineno (nil)
173
+ def initialize(message, token: nil, lineno: nil)
174
+ @token = token
175
+ @lineno = lineno || (@token.lineno if @token.respond_to?(:lineno))
176
+ super(message.to_s)
131
177
  end
132
178
  end
133
179
  end
@@ -31,8 +31,6 @@ module ShEx
31
31
  autoload :TripleExpression, 'shex/algebra/triple_expression'
32
32
  autoload :UnaryShape, 'shex/algebra/unary_shape'
33
33
  autoload :Value, 'shex/algebra/value'
34
-
35
- class NotMatched < ShEx::Error; end
36
34
  end
37
35
  end
38
36
 
@@ -14,19 +14,23 @@ module ShEx::Algebra
14
14
 
15
15
  #
16
16
  # S is a ShapeAnd and for every shape expression se2 in shapeExprs, satisfies(n, se2, G, m).
17
- # @param [RDF::Resource] n
18
- # @return [Boolean] `true` when satisfied
19
- # @raise [ShEx::NotSatisfied] if not satisfied
20
- def satisfies?(n)
17
+ # @param (see Satisfiable#satisfies?)
18
+ # @return (see Satisfiable#satisfies?)
19
+ # @raise (see Satisfiable#satisfies?)
20
+ def satisfies?(focus)
21
21
  status ""
22
+ expressions = operands.select {|o| o.is_a?(Satisfiable)}
23
+ satisfied = []
22
24
 
23
25
  # Operand raises NotSatisfied, so no need to check here.
24
- operands.select {|o| o.is_a?(Satisfiable)}.each {|op| op.satisfies?(n)}
25
- status("satisfied")
26
- true
26
+ expressions.each do |op|
27
+ satisfied << op.satisfies?(focus)
28
+ end
29
+ satisfy satisfied: satisfied
27
30
  rescue ShEx::NotSatisfied => e
28
- not_satisfied(e.message)
29
- raise
31
+ not_satisfied e.message,
32
+ satisfied: satisfied,
33
+ unsatisfied: (expressions - satisfied)
30
34
  end
31
35
  end
32
36
  end
@@ -7,47 +7,55 @@ module ShEx::Algebra
7
7
  ##
8
8
  # expr is an EachOf and there is some partition of T into T1, T2,… such that for every expression expr1, expr2,… in shapeExprs, matches(Tn, exprn, m)...
9
9
  #
10
- # @param [Array<RDF::Statement>] t
10
+ # @param [Array<RDF::Statement>] statements
11
11
  # @return [Array<RDF::Statement>]
12
- # @raise NotMatched, ShEx::NotSatisfied
13
- def matches(t)
12
+ # @raise [ShEx::NotMatched]
13
+ def matches(statements)
14
14
  status ""
15
- results = []
16
- statements = t.dup
17
- num_iters = 0
18
- max = maximum
15
+ results, satisfied, unsatisfied = [], [], []
16
+ num_iters, max = 0, maximum
19
17
 
20
18
  while num_iters < max
21
19
  begin
22
20
  matched_this_iter = []
23
21
  operands.select {|o| o.is_a?(TripleExpression)}.all? do |op|
24
- matched = op.matches(statements - matched_this_iter)
25
- matched_this_iter += matched
22
+ begin
23
+ matched_op = op.matches(statements - matched_this_iter)
24
+ satisfied << matched_op
25
+ matched_this_iter += matched_op.matched
26
+ rescue ShEx::NotMatched => e
27
+ status "not matched: #{e.message}"
28
+ op = op.dup
29
+ op.unmatched = statements - matched_this_iter
30
+ unsatisfied << op
31
+ raise
32
+ end
26
33
  end
27
34
  results += matched_this_iter
28
35
  statements -= matched_this_iter
29
36
  num_iters += 1
30
37
  status "matched #{results.length} statements after #{num_iters} iterations"
31
- rescue NotMatched => e
32
- log_recover("eachOf: ignore error: #{e.message}", depth: options.fetch(:depth, 0))
38
+ rescue ShEx::NotMatched => e
39
+ status "no match after #{num_iters} iterations (ignored)"
33
40
  break
34
41
  end
35
42
  end
36
43
 
37
44
  # Max violations handled in Shape
38
- not_matched "Minimum Cardinality Violation: #{num_iters} < #{minimum}" if
39
- num_iters < minimum
45
+ if num_iters < minimum
46
+ raise ShEx::NotMatched, "Minimum Cardinality Violation: #{results.length} < #{minimum}"
47
+ end
40
48
 
41
49
  # Last, evaluate semantic acts
42
50
  semantic_actions.all? do |op|
43
51
  op.satisfies?(results)
44
52
  end unless results.empty?
45
53
 
46
- status "each of satisfied"
47
- results
48
- rescue NotMatched => e
49
- not_matched(e.message)
50
- raise
54
+ satisfy matched: results, satisfied: satisfied
55
+ rescue ShEx::NotMatched, ShEx::NotSatisfied => e
56
+ not_matched e.message,
57
+ matched: results, unmatched: (statements - results),
58
+ satisfied: satisfied, unsatisfied: unsatisfied
51
59
  end
52
60
  end
53
61
  end
@@ -6,7 +6,7 @@ module ShEx::Algebra
6
6
 
7
7
  #
8
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)
9
+ def satisfies?(focus)
10
10
  extern_shape = nil
11
11
 
12
12
  # Find the label for this external
@@ -18,7 +18,7 @@ module ShEx::Algebra
18
18
  end
19
19
 
20
20
  not_satisfied("External not configured for this shape") unless extern_shape
21
- extern_shape.satisfies?(n)
21
+ extern_shape.satisfies?(focus)
22
22
  end
23
23
  end
24
24
  end
@@ -12,20 +12,17 @@ module ShEx::Algebra
12
12
  ##
13
13
  # In this case, we accept an array of statements, and match based on cardinality.
14
14
  #
15
- # @param [Array<RDF::Statement>] t
15
+ # @param [Array<RDF::Statement>] statements
16
16
  # @return [Array<RDF::Statement>]
17
- # @raise NotMatched, ShEx::NotSatisfied
18
- def matches(t)
17
+ # @raise [ShEx::NotMatched]
18
+ def matches(statements)
19
19
  status "referenced_shape: #{operands.first}"
20
20
  expression = referenced_shape.triple_expressions.first
21
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
22
+ matched_expression = expression.matches(statements)
23
+ satisfy matched: matched_expression.matched
24
+ rescue ShEx::NotMatched => e
25
+ not_matched e.message, unsatisfied: e.expression
29
26
  end
30
27
 
31
28
  ##
@@ -5,17 +5,18 @@ module ShEx::Algebra
5
5
  NAME = :nodeConstraint
6
6
 
7
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)
8
+ # 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.
9
+ # @param (see Satisfiable#satisfies?)
10
+ # @return (see Satisfiable#satisfies?)
11
+ # @raise (see Satisfiable#satisfies?)
12
+ def satisfies?(focus)
13
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)
14
+ satisfies_node_kind?(focus) &&
15
+ satisfies_datatype?(focus) &&
16
+ satisfies_string_facet?(focus) &&
17
+ satisfies_numeric_facet?(focus) &&
18
+ satisfies_values?(focus) &&
19
+ satisfy
19
20
  end
20
21
 
21
22
  private
@@ -6,14 +6,18 @@ module ShEx::Algebra
6
6
 
7
7
  #
8
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)
9
+ # @param (see Satisfiable#satisfies?)
10
+ # @return (see Satisfiable#satisfies?)
11
+ # @raise (see Satisfiable#satisfies?)
12
+ # @see [https://shexspec.github.io/spec/#shape-expression-semantics]
13
+ def satisfies?(focus)
13
14
  status ""
14
- operands.last.not_satisfies?(n)
15
- status "not satisfied"
16
- true
15
+ satisfied_op = begin
16
+ operands.first.satisfies?(focus)
17
+ rescue ShEx::NotSatisfied => e
18
+ return satisfy satisfied: e.expression.unsatisfied
19
+ end
20
+ not_satisfied "Expression should not have matched", unsatisfied: satisfied_op
17
21
  end
18
22
  end
19
23
  end
@@ -7,25 +7,27 @@ module ShEx::Algebra
7
7
  ##
8
8
  # `expr` is a OneOf and there is some shape expression `se2` in shapeExprs such that a `matches(T, se2, m)`...
9
9
  #
10
- # @param [Array<RDF::Statement>] t
10
+ # @param [Array<RDF::Statement>] statements
11
11
  # @return [Array<RDF::Statement]
12
- def matches(t)
13
- results = []
14
- statements = t.dup
15
- num_iters = 0
16
- max = maximum
12
+ def matches(statements)
13
+ results, satisfied, unsatisfied = [], [], []
14
+ num_iters, max = 0, maximum
17
15
 
18
16
  # 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
17
  status ""
20
18
  while num_iters < max
21
19
  matched_something = operands.select {|o| o.is_a?(TripleExpression)}.any? do |op|
22
20
  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))
21
+ matched_op = op.matches(statements)
22
+ satisfied << matched_op
23
+ results += matched_op.matched
24
+ statements -= matched_op.matched
25
+ status "matched #{matched_op.matched.to_sxp}"
26
+ rescue ShEx::NotMatched => e
27
+ status "not matched: #{e.message}"
28
+ op = op.dup
29
+ op.unmatched = statements - results
30
+ unsatisfied << op
29
31
  false
30
32
  end
31
33
  end
@@ -35,16 +37,20 @@ module ShEx::Algebra
35
37
  end
36
38
 
37
39
  # Max violations handled in Shape
38
- not_matched "Minimum Cardinality Violation: #{num_iters} < #{minimum}" if
39
- num_iters < minimum
40
+ if num_iters < minimum
41
+ raise ShEx::NotMatched, "Minimum Cardinality Violation: #{results.length} < #{minimum}"
42
+ end
40
43
 
41
44
  # Last, evaluate semantic acts
42
45
  semantic_actions.all? do |op|
43
46
  op.satisfies?(results)
44
47
  end unless results.empty?
45
48
 
46
- status "one of satisfied"
47
- results
49
+ satisfy matched: results, satisfied: satisfied, unsatisfied: unsatisfied
50
+ rescue ShEx::NotMatched, ShEx::NotSatisfied => e
51
+ not_matched e.message,
52
+ matched: results, unmatched: (statements - results),
53
+ satisfied: satisfied, unsatisfied: unsatisfied
48
54
  end
49
55
  end
50
56
  end
@@ -1,5 +1,4 @@
1
1
  require 'sparql/algebra'
2
- require 'sparql/extensions'
3
2
 
4
3
  module ShEx::Algebra
5
4
 
@@ -86,18 +85,86 @@ module ShEx::Algebra
86
85
  # Does this operator a SemAct?
87
86
  def semact?; false; end
88
87
 
88
+ ##
89
+ # On a result instance, the statements that matched this expression.
90
+ # @return [Array<Statement>]
91
+ def matched
92
+ Array((operands.detect {|op| op.is_a?(Array) && op[0] == :matched} || [:matched])[1..-1])
93
+ end
94
+ def matched=(statements)
95
+ operands.delete_if {|op| op.is_a?(Array) && op[0] == :matched}
96
+ operands << statements.unshift(:matched) unless (statements || []).empty?
97
+ end
98
+
99
+ ##
100
+ # On a result instance, the statements that did not match this expression (failure only).
101
+ # @return [Array<Statement>]
102
+ def unmatched
103
+ Array((operands.detect {|op| op.is_a?(Array) && op[0] == :unmatched} || [:unmatched])[1..-1])
104
+ end
105
+ def unmatched=(statements)
106
+ operands.delete_if {|op| op.is_a?(Array) && op[0] == :unmatched}
107
+ operands << statements.unshift(:unmatched) unless (statements || []).empty?
108
+ end
109
+
110
+ ##
111
+ # On a result instance, the sub-expressions which were matched.
112
+ # @return [Array<Operator>]
113
+ def satisfied
114
+ Array((operands.detect {|op| op.is_a?(Array) && op[0] == :satisfied} || [:satisfied])[1..-1])
115
+ end
116
+ def satisfied=(ops)
117
+ operands.delete_if {|op| op.is_a?(Array) && op[0] == :satisfied}
118
+ operands << ops.unshift(:satisfied) unless (ops || []).empty?
119
+ end
120
+
121
+ ##
122
+ # On a result instance, the sub-satisfieables which were not satisfied. (failure only).
123
+ # @return [Array<Operator>]
124
+ def unsatisfied
125
+ Array((operands.detect {|op| op.is_a?(Array) && op[0] == :unsatisfied} || [:unsatisfied])[1..-1])
126
+ end
127
+ def unsatisfied=(ops)
128
+ operands.delete_if {|op| op.is_a?(Array) && op[0] == :unsatisfied}
129
+ operands << ops.unshift(:unsatisfied) unless (ops || []).empty?
130
+ end
131
+
132
+ ##
133
+ # Duplication this operand, and add `matched`, `unmatched`, `satisfied`, and `unsatisfied` operands for accessing downstream.
134
+ #
135
+ # @return [Operand]
136
+ def satisfy(matched: nil, unmatched: nil, satisfied: nil, unsatisfied: nil)
137
+ log_debug(self.class.const_get(:NAME), "satisfied", depth: options.fetch(:depth, 0))
138
+ expression = self.dup
139
+ expression.matched = Array(matched) if matched
140
+ expression.unmatched = Array(unmatched) if unmatched
141
+ expression.satisfied = Array(satisfied) if satisfied
142
+ expression.unsatisfied = Array(unsatisfied) if unsatisfied
143
+ expression
144
+ end
145
+
89
146
  ##
90
147
  # 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}"}
148
+ def not_matched(message, matched: nil, unmatched: nil, satisfied: nil, unsatisfied: nil, **opts, &block)
149
+ expression = opts.fetch(:expression, self).satisfy(
150
+ matched: matched,
151
+ unmatched: unmatched,
152
+ satisfied: satisfied,
153
+ unsatisfied: unsatisfied)
154
+ exception = opts.fetch(:exception, ShEx::NotMatched)
155
+ status(message) {(block_given? ? block.call : "") + "expression: #{expression.to_sxp}"}
156
+ raise exception.new(message, expression: expression)
95
157
  end
96
158
 
97
- def not_satisfied(message, **opts)
98
- expression = opts.fetch(:expression, self)
159
+ def not_satisfied(message, matched: nil, unmatched: nil, satisfied: nil, unsatisfied: nil, **opts)
160
+ expression = opts.fetch(:expression, self).satisfy(
161
+ matched: matched,
162
+ unmatched: unmatched,
163
+ satisfied: satisfied,
164
+ unsatisfied: unsatisfied)
99
165
  exception = opts.fetch(:exception, ShEx::NotSatisfied)
100
- log_error(message, depth: options.fetch(:depth, 0), exception: exception) {"expression: #{expression.to_sxp}"}
166
+ status(message) {(block_given? ? block.call : "") + "expression: #{expression.to_sxp}"}
167
+ raise exception.new(message, expression: expression)
101
168
  end
102
169
 
103
170
  def structure_error(message, **opts)
@@ -107,7 +174,7 @@ module ShEx::Algebra
107
174
  end
108
175
 
109
176
  def status(message, &block)
110
- log_info(self.class.const_get(:NAME), message, depth: options.fetch(:depth, 0), &block)
177
+ log_debug(self.class.const_get(:NAME), message, depth: options.fetch(:depth, 0), &block)
111
178
  true
112
179
  end
113
180
 
@@ -128,10 +195,10 @@ module ShEx::Algebra
128
195
  end
129
196
 
130
197
  ##
131
- # Returns the SPARQL S-Expression (SSE) representation of this operator.
198
+ # Returns the binary S-Expression (SXP) representation of this operator.
132
199
  #
133
200
  # @return [Array]
134
- # @see http://openjena.org/wiki/SSE
201
+ # @see https://en.wikipedia.org/wiki/S-expression
135
202
  def to_sxp_bin
136
203
  operator = [self.class.const_get(:NAME)].flatten.first
137
204
  [operator, *(operands || []).map(&:to_sxp_bin)]
@@ -14,24 +14,30 @@ module ShEx::Algebra
14
14
 
15
15
  #
16
16
  # S is a ShapeOr and there is some shape expression se2 in shapeExprs such that satisfies(n, se2, G, m).
17
- # @param [RDF::Resource] n
18
- # @return [Boolean] `true` if satisfied, `false` if it does not apply
19
- # @raise [ShEx::NotSatisfied] if not satisfied
20
- def satisfies?(n)
21
- any_not_satisfied = false
22
- operands.select {|o| o.is_a?(Satisfiable)}.any? do |op|
17
+ # @param (see Satisfiable#satisfies?)
18
+ # @return (see Satisfiable#satisfies?)
19
+ # @raise (see Satisfiable#satisfies?)
20
+ def satisfies?(focus)
21
+ status ""
22
+ expressions = operands.select {|o| o.is_a?(Satisfiable)}
23
+ unsatisfied = []
24
+ expressions.any? do |op|
23
25
  begin
24
- op.satisfies?(n)
25
- status "satisfied #{n}"
26
- return true
26
+ matched_op = op.satisfies?(focus)
27
+ return satisfy satisfied: matched_op, unsatisfied: unsatisfied
27
28
  rescue ShEx::NotSatisfied => e
28
- log_recover("or: ignore error: #{e.message}", depth: options.fetch(:depth, 0))
29
- any_not_satisfied = e
29
+ status "unsatisfied #{focus}"
30
+ op = op.dup
31
+ op.satisfied = e.expression.satisfied
32
+ op.unsatisfied = e.expression.unsatisfied
33
+ unsatisfied << op
34
+ status("unsatisfied: #{e.message}")
30
35
  false
31
36
  end
32
37
  end
33
38
 
34
- not_satisfied "Expected some expression to be satisfied"
39
+ not_satisfied "Expected some expression to be satisfied",
40
+ unsatisfied: unsatisfied
35
41
  end
36
42
  end
37
43
  end
@@ -1,36 +1,18 @@
1
1
  require 'sparql/algebra'
2
- require 'sparql/extensions'
3
2
 
4
3
  module ShEx::Algebra
5
4
  # Implements `satisfies?` and `not_satisfies?`
6
5
  module Satisfiable
7
6
  ##
8
7
  # Satisfies method
9
- # @param [RDF::Resource] n
10
- # @return [Boolean] `true` if satisfied, `false` if it does not apply
11
- # @raise [ShEx::NotSatisfied] if not satisfied
8
+ # @param [RDF::Resource] focus
9
+ # @return [TripleExpression] with `matched` and `satisfied` accessors for matched triples and sub-expressions
10
+ # @raise [ShEx::NotMatched] with `expression` accessor to access `matched` and `unmatched` statements along with `satisfied` and `unsatisfied` operations.
12
11
  # @see [https://shexspec.github.io/spec/#shape-expression-semantics]
13
- def satisfies?(n)
12
+ def satisfies?(focus)
14
13
  raise NotImplementedError, "#satisfies? Not implemented in #{self.class}"
15
14
  end
16
15
 
17
- ##
18
- # Satisfies method
19
- # @param [RDF::Resource] n
20
- # @return [Boolean] `true` if not satisfied, `false` if it does not apply
21
- # @raise [ShEx::NotSatisfied] if satisfied
22
- # @see [https://shexspec.github.io/spec/#shape-expression-semantics]
23
- def not_satisfies?(n)
24
- begin
25
- satisfies?(n)
26
- rescue ShEx::NotSatisfied => e
27
- log_recover(self.class.const_get(:NAME), "ignore error: #{e.message}", depth: options.fetch(:depth, 0))
28
- return true # Expected it to not satisfy
29
- end
30
- not_satisfied "Expression should not have matched"
31
- end
32
- alias_method :notSatisfies?, :not_satisfies?
33
-
34
16
  ##
35
17
  # Included TripleExpressions
36
18
  # @return [Array<TripleExpressions>]
@@ -1,7 +1,6 @@
1
1
  module ShEx::Algebra
2
2
  ##
3
3
  class Schema < Operator
4
- include Satisfiable
5
4
  NAME = :schema
6
5
 
7
6
  # Graph to validate
@@ -15,46 +14,69 @@ module ShEx::Algebra
15
14
  ##
16
15
  # Match on schema. Finds appropriate shape for node, and matches that shape.
17
16
  #
18
- # @param [RDF::Resource] n
19
- # @param [RDF::Queryable] g
20
- # @param [Hash{RDF::Resource => RDF::Resource}] m
17
+ # @param [RDF::Resource] focus
18
+ # @param [RDF::Queryable] graph
19
+ # @param [Hash{RDF::Resource => RDF::Resource}] map
21
20
  # @param [Array<Schema, String>] shapeExterns ([])
22
21
  # One or more schemas, or paths to ShEx schema resources used for finding external shapes.
23
- # @return [Boolean] `true` if satisfied, `false` if it does not apply
24
- # @raise [ShEx::NotSatisfied] if not satisfied
25
- # FIXME: set of node/shape pairs
26
- def satisfies?(n, g, m, shapeExterns: [], **options)
27
- @graph = g
22
+ # @return [Operand] Returns operand graph annotated with satisfied and unsatisfied operations.
23
+ # @raise [ShEx::NotSatisfied] along with operand graph described for return
24
+ def execute(focus, graph, map, shapeExterns: [], **options)
25
+ @graph = graph
28
26
  @external_schemas = shapeExterns
27
+ focus = iri(focus)
29
28
  # Make sure they're URIs
30
- @map = m.inject({}) {|memo, (k,v)| memo.merge(k.to_s => v.to_s)}
29
+ @map = (map || {}).inject({}) {|memo, (k,v)| memo.merge(iri(k).to_s => iri(v).to_s)}
31
30
 
32
31
  # First, evaluate semantic acts
33
32
  semantic_actions.all? do |op|
34
33
  op.satisfies?([])
35
34
  end
36
35
 
36
+ # Keep a new Schema, specifically for recording actions
37
+ satisfied_schema = Schema.new
37
38
  # Next run any start expression
38
39
  if start
39
40
  status("start") {"expression: #{start.to_sxp}"}
40
- start.satisfies?(n)
41
+ satisfied_schema.operands << start.satisfies?(focus)
41
42
  end
42
43
 
43
- label = @map[n.to_s]
44
+ # Add shape result(s)
45
+ satisfied_shapes = {}
46
+ satisfied_schema.operands << [:shapes, satisfied_shapes] unless shapes.empty?
47
+
48
+ label = @map[focus.to_s]
44
49
  if label && !label.empty?
45
50
  shape = shapes[label]
46
51
  structure_error("No shape found for #{label}") unless shape
47
52
 
48
53
  # If `n` is a Blank Node, we won't find it through normal matching, find an equivalent node in the graph having the same label
49
- if n.is_a?(RDF::Node)
50
- nn = graph.enum_term.detect {|t| t.id == n.id}
51
- n = nn if nn
54
+ if focus.is_a?(RDF::Node)
55
+ n = graph.enum_term.detect {|t| t.id == focus.id}
56
+ focus = n if n
52
57
  end
53
58
 
54
- shape.satisfies?(n)
59
+ satisfied_shapes[label] = shape.satisfies?(focus)
55
60
  end
56
61
  status "schema satisfied"
57
- true
62
+ satisfied_schema
63
+ end
64
+
65
+ ##
66
+ # Match on schema. Finds appropriate shape for node, and matches that shape.
67
+ #
68
+ # @param [RDF::Resource] focus
69
+ # @param [RDF::Queryable] graph
70
+ # @param [Hash{RDF::Resource => RDF::Resource}] map
71
+ # @param [Array<Schema, String>] shapeExterns ([])
72
+ # One or more schemas, or paths to ShEx schema resources used for finding external shapes.
73
+ # @param [Hash{Symbol => Object}] options
74
+ # @option options [String] :base_uri
75
+ # @return [Boolean]
76
+ def satisfies?(focus, graph, map, shapeExterns: [], **options)
77
+ execute(focus, graph, map, options.merge(shapeExterns: shapeExterns))
78
+ rescue ShEx::NotSatisfied
79
+ false
58
80
  end
59
81
 
60
82
  ##
@@ -62,8 +84,7 @@ module ShEx::Algebra
62
84
  # @return [Hash{RDF::Resource => Operator}]
63
85
  def shapes
64
86
  @shapes ||= begin
65
- shapes = operands.
66
- detect {|op| op.is_a?(Array) && op.first == :shapes}
87
+ shapes = operands.detect {|op| op.is_a?(Array) && op.first == :shapes}
67
88
  shapes = shapes ? shapes.last : {}
68
89
  shapes.inject({}) do |memo, (label, operand)|
69
90
  memo.merge(label.to_s => operand)
@@ -107,6 +128,34 @@ module ShEx::Algebra
107
128
  enum_for(:each_descendant)
108
129
  end
109
130
 
131
+ ##
132
+ # Returns the Base URI defined for the parser,
133
+ # as specified or when parsing a BASE prologue element.
134
+ #
135
+ # @example
136
+ # base #=> RDF::URI('http://example.com/')
137
+ #
138
+ # @return [HRDF::URI]
139
+ def base_uri
140
+ RDF::URI(@options[:base_uri]) if @options[:base_uri]
141
+ end
142
+
143
+ # Create URIs
144
+ def iri(value)
145
+ # If we have a base URI, use that when constructing a new URI
146
+ case value
147
+ when RDF::Value then value
148
+ when /^_:/ then RDF::Node(value[2..-1].to_s)
149
+ else
150
+ value = RDF::URI(value)
151
+ if base_uri && value.relative?
152
+ base_uri.join(value)
153
+ else
154
+ value
155
+ end
156
+ end
157
+ end
158
+
110
159
  ##
111
160
  # Start action, if any
112
161
  def start
@@ -4,59 +4,77 @@ module ShEx::Algebra
4
4
  include Satisfiable
5
5
  NAME = :shape
6
6
 
7
- #
7
+ ##
8
+ # Let `outs` be the `arcsOut` in `remainder`: `outs = remainder ∩ arcsOut(G, n)`.
9
+ # @return [Array<RDF::Statement>]
10
+ attr_accessor :outs
11
+
12
+ ##
13
+ # Let `matchables` be the triples in `outs` whose predicate appears in a {TripleConstraint} in `expression`. If `expression` is absent, `matchables = Ø` (the empty set).
14
+ # @return [Array<RDF::Statement>]
15
+ attr_accessor :matchables
16
+
17
+ ##
18
+ # Let `unmatchables` be the triples in `outs` which are not in `matchables`. `matchables ∪ unmatchables = outs.`
19
+ # @return [Array<RDF::Statement>]
20
+ attr_accessor :unmatchables
21
+
8
22
  # The `satisfies` semantics for a `Shape` depend on a matches function defined below. For a node `n`, shape `S`, graph `G`, and shapeMap `m`, `satisfies(n, S, G, m)`.
9
- # @param [RDF::Resource] n
10
- # @return [Boolean] `true` if satisfied
11
- # @raise [ShEx::NotSatisfied] if not satisfied
12
- def satisfies?(n)
23
+ # @param (see Satisfiable#satisfies?)
24
+ # @return (see Satisfiable#satisfies?)
25
+ # @raise (see Satisfiable#satisfies?)
26
+ def satisfies?(focus)
13
27
  expression = operands.detect {|op| op.is_a?(TripleExpression)}
14
28
 
15
29
  # neigh(G, n) is the neighbourhood of the node n in the graph G.
16
30
  #
17
31
  # neigh(G, n) = arcsOut(G, n) ∪ arcsIn(G, n)
18
- arcs_in = schema.graph.query(object: n).to_a.sort_by(&:to_sxp)
19
- arcs_out = schema.graph.query(subject: n).to_a.sort_by(&:to_sxp)
32
+ arcs_in = schema.graph.query(object: focus).to_a.sort_by(&:to_sxp)
33
+ arcs_out = schema.graph.query(subject: focus).to_a.sort_by(&:to_sxp)
20
34
  neigh = (arcs_in + arcs_out).uniq
21
35
 
22
36
  # `matched` is the subset of statements which match `expression`.
23
37
  status("arcsIn: #{arcs_in.count}, arcsOut: #{arcs_out.count}")
24
- matched = expression ? expression.matches(neigh) : []
38
+ matched_expression = expression.matches(neigh) if expression
39
+ matched = Array(matched_expression && matched_expression.matched)
25
40
 
26
41
  # `remainder` is the set of unmatched statements
27
42
  remainder = neigh - matched
28
43
 
29
44
  # Let `outs` be the `arcsOut` in `remainder`: `outs = remainder ∩ arcsOut(G, n)`.
30
- outs = remainder.select {|s| s.subject == n}
45
+ @outs = remainder.select {|s| s.subject == focus}
31
46
 
32
47
  # Let `matchables` be the triples in `outs` whose predicate appears in a `TripleConstraint` in `expression`. If `expression` is absent, `matchables = Ø` (the empty set).
33
48
  predicates = expression ? expression.triple_constraints.map(&:predicate).uniq : []
34
- matchables = outs.select {|s| predicates.include?(s.predicate)}
49
+ @matchables = outs.select {|s| predicates.include?(s.predicate)}
50
+
51
+ # Let `unmatchables` be the triples in `outs` which are not in `matchables`.
52
+ @unmatchables = outs - matchables
35
53
 
36
54
  # No matchable can be matched by any TripleConstraint in expression
37
- matchables.each do |statement|
38
- expression.triple_constraints.each do |expr|
55
+ unmatched = matchables.select do |statement|
56
+ expression.triple_constraints.any? do |expr|
39
57
  begin
40
- status "check matchable #{statement.to_sxp} against #{expr.to_sxp}"
41
- if statement.predicate == expr.predicate && expr.matches([statement])
42
- not_satisfied "Unmatched statement: #{statement.to_sxp} matched #{expr.to_sxp}"
43
- end
44
- rescue NotMatched
45
- logger.recovering = false
46
- # Expected not to match
58
+ statement.predicate == expr.predicate && expr.matches([statement])
59
+ rescue ShEx::NotMatched
60
+ false # Expected not to match
47
61
  end
48
- end
49
- end if expression
50
-
51
- # There is no triple in `matchables` which matches a `TripleConstraint` in `expression`.
52
- # FIXME: Really run against every TripleConstraint?
53
-
54
- # Let `unmatchables` be the triples in `outs` which are not in `matchables`.
55
- unmatchables = outs - matchables
62
+ end if expression
63
+ end
64
+ unless unmatched.empty?
65
+ not_satisfied "Statements remain matching TripleConstraints",
66
+ matched: matched,
67
+ unmatched: unmatched,
68
+ satisfied: expression
69
+ end
56
70
 
57
71
  # There is no triple in matchables whose predicate does not appear in extra.
58
- matchables.each do |statement|
59
- not_satisfied "Statement remains with predicate #{statement.predicate} not in extra" unless extra.include?(statement.predicate)
72
+ unmatched = matchables.reject {|st| extra.include?(st.predicate)}
73
+ unless unmatched.empty?
74
+ not_satisfied "Statements remains with predicate #{unmatched.map(&:predicate).compact.join(',')} not in extra",
75
+ matched: matched,
76
+ unmatched: unmatched,
77
+ satisfied: expression
60
78
  end
61
79
 
62
80
  # closed is false or unmatchables is empty.
@@ -69,10 +87,10 @@ module ShEx::Algebra
69
87
  op.satisfies?(matched)
70
88
  end unless matched.empty?
71
89
 
72
- true
73
- rescue NotMatched => e
74
- logger.recovering = false
75
- not_satisfied e.message
90
+ # FIXME: also record matchables, outs and others?
91
+ satisfy matched: matched
92
+ rescue ShEx::NotMatched => e
93
+ not_satisfied e.message, unsatisfied: e.expression
76
94
  end
77
95
 
78
96
  ##
@@ -10,18 +10,17 @@ module ShEx::Algebra
10
10
  end
11
11
 
12
12
  ##
13
- # Satisfies method
14
- # @param [RDF::Resource] n
15
- # @return [Boolean] `true` if satisfied
16
- # @raise [ShEx::NotSatisfied] if not satisfied
13
+ # Satisfies referenced shape.
14
+ # @param (see Satisfiable#satisfies?)
15
+ # @return (see Satisfiable#satisfies?)
16
+ # @raise (see Satisfiable#satisfies?)
17
17
  # @see [https://shexspec.github.io/spec/#shape-expression-semantics]
18
- def satisfies?(n)
18
+ def satisfies?(focus)
19
19
  status "ref #{operands.first.to_s}"
20
- referenced_shape.satisfies?(n)
21
- status "ref satisfied"
22
- true
20
+ matched_shape = referenced_shape.satisfies?(focus)
21
+ satisfy satisfied: matched_shape
23
22
  rescue ShEx::NotSatisfied => e
24
- not_satisfied e.message
23
+ not_satisfied e.message, unsatisfied: e.expression
25
24
  raise
26
25
  end
27
26
 
@@ -1,19 +1,19 @@
1
1
  module ShEx::Algebra
2
2
  ##
3
3
  class Start < Operator::Unary
4
+ include Satisfiable
4
5
  NAME = :start
5
6
 
6
7
  #
7
- # @param [RDF::Resource] n
8
- # @return [Boolean] `true` if satisfied
9
- # @raise [ShEx::NotSatisfied] if not satisfied
10
- def satisfies?(n)
8
+ # @param (see Satisfiable#satisfies?)
9
+ # @return (see Satisfiable#satisfies?)
10
+ # @raise (see Satisfiable#satisfies?)
11
+ def satisfies?(focus)
11
12
  status ""
12
- operands.first.satisfies?(n)
13
- status("satisfied")
14
- true
13
+ matched_op = operands.first.satisfies?(focus)
14
+ satisfy satisfied: matched_op
15
15
  rescue ShEx::NotSatisfied => e
16
- not_satisfied e.message
16
+ not_satisfied e.message, unsatisfied: e.expression
17
17
  raise
18
18
  end
19
19
  end
@@ -7,46 +7,52 @@ module ShEx::Algebra
7
7
  ##
8
8
  # In this case, we accept an array of statements, and match based on cardinality.
9
9
  #
10
- # @param [Array<RDF::Statement>] t
11
- # @return [Array<RDF::Statement>]
12
- # @raise NotMatched, ShEx::NotSatisfied
13
- def matches(t)
10
+ # @param (see TripleExpression#matches)
11
+ # @return (see TripleExpression#matches)
12
+ # @raise (see TripleExpression#matches)
13
+ def matches(statements)
14
14
  status "predicate #{predicate}"
15
- max = maximum
16
- results = t.select do |statement|
17
- if max > 0
18
- value = inverse? ? statement.subject : statement.object
15
+ results, satisfied, unsatisfied = [], [], []
16
+ num_iters, max = 0, maximum
19
17
 
20
- if statement.predicate == predicate && shape_expr_satisfies?(shape, value)
21
- status "matched #{statement.to_sxp}"
22
- max -= 1
23
- else
24
- status "no match #{statement.to_sxp}"
25
- false
18
+ statements.select {|st| st.predicate == predicate}.each do |statement|
19
+ break if num_iters == max # matched enough
20
+
21
+ value = inverse? ? statement.subject : statement.object
22
+
23
+ begin
24
+ shape && (matched_shape = shape.satisfies?(value))
25
+ status "matched #{statement.to_sxp}"
26
+ if matched_shape
27
+ matched_shape.matched = [statement]
28
+ statement = statement.dup.extend(ReferencedStatement)
29
+ statement.referenced = matched_shape
30
+ satisfied << matched_shape
26
31
  end
27
- else
28
- false # matched enough
32
+ results << statement
33
+ num_iters += 1
34
+ rescue ShEx::NotSatisfied => e
35
+ status "not satisfied: #{e.message}"
36
+ statement = statement.dup.extend(ReferencedStatement)
37
+ unmatched << statement.referenced = shape
29
38
  end
30
39
  end
31
40
 
32
41
  # Max violations handled in Shape
33
- not_matched "Minimum Cardinality Violation: #{results.length} < #{minimum}" if
34
- results.length < minimum
42
+ if results.length < minimum
43
+ raise ShEx::NotMatched, "Minimum Cardinality Violation: #{results.length} < #{minimum}"
44
+ end
35
45
 
36
46
  # Last, evaluate semantic acts
37
47
  semantic_actions.all? do |op|
38
48
  op.satisfies?(results)
39
49
  end unless results.empty?
40
50
 
41
- results
42
- end
43
-
44
- def shape_expr_satisfies?(shape, value)
45
- shape.nil? || shape.satisfies?(value)
46
- rescue ShEx::NotSatisfied => e
47
- status "ignore error: #{e.message}"
48
- logger.recovering = false
49
- false
51
+ satisfy matched: results, satisfied: satisfied, unsatisfied: unsatisfied
52
+ rescue ShEx::NotMatched, ShEx::NotSatisfied => e
53
+ not_matched e.message,
54
+ matched: results, unmatched: (statements - results),
55
+ satisfied: satisfied, unsatisfied: unsatisfied
50
56
  end
51
57
 
52
58
  def predicate
@@ -1,19 +1,17 @@
1
1
  require 'sparql/algebra'
2
- require 'sparql/extensions'
3
2
 
4
3
  module ShEx::Algebra
5
4
  # Implements `neigh`, `arcs_out`, `args_in` and `matches`
6
5
  module TripleExpression
7
-
8
6
  ##
9
7
  # `matches`: asserts that a triple expression is matched by a set of triples that come from the neighbourhood of a node in an RDF graph. The expression `matches(T, expr, m)` indicates that a set of triples `T` can satisfy these rules...
10
8
  #
11
9
  # Behavior should be overridden in subclasses, which end by calling this through `super`.
12
10
  #
13
- # @param [Array<RDF::Statement>] t
14
- # @return [Array<RDF::Statement>]
15
- # @raise NotMatched, ShEx::NotSatisfied
16
- def matches(t)
11
+ # @param [Array<RDF::Statement>] statements
12
+ # @return [TripleExpression] with `matched` accessor for matched triples
13
+ # @raise [ShEx::NotMatched] with `expression` accessor to access `matched` and `unmatched` statements along with `satisfied` and `unsatisfied` operations.
14
+ def matches(statements)
17
15
  raise NotImplementedError, "#matches Not implemented in #{self.class}"
18
16
  end
19
17
 
@@ -21,26 +19,44 @@ module ShEx::Algebra
21
19
  # Included TripleConstraints
22
20
  # @return [Array<TripleConstraints>]
23
21
  def triple_constraints
24
- operands.select {|o| o.is_a?(TripleExpression)}.map(&:triple_constraints).flatten.uniq
22
+ @triple_contraints ||= operands.select do |o|
23
+ o.is_a?(TripleExpression)
24
+ end.
25
+ map(&:triple_constraints).
26
+ flatten.
27
+ uniq
25
28
  end
26
29
 
27
30
  ##
28
31
  # Minimum constraint (defaults to 1)
29
32
  # @return [Integer]
30
33
  def minimum
31
- op = operands.detect {|o| o.is_a?(Array) && o.first == :min} || [:min, 1]
32
- op[1]
34
+ @minimum ||= begin
35
+ op = operands.detect {|o| o.is_a?(Array) && o.first == :min} || [:min, 1]
36
+ op[1]
37
+ end
33
38
  end
34
39
 
35
40
  ##
36
41
  # Maximum constraint (defaults to 1)
37
42
  # @return [Integer, Float::INFINITY]
38
43
  def maximum
39
- op = operands.detect {|o| o.is_a?(Array) && o.first == :max} || [:max, 1]
40
- op[1] == '*' ? Float::INFINITY : op[1]
44
+ @maximum ||= begin
45
+ op = operands.detect {|o| o.is_a?(Array) && o.first == :max} || [:max, 1]
46
+ op[1] == '*' ? Float::INFINITY : op[1]
47
+ end
41
48
  end
42
49
 
43
50
  # This operator includes TripleExpression
44
51
  def triple_expression?; true; end
45
52
  end
53
+
54
+ module ReferencedStatement
55
+ # @return [ShEx::Algebra::Satisfiable] referenced operand which satisfied some of this statement
56
+ attr_accessor :referenced
57
+
58
+ def to_sxp_bin
59
+ referenced ? super + [referenced.to_sxp_bin] : super
60
+ end
61
+ end
46
62
  end
@@ -12,6 +12,7 @@ module ShEx
12
12
  include ShEx::Meta
13
13
  include ShEx::Terminals
14
14
  include EBNF::LL1::Parser
15
+ include RDF::Util::Logger
15
16
 
16
17
  ##
17
18
  # Any additional options for the parser.
@@ -568,7 +569,7 @@ module ShEx
568
569
  # @option options [#to_s] :anon_base ("b0")
569
570
  # Basis for generating anonymous Nodes
570
571
  # @option options [Boolean] :resolve_iris (false)
571
- # Resolve prefix and relative IRIs, otherwise, when serializing the parsed SSE
572
+ # Resolve prefix and relative IRIs, otherwise, when serializing the parsed SXP
572
573
  # as S-Expressions, use the original prefixed and relative URIs along with `base` and `prefix`
573
574
  # definitions.
574
575
  # @option options [Boolean] :validate (false)
@@ -591,10 +592,6 @@ module ShEx
591
592
  end
592
593
  @input.encode!(Encoding::UTF_8) if @input.respond_to?(:encode!)
593
594
  @options = {anon_base: "b0", validate: false}.merge(options)
594
- @options[:debug] ||= case
595
- when options[:progress] then 2
596
- when options[:validate] then 1
597
- end
598
595
 
599
596
  debug("base IRI") {base_uri.inspect}
600
597
  debug("validate") {validate?.inspect}
@@ -630,7 +627,9 @@ module ShEx
630
627
  #
631
628
  # @param [Symbol, #to_s] prod The starting production for the parser.
632
629
  # It may be a URI from the grammar, or a symbol representing the local_name portion of the grammar URI.
633
- # @return [Array]
630
+ # @return [ShEx::Algebra::Schema] The executable parsed expression.
631
+ # @raise [ShEx::ParseError] when a syntax error is detected
632
+ # @raise [ShEx::StructureError, ArgumentError] on structural problems with schema
634
633
  # @see http://www.w3.org/TR/sparql11-query/#sparqlAlgebra
635
634
  # @see http://axel.deri.ie/sparqltutorial/ESWC2007_SPARQL_Tutorial_unit2b.pdf
636
635
  def parse(prod = START)
@@ -641,17 +640,18 @@ module ShEx
641
640
  ) do |context, *data|
642
641
  case context
643
642
  when :trace
644
- level, lineno, depth, *args = data
645
- message = args.to_sse
646
- d_str = depth > 100 ? ' ' * 100 + '+' : ' ' * depth
647
- str = "[#{lineno}](#{level})#{d_str}#{message}".chop
648
- case @options[:debug]
649
- when Array
650
- @options[:debug] << str unless level > 2
651
- when TrueClass
652
- $stderr.puts str
653
- when Integer
654
- $stderr.puts(str) if level <= @options[:debug]
643
+ if options[:logger]
644
+ level, lineno, depth, *args = data
645
+ case level
646
+ when 0
647
+ log_error(*args, depth: depth, lineno: lineno)
648
+ when 1
649
+ log_warning(*args, depth: depth, lineno: lineno)
650
+ when 2
651
+ log_info(*args, depth: depth, lineno: lineno)
652
+ else
653
+ log_debug(*args, depth: depth, lineno: lineno)
654
+ end
655
655
  end
656
656
  end
657
657
  end
@@ -735,7 +735,7 @@ module ShEx
735
735
  #
736
736
  # @return [HRDF::URI]
737
737
  def base_uri
738
- RDF::URI(@options[:base_uri])
738
+ RDF::URI(@options[:base_uri]) if @options[:base_uri]
739
739
  end
740
740
 
741
741
  ##
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregg Kellogg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-11 00:00:00.000000000 Z
11
+ date: 2016-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdf