RubyToRuby
Published 2005-02-06 @ 23:29
Tagged ruby, parsetree, ruby2c, toys
So… drbrain comes up to me in an IM and says flgr is saying it’d be really cool if you could ask a method for its source. I know what he is doing, baiting me like that, but I play along anyways to see what the outcome is like. drbrain and I talked about it and thought it’d be really cool if our ruby2c system added a to_c method to the Method class. That isn’t hard at all really, so we added:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Method # with_class_and_method_name is a silly method. # Implementation is an exercise for the reader. def to_sexp with_class_and_method_name do |klass, method| ParseTree.new.parse_tree_for_method(klass, method) end end def to_c with_class_and_method_name do |klass, method| RubyToC.translate(klass, method) end end end |
But the question came up… can we do this to display ruby code? The answer is yes, and it only took me about 30 minutes to get the proof of concept up and running. First, the example code:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Example def example(arg1) return "Blah: " + arg1.to_s end end e = Example.new puts "sexp:" p e.method(:example).to_sexp puts "C:" puts e.method(:example).to_c puts "Ruby:" puts e.method(:example).to_ruby |
and now the output:
1 2 3 4 5 6 7 8 9 10 11 |
sexp: [:defn, :example, [:scope, [:block, [:args, :arg1], [:return, [:call, [:str, "Blah: "], :+, [:array, [:call, [:lvar, :arg1], :to_s]]]]]]] C: str example(long arg1) { return strcat("Blah: ", to_s(arg1)); } Ruby: def example(arg1) return "Blah: " + arg1.to_s end |
Cool huh? I can now translate any method to C or get the ruby code for it (sans-comments
unfortunately) simply by calling to_c
or to_ruby
on the method itself!
RubyToRuby.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
require 'parse_tree' require 'support' require 'sexp_processor' class RubyToRuby < SexpProcessor def self.translate(klass, method=nil) unless method.nil? then self.new.process(ParseTree.new.parse_tree_for_method(klass, method)) else self.new.process(ParseTree.new.parse_tree(klass)) end end def initialize super @env = Environment.new self.auto_shift_type = true self.strict = true self.expected = String end def process_args(exp) args = [] until exp.empty? do args << exp.shift end return "(#{args.join ', '})" end def process_array(exp) code = [] until exp.empty? do code << process(exp.shift) end return code.join(", ") end def process_block(exp) code = [] until exp.empty? do code << process(exp.shift) end body = code.join("\n") body += "\n" return body end def process_call(exp) receiver = process exp.shift name = exp.shift args = process exp.shift case name when :<=>, :==, :<, :>, :<=, :>=, :-, :+, :*, :/, :% then # "#{receiver} #{name} #{args}" when :[] then "#{receiver}[#{args}]" else "#{receiver}.#{name}#{args}" end end def process_defn(exp) name = exp.shift args = process exp.shift body = process exp.shift return "def #{name}#{args}#{body}end".gsub(/\n\n+/, "\n") end def process_fcall(exp) exp_orig = exp.deep_clone # [:fcall, :puts, [:array, [:str, "This is a weird loop"]]] name = exp.shift.to_s args = exp.shift code = [] unless args.nil? then assert_type args, :array args.shift # :array until args.empty? do code << process(args.shift) end end return "#{name}(#{code.join(', ')})" end def process_lvar(exp) exp.shift.to_s end def process_return(exp) return "return #{process exp.shift}" end def process_scope(exp) return process(exp.shift) end def process_str(exp) return "\"#{exp.shift}\"" end def process_vcall(exp) return exp.shift.to_s end def process_while(exp) cond = process(exp.shift) body = process(exp.shift) post = exp.empty? ? false : exp.shift code = [] unless post then code << "while #{cond} do" code << body code << "end" else code << "begin" code << body code << "end while #{cond}" end body = code.join("\n") return body end end |