class JMESPath::TreeInterpreter

@api private

Public Instance Methods

method_missing(method_name, *args) click to toggle source

@api private

Calls superclass method
# File lib/jmespath/tree_interpreter.rb, line 10
def method_missing(method_name, *args)
  if matches = method_name.to_s.match(/^function_(.*)/)
    raise Errors::UnknownFunctionError, "unknown function #{matches[1]}()"
  else
    super
  end
end
visit(node, data) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 5
def visit(node, data)
  dispatch(node, data)
end

Private Instance Methods

_adjust_endpoint(length, endpoint, step) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 486
def _adjust_endpoint(length, endpoint, step)
  if endpoint < 0
    endpoint += length
    endpoint = 0 if endpoint < 0
    endpoint
  elsif endpoint >= length
    step < 0 ? length - 1 : length
  else
    endpoint
  end
end
_adjust_slice(length, start, stop, step) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 464
def _adjust_slice(length, start, stop, step)
  if step.nil?
    step = 1
  elsif step == 0
    raise Errors::RuntimeError, 'slice step cannot be 0'
  end

  if start.nil?
    start = step < 0 ? length - 1 : 0
  else
    start = _adjust_endpoint(length, start, step)
  end

  if stop.nil?
    stop = step < 0 ? -1 : length
  else
    stop = _adjust_endpoint(length, stop, step)
  end

  [start, stop, step]
end
_slice(values, start, stop, step) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 445
def _slice(values, start, stop, step)
  start, stop, step = _adjust_slice(values.size, start, stop, step)
  result = []
  if step > 0
    i = start
    while i < stop
      result << values[i]
      i += step
    end
  else
    i = start
    while i > stop
      result << values[i]
      i += step
    end
  end
  String === values ? result.join : result
end
compare_values(a, b) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 498
def compare_values(a, b)
  if a == b
    true
  else
    false
  end
end
dispatch(node, value) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 20
def dispatch(node, value)
  case node[:type]

  when :field
    # hash_like?
    key = node[:key]
    case value
    when Hash then value.key?(key) ? value[key] : value[key.to_sym]
    when Struct then value.respond_to?(key) ? value[key] : nil
    else nil
    end

  when :subexpression
    dispatch(node[:children][1], dispatch(node[:children][0], value))

  when :index
    if Array === value
      value[node[:index]]
    else
      nil
    end

  when :projection
    # Interprets a projection node, passing the values of the left
    # child through the values of the right child and aggregating
    # the non-null results into the return value.
    left = dispatch(node[:children][0], value)
    if node[:from] == :object && hash_like?(left)
      projection(left.values, node)
    elsif node[:from] == :object && left == []
      projection(left, node)
    elsif node[:from] == :array && Array === left
      projection(left, node)
    else
      nil
    end

  when :flatten
    value = dispatch(node[:children][0], value)
    if Array === value
      value.inject([]) do |values, v|
        values + (Array === v ? v : [v])
      end
    else
      nil
    end

  when :literal
    node[:value]

  when :current
    value

  when :or
    result = dispatch(node[:children][0], value)
    if result.nil? or result.empty?
      dispatch(node[:children][1], value)
    else
      result
    end

  when :pipe
    dispatch(node[:children][1], dispatch(node[:children][0], value))

  when :multi_select_list
    if value.nil?
      value
    else
      node[:children].map { |n| dispatch(n, value) }
    end

  when :multi_select_hash
    if value.nil?
      nil
    else
      node[:children].each.with_object({}) do |child, hash|
        hash[child[:key]] = dispatch(child[:children][0], value)
      end
    end


  when :comparator
    left = dispatch(node[:children][0], value)
    right = dispatch(node[:children][1], value)
    case node[:relation]
    when '==' then compare_values(left, right)
    when '!=' then !compare_values(left, right)
    when '>' then is_int(left) && is_int(right) && left > right
    when '>=' then is_int(left) && is_int(right) && left >= right
    when '<' then is_int(left) && is_int(right) && left < right
    when '<=' then is_int(left) && is_int(right) && left <= right
    end

  when :condition
    true == dispatch(node[:children][0], value) ?
      dispatch(node[:children][1], value) :
      nil

  when :function
    args = node[:children].map { |child| dispatch(child, value) }
    send("function_#{node[:fn]}", *args)

  when :slice
    function_slice(value, *node[:args])

  when :expression
    ExprNode.new(self, node[:children][0])

  else
    raise NotImplementedError
  end
end
function_abs(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 143
def function_abs(*args)
  if args.count == 1
    value = args.first
  else
    raise Errors::InvalidArityError, "function abs() expects one argument"
  end
  if Numeric === value
    value.abs
  else
    raise Errors::InvalidTypeError, "function abs() expects a number"
  end
end
function_avg(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 156
def function_avg(*args)
  if args.count == 1
    values = args.first
  else
    raise Errors::InvalidArityError, "function avg() expects one argument"
  end
  if Array === values
    values.inject(0) do |total,n|
      if Numeric === n
        total + n
      else
        raise Errors::InvalidTypeError, "function avg() expects numeric values"
      end
    end / values.size.to_f
  else
    raise Errors::InvalidTypeError, "function avg() expects a number"
  end
end
function_ceil(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 175
def function_ceil(*args)
  if args.count == 1
    value = args.first
  else
    raise Errors::InvalidArityError, "function ceil() expects one argument"
  end
  if Numeric === value
    value.ceil
  else
    raise Errors::InvalidTypeError, "function ceil() expects a numeric value"
  end
end
function_contains(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 188
def function_contains(*args)
  if args.count == 2
    if String === args[0] || Array === args[0]
      args[0].include?(args[1])
    else
      raise Errors::InvalidTypeError, "contains expects 2nd arg to be a list"
    end
  else
    raise Errors::InvalidArityError, "function contains() expects 2 arguments"
  end
end
function_floor(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 200
def function_floor(*args)
  if args.count == 1
    value = args.first
  else
    raise Errors::InvalidArityError, "function floor() expects one argument"
  end
  if Numeric === value
    value.floor
  else
    raise Errors::InvalidTypeError, "function floor() expects a numeric value"
  end
end
function_join(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 303
def function_join(*args)
  if args.count == 2
    glue = args[0]
    values = args[1]
    if !(String === glue)
      raise Errors::InvalidTypeError, "function join() expects the first argument to be a string"
    elsif Array === values && values.all? { |v| String === v }
      values.join(glue)
    else
      raise Errors::InvalidTypeError, "function join() expects values to be an array of strings"
    end
  else
    raise Errors::InvalidArityError, "function join() expects an array of strings"
  end
end
function_keys(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 271
def function_keys(*args)
  if args.count == 1
    value = args.first
    if hash_like?(value)
      case value
      when Hash then value.keys.map(&:to_s)
      when Struct then value.members.map(&:to_s)
      else raise NotImplementedError
      end
    else
      raise Errors::InvalidTypeError, "function keys() expects a hash"
    end
  else
    raise Errors::InvalidArityError, "function keys() expects one argument"
  end
end
function_length(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 213
def function_length(*args)
  if args.count == 1
    value = args.first
  else
    raise Errors::InvalidArityError, "function length() expects one argument"
  end
  case value
  when Hash, Array, String then value.size
  else raise Errors::InvalidTypeError, "function length() expects string, array or object"
  end
end
function_max(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 225
def function_max(*args)
  if args.count == 1
    values = args.first
  else
    raise Errors::InvalidArityError, "function max() expects one argument"
  end
  if Array === values
    values.inject(values.first) do |max, v|
      if Numeric === v
        v > max ? v : max
      else
        raise Errors::InvalidTypeError, "function max() expects numeric values"
      end
    end
  else
    raise Errors::InvalidTypeError, "function max() expects an array"
  end
end
function_max_by(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 408
def function_max_by(*args)
  number_compare(:max, *args)
end
function_min(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 244
def function_min(*args)
  if args.count == 1
    values = args.first
  else
    raise Errors::InvalidArityError, "function min() expects one argument"
  end
  if Array === values
    values.inject(values.first) do |min, v|
      if Numeric === v
        v < min ? v : min
      else
        raise Errors::InvalidTypeError, "function min() expects numeric values"
      end
    end
  else
    raise Errors::InvalidTypeError, "function min() expects an array"
  end
end
function_min_by(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 412
def function_min_by(*args)
  number_compare(:min, *args)
end
function_not_null(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 355
def function_not_null(*args)
  if args.count > 0
    args.find { |value| !value.nil? }
  else
    raise Errors::InvalidArityError, "function not_null() expects one or more arguments"
  end
end
function_slice(values, *args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 437
def function_slice(values, *args)
  if String === values || Array === values
    _slice(values, *args)
  else
    nil
  end
end
function_sort(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 363
def function_sort(*args)
  if args.count == 1
    value = args.first
    if Array === value
      value.sort do |a, b|
        a_type = get_type(a)
        b_type = get_type(b)
        if ['string', 'number'].include?(a_type) && a_type == b_type
          a <=> b
        else
          raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
        end
      end
    else
      raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
    end
  else
    raise Errors::InvalidArityError, "function sort() expects one argument"
  end
end
function_sort_by(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 384
def function_sort_by(*args)
  if args.count == 2
    if get_type(args[0]) == 'array' && get_type(args[1]) == 'expression'
      values = args[0]
      expression = args[1]
      values.sort do |a,b|
        a_value = expression.interpreter.visit(expression.node, a)
        b_value = expression.interpreter.visit(expression.node, b)
        a_type = get_type(a_value)
        b_type = get_type(b_value)
        if ['string', 'number'].include?(a_type) && a_type == b_type
          a_value <=> b_value
        else
          raise Errors::InvalidTypeError, "function sort() expects values to be an array of numbers or integers"
        end
      end
    else
      raise Errors::InvalidTypeError, "function sort_by() expects an array and an expression"
    end
  else
    raise Errors::InvalidArityError, "function sort_by() expects two arguments"
  end
end
function_sum(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 341
def function_sum(*args)
  if args.count == 1 && Array === args.first
    args.first.inject(0) do |sum,n|
      if Numeric === n
        sum + n
      else
        raise Errors::InvalidTypeError, "function sum() expects values to be numeric"
      end
    end
  else
    raise Errors::InvalidArityError, "function sum() expects one argument"
  end
end
function_to_number(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 328
def function_to_number(*args)
  if args.count == 1
    begin
      value = Float(args.first)
      Integer(value) === value ? value.to_i : value
    rescue
      nil
    end
  else
    raise Errors::InvalidArityError, "function to_number() expects one argument"
  end
end
function_to_string(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 319
def function_to_string(*args)
  if args.count == 1
    value = args.first
    String === value ? value : MultiJson.dump(value)
  else
    raise Errors::InvalidArityError, "function to_string() expects one argument"
  end
end
function_type(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 263
def function_type(*args)
  if args.count == 1
    get_type(args.first)
  else
    raise Errors::InvalidArityError, "function type() expects one argument"
  end
end
function_values(*args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 288
def function_values(*args)
  if args.count == 1
    value = args.first
    if hash_like?(value)
      value.values
    elsif Array === value
      value
    else
      raise Errors::InvalidTypeError, "function values() expects an array or a hash"
    end
  else
    raise Errors::InvalidArityError, "function values() expects one argument"
  end
end
get_type(value) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 510
def get_type(value)
  case
  when ExprNode === value then 'expression'
  when String === value then 'string'
  when hash_like?(value) then 'object'
  when Array === value then 'array'
  when [true, false].include?(value) then 'boolean'
  when value.nil? then 'null'
  when Numeric === value then 'number'
  end
end
hash_like?(value) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 133
def hash_like?(value)
  Hash === value || Struct === value
end
is_int(value) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 506
def is_int(value)
  Integer === value
end
number_compare(mode, *args) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 416
def number_compare(mode, *args)
  if args.count == 2
    if get_type(args[0]) == 'array' && get_type(args[1]) == 'expression'
      values = args[0]
      expression = args[1]
      args[0].send("#{mode}_by") do |entry|
        value = expression.interpreter.visit(expression.node, entry)
        if get_type(value) == 'number'
          value
        else
          raise Errors::InvalidTypeError, "function #{mode}_by() expects values to be an numbers"
        end
      end
    else
      raise Errors::InvalidTypeError, "function #{mode}_by() expects an array and an expression"
    end
  else
    raise Errors::InvalidArityError, "function #{mode}_by() expects two arguments"
  end
end
projection(values, node) click to toggle source
# File lib/jmespath/tree_interpreter.rb, line 137
def projection(values, node)
  values.inject([]) do |list, v|
    list << dispatch(node[:children][1], v)
  end.compact
end