Skip to content

Instantly share code, notes, and snippets.

@rubiety
Created October 27, 2010 21:31
Show Gist options
  • Save rubiety/650059 to your computer and use it in GitHub Desktop.
Save rubiety/650059 to your computer and use it in GitHub Desktop.
Awesome "idiomizing transformations" just written for ruby_scribe.
# = For to Each Block Transform
# Finds instances using "for something in collection"-style of iteration and converts
# it to using a standard collection.each block.
#
# Example:
#
# for element in collection
# do_something(element)
# end
#
# Converts To:
#
# collection.each do |element|
# do_something(element)
# end
#
class Eachifier < Transformer
def transform(e)
if e.is_a?(Sexp) && e.kind == :for
transform_for_to_each(e)
else
super
end
end
def transform_for_to_each(e)
s(:iter,
s(:call, e.body[0], :each, s(:arglist)),
e.body[1],
e.body[2]
)
end
end
# = Block Method To Proc Transform
# Looks for cases where we're calling a single method on a single-parameter block argument.
# These instances can use the Ruby 1.9 and ActiveSupport "Symbol#to_proc" trick.
#
# Example:
#
# collection.map {|d| d.name }
#
# Transforms To:
#
# collect.map(&:name)
#
class BlockMethodToProcifier < Transformer
def transform(e)
if matches_block_to_use_tap?(e)
transform_block_to_use_tap(e)
else
super
end
end
def matches_block_to_use_tap?(e)
e.is_a?(Sexp) && e.kind == :iter && # Calls block
e.body[2] && e.body[2].kind == :call && # Body of block is a simple method call
e.body[2].body[0].kind == :lvar && # Simple method call is on a local variable
e.body[1].kind == :lasgn && # Block parameter is a single assign
e.body[2].body[0].body[0] == e.body[1].body[0] # Local variable is identical to the first block argument
end
def transform_block_to_use_tap(e)
s(:call,
e.body[0].body[0],
e.body[0].body[1],
s(:arglist, s(:block_pass, s(:lit, e.body[2].body[1])))
)
end
end
# = Method Temporary Variables to Tap Block
# Looks for instances of method declarations where:
# 1. A local variable is assigned to something as the first statement.
# 2. As the last statement, that local variable is returned is expressed.
#
# These instances of using "temporary variables" to construct something for returning can be more
# elegantly expressed by using the .tap idiom.
#
# Example:
#
# def my_method
# temp = ""
# temp << "Something"
# call_something(temp)
# temp
# end
#
# Converts To:
#
# def my_method
# "".tap do |temp|
# temp << "Something"
# call_something(temp)
# end
# end
#
class Tapifier < Transformer
def transform(e)
if matches_method_to_use_tap?(e)
transform_method_to_use_tap(e)
else
super
end
end
def matches_method_to_use_tap?(e)
e.is_a?(Sexp) && [:defn, :defs].include?(e.kind) &&
matches_block_to_use_tap?(e.body[2])
end
def matches_block_to_use_tap?(e)
return matches_block_to_use_tap?(e.body[0]) if e.body.size == 1 && e.body[0].kind == :block
e.is_a?(Sexp) && e.kind == :block && # Some block (or method body)
e.body[0].kind == :lasgn && # The first line assigns to a local variable
expresses_local_variable?(e.body[-1], e.body[0].body[0])
end
def expresses_local_variable?(e, local_variable)
(e.kind == :lvar && e.body[0] == local_variable) ||
(e.kind == :return && expresses_local_variable?(e.body[0], local_variable))
end
def transform_method_to_use_tap(e)
s(e.kind, e.body[0], e.body[1], transform_block_to_use_tap(e.body[2]))
end
def transform_block_to_use_tap(e)
return s(:scope, transform_block_to_use_tap(e.body[0])) if e.body.size == 1 && e.kind == :scope && e.body[0].kind == :block
s(:block, s(:iter,
s(:call, e.body[0].body[1], :tap, s(:arglist)),
s(:lasgn, e.body[0].body[0]),
s(*([:block] + e.body[1..-2]))
))
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment