Module: RipperPlus::Transformer

Extended by:
Transformer
Included in:
Transformer
Defined in:
lib/ripper-plus/transformer.rb

Overview

Transforms a 1.9.2 Ripper AST into a RipperPlus AST. The only change as a result of this transformation is that the nodes for local variable references and vcalls (bareword calls to self) have different node types:

def foo(x)
  y
  y = x
end

becomes

[:def,

[:@ident, "foo", [1, 4]],
[:paren, [:params, [[:@ident, "x", [1, 8]]], nil, nil, nil, nil]],
[:bodystmt,
 [[:vcall, [:@ident, "y", [2, 2]]],
  [:assign,
   [:var_field, [:@ident, "y", [3, 2]]],
   [:var_ref, [:@ident, "x", [3, 6]]]]],
 nil, nil, nil]]

while:

def foo(x)
  y = x
  y
end

becomes

[:def,

[:@ident, "foo", [1, 4]],
[:paren, [:params, [[:@ident, "x", [1, 8]]], nil, nil, nil, nil]],
[:bodystmt,
 [[:assign,
   [:var_field, [:@ident, "y", [2, 2]]],
   [:var_ref, [:@ident, "x", [2, 6]]]],
  [:var_ref, [:@ident, "y", [3, 2]]]],
 nil, nil, nil]]

Instance Method Summary collapse

Instance Method Details

#add_locals_from_regexp(regexp, scope_stack) ⇒ Object



194
195
196
197
198
199
200
201
# File 'lib/ripper-plus/transformer.rb', line 194

def add_locals_from_regexp(regexp, scope_stack)
  regexp_parts = regexp[1]
  if regexp_parts.one? && regexp_parts[0][0] == :@tstring_content
    regexp_text = regexp_parts[0][1]
    captures = Regexp.new(regexp_text).names
    captures.each { |var_name| scope_stack.add_variable(var_name) }
  end
end

#add_variable_list(list, scope_stack, allow_duplicates = true) ⇒ Object



203
204
205
# File 'lib/ripper-plus/transformer.rb', line 203

def add_variable_list(list, scope_stack, allow_duplicates=true)
  list.each { |var| add_variables_from_node(var, scope_stack, allow_duplicates) }
end

#add_variables_from_node(lhs, scope_stack, allow_duplicates = true) ⇒ Object

Adds variables to the given scope stack from the given node. Allows nodes from parameter lists, left-hand-sides, block argument lists, and so on.



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
# File 'lib/ripper-plus/transformer.rb', line 210

def add_variables_from_node(lhs, scope_stack, allow_duplicates=true)
  case lhs[0]
  when :@ident
    scope_stack.add_variable(lhs[1], allow_duplicates)
  when :const_path_field, :@const, :top_const_field
    if scope_stack.in_method?
      raise DynamicConstantError.new
    end
  when Array
    add_variable_list(lhs, scope_stack, allow_duplicates)
  when :mlhs_paren, :var_field, :rest_param, :blockarg
    add_variables_from_node(lhs[1], scope_stack, allow_duplicates)
  when :mlhs_add_star
    pre_star, star, post_star = lhs[1..3]
    add_variable_list(pre_star, scope_stack, allow_duplicates)
    if star
      add_variables_from_node(star, scope_stack, allow_duplicates)
    end
    add_variable_list(post_star, scope_stack, allow_duplicates) if post_star
  when :param_error
    raise InvalidArgumentError.new
  when :assign_error
    raise LHSError.new
  end
end

#transform(root, opts = {}) ⇒ Object

Transforms the given AST into a RipperPlus AST.



52
53
54
55
56
57
# File 'lib/ripper-plus/transformer.rb', line 52

def transform(root, opts={})
  new_copy = opts[:in_place] ? root : clone_sexp(root)
  scope_stack = ScopeStack.new
  transform_tree(new_copy, scope_stack)
  new_copy
end

#transform_in_order(tree, scope_stack) ⇒ Object

If this node’s subtrees are ordered as they are lexically, as most are, transform each subtree in order.



238
239
240
241
242
243
244
245
246
247
# File 'lib/ripper-plus/transformer.rb', line 238

def transform_in_order(tree, scope_stack)
  # some nodes have no type: include the first element in this case
  range = Symbol === tree[0] ? 1..-1 : 0..-1
  tree[range].each do |subtree|
    # obviously don't transform literals or token locations
    if Array === subtree && !(Fixnum === subtree[0])
      transform_tree(subtree, scope_stack)
    end
  end
end

#transform_params(param_node, scope_stack) ⇒ Object

Transforms a parameter list, and adds the new variables to current scope. Used by both block args and method args.



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/ripper-plus/transformer.rb', line 251

def transform_params(param_node, scope_stack)
  param_node = param_node[1] if param_node[0] == :paren
  if param_node
    positional_1, optional, rest, positional_2, block = param_node[1..5]
    add_variable_list(positional_1, scope_stack, false) if positional_1
    if optional
      optional.each do |var, value|
        # MUST walk value first. (def foo(y=y); end) == (def foo(y=y()); end)
        transform_tree(value, scope_stack)
        add_variables_from_node(var, scope_stack, false)
      end
    end
    if rest && rest[1]
      add_variables_from_node(rest, scope_stack, false)
    end
    add_variable_list(positional_2, scope_stack, false) if positional_2
    add_variables_from_node(block, scope_stack, false) if block
  end
end

#transform_params_then_body(tree, params, body, scope_stack) ⇒ Object



186
187
188
189
190
191
192
# File 'lib/ripper-plus/transformer.rb', line 186

def transform_params_then_body(tree, params, body, scope_stack)
  transform_params(params, scope_stack)
rescue SyntaxError
  wrap_node_with_error(tree)
else
  transform_tree(body, scope_stack)
end

#transform_tree(tree, scope_stack) ⇒ Object

Transforms the given tree into a RipperPlus AST, using a scope stack. This will be recursively called through each level of the tree.



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
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
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/ripper-plus/transformer.rb', line 61

def transform_tree(tree, scope_stack)
  if Symbol === tree[0]
    case tree[0]
    when :assign, :massign
      lhs, rhs = tree[1..2]
      begin
        add_variables_from_node(lhs, scope_stack)
      rescue SyntaxError => err
        wrap_node_with_error(tree)
      else
        transform_tree(rhs, scope_stack)
      end
    when :for
      vars, iterated, body = tree[1..3]
      add_variables_from_node(vars, scope_stack)
      transform_tree(iterated, scope_stack)
      transform_tree(body, scope_stack)
    when :var_ref
      # When we reach a :var_ref, we should know everything we need to know
      # in order to tell if it should be transformed into a :vcall.
      if tree[1][0] == :@ident && !scope_stack.has_variable?(tree[1][1])
        tree[0] = :vcall
      end
    when :class
      name, superclass, body = tree[1..3]
      if name[1][0] == :class_name_error || scope_stack.in_method?
        wrap_node_with_error(tree)
      else
        transform_tree(superclass, scope_stack) if superclass  # superclass node
        scope_stack.with_closed_scope do
          transform_tree(body, scope_stack)
        end
      end
    when :module
      name, body = tree[1..2]
      if name[1][0] == :class_name_error || scope_stack.in_method?
        wrap_node_with_error(tree)
      else
        scope_stack.with_closed_scope do
          transform_tree(body, scope_stack)  # body
        end
      end
    when :sclass
      singleton, body = tree[1..2]
      transform_tree(singleton, scope_stack)
      scope_stack.with_closed_scope do
        transform_tree(body, scope_stack)
      end
    when :def
      scope_stack.with_closed_scope(true) do
        param_node = tree[2]
        body = tree[3]
        transform_params_then_body(tree, param_node, body, scope_stack)
      end
    when :defs
      transform_tree(tree[1], scope_stack)  # singleton could be a method call!
      scope_stack.with_closed_scope(true) do
        param_node = tree[4]
        body = tree[5]
        transform_params_then_body(tree, param_node, body, scope_stack)
      end
    when :lambda
      param_node, body = tree[1..2]
      scope_stack.with_open_scope do
        transform_params_then_body(tree, param_node, body, scope_stack)
      end
    when :rescue
      list, name, body = tree[1..3]
      transform_tree(list, scope_stack) if list
      # Don't forget the rescue argument!
      if name
        add_variables_from_node(name, scope_stack)
      end
      transform_tree(body, scope_stack)
    when :method_add_block
      call, block = tree[1..2]
      # first transform the call
      transform_tree(call, scope_stack)
      # then transform the block
      param_node, body = block[1..2]
      scope_stack.with_open_scope do
        begin
          if param_node
            transform_params(param_node[1], scope_stack)
            if param_node[2]
              add_variable_list(param_node[2], scope_stack, false)
            end
          end
        rescue SyntaxError
          wrap_node_with_error(tree)
        else
          transform_tree(body, scope_stack)
        end
      end
    when :binary
      # must check for named groups in a literal match. wowzerz.
      lhs, op, rhs = tree[1..3]
      if op == :=~
        if lhs[0] == :regexp_literal
          add_locals_from_regexp(lhs, scope_stack)
          transform_tree(rhs, scope_stack)
        elsif lhs[0] == :paren && !lhs[1].empty? && lhs[1] != [[:void_stmt]] && lhs[1].last[0] == :regexp_literal
          lhs[1][0..-2].each { |node| transform_tree(node, scope_stack) }
          add_locals_from_regexp(lhs[1].last, scope_stack)
          transform_tree(rhs, scope_stack)
        else
          transform_in_order(tree, scope_stack)
        end
      else
        transform_in_order(tree, scope_stack)
      end
    when :if_mod, :unless_mod, :while_mod, :until_mod, :rescue_mod
      # The AST is the reverse of the parse order for these nodes.
      transform_tree(tree[2], scope_stack)
      transform_tree(tree[1], scope_stack)
    when :alias_error, :assign_error  # error already top-level! wrap it again.
      wrap_node_with_error(tree)
    else
      transform_in_order(tree, scope_stack)
    end
  else
    transform_in_order(tree, scope_stack)
  end
end

#wrap_node_with_error(tree) ⇒ Object

Wraps the given node as an error node with minimal space overhead.



272
273
274
275
# File 'lib/ripper-plus/transformer.rb', line 272

def wrap_node_with_error(tree)
  new_tree = [:error, tree.dup]
  tree.replace(new_tree)
end