shex 0.2.0 → 0.3.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 +4 -4
- data/README.md +119 -2
- data/VERSION +1 -1
- data/etc/doap.ttl +2 -2
- data/lib/shex.rb +21 -2
- data/lib/shex/algebra.rb +41 -3
- data/lib/shex/algebra/and.rb +27 -6
- data/lib/shex/algebra/annotation.rb +19 -0
- data/lib/shex/algebra/each_of.rb +32 -19
- data/lib/shex/algebra/external.rb +9 -6
- data/lib/shex/algebra/inclusion.rb +29 -18
- data/lib/shex/algebra/node_constraint.rb +45 -36
- data/lib/shex/algebra/not.rb +19 -4
- data/lib/shex/algebra/one_of.rb +26 -16
- data/lib/shex/algebra/operator.rb +350 -34
- data/lib/shex/algebra/or.rb +26 -9
- data/lib/shex/algebra/satisfiable.rb +5 -9
- data/lib/shex/algebra/schema.rb +87 -75
- data/lib/shex/algebra/semact.rb +69 -19
- data/lib/shex/algebra/shape.rb +28 -19
- data/lib/shex/algebra/shape_ref.rb +36 -10
- data/lib/shex/algebra/start.rb +5 -5
- data/lib/shex/algebra/stem.rb +18 -3
- data/lib/shex/algebra/stem_range.rb +24 -5
- data/lib/shex/algebra/triple_constraint.rb +26 -13
- data/lib/shex/algebra/triple_expression.rb +3 -2
- data/lib/shex/algebra/value.rb +5 -5
- data/lib/shex/extensions/extension.rb +160 -0
- data/lib/shex/extensions/test.rb +26 -0
- data/lib/shex/parser.rb +12 -25
- data/lib/shex/shex_context.rb +85 -0
- data/lib/shex/version.rb +19 -0
- metadata +35 -11
- data/lib/shex/algebra/base.rb +0 -6
- data/lib/shex/algebra/prefix.rb +0 -6
- data/lib/shex/algebra/unary_shape.rb +0 -6
data/lib/shex/algebra/or.rb
CHANGED
@@ -6,9 +6,22 @@ module ShEx::Algebra
|
|
6
6
|
|
7
7
|
def initialize(*args, **options)
|
8
8
|
case
|
9
|
-
when args.length
|
10
|
-
raise ArgumentError, "wrong number of arguments (given #{args.length}, expected
|
9
|
+
when args.length < 2
|
10
|
+
raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 2..)"
|
11
11
|
end
|
12
|
+
|
13
|
+
# All arguments must be Satisfiable
|
14
|
+
raise ArgumentError, "All operands must be Shape operands" unless args.all? {|o| o.is_a?(Satisfiable)}
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Creates an operator instance from a parsed ShExJ representation
|
20
|
+
# @param (see Operator#from_shexj)
|
21
|
+
# @return [Operator]
|
22
|
+
def self.from_shexj(operator, options = {})
|
23
|
+
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == 'ShapeOr'
|
24
|
+
raise ArgumentError, "missing shapeExprs in #{operator.inspect}" unless operator.is_a?(Hash) && operator.has_key?('shapeExprs')
|
12
25
|
super
|
13
26
|
end
|
14
27
|
|
@@ -17,27 +30,31 @@ module ShEx::Algebra
|
|
17
30
|
# @param (see Satisfiable#satisfies?)
|
18
31
|
# @return (see Satisfiable#satisfies?)
|
19
32
|
# @raise (see Satisfiable#satisfies?)
|
20
|
-
def satisfies?(focus)
|
21
|
-
status ""
|
33
|
+
def satisfies?(focus, depth: 0)
|
34
|
+
status "", depth: depth
|
22
35
|
expressions = operands.select {|o| o.is_a?(Satisfiable)}
|
23
36
|
unsatisfied = []
|
24
37
|
expressions.any? do |op|
|
25
38
|
begin
|
26
|
-
matched_op = op.satisfies?(focus)
|
27
|
-
return satisfy satisfied: matched_op,
|
39
|
+
matched_op = op.satisfies?(focus, depth: depth + 1)
|
40
|
+
return satisfy focus: focus, satisfied: matched_op, depth: depth
|
28
41
|
rescue ShEx::NotSatisfied => e
|
29
|
-
status "unsatisfied #{focus}"
|
42
|
+
status "unsatisfied #{focus}", depth: depth
|
30
43
|
op = op.dup
|
31
44
|
op.satisfied = e.expression.satisfied
|
32
45
|
op.unsatisfied = e.expression.unsatisfied
|
33
46
|
unsatisfied << op
|
34
|
-
status
|
47
|
+
status "unsatisfied: #{e.message}", depth: depth
|
35
48
|
false
|
36
49
|
end
|
37
50
|
end
|
38
51
|
|
39
52
|
not_satisfied "Expected some expression to be satisfied",
|
40
|
-
unsatisfied: unsatisfied
|
53
|
+
focus: focus, unsatisfied: unsatisfied, depth: depth
|
54
|
+
end
|
55
|
+
|
56
|
+
def json_type
|
57
|
+
"ShapeOr"
|
41
58
|
end
|
42
59
|
end
|
43
60
|
end
|
@@ -6,20 +6,16 @@ module ShEx::Algebra
|
|
6
6
|
##
|
7
7
|
# Satisfies method
|
8
8
|
# @param [RDF::Resource] focus
|
9
|
-
# @
|
9
|
+
# @param [Integer] depth for logging
|
10
|
+
# @param [Hash{Symbol => Object}] options
|
11
|
+
# Other, operand-specific options
|
12
|
+
# @return [Operator] with `matched` and `satisfied` accessors for matched triples and sub-expressions
|
10
13
|
# @raise [ShEx::NotMatched] with `expression` accessor to access `matched` and `unmatched` statements along with `satisfied` and `unsatisfied` operations.
|
11
14
|
# @see [https://shexspec.github.io/spec/#shape-expression-semantics]
|
12
|
-
def satisfies?(focus)
|
15
|
+
def satisfies?(focus, depth: 0, **options)
|
13
16
|
raise NotImplementedError, "#satisfies? Not implemented in #{self.class}"
|
14
17
|
end
|
15
18
|
|
16
|
-
##
|
17
|
-
# Included TripleExpressions
|
18
|
-
# @return [Array<TripleExpressions>]
|
19
|
-
def triple_expressions
|
20
|
-
operands.select {|o| o.is_a?(Satisfiable)}.map(&:triple_expressions).flatten.uniq
|
21
|
-
end
|
22
|
-
|
23
19
|
# This operator includes Satisfiable
|
24
20
|
def satisfiable?; true; end
|
25
21
|
end
|
data/lib/shex/algebra/schema.rb
CHANGED
@@ -11,55 +11,95 @@ module ShEx::Algebra
|
|
11
11
|
# @return [Hash{RDF::Resource => RDF::Resource}]
|
12
12
|
attr_reader :map
|
13
13
|
|
14
|
+
# Map of Semantic Action instances
|
15
|
+
# @return [Hash{String => ShEx::Extension}]
|
16
|
+
attr_reader :extensions
|
17
|
+
|
18
|
+
##
|
19
|
+
# Creates an operator instance from a parsed ShExJ representation
|
20
|
+
# @param (see Operator#from_shexj)
|
21
|
+
# @return [Operator]
|
22
|
+
def self.from_shexj(operator, options = {})
|
23
|
+
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == "Schema"
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
# (see Operator#initialize)
|
28
|
+
def initialize(*operands)
|
29
|
+
super
|
30
|
+
each_descendant do |op|
|
31
|
+
# Set schema everywhere
|
32
|
+
op.schema = self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
14
36
|
##
|
15
37
|
# Match on schema. Finds appropriate shape for node, and matches that shape.
|
16
38
|
#
|
17
|
-
# @param [RDF::
|
39
|
+
# @param [RDF::Term] focus
|
18
40
|
# @param [RDF::Queryable] graph
|
19
41
|
# @param [Hash{RDF::Resource => RDF::Resource}] map
|
20
42
|
# @param [Array<Schema, String>] shapeExterns ([])
|
21
43
|
# One or more schemas, or paths to ShEx schema resources used for finding external shapes.
|
22
44
|
# @return [Operand] Returns operand graph annotated with satisfied and unsatisfied operations.
|
45
|
+
# @param [Hash{Symbol => Object}] options
|
46
|
+
# @option options [String] :base_uri
|
23
47
|
# @raise [ShEx::NotSatisfied] along with operand graph described for return
|
24
|
-
def execute(focus, graph, map, shapeExterns: [], **options)
|
25
|
-
@graph = graph
|
48
|
+
def execute(focus, graph, map, shapeExterns: [], depth: 0, **options)
|
49
|
+
@graph, @shapes_entered = graph, {}
|
26
50
|
@external_schemas = shapeExterns
|
27
|
-
focus =
|
51
|
+
focus = value(focus)
|
52
|
+
|
53
|
+
logger = options[:logger] || @options[:logger]
|
54
|
+
each_descendant do |op|
|
55
|
+
# Set logging everywhere
|
56
|
+
op.logger = logger
|
57
|
+
end
|
58
|
+
|
59
|
+
# Initialize Extensions
|
60
|
+
@extensions = {}
|
61
|
+
each_descendant do |op|
|
62
|
+
next unless op.is_a?(SemAct)
|
63
|
+
name = op.operands.first.to_s
|
64
|
+
if ext_class = ShEx::Extension.find(name)
|
65
|
+
@extensions[name] ||= ext_class.new(schema: self, depth: depth, **options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# 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
|
70
|
+
graph_focus = graph.enum_term.detect {|t| t.node? && t.id == focus.id} if focus.is_a?(RDF::Node)
|
71
|
+
graph_focus ||= focus
|
72
|
+
|
28
73
|
# Make sure they're URIs
|
29
|
-
@map = (map || {}).inject({}) {|memo, (k,v)| memo.merge(
|
74
|
+
@map = (map || {}).inject({}) {|memo, (k,v)| memo.merge(value(k) => iri(v))}
|
30
75
|
|
31
76
|
# First, evaluate semantic acts
|
32
77
|
semantic_actions.all? do |op|
|
33
|
-
op.satisfies?([])
|
78
|
+
op.satisfies?([], depth: depth + 1)
|
34
79
|
end
|
35
80
|
|
36
81
|
# Keep a new Schema, specifically for recording actions
|
37
82
|
satisfied_schema = Schema.new
|
38
83
|
# Next run any start expression
|
39
84
|
if start
|
40
|
-
|
41
|
-
satisfied_schema.operands << start.satisfies?(focus)
|
85
|
+
satisfied_schema.operands << start.satisfies?(focus, depth: depth + 1)
|
42
86
|
end
|
43
87
|
|
44
88
|
# Add shape result(s)
|
45
89
|
satisfied_shapes = {}
|
46
90
|
satisfied_schema.operands << [:shapes, satisfied_shapes] unless shapes.empty?
|
47
91
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
54
|
-
if focus.is_a?(RDF::Node)
|
55
|
-
n = graph.enum_term.detect {|t| t.id == focus.id}
|
56
|
-
focus = n if n
|
92
|
+
# Match against all shapes associated with the labels for focus
|
93
|
+
Array(@map[focus]).each do |label|
|
94
|
+
enter_shape(label, focus) do |shape|
|
95
|
+
satisfied_shapes[label] = shape.satisfies?(graph_focus, depth: depth + 1)
|
57
96
|
end
|
58
|
-
|
59
|
-
satisfied_shapes[label] = shape.satisfies?(focus)
|
60
97
|
end
|
61
|
-
status "schema satisfied"
|
98
|
+
status "schema satisfied", depth: depth
|
62
99
|
satisfied_schema
|
100
|
+
ensure
|
101
|
+
# Close Semantic Action extensions
|
102
|
+
@extensions.values.each {|ext| ext.close(schema: self, depth: depth, **options)}
|
63
103
|
end
|
64
104
|
|
65
105
|
##
|
@@ -81,17 +121,37 @@ module ShEx::Algebra
|
|
81
121
|
|
82
122
|
##
|
83
123
|
# Shapes as a hash
|
84
|
-
# @return [
|
124
|
+
# @return [Array<Operator>]
|
85
125
|
def shapes
|
86
126
|
@shapes ||= begin
|
87
|
-
shapes = operands.detect {|op| op.is_a?(Array) && op.first == :shapes}
|
88
|
-
shapes
|
89
|
-
shapes.inject({}) do |memo, (label, operand)|
|
90
|
-
memo.merge(label.to_s => operand)
|
91
|
-
end
|
127
|
+
shapes = Array(operands.detect {|op| op.is_a?(Array) && op.first == :shapes})
|
128
|
+
Array(shapes[1..-1])
|
92
129
|
end
|
93
130
|
end
|
94
131
|
|
132
|
+
##
|
133
|
+
# Indicate that a shape has been entered with a specific focus node. Any future attempt to enter the same shape with the same node raises an exception.
|
134
|
+
# @param [RDF::Resource] label
|
135
|
+
# @param [RDF::Resource] node
|
136
|
+
# @yield :shape
|
137
|
+
# @yieldparam [Satisfiable] shape, or `nil` if shape already entered
|
138
|
+
# @return [Satisfiable]
|
139
|
+
def enter_shape(label, node, &block)
|
140
|
+
shape = shapes.detect {|s| s.label == label}
|
141
|
+
structure_error("No shape found for #{label}") unless shape
|
142
|
+
@shapes_entered[label] ||= {}
|
143
|
+
if @shapes_entered[label][node]
|
144
|
+
block.call(false)
|
145
|
+
else
|
146
|
+
@shapes_entered[label][node] = self
|
147
|
+
begin
|
148
|
+
block.call(shape)
|
149
|
+
ensure
|
150
|
+
@shapes_entered[label].delete(node)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
95
155
|
##
|
96
156
|
# Externally loaded schemas, lazily evaluated
|
97
157
|
# @return [Array<Schema>]
|
@@ -108,54 +168,6 @@ module ShEx::Algebra
|
|
108
168
|
end
|
109
169
|
end
|
110
170
|
|
111
|
-
##
|
112
|
-
# Enumerate via depth-first recursive descent over operands, yielding each operator
|
113
|
-
# @yield operator
|
114
|
-
# @yieldparam [Object] operator
|
115
|
-
# @return [Enumerator]
|
116
|
-
def each_descendant(depth = 0, &block)
|
117
|
-
if block_given?
|
118
|
-
super(depth + 1, &block)
|
119
|
-
shapes.values.each do |op|
|
120
|
-
op.each_descendant(depth + 1, &block) if op.respond_to?(:each_descendant)
|
121
|
-
|
122
|
-
case block.arity
|
123
|
-
when 1 then block.call(op)
|
124
|
-
else block.call(depth, op)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
enum_for(:each_descendant)
|
129
|
-
end
|
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
|
-
|
159
171
|
##
|
160
172
|
# Start action, if any
|
161
173
|
def start
|
@@ -167,7 +179,7 @@ module ShEx::Algebra
|
|
167
179
|
# @return [SPARQL::Algebra::Expression] `self`
|
168
180
|
# @raise [ArgumentError] if the value is invalid
|
169
181
|
def validate!
|
170
|
-
shapes.
|
182
|
+
shapes.each {|op| op.validate! if op.respond_to?(:validate!)}
|
171
183
|
super
|
172
184
|
end
|
173
185
|
end
|
data/lib/shex/algebra/semact.rb
CHANGED
@@ -3,35 +3,85 @@ module ShEx::Algebra
|
|
3
3
|
class SemAct < Operator
|
4
4
|
NAME = :semact
|
5
5
|
|
6
|
+
##
|
7
|
+
# Creates an operator instance from a parsed ShExJ representation
|
8
|
+
# @param (see Operator#from_shexj)
|
9
|
+
# @return [Operator]
|
10
|
+
def self.from_shexj(operator, options = {})
|
11
|
+
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == "SemAct"
|
12
|
+
raise ArgumentError, "missing name in #{operator.inspect}" unless operator.has_key?('name')
|
13
|
+
code = operator.delete('code')
|
14
|
+
operator['code'] = code if code # Reorders operands appropriately
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Called on entry
|
20
|
+
#
|
21
|
+
# @param [String] code
|
22
|
+
# @param [Array<RDF::Statement>] arcs_in available statements to be matched having `focus` as an object
|
23
|
+
# @param [Array<RDF::Statement>] arcs_out available statements to be matched having `focus` as a subject
|
24
|
+
# @param [Integer] depth for logging
|
25
|
+
# @param [Hash{Symbol => Object}] options
|
26
|
+
# Other, operand-specific options
|
27
|
+
# @return [Boolean] Returning `false` results in {ShEx::NotSatisfied} exception
|
28
|
+
def enter(**options)
|
29
|
+
if implementation = schema.extensions[operands.first.to_s]
|
30
|
+
implementation.enter(code: operands[0], expression: parent, **options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
6
34
|
#
|
7
35
|
# The evaluation semActsSatisfied on a list of SemActs returns success or failure. The evaluation of an individual SemAct is implementation-dependent.
|
8
|
-
#
|
36
|
+
#
|
37
|
+
# In addition to standard arguments `satsisfies` arguments, the current `matched` and `unmatched` statements may be passed. Additionally, all sub-classes of `Operator` have available `parent`, and `schema` accessors, which allows access to the operands of the parent, for example.
|
38
|
+
#
|
39
|
+
# @param [Object] focus (ignored)
|
40
|
+
# @param [Array<RDF::Statement>] matched matched statements
|
41
|
+
# @param [Array<RDF::Statement>] unmatched unmatched statements
|
9
42
|
# @return [Boolean] `true` if satisfied, `false` if it does not apply
|
10
43
|
# @raise [ShEx::NotSatisfied] if not satisfied
|
11
|
-
def satisfies?(
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
when 'p' then statements.first.predicate
|
19
|
-
when 'o' then statements.first.object
|
20
|
-
else statements.first.to_sxp
|
21
|
-
end.to_s
|
22
|
-
else
|
23
|
-
statements.empty? ? 'no statement' : statements.first.to_sxp
|
44
|
+
def satisfies?(focus, matched: [], unmatched: [], depth: 0)
|
45
|
+
if implementation = schema.extensions[operands.first.to_s]
|
46
|
+
if matched.empty?
|
47
|
+
implementation.visit(code: operands[1],
|
48
|
+
expression: parent,
|
49
|
+
depth: depth) ||
|
50
|
+
not_satisfied("SemAct failed", unmatched: unmatched)
|
24
51
|
end
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
52
|
+
matched.all? do |statement|
|
53
|
+
implementation.visit(code: operands[1],
|
54
|
+
matched: statement,
|
55
|
+
expression: parent,
|
56
|
+
depth: depth)
|
57
|
+
end || not_satisfied("SemAct failed", matched: matched, unmatched: unmatched)
|
29
58
|
else
|
30
|
-
status("unknown SemAct name #{operands.first}") {"expression: #{self.to_sxp}"}
|
59
|
+
status("unknown SemAct name #{operands.first}", depth: depth) {"expression: #{self.to_sxp}"}
|
31
60
|
false
|
32
61
|
end
|
33
62
|
end
|
34
63
|
|
64
|
+
##
|
65
|
+
# Called on exit from containing {ShEx::TripleExpression}
|
66
|
+
#
|
67
|
+
# @param [String] code
|
68
|
+
# @param [Array<RDF::Statement>] matched statements matched by this expression
|
69
|
+
# @param [Array<RDF::Statement>] unmatched statements considered, but not matched by this expression
|
70
|
+
# @param [ShEx::Algebra::TripleExpression] expression containing this semantic act
|
71
|
+
# @param [Integer] depth for logging
|
72
|
+
# @param [Hash{Symbol => Object}] options
|
73
|
+
# Other, operand-specific options
|
74
|
+
# @return [void]
|
75
|
+
def exit(code: nil, matched: [], unmatched: [], depth: 0, **options)
|
76
|
+
if implementation = schema.extensions[operands.first.to_s]
|
77
|
+
implementation.exit(code: operands[1],
|
78
|
+
matched: matched,
|
79
|
+
unmatched: unmatched,
|
80
|
+
expresssion: parent,
|
81
|
+
depth: depth)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
35
85
|
# Does This operator is SemAct
|
36
86
|
def semact?; true; end
|
37
87
|
end
|
data/lib/shex/algebra/shape.rb
CHANGED
@@ -19,13 +19,21 @@ module ShEx::Algebra
|
|
19
19
|
# @return [Array<RDF::Statement>]
|
20
20
|
attr_accessor :unmatchables
|
21
21
|
|
22
|
+
##
|
23
|
+
# Creates an operator instance from a parsed ShExJ representation
|
24
|
+
# @param (see Operator#from_shexj)
|
25
|
+
# @return [Operator]
|
26
|
+
def self.from_shexj(operator, options = {})
|
27
|
+
raise ArgumentError unless operator.is_a?(Hash) && operator['type'] == "Shape"
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
22
31
|
# 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)`.
|
23
32
|
# @param (see Satisfiable#satisfies?)
|
24
33
|
# @return (see Satisfiable#satisfies?)
|
25
34
|
# @raise (see Satisfiable#satisfies?)
|
26
|
-
def satisfies?(focus)
|
27
|
-
expression =
|
28
|
-
|
35
|
+
def satisfies?(focus, depth: 0)
|
36
|
+
expression = self.expression
|
29
37
|
# neigh(G, n) is the neighbourhood of the node n in the graph G.
|
30
38
|
#
|
31
39
|
# neigh(G, n) = arcsOut(G, n) ∪ arcsIn(G, n)
|
@@ -34,8 +42,8 @@ module ShEx::Algebra
|
|
34
42
|
neigh = (arcs_in + arcs_out).uniq
|
35
43
|
|
36
44
|
# `matched` is the subset of statements which match `expression`.
|
37
|
-
status("arcsIn: #{arcs_in.count}, arcsOut: #{arcs_out.count}")
|
38
|
-
matched_expression = expression.matches(
|
45
|
+
status("arcsIn: #{arcs_in.count}, arcsOut: #{arcs_out.count}", depth: depth)
|
46
|
+
matched_expression = expression.matches(arcs_in, arcs_out, depth: depth + 1) if expression
|
39
47
|
matched = Array(matched_expression && matched_expression.matched)
|
40
48
|
|
41
49
|
# `remainder` is the set of unmatched statements
|
@@ -55,7 +63,7 @@ module ShEx::Algebra
|
|
55
63
|
unmatched = matchables.select do |statement|
|
56
64
|
expression.triple_constraints.any? do |expr|
|
57
65
|
begin
|
58
|
-
statement.predicate == expr.predicate && expr.matches([statement])
|
66
|
+
statement.predicate == expr.predicate && expr.matches([], [statement], depth: depth + 1)
|
59
67
|
rescue ShEx::NotMatched
|
60
68
|
false # Expected not to match
|
61
69
|
end
|
@@ -65,7 +73,8 @@ module ShEx::Algebra
|
|
65
73
|
not_satisfied "Statements remain matching TripleConstraints",
|
66
74
|
matched: matched,
|
67
75
|
unmatched: unmatched,
|
68
|
-
satisfied: expression
|
76
|
+
satisfied: expression,
|
77
|
+
depth: depth
|
69
78
|
end
|
70
79
|
|
71
80
|
# There is no triple in matchables whose predicate does not appear in extra.
|
@@ -74,30 +83,30 @@ module ShEx::Algebra
|
|
74
83
|
not_satisfied "Statements remains with predicate #{unmatched.map(&:predicate).compact.join(',')} not in extra",
|
75
84
|
matched: matched,
|
76
85
|
unmatched: unmatched,
|
77
|
-
satisfied: expression
|
86
|
+
satisfied: expression,
|
87
|
+
depth: depth
|
78
88
|
end
|
79
89
|
|
80
90
|
# closed is false or unmatchables is empty.
|
81
|
-
not_satisfied "Unmatchables remain on a closed shape" unless !closed? || unmatchables.empty?
|
91
|
+
not_satisfied "Unmatchables remain on a closed shape", depth: depth unless !closed? || unmatchables.empty?
|
82
92
|
|
83
93
|
# Presumably, to be satisfied, there must be some triples in matches
|
84
|
-
|
85
|
-
|
86
|
-
# FIXME: what triples to run against satisfies?
|
87
|
-
op.satisfies?(matched)
|
94
|
+
semantic_actions.each do |op|
|
95
|
+
op.satisfies?(matched, matched: matched, depth: depth + 1)
|
88
96
|
end unless matched.empty?
|
89
97
|
|
90
98
|
# FIXME: also record matchables, outs and others?
|
91
|
-
satisfy matched: matched
|
99
|
+
satisfy focus: focus, matched: matched, depth: depth
|
92
100
|
rescue ShEx::NotMatched => e
|
93
|
-
not_satisfied e.message, unsatisfied: e.expression
|
101
|
+
not_satisfied e.message, focus: focus, unsatisfied: e.expression, depth: depth
|
94
102
|
end
|
95
103
|
|
104
|
+
|
96
105
|
##
|
97
|
-
#
|
98
|
-
# @return [
|
99
|
-
def
|
100
|
-
operands.
|
106
|
+
# The optional TripleExpression for this Shape.
|
107
|
+
# @return [TripleExpression]
|
108
|
+
def expression
|
109
|
+
operands.detect {|op| op.is_a?(TripleExpression)}
|
101
110
|
end
|
102
111
|
|
103
112
|
private
|