🕷 zenspider.com

by ryan davis



sitemap
Looking for the Ruby Quickref?

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