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
19 def parse
20   @tokens = tokenize
21   while @tokens.last.is_a?(Symbol) do
22     @tokens.delete_at(@tokens.size - 1)
23   end
24   parse_expression_sequence(true).simplify
25 end
parse_comparison() click to toggle source

Parses a comparison

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

Parses a sequence of expressions

   # File lib/scoped_search/query_language/parser.rb
28 def parse_expression_sequence(root_node = false)
29   expressions = []
30 
31   next_token if !root_node && peek_token == :lparen # skip starting :lparen
32   expressions << parse_logical_expression until peek_token.nil? || peek_token == :rparen
33   next_token if !root_node && peek_token == :rparen # skip final :rparen
34 
35   return ScopedSearch::QueryLanguage::AST::LogicalOperatorNode.new(DEFAULT_SEQUENCE_OPERATOR, expressions, root_node)
36 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
109 def parse_infix_comparison
110   lhs = parse_value
111   return case peek_token
112     when nil
113       lhs
114     when :comma
115       next_token # skip comma
116       lhs
117     else
118       if COMPARISON_OPERATORS.include?(peek_token)
119         comparison_operator = next_token
120         rhs = parse_value
121         ScopedSearch::QueryLanguage::AST::OperatorNode.new(comparison_operator, [lhs, rhs])
122       else
123         lhs
124       end
125   end
126 end
parse_logical_expression() click to toggle source

Parses a logical expression.

   # File lib/scoped_search/query_language/parser.rb
39 def parse_logical_expression
40   lhs = case peek_token
41     when nil;             nil
42     when :lparen;         parse_expression_sequence
43     when :not;            parse_logical_not_expression
44     when :null, :notnull; parse_null_expression
45     when *LOGICAL_INFIX_OPERATORS; parse_logical_infix_expression
46     else;                 parse_comparison
47   end
48 
49   if LOGICAL_INFIX_OPERATORS.include?(peek_token)
50     parse_logical_infix_expression([lhs])
51   else
52     lhs
53   end
54 end
parse_logical_infix_expression(previous = []) click to toggle source
   # File lib/scoped_search/query_language/parser.rb
56 def parse_logical_infix_expression(previous = [])
57   operator = next_token
58   rhs = parse_logical_expression
59   ScopedSearch::QueryLanguage::AST::LogicalOperatorNode.new(operator, previous + [rhs])
60 end
parse_logical_not_expression() click to toggle source

Parses a NOT expression

   # File lib/scoped_search/query_language/parser.rb
63 def parse_logical_not_expression
64   next_token # = skip NOT operator
65   negated_expression = case peek_token
66     when :not;    parse_logical_not_expression
67     when :lparen; parse_expression_sequence
68     else          parse_comparison
69   end
70 
71   raise ScopedSearch::QueryNotSupported, "No operands found" if negated_expression.empty?
72   return ScopedSearch::QueryLanguage::AST::OperatorNode.new(:not, [negated_expression])
73 end
parse_multiple_values() click to toggle source

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

    # File lib/scoped_search/query_language/parser.rb
129 def parse_multiple_values
130   next_token if  peek_token == :lparen #skip :lparen
131   value = []
132   value << current_token if String === next_token until peek_token.nil? || peek_token == :rparen
133   next_token if peek_token == :rparen  # consume the :rparen
134   value
135 end
parse_null_expression() click to toggle source

Parses a set? or null? expression

   # File lib/scoped_search/query_language/parser.rb
76 def parse_null_expression
77   return ScopedSearch::QueryLanguage::AST::OperatorNode.new(next_token, [parse_value])
78 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
87 def parse_prefix_comparison
88   token = next_token
89   case token
90   when :in
91     parse_prefix_in(true)
92   when :notin
93     parse_prefix_in(false)
94   else
95     ScopedSearch::QueryLanguage::AST::OperatorNode.new(token, [parse_value])
96   end
97 end
parse_prefix_in(inclusion) click to toggle source
    # File lib/scoped_search/query_language/parser.rb
 99 def parse_prefix_in(inclusion)
100   cmp, log = inclusion ? [:eq, :or] : [:ne, :and]
101   leaves = parse_multiple_values.map do |x|
102     leaf = ScopedSearch::QueryLanguage::AST::LeafNode.new(x)
103     ScopedSearch::QueryLanguage::AST::OperatorNode.new(cmp, [leaf])
104   end
105   ScopedSearch::QueryLanguage::AST::LogicalOperatorNode.new(log, leaves)
106 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
138 def parse_value
139   if String === peek_token
140     ScopedSearch::QueryLanguage::AST::LeafNode.new(next_token)
141   elsif ([:in, :notin].include? current_token)
142     value = parse_multiple_values().join(',')
143     ScopedSearch::QueryLanguage::AST::LeafNode.new(value)
144   else
145     raise ScopedSearch::QueryNotSupported, "Value expected but found #{peek_token.inspect}"
146   end
147 end