Created
October 27, 2010 21:31
-
-
Save rubiety/650059 to your computer and use it in GitHub Desktop.
Awesome "idiomizing transformations" just written for ruby_scribe.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# = 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