This post originated from an RSS feed registered with Ruby Buzz
by Rudi Cilibrasi.
Original Post: Refax the Automatic Refactoring Engine
Feed Title: Esoteric Ruby Blog
Feed URL: http://cilibrar.com/~cilibrar/erblog.cgi/index.rss
Feed Description: A weblog made to explore some Ruby ideas in great detail and try to work out ideal solutions to real problems.
Refax is an idea for an automatic refactoring system. It scans Ruby code
for common redundancies and alerts the developer to what it finds.
Here is the fixloops.rb code:
# Refax automatic refactoring system by Rudi Cilibrasi (cilibrar@gmail.com)
# A simple example test of the new ParseTree library.
#
# This program is meant to suggest possible refactoring opportunities
#
# to convert loops of the form
# stmt A
# stmt B
# stmt C
# while cond
# stmt A
# stmt B
# stmt C
# end
#
# to
#
# begin
# stmt A
# stmt B
# stmt C
# end while cond
#
# to use this system, just put
#
# require 'fixloops'
# at the bottom of your ruby source file after requiring all classes to check
#
# ParseTree notes:
# 1) How can you distinguish while loops with preconditions
# versus those with postconditional guards? It looks like the parse tree
# is showing them the same at this moment.
# 2) Trying to parse_tree(Object) throws a nil ptr
require 'rubygems'
require 'parse_tree'
class Refax
def refactor(c)
fail "Must have class or module" unless c.is_a?(Module)
p = ParseTree.new.parse_tree(c)
recurseOn(p)
end
def couldPossiblyRefactor?(p, ind)
return false unless p[ind].is_a?(Array)
return false unless p[ind][0] == :while
return true unless p[ind][2].is_a?(Array)
p[ind][2][0] == :block
end
def howManyInsn(p)
fail "Must be a while, not a #{p}" unless p[0] == :while
if p[2].is_a?(Array)
fail unless p[2][0] == :block
p[2].size - 1
else
1
end
end
def grabInsnArray(p)
fail "Must be a while, not a #{p}" unless p[0] == :while
if p[2].is_a?(Array)
p[2][1..-1]
else
[p[2]]
end
end
def isEquiv(a, b)
a.to_s == b.to_s
end
def fixcode(p, ind)
loopsize = howManyInsn(p[ind])
goodcode = p.clone
goodcode.slice!(ind-loopsize..ind-1)
goodcode # todo : make correcter
end
def recurseOn(p)
if p.is_a?(Array)
@lastclass = p[1] if p[0] == :class
@lastfunc = p[1] if p[0] == :defn
p.each { |i| recurseOn(i) }
# puts "func:#{p[1]}: #{p[2].inspect}" if p[0] == :defn
p.each_index { |ind|
if couldPossiblyRefactor?(p,ind)
loopsize = howManyInsn(p[ind])
if loopsize < ind
# puts "The size is right."
if isEquiv(p[ind-loopsize,loopsize], grabInsnArray(p[ind]))
# puts "The code matches."
goodstuff = fixcode(p, ind)
puts "Suggest refactoring #{@lastfunc} in #{@lastclass}: #{goodstuff.inspect}"
else
# puts "The code doesn't match: #{grabInsnArray(p[ind]).inspect}"
# puts p[ind-loopsize,loopsize].inspect
end
else
# puts "The loop is too large."
end
end
}
end
end
r = Refax.new
ObjectSpace.each_object(Module) { |c|
r.refactor(c) unless c == Object
}
end
And here is an example hastily.rb that can be refactored using it:
# Example Ruby program to test automatic refactoring engine based on ParseTree
# by Rudi Cilibrasi (cilibrar@gmail.com)
class HastilyWritten
def keepgoing() rand(2) == 0 end
def doSomethingWeird() puts "zzzzz" end
def weirdfunc
puts "This is a weird loop"
doSomethingWeird()
while keepgoing
puts "This is a weird loop"
doSomethingWeird()
end
end
def finefunc
begin
puts "This is a weird loop"
doSomethingWeird()
end while keepgoing
end
end
# Here is the line you need to use Refax automatic refactoring system
require 'fixloops'
And here is the output
Suggest refactoring weirdfunc in HastilyWritten: [:block, [:args], [:while, [:vcall, :keepgoing], [:block, [:fcall, :puts, [:array, [:str, "This is a weird loop"]]], [:fcall, :doSomethingWeird]]]]