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
Source
# 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
Start the parsing process by parsing an expression sequence
Source
# 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
Parses a comparison
Source
# 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
Parses a sequence of expressions
Source
# 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
Parses an infix expression, i.e. <field> <operator> <value>
Source
# 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
Parses a logical expression.
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
Source
# 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
Parses a NOT expression
Source
# 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 values in the format (val, val, val)
Source
# 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
Parses a set? or null? expression
Source
# 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
Parses a prefix comparison, i.e. without an explicit field: <operator> <value>
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
Source
# 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
This can either be a constant value or a field name.