Module: RipperPlus::Transformer
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
- #add_locals_from_regexp(regexp, scope_stack) ⇒ Object
- #add_variable_list(list, scope_stack, allow_duplicates = true) ⇒ Object
-
#add_variables_from_node(lhs, scope_stack, allow_duplicates = true) ⇒ Object
Adds variables to the given scope stack from the given node.
-
#transform(root, opts = {}) ⇒ Object
Transforms the given AST into a RipperPlus AST.
-
#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.
-
#transform_params(param_node, scope_stack) ⇒ Object
Transforms a parameter list, and adds the new variables to current scope.
- #transform_params_then_body(tree, params, body, scope_stack) ⇒ Object
-
#transform_tree(tree, scope_stack) ⇒ Object
Transforms the given tree into a RipperPlus AST, using a scope stack.
-
#wrap_node_with_error(tree) ⇒ Object
Wraps the given node as an error node with minimal space overhead.
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 |