module ScopedSearch::QueryLanguage::Parser

The Parser module adss methods to the query language compiler that transform a string into an abstract syntax tree, which can be used for query generation.

This module depends on the tokeinzer module to transform the string into a stream of tokens, which is more appropriate for parsing. The parser itself is a LL(1) recursive descent parser.

Constants

ALL_INFIX_OPERATORS
ALL_PREFIX_OPERATORS
COMPARISON_OPERATORS
DEFAULT_SEQUENCE_OPERATOR
LOGICAL_INFIX_OPERATORS
LOGICAL_PREFIX_OPERATORS
NULL_PREFIX_OPERATORS

Public Instance Methods

parse() click to toggle source

Start the parsing process by parsing an expression sequence

# File lib/scoped_search/query_language/parser.rb, line 19
def parse
  @tokens = tokenize
  while @tokens.last.is_a?(Symbol) do
    @tokens.delete_at(@tokens.size - 1)
  end
  parse_expression_sequence(true).simplify
end
parse_comparison() click to toggle source

Parses a comparison

# File lib/scoped_search/query_language/parser.rb, line 76
def parse_comparison
  next_token if peek_token == :comma # skip comma
  return (String === peek_token) ? parse_infix_comparison : parse_prefix_comparison
end
parse_expression_sequence(root_node = false) click to toggle source

Parses a sequence of expressions

# File lib/scoped_search/query_language/parser.rb, line 28
def parse_expression_sequence(root_node = false)
  expressions = []

  next_token if !root_node && peek_token == :lparen # skip starting :lparen
  expressions << parse_logical_expression until peek_token.nil? || peek_token == :rparen
  next_token if !root_node && peek_token == :rparen # skip final :rparen
  
  return ScopedSearch::QueryLanguage::AST::LogicalOperatorNode.new(DEFAULT_SEQUENCE_OPERATOR, expressions, root_node)
end
parse_infix_comparison() click to toggle source

Parses an infix expression, i.e. <field> <operator> <value>

# File lib/scoped_search/query_language/parser.rb, line 87
def parse_infix_comparison
  lhs = parse_value
  return case peek_token
    when nil
      lhs
    when :comma
      next_token # skip comma
      lhs
    else
      if COMPARISON_OPERATORS.include?(peek_token)
        comparison_operator = next_token
        rhs = parse_value
        ScopedSearch::QueryLanguage::AST::OperatorNode.new(comparison_operator, [lhs, rhs])
      else
        lhs
      end
  end
end
parse_logical_expression() click to toggle source

Parses a logical expression.

# File lib/scoped_search/query_language/parser.rb, line 39
def parse_logical_expression
  lhs = case peek_token
    when nil;             nil
    when :lparen;         parse_expression_sequence
    when :not;            parse_logical_not_expression
    when :null, :notnull; parse_null_expression
    else;                 parse_comparison
  end

  if LOGICAL_INFIX_OPERATORS.include?(peek_token)
    operator = next_token
    rhs = parse_logical_expression
    ScopedSearch::QueryLanguage::AST::LogicalOperatorNode.new(operator, [lhs, rhs])
  else
    lhs
  end
end
parse_logical_not_expression() click to toggle source

Parses a NOT expression

# File lib/scoped_search/query_language/parser.rb, line 58
def parse_logical_not_expression
  next_token # = skip NOT operator
  negated_expression = case peek_token
    when :not;    parse_logical_not_expression
    when :lparen; parse_expression_sequence
    else          parse_comparison
  end

  raise ScopedSearch::QueryNotSupported, "No operands found" if negated_expression.empty?
  return ScopedSearch::QueryLanguage::AST::OperatorNode.new(:not, [negated_expression])
end
parse_multiple_values() click to toggle source

Parse values in the format (val, val, val)

# File lib/scoped_search/query_language/parser.rb, line 107
def parse_multiple_values
  next_token if  peek_token == :lparen #skip :lparen
  value = []
  value << current_token if String === next_token until peek_token.nil? || peek_token == :rparen
  next_token if peek_token == :rparen  # consume the :rparen
  value.join(',')
end
parse_null_expression() click to toggle source

Parses a set? or null? expression

# File lib/scoped_search/query_language/parser.rb, line 71
def parse_null_expression
  return ScopedSearch::QueryLanguage::AST::OperatorNode.new(next_token, [parse_value])
end
parse_prefix_comparison() click to toggle source

Parses a prefix comparison, i.e. without an explicit field: <operator> <value>

# File lib/scoped_search/query_language/parser.rb, line 82
def parse_prefix_comparison
  return ScopedSearch::QueryLanguage::AST::OperatorNode.new(next_token, [parse_value])
end
parse_value() click to toggle source

This can either be a constant value or a field name.

# File lib/scoped_search/query_language/parser.rb, line 116
def parse_value
  if String === peek_token
    ScopedSearch::QueryLanguage::AST::LeafNode.new(next_token)
  elsif ([:in, :notin].include? current_token)
    value = parse_multiple_values()
    ScopedSearch::QueryLanguage::AST::LeafNode.new(value)
  else
    raise ScopedSearch::QueryNotSupported, "Value expected but found #{peek_token.inspect}"
  end
end