ruby-rdf / sparql

Ruby SPARQL library
http://rubygems.org/gems/sparql
The Unlicense
88 stars 14 forks source link

Implement basic AST-walking operators #23

Closed artob closed 8 years ago

artob commented 8 years ago

Quite often, one needs to process or transform the result of SPARQL.parse() in some way, which typically involves locating a child node of a particular kind. Here follows a simple proposal for methods to aid in this:

module SPARQL::Algebra
  class Operator
    ##
    # @param  [Class] sought_class
    # @return [Operator]
    def find_node_by_class(sought_class)
      return self if sought_class === self
      self.operands.find do |operand|
        operand.find_node_by_class(sought_class)
      end
    end

    ##
    # @param  [Symbol] sought_name
    # @return [Operator]
    def find_node_by_name(sought_name)
      return self if [self.class.const_get(:NAME)].flatten.include?(sought_name)
      self.operands.find do |operand|
        operand.find_node_by_name(sought_name)
      end
    end

    ##
    # @param  [Symbol, Class] sought_child
    # @return [Operator]
    def find_node(sought_child)
      case sought_child
        when Symbol then self.find_node_by_name(sought_child)
        when Class  then self.find_node_by_class(sought_child)
        else raise ArgumentError, "expected Symbol or Class, but got #{sought_child.inspect}"
      end
    end
  end
end # SPARQL::Algebra

I've opened this as a ticket, instead of directly committing it, in order to check with @gkellogg regarding naming and/or duplication of functionality with whatever facilities may already exist to do this:

ast = SPARQL.parse("SELECT * WHERE {?s ?p ?o FILTER(?o >= 123)}")
ast.find_node(SPARQL::Algebra::Operator::GreaterThanOrEqual)
ast.find_node(:>=)

CC: @helios

gkellogg commented 8 years ago

This will just find the first such node, rather than all matching nodes. It could be considered analogous to Enumerable#first_object, which is a special case returning the first of all such objects. A pattern such as:

ast.each_descendant {|node| node.is_a?(klass)}

The first-case could be implemented as

ast.each_descendant.detect {|node| node.is_a?(klass)}

Then #each_descendant simply recursively enumerates each operand. #find_node could be sugar for this. WDYT?

rjpbonnal commented 8 years ago

The ast.each_descendant {|node| node.is_a?(klass)} seems to me more neat. And yes then a bit of sugar to make it intuitive.

artob commented 8 years ago

OK, let's see if we can manage with SPARQL::Algebra::Operator#each_descendant for now. We can add sugar to the gem if it turns out to be needed.

gkellogg commented 8 years ago

So, this is implemented as SPARQL::Algebra::Operator#descendants, which does what I described for #each_descendant; I renamed it #each_descendant and aliased as #each and #each_descendant. I'm not able to make Operator and Enumerable due to dependencies in the Parser (Array(sxp) does the wrong thing).

Also, it does not descend into RDF::Query::Patterns, so you need to do something like the following:

ast.each_descendant.select {|op| op.is_a?(RDF::Query)}.map(&:patterns).flatten
rjpbonnal commented 8 years ago

Sounds reasonable, let see how far we can go with them.

On Sat, Sep 19, 2015 at 7:49 AM, Gregg Kellogg notifications@github.com wrote:

So, this is implemented as SPARQL::Algebra::Operator#descendants, which does what I described for #each_descendant; I renamed it #each_descendant and aliased as #each and #each_descendant. I'm not able to make Operator and Enumerable due to dependencies in the Parser (Array(sxp) does the wrong thing).

Also, it does not descend into RDF::Query::Patterns, so you need to do something like the following:

ast.each_descendant.select {|op| op.is_a?(RDF::Query)}.map(&:patterns).flatten

— Reply to this email directly or view it on GitHub https://github.com/ruby-rdf/sparql/issues/23#issuecomment-141587938.

Ra

gkellogg commented 8 years ago

Released in 1.1.9.