Module: ScopedSearch::QueryLanguage::Parser

Included in:
Compiler
Defined in:
lib/scoped_search/query_language/parser.rb

Overview

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.

Constant Summary collapse

DEFAULT_SEQUENCE_OPERATOR =
:and
LOGICAL_INFIX_OPERATORS =
[:and, :or]
LOGICAL_PREFIX_OPERATORS =
[:not]
NULL_PREFIX_OPERATORS =
[:null, :notnull]
COMPARISON_OPERATORS =
[:eq, :ne, :gt, :gte, :lt, :lte, :like, :unlike, :in, :notin]
ALL_INFIX_OPERATORS =
LOGICAL_INFIX_OPERATORS + COMPARISON_OPERATORS
ALL_PREFIX_OPERATORS =
LOGICAL_PREFIX_OPERATORS + COMPARISON_OPERATORS + NULL_PREFIX_OPERATORS

Instance Method Summary collapse

Instance Method Details

#parseObject

Start the parsing process by parsing an expression sequence



19
20
21
22
23
24
25
# 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_comparisonObject

Parses a comparison



76
77
78
79
# 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) ⇒ Object

Parses a sequence of expressions



28
29
30
31
32
33
34
35
36
# 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_comparisonObject

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



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# 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_expressionObject

Parses a logical expression.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 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_expressionObject

Parses a NOT expression



58
59
60
61
62
63
64
65
66
67
68
# 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_valuesObject

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



107
108
109
110
111
112
113
# 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_expressionObject

Parses a set? or null? expression



71
72
73
# 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_comparisonObject

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



82
83
84
# 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_valueObject

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



116
117
118
119
120
121
122
123
124
125
# 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