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
         
     |