Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewritten interpreter and AST optimizations #4

Merged
merged 49 commits into from
Sep 16, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
e9c732e
Make the AST interpret itself
iconara Jan 21, 2015
6820e75
Inline comparisons and type checks in Comparator
iconara Jan 21, 2015
d98bb11
Rewrite Condition to use an if instead of the ternary operator
iconara Jan 21, 2015
a8b9002
Simplify the interpretation of Expression
iconara Jan 21, 2015
7cc2729
Optimize Flatten
iconara Jan 21, 2015
666347a
Inline #projection into #visit in Projection
iconara Jan 21, 2015
f729e40
Inline slicing logic in Slice#visit
iconara Jan 21, 2015
9d6ce38
Divide the nodes into nodes with children and leaf nodes
iconara Jan 21, 2015
c853e20
Remove all unnecessary Node/Leaf accessors
iconara Jan 21, 2015
54bd3cf
Don't re-use Field for function names
iconara Jan 21, 2015
348010b
Use #each_with_object instead of #each + #with_object
iconara Jan 21, 2015
f004a6c
Change Comparator to take explicit arguments
iconara Jan 21, 2015
19c2535
Change Condition to take explicit arguments
iconara Jan 21, 2015
4c7f84e
Change Flatten to take explicit arguments
iconara Jan 21, 2015
765bf10
Change KeyValuePair to take explicit arguments
iconara Jan 21, 2015
727d837
Change Or to take explicit arguments
iconara Jan 21, 2015
cd38ec0
Change Pipe to take explicit arguments
iconara Jan 21, 2015
be89ffa
Change Projection to take explicit arguments
iconara Jan 21, 2015
08be998
Change Subexpression to take explicit arguments
iconara Jan 21, 2015
5c22392
Get rid of Leaf, everything is a Node
iconara Jan 21, 2015
8530247
Break Projection apart into {Array,Object}Protection
iconara Jan 21, 2015
3fa270f
Break apart Comparator into {Eq,Neq,Gt,Gte,Lt,Lte}Comparator
iconara Jan 21, 2015
c016b84
Break apart Function into subclasses for each function
iconara Jan 21, 2015
36eb6e9
Optimize type detection in Function
iconara Jan 21, 2015
da83fc4
Remove #to_h from all AST nodes
iconara Jan 21, 2015
235f322
Make Pipe an alias for Subexpression
iconara Jan 21, 2015
a5077fc
Rename things in Projection
iconara Jan 21, 2015
a97e20a
Change how Expression and Function interacts
iconara Jan 21, 2015
2b422cd
Avoid creating so many strings and arrays in Function
iconara Jan 21, 2015
96659b0
Dispatch max_by and min_by on a symbol instead of interpolating a string
iconara Jan 21, 2015
00779d2
Name the parameters in Function#number_compare and ContainsFunction
iconara Jan 21, 2015
07eb986
Move utility functions out of Function and into mixins
iconara Jan 21, 2015
4e20823
Attempt to optimize runs of Field to a single Node
iconara Jan 21, 2015
b585f1a
Make a slightly ugly optimization in Projection
iconara Jan 21, 2015
283c88f
Fix a bug in Projection
iconara Jan 21, 2015
65f4dd2
Flatten runs of Subexpression
iconara Jan 21, 2015
b1fe522
Make sure all nodes with children propagate the #optimize call
iconara Jan 21, 2015
8b95d61
Make sure projections get optimized
iconara Jan 21, 2015
925f928
Inline Comparator into Condition on #optimize
iconara Jan 21, 2015
296582e
Optimize the case when the RHS of a comparison in a condition is a Li…
iconara Jan 21, 2015
c48eced
Rename "children" in MultiSelectHash to "kv_pairs"
iconara Jan 21, 2015
fd00e14
Make the arguments to Slice explicit
iconara Jan 21, 2015
be4ac61
Optimze single step slices with positive start and stop
iconara Jan 21, 2015
407f236
Optimize an extra time in Chain
iconara Jan 21, 2015
a244238
Make the chaining done in Chain more generic
iconara Jan 21, 2015
bd9edca
Optimize runs of Index to ChainIndex
iconara Jan 21, 2015
e6e7b9b
Make Index an alias for Field
iconara Jan 21, 2015
033afd2
Cache the symbolized key in Key
iconara Jan 21, 2015
de5e54d
Rewrite the case/whens in Field to if/else
iconara Jan 21, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/jmespath.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ module JMESPath
autoload :Errors, 'jmespath/errors'
autoload :ExprNode, 'jmespath/expr_node'
autoload :Lexer, 'jmespath/lexer'
autoload :Nodes, 'jmespath/nodes'
autoload :Parser, 'jmespath/parser'
autoload :Runtime, 'jmespath/runtime'
autoload :Token, 'jmespath/token'
autoload :TokenStream, 'jmespath/token_stream'
autoload :TreeInterpreter, 'jmespath/tree_interpreter'
autoload :VERSION, 'jmespath/version'

class << self
Expand Down
15 changes: 0 additions & 15 deletions lib/jmespath/expr_node.rb

This file was deleted.

40 changes: 40 additions & 0 deletions lib/jmespath/nodes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module JMESPath
# @api private
module Nodes
class Node
def visit(value)
end

def hash_like?(value)
Hash === value || Struct === value
end

def optimize
self
end

def chains_with?(other)
false
end
end

autoload :Comparator, 'jmespath/nodes/comparator'
autoload :Condition, 'jmespath/nodes/condition'
autoload :Current, 'jmespath/nodes/current'
autoload :Expression, 'jmespath/nodes/expression'
autoload :Field, 'jmespath/nodes/field'
autoload :Flatten, 'jmespath/nodes/flatten'
autoload :Function, 'jmespath/nodes/function'
autoload :Index, 'jmespath/nodes/index'
autoload :Literal, 'jmespath/nodes/literal'
autoload :MultiSelectHash, 'jmespath/nodes/multi_select_hash'
autoload :MultiSelectList, 'jmespath/nodes/multi_select_list'
autoload :Or, 'jmespath/nodes/or'
autoload :Pipe, 'jmespath/nodes/pipe'
autoload :Projection, 'jmespath/nodes/projection'
autoload :ArrayProjection, 'jmespath/nodes/projection'
autoload :ObjectProjection, 'jmespath/nodes/projection'
autoload :Slice, 'jmespath/nodes/slice'
autoload :Subexpression, 'jmespath/nodes/subexpression'
end
end
77 changes: 77 additions & 0 deletions lib/jmespath/nodes/comparator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
module JMESPath
# @api private
module Nodes
class Comparator < Node
attr_reader :left, :right

def initialize(left, right)
@left = left
@right = right
end

def self.create(relation, left, right)
type = begin
case relation
when '==' then EqComparator
when '!=' then NeqComparator
when '>' then GtComparator
when '>=' then GteComparator
when '<' then LtComparator
when '<=' then LteComparator
end
end
type.new(left, right)
end

def visit(value)
check(@left.visit(value), @right.visit(value))
end

def optimize
self.class.new(@left.optimize, @right.optimize)
end

private

def check(left_value, right_value)
nil
end
end

class EqComparator < Comparator
def check(left_value, right_value)
left_value == right_value
end
end

class NeqComparator < Comparator
def check(left_value, right_value)
left_value != right_value
end
end

class GtComparator < Comparator
def check(left_value, right_value)
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value > right_value
end
end

class GteComparator < Comparator
def check(left_value, right_value)
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value >= right_value
end
end

class LtComparator < Comparator
def check(left_value, right_value)
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value < right_value
end
end

class LteComparator < Comparator
def check(left_value, right_value)
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value <= right_value
end
end
end
end
136 changes: 136 additions & 0 deletions lib/jmespath/nodes/condition.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
module JMESPath
# @api private
module Nodes
class Condition < Node
def initialize(test, child)
@test = test
@child = child
end

def visit(value)
if @test.visit(value)
@child.visit(value)
else
nil
end
end

def optimize
test = @test.optimize
if (new_type = ComparatorCondition::COMPARATOR_TO_CONDITION[@test.class])
new_type.new(test.left, test.right, @child).optimize
else
self.class.new(test, @child.optimize)
end
end
end

class ComparatorCondition < Node
COMPARATOR_TO_CONDITION = {}

def initialize(left, right, child)
@left = left
@right = right
@child = child
end

def visit(value)
nil
end
end

class EqCondition < ComparatorCondition
COMPARATOR_TO_CONDITION[EqComparator] = self

def visit(value)
@left.visit(value) == @right.visit(value) ? @child.visit(value) : nil
end

def optimize
if @right.is_a?(Literal)
LiteralRightEqCondition.new(@left, @right, @child)
else
self
end
end
end

class LiteralRightEqCondition < EqCondition
def initialize(left, right, child)
super
@right = @right.value
end

def visit(value)
@left.visit(value) == @right ? @child.visit(value) : nil
end
end

class NeqCondition < ComparatorCondition
COMPARATOR_TO_CONDITION[NeqComparator] = self

def visit(value)
@left.visit(value) != @right.visit(value) ? @child.visit(value) : nil
end

def optimize
if @right.is_a?(Literal)
LiteralRightNeqCondition.new(@left, @right, @child)
else
self
end
end
end

class LiteralRightNeqCondition < NeqCondition
def initialize(left, right, child)
super
@right = @right.value
end

def visit(value)
@left.visit(value) != @right ? @child.visit(value) : nil
end
end

class GtCondition < ComparatorCondition
COMPARATOR_TO_CONDITION[GtComparator] = self

def visit(value)
left_value = @left.visit(value)
right_value = @right.visit(value)
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value > right_value ? @child.visit(value) : nil
end
end

class GteCondition < ComparatorCondition
COMPARATOR_TO_CONDITION[GteComparator] = self

def visit(value)
left_value = @left.visit(value)
right_value = @right.visit(value)
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value >= right_value ? @child.visit(value) : nil
end
end

class LtCondition < ComparatorCondition
COMPARATOR_TO_CONDITION[LtComparator] = self

def visit(value)
left_value = @left.visit(value)
right_value = @right.visit(value)
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value < right_value ? @child.visit(value) : nil
end
end

class LteCondition < ComparatorCondition
COMPARATOR_TO_CONDITION[LteComparator] = self

def visit(value)
left_value = @left.visit(value)
right_value = @right.visit(value)
left_value.is_a?(Integer) && right_value.is_a?(Integer) && left_value <= right_value ? @child.visit(value) : nil
end
end
end
end
10 changes: 10 additions & 0 deletions lib/jmespath/nodes/current.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module JMESPath
# @api private
module Nodes
class Current < Node
def visit(value)
value
end
end
end
end
25 changes: 25 additions & 0 deletions lib/jmespath/nodes/expression.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module JMESPath
# @api private
module Nodes
class Expression < Node
attr_reader :expression

def initialize(expression)
@expression = expression
end

def visit(value)
self
end

def eval(value)
@expression.visit(value)
end

def optimize
self.class.new(@expression.optimize)
end
end
end
end

74 changes: 74 additions & 0 deletions lib/jmespath/nodes/field.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module JMESPath
# @api private
module Nodes
class Field < Node
def initialize(key)
@key = key
@key_sym = key.respond_to?(:to_sym) ? key.to_sym : nil
end

def visit(value)
if value.is_a?(Array) && @key.is_a?(Integer)
value[@key]
elsif value.is_a?(Hash)
if !(v = value[@key]).nil?
v
elsif @key_sym && !(v = value[@key_sym]).nil?
v
end
elsif value.is_a?(Struct) && value.respond_to?(@key)
value[@key]
end
end

def chains_with?(other)
other.is_a?(Field)
end

def chain(other)
ChainedField.new([@key, *other.keys])
end

protected

def keys
[@key]
end
end

class ChainedField < Field
def initialize(keys)
@keys = keys
@key_syms = keys.each_with_object({}) do |k, syms|
if k.respond_to?(:to_sym)
syms[k] = k.to_sym
end
end
end

def visit(value)
@keys.reduce(value) do |value, key|
if value.is_a?(Array) && key.is_a?(Integer)
value[key]
elsif value.is_a?(Hash)
if !(v = value[key]).nil?
v
elsif (sym = @key_syms[key]) && !(v = value[sym]).nil?
v
end
elsif value.is_a?(Struct) && value.respond_to?(key)
value[key]
end
end
end

def chain(other)
ChainedField.new([*@keys, *other.keys])
end

private

attr_reader :keys
end
end
end
Loading