Class: Rewriter

Inherits:
SexpProcessor
  • Object
show all
Defined in:
lib/rewriter.rb

Overview

Rewriter (probably should be renamed) is a first-pass filter that normalizes some of ruby’s ASTs to make them more processable later in the pipeline. It only has processors for what it is interested in, so real the individual methods for a better understanding of what it does.

Instance Method Summary collapse

Constructor Details

#initializeRewriter

:nodoc:



20
21
22
23
24
25
# File 'lib/rewriter.rb', line 20

def initialize # :nodoc:
  super
  self.auto_shift_type = true
  self.unsupported = [ :cfunc, ]
  # self.debug[:defn] = /method/ # coolest debugging feature ever
end

Instance Method Details

#process_attrasgn(exp) ⇒ Object

Rewrites :attrasgn nodes to the unified :call format:

[:attrasgn, lhs, :name=, args],

becomes:

:call, lhs, :name=, args


36
37
38
39
40
41
42
43
# File 'lib/rewriter.rb', line 36

def process_attrasgn(exp)
  lhs = process exp.shift
  name = exp.shift
  args = process exp.shift
  args[0] = :arglist unless args.nil?

  s(:call, lhs, name, args)
end

#process_call(exp) ⇒ Object

Rewrites :call nodes to the unified :call format:

:call, lhs, :name, args


49
50
51
52
53
54
55
56
# File 'lib/rewriter.rb', line 49

def process_call(exp)
  lhs = process exp.shift
  name = exp.shift
  args = process exp.shift
  args[0] = :arglist unless args.nil?

  s(:call, lhs, name, args)
end

#process_case(exp) ⇒ Object

Rewrites :case/:when nodes as nested :if nodes.



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
# File 'lib/rewriter.rb', line 61

def process_case(exp)
  result = s()
  var = process exp.shift
  else_stmt = process exp.pop

  new_exp = result
  
  until exp.empty? do
    c = exp.shift
    # start a new scope and move to it
    new_exp << s(:if)
    new_exp = new_exp.last

    assert_type c, :when
    ignored_type, vars, stmts = process(c)

    vars = vars.map { |v| s(:call,
                            var.deep_clone,
                            :===,
                            s(:arglist, process(v)))}
    if vars.size > 1 then

      # building from the bottom up, so everything is bizarro-sexp
      # BIZARRO-SEXP NO LIKE OR!
      or_sexp = vars.inject(s(:or, *vars.slice!(-2,2))) do |lhs, rhs|
        s(:or, rhs, lhs)
      end

      new_exp << or_sexp
    else
      new_exp << vars.first
    end
    new_exp << stmts
  end
  new_exp << else_stmt

  result.first
end

#process_defn(exp) ⇒ Object

Rewrites :defn nodes to pull the functions arguments to the top:

Input:

[:defn, name, [:scope, [:block, [:args, ...]]]]
[:defn, name, [:fbody, [:scope, [:block, [:args, ...]]]]]
[:defn, name, [:ivar, name]]
[:defn, name, [:attrset, name]]

Output:

[:defn, name, args, body]


114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/rewriter.rb', line 114

def process_defn(exp)
  name = exp.shift
  args = s(:args)
  body = process exp.shift

  case body.first
  when :scope, :fbody then
    body = body[1] if body.first == :fbody
    args = body.last[1]
    assert_type args, :args
    assert_type body, :scope
    assert_type body[1], :block
    body.last.delete_at 1
  when :bmethod then
    # BEFORE: [:defn, :bmethod_added, [:bmethod, [:dasgn_curr, :x], ...]]
    # AFTER:  [:defn, :bmethod_added, [:args, :x], [:scope, [:block, ...]]]
    body.shift # :bmethod
    # [:dasgn_curr, :x],
    # [:call, [:dvar, :x], :+, [:arglist, [:lit, 1]]]]]
    dasgn = body.shift
    assert_type dasgn, :dasgn_curr
    dasgn.shift # type
    args.push(*dasgn)
    body.find_and_replace_all(:dvar, :lvar)
    if body.first.first == :block then
      body = s(:scope, body.shift)
    else
      body = s(:scope, s(:block, body.shift)) # single statement
    end
  when :dmethod
    # BEFORE: [:defn, :dmethod_added, [:dmethod, :bmethod_maker, ...]]
    # AFTER:  [:defn, :dmethod_added, ...]
    body = body[2][1][2] # UGH! FIX
    args = body[1]
    body.delete_at 1
    body = s(:scope, body)
  when :ivar then
    body = s(:scope, s(:block, s(:return, body)))
  when :attrset then
    argname = body.last
    args << :arg
    body = s(:scope, s(:block, s(:return, s(:iasgn, argname, s(:lvar, :arg)))))
  else
    raise "Unknown :defn format: #{name.inspect} #{args.inspect} #{body.inspect}"
  end

  if Array === args.last and args.last.first == :block then
    cond = args.pop
    cond.shift # take off :block
    new_code =  cond.map do |t, var, val|
      s(:if, s(:call, s(:lvar, var), :nil?), s(:lasgn, var, val), nil)
    end
    body[1].insert 1, *new_code
  end


  return s(:defn, name, args, body)
end

#process_fcall(exp) ⇒ Object

Rewrites :fcall nodes to the unified :call format:

:call, lhs, :name, args


177
178
179
180
181
182
183
# File 'lib/rewriter.rb', line 177

def process_fcall(exp)
  name = exp.shift
  args = process exp.shift
  args[0] = :arglist unless args.nil? # for :fcall with block (:iter)

  return s(:call, nil, name, args)
end

#process_if(exp) ⇒ Object

I’m not really sure what this is for, other than to guarantee that there are 4 elements in the sexp.



189
190
191
192
193
194
# File 'lib/rewriter.rb', line 189

def process_if(exp)
  cond = process exp.shift
  t = process(exp.shift) || nil # FIX: nil is bad, we need to switch to dummy
  f = process(exp.shift) || nil
  return s(:if, cond, t, f)
end

#process_iter(exp) ⇒ Object

Rewrites specific :iter nodes into while loops:

DOC


200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/rewriter.rb', line 200

def process_iter(exp)
  call = process exp.shift
  var  = process exp.shift
  body = process exp.shift

  var = s(:dasgn_curr, Unique.next) if var.nil?

  assert_type call, :call

  if call[2] != :each then # TODO: fix call[n] (api)
    call.shift # :call
    lhs = call.shift
    method_name = call.shift

    case method_name
    when :downto then
      var.shift # 
      start_value = lhs
      finish_value = call.pop.pop # not sure about this
      var_name = var.shift
      body.find_and_replace_all(:dvar, :lvar)
      result = s(:dummy,
                 s(:lasgn, var_name, start_value),
                 s(:while,
                   s(:call, s(:lvar, var_name), :>=,
                     s(:arglist, finish_value)),
                   s(:block,
                     body,
                     s(:lasgn, var_name,
                       s(:call, s(:lvar, var_name), :-,
                         s(:arglist, s(:lit, 1))))), true))
    when :upto then
      # REFACTOR: completely duped from above and direction changed
      var.shift # 
      start_value = lhs
      finish_value = call.pop.pop # not sure about this
      var_name = var.shift
      body.find_and_replace_all(:dvar, :lvar)
      result = s(:dummy,
                 s(:lasgn, var_name, start_value),
                 s(:while,
                   s(:call, s(:lvar, var_name), :<=,
                     s(:arglist, finish_value)),
                   s(:block,
                     body,
                     s(:lasgn, var_name,
                       s(:call, s(:lvar, var_name), :+,
                         s(:arglist, s(:lit, 1))))), true))
    when :define_method then
      # BEFORE: [:iter, [:call, nil, :define_method, [:array, [:lit, :bmethod_added]]], [:dasgn_curr, :x], [:call, [:dvar, :x], :+, [:array, [:lit, 1]]]]
      # we want to get it rewritten for the scope/block context, so:
      #   - throw call away
      #   - rewrite to args
      #   - plop body into a scope
      # AFTER:  [:block, [:args, :x], [:call, [:lvar, :x], :+, [:arglist, [:lit, 1]]]]
      var.find_and_replace_all(:dasgn_curr, :args)
      body.find_and_replace_all(:dvar, :lvar)
      result = s(:block, var, body)
    else
      # HACK we butchered call up top
      result = s(:iter, s(:call, lhs, method_name, call.shift), var, body)
    end
  else
    if var.nil? then
      var = s(:lvar, Unique.next)
    end

    s(:iter, call, var, body)
  end
end

#process_self(exp) ⇒ Object

Rewrites self into lvars



274
275
276
# File 'lib/rewriter.rb', line 274

def process_self(exp)
  s(:lvar, :self)
end

#process_until(exp) ⇒ Object

Rewrites until nodes into while nodes.



281
282
283
284
285
286
287
# File 'lib/rewriter.rb', line 281

def process_until(exp)
  cond = process s(:not, exp.shift)
  body = process exp.shift
  raise "boo" if exp.empty?
  is_precondition = exp.shift
  s(:while, cond, body, is_precondition)
end

#process_vcall(exp) ⇒ Object

Rewrites :vcall nodes to the unified :call format:

:call, lhs, :name, args


293
294
295
296
297
# File 'lib/rewriter.rb', line 293

def process_vcall(exp)
  name = exp.shift

  s(:call, nil, name, nil) # TODO: never has any args?
end

#process_when(exp) ⇒ Object

Rewrites :when nodes so :case can digest it into if/else structure

:when, [args], body


303
304
305
306
307
308
309
# File 'lib/rewriter.rb', line 303

def process_when(exp)
  vars = exp.shift
  assert_type vars, :array
  vars.shift # nuke vars type
  stmts = process(exp)
  return s(:when, vars, stmts.first)
end

#process_zarray(exp) ⇒ Object

Rewrites :zarray nodes to :array with no args.



314
315
316
# File 'lib/rewriter.rb', line 314

def process_zarray(exp)
  return s(:array)
end