class JMESPath::Parser

@api private

Constants

AFTER_DOT
COLON_RBRACKET
CURRENT_NODE
NUM_COLON_RBRACKET

Public Class Methods

new(options = {}) click to toggle source

@option options [Lexer] :lexer

# File lib/jmespath/parser.rb, line 30
def initialize(options = {})
  @lexer = options[:lexer] || Lexer.new
  @disable_visit_errors = options[:disable_visit_errors]
end

Public Instance Methods

method_missing(method_name, *args) click to toggle source

@api private

Calls superclass method
# File lib/jmespath/parser.rb, line 48
def method_missing(method_name, *args)
  if matches = method_name.to_s.match(/^(nud_|led_)(.*)/)
    raise Errors::SyntaxError, "unexpected token #{matches[2]}"
  else
    super
  end
end
parse(expression) click to toggle source

@param [String<JMESPath>] expression

# File lib/jmespath/parser.rb, line 36
def parse(expression)
  tokens =  @lexer.tokenize(expression)
  stream = TokenStream.new(expression, tokens)
  result = expr(stream)
  if stream.token.type != Lexer::T_EOF
    raise Errors::SyntaxError, "expected :eof got #{stream.token.type}"
  else
    result
  end
end

Private Instance Methods

expr(stream, rbp = 0) click to toggle source

@param [TokenStream] stream @param [Integer] rbp Right binding power

# File lib/jmespath/parser.rb, line 60
def expr(stream, rbp = 0)
  left = send("nud_#{stream.token.type}", stream)
  while rbp < (stream.token.binding_power || 0)
    left = send("led_#{stream.token.type}", stream, left)
  end
  left
end
led_and(stream, left) click to toggle source
# File lib/jmespath/parser.rb, line 234
def led_and(stream, left)
  stream.next
  right = expr(stream, Token::BINDING_POWER[:or])
  Nodes::And.new(left, right)
end
led_comparator(stream, left) click to toggle source
# File lib/jmespath/parser.rb, line 162
def led_comparator(stream, left)
  token = stream.token
  stream.next
  right = expr(stream, Token::BINDING_POWER[:comparator])
  Nodes::Comparator.create(token.value, left, right)
end
led_dot(stream, left) click to toggle source
# File lib/jmespath/parser.rb, line 169
def led_dot(stream, left)
  stream.next(match:AFTER_DOT)
  if stream.token.type == :star
    parse_wildcard_object(stream, left)
  else
    right = parse_dot(stream, Token::BINDING_POWER[:dot])
    Nodes::Subexpression.new(left, right)
  end
end
led_filter(stream, left) click to toggle source
# File lib/jmespath/parser.rb, line 179
def led_filter(stream, left)
  stream.next
  expression = expr(stream)
  if stream.token.type != Lexer::T_RBRACKET
    raise Errors::SyntaxError, 'expected a closing rbracket for the filter'
  end
  stream.next
  rhs = parse_projection(stream, Token::BINDING_POWER[Lexer::T_FILTER])
  left ||= CURRENT_NODE
  right = Nodes::Condition.new(expression, rhs)
  Nodes::ArrayProjection.new(left, right)
end
led_flatten(stream, left) click to toggle source
# File lib/jmespath/parser.rb, line 192
def led_flatten(stream, left)
  stream.next
  left = Nodes::Flatten.new(left)
  right = parse_projection(stream, Token::BINDING_POWER[:flatten])
  Nodes::ArrayProjection.new(left, right)
end
led_lbracket(stream, left) click to toggle source
# File lib/jmespath/parser.rb, line 199
def led_lbracket(stream, left)
  stream.next(match: Set.new([:number, :colon, :star]))
  type = stream.token.type
  if type == :number || type == :colon
    right = parse_array_index_expression(stream)
    Nodes::Subexpression.new(left, right)
  else
    parse_wildcard_array(stream, left)
  end
end
led_lparen(stream, left) click to toggle source
# File lib/jmespath/parser.rb, line 210
def led_lparen(stream, left)
  args = []
  if Nodes::Function::FunctionName === left
    name = left.name
  else
    raise Errors::SyntaxError, 'invalid function invocation'
  end
  stream.next
  while stream.token.type != :rparen
    args << expr(stream, 0)
    if stream.token.type == :comma
      stream.next
    end
  end
  stream.next
  Nodes::Function.create(name, args, :disable_visit_errors => @disable_visit_errors)
end
led_or(stream, left) click to toggle source
# File lib/jmespath/parser.rb, line 228
def led_or(stream, left)
  stream.next
  right = expr(stream, Token::BINDING_POWER[:or])
  Nodes::Or.new(left, right)
end
led_pipe(stream, left) click to toggle source
# File lib/jmespath/parser.rb, line 240
def led_pipe(stream, left)
  stream.next
  right = expr(stream, Token::BINDING_POWER[:pipe])
  Nodes::Pipe.new(left, right)
end
nud_current(stream) click to toggle source
# File lib/jmespath/parser.rb, line 68
def nud_current(stream)
  stream.next
  CURRENT_NODE
end
nud_expref(stream) click to toggle source
# File lib/jmespath/parser.rb, line 73
def nud_expref(stream)
  stream.next
  Nodes::Expression.new(expr(stream, Token::BINDING_POWER[:expref]))
end
nud_filter(stream) click to toggle source
# File lib/jmespath/parser.rb, line 93
def nud_filter(stream)
  led_filter(stream, CURRENT_NODE)
end
nud_flatten(stream) click to toggle source
# File lib/jmespath/parser.rb, line 97
def nud_flatten(stream)
  led_flatten(stream, CURRENT_NODE)
end
nud_identifier(stream) click to toggle source
# File lib/jmespath/parser.rb, line 101
def nud_identifier(stream)
  token = stream.token
  n = stream.next
  if n.type == :lparen
    Nodes::Function::FunctionName.new(token.value)
  else
    Nodes::Field.new(token.value)
  end
end
nud_lbrace(stream) click to toggle source
# File lib/jmespath/parser.rb, line 111
def nud_lbrace(stream)
  valid_keys = Set.new([:quoted_identifier, :identifier])
  stream.next(match:valid_keys)
  pairs = []
  begin
    pairs << parse_key_value_pair(stream)
    if stream.token.type == :comma
      stream.next(match:valid_keys)
    end
  end while stream.token.type != :rbrace
  stream.next
  Nodes::MultiSelectHash.new(pairs)
end
nud_lbracket(stream) click to toggle source
# File lib/jmespath/parser.rb, line 125
def nud_lbracket(stream)
  stream.next
  type = stream.token.type
  if type == :number || type == :colon
    parse_array_index_expression(stream)
  elsif type == :star && stream.lookahead(1).type == :rbracket
    parse_wildcard_array(stream)
  else
    parse_multi_select_list(stream)
  end
end
nud_literal(stream) click to toggle source
# File lib/jmespath/parser.rb, line 137
def nud_literal(stream)
  value = stream.token.value
  stream.next
  Nodes::Literal.new(value)
end
nud_lparen(stream) click to toggle source
# File lib/jmespath/parser.rb, line 83
def nud_lparen(stream)
  stream.next
  result = expr(stream, 0)
  if stream.token.type != Lexer::T_RPAREN
    raise Errors::SyntaxError, 'Unclosed `(`'
  end
  stream.next
  result
end
nud_not(stream) click to toggle source
# File lib/jmespath/parser.rb, line 78
def nud_not(stream)
  stream.next
  Nodes::Not.new(expr(stream, Token::BINDING_POWER[:not]))
end
nud_quoted_identifier(stream) click to toggle source
# File lib/jmespath/parser.rb, line 143
def nud_quoted_identifier(stream)
  token = stream.token
  next_token = stream.next
  if next_token.type == :lparen
    msg = 'quoted identifiers are not allowed for function names'
    raise Errors::SyntaxError, msg
  else
    Nodes::Field.new(token[:value])
  end
end
nud_star(stream) click to toggle source
# File lib/jmespath/parser.rb, line 154
def nud_star(stream)
  parse_wildcard_object(stream, CURRENT_NODE)
end
nud_unknown(stream) click to toggle source
# File lib/jmespath/parser.rb, line 158
def nud_unknown(stream)
  raise Errors::SyntaxError, "unknown token #{stream.token.value.inspect}"
end
parse_array_index_expression(stream) click to toggle source

parse array index expressions, for example [0], [1:2:3], etc.

# File lib/jmespath/parser.rb, line 247
def parse_array_index_expression(stream)
  pos = 0
  parts = [nil, nil, nil]
  expected = NUM_COLON_RBRACKET

  begin
    if stream.token.type == Lexer::T_COLON
      pos += 1
      expected = NUM_COLON_RBRACKET
    elsif stream.token.type == Lexer::T_NUMBER
      parts[pos] = stream.token.value
      expected = COLON_RBRACKET
    end
    stream.next(match: expected)
  end while stream.token.type != Lexer::T_RBRACKET

  stream.next # consume the closing bracket

  if pos == 0
    # no colons found, this is a single index extraction
    Nodes::Index.new(parts[0])
  elsif pos > 2
    raise Errors::SyntaxError, 'invalid array slice syntax: too many colons'
  else
    Nodes::ArrayProjection.new(
      Nodes::Slice.new(*parts),
      parse_projection(stream, Token::BINDING_POWER[Lexer::T_STAR])
    )
  end
end
parse_dot(stream, binding_power) click to toggle source
# File lib/jmespath/parser.rb, line 278
def parse_dot(stream, binding_power)
  if stream.token.type == :lbracket
    stream.next
    parse_multi_select_list(stream)
  else
    expr(stream, binding_power)
  end
end
parse_key_value_pair(stream) click to toggle source
# File lib/jmespath/parser.rb, line 287
def parse_key_value_pair(stream)
  key = stream.token.value
  stream.next(match:Set.new([:colon]))
  stream.next
  Nodes::MultiSelectHash::KeyValuePair.new(key, expr(stream))
end
parse_multi_select_list(stream) click to toggle source
# File lib/jmespath/parser.rb, line 294
def parse_multi_select_list(stream)
  nodes = []
  begin
    nodes << expr(stream)
    if stream.token.type == :comma
      stream.next
      if stream.token.type == :rbracket
        raise Errors::SyntaxError, 'expression epxected, found rbracket'
      end
    end
  end while stream.token.type != :rbracket
  stream.next
  Nodes::MultiSelectList.new(nodes)
end
parse_projection(stream, binding_power) click to toggle source
# File lib/jmespath/parser.rb, line 309
def parse_projection(stream, binding_power)
  type = stream.token.type
  if stream.token.binding_power < 10
    CURRENT_NODE
  elsif type == :dot
    stream.next(match:AFTER_DOT)
    parse_dot(stream, binding_power)
  elsif type == :lbracket || type == :filter
    expr(stream, binding_power)
  else
    raise Errors::SyntaxError, 'syntax error after projection'
  end
end
parse_wildcard_array(stream, left = nil) click to toggle source
# File lib/jmespath/parser.rb, line 323
def parse_wildcard_array(stream, left = nil)
  stream.next(match:Set.new([:rbracket]))
  stream.next
  left ||= CURRENT_NODE
  right = parse_projection(stream, Token::BINDING_POWER[:star])
  Nodes::ArrayProjection.new(left, right)
end
parse_wildcard_object(stream, left = nil) click to toggle source
# File lib/jmespath/parser.rb, line 331
def parse_wildcard_object(stream, left = nil)
  stream.next
  left ||= CURRENT_NODE
  right = parse_projection(stream, Token::BINDING_POWER[:star])
  Nodes::ObjectProjection.new(left, right)
end