ripper-plus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "yard", "~> 0.6.0"
11
+ gem "bundler", "~> 1.0.0"
12
+ gem "jeweler", "~> 1.5.2"
13
+ gem "rcov", ">= 0"
14
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Michael Edgar
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # ripper-plus
2
+
3
+ Ripper is the Ruby parser library packaged with Ruby 1.9. While quite complete, it has some quirks that can make use frustrating. This gem intends to correct them. The most important is bareword resolution in Ruby ASTs.
4
+
5
+ ## Bareword Resolution
6
+
7
+ In Ruby, a local variable is created when it is assigned to. If Ruby finds a bareword `foo` before an assignment `foo = 5`, then Ruby will call `self.foo()`.
8
+
9
+ Kind of.
10
+
11
+ What happens when you run this code?
12
+
13
+ def label
14
+ 'hello'
15
+ end
16
+ def print_label
17
+ label = 'Label: ' + label
18
+ puts label
19
+ end
20
+ print_label
21
+
22
+ If one takes the intuitive approach, one would assume it prints "Label: hello", right? Afraid not: it errors out. The intuitive approach says that `'Label: ' + label` is run before the assignment to the local variable `label`, so the `label` method will be called and appended to `"Label: "`. In reality, it won't, because local variables aren't created on assignment. Local variables are created *immediately when the Ruby parser parses a left-hand-side local variable*. Before the right-hand-side is even parsed, the local variable has been created. This also results in an infamous anomaly:
23
+
24
+ def say_what?
25
+ x = 'hello' unless defined?(x)
26
+ puts x
27
+ end
28
+ say_what?
29
+
30
+ `say_what?` prints a blank line! This is because as soon as the `x` on the LHS is parsed, `x` is a local variable with value `nil`. By the time `defined?(x)` is executed, `x` has long since been a local variable!
31
+
32
+ ## Ripper's Mistake
33
+
34
+ Ripper doesn't carry scope information as it parses, and as such, parses any lone identifier as an AST node of type `:var_ref`. It is up to consumers of the AST to figure out the meaning of the `:var_ref` node. It can be reasonably argued that the semantics of the `:var_ref` node should not be part of the AST, as my thesis adviser pointed out when I complained about this, as the two are syntactically identical. Unfortunately, the meaning of the `var_ref` node comes from the parser itself; any attempt to determine semantics based solely on the AST is simply re-creating the work the parser ordinarily does! Indeed, when Ruby parses the code for execution, it *does* create different internal node types upon seeing a method-call bareword and a local variable bareword!
35
+
36
+ I'd like to see this behavior rolled into Ripper proper. Until then, `ripper-plus` is a reasonable replacement.
37
+
38
+ ## ripper-plus
39
+
40
+ By using our knowledge of Ruby's parser, we can recreate the scope-tracking behavior by walking the AST generated by Ripper. We simply must do normal scope tracking, observe the creation of local variables, and ensure that we walk each node of the tree in the order in which those nodes would have been parsed. Most subtrees generated by Ripper are already in this order, with the exception of the modifier nodes (`foo.bar if baz`, `x = bar() while x != nil`). Most importantly, since everything is an expression in Ruby and local variable assignments can occur just about anywhere, exact scoping semantics must be recreated. Corner cases such as this:
41
+
42
+ def foo(x, y = y)
43
+ end
44
+
45
+ Need to be properly resolved. Did you know that, unlike the `label = label` example above, the above code is equivalent to:
46
+
47
+ def foo(x, y = y())
48
+ end
49
+
50
+ Anyway, ripper-plus turns all method-call `:var_ref` nodes into `zcall` nodes; the node structure is otherwise unchanged.
51
+
52
+ The truth is, everybody who is using Ripper right now *should* be doing *all* of this. Anything short, and you have bugs. [Laser](https://github.com/michaeledgar/laser/) has bugs as a result. It's a pain in the ass to get it all right. So hopefully, in Ruby 1.9.x, this will be the default. For now, you *should* use ripper-plus.
53
+
54
+ ## Contributing to ripper-plus
55
+
56
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
57
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
58
+ * Fork the project
59
+ * Start a feature/bugfix branch
60
+ * Commit and push until you are happy with your contribution
61
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
62
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
63
+
64
+ ## Copyright
65
+
66
+ Copyright (c) 2011 Michael Edgar. See LICENSE.txt for
67
+ further details.
68
+
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "ripper-plus"
16
+ gem.homepage = "http://github.com/michaeledgar/ripper-plus"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Parses Ruby code into an improved Ripper AST format}
19
+ gem.description = %Q{Ripper is the Ruby parser library packaged
20
+ with Ruby 1.9. While quite complete, it has some quirks that can
21
+ make use frustrating. This gem intends to correct them.}
22
+ gem.email = "michael.j.edgar@dartmouth.edu"
23
+ gem.authors = ["Michael Edgar"]
24
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
25
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
26
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
27
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
28
+ end
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ require 'rspec/core'
32
+ require 'rspec/core/rake_task'
33
+ RSpec::Core::RakeTask.new(:spec) do |spec|
34
+ spec.pattern = FileList['spec/**/*_spec.rb']
35
+ end
36
+
37
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
38
+ spec.pattern = 'spec/**/*_spec.rb'
39
+ spec.rcov = true
40
+ end
41
+
42
+ task :default => :spec
43
+
44
+ require 'yard'
45
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,7 @@
1
+ if RUBY_VERSION < "1.9"
2
+ raise 'ripper-plus requires Ruby 1.9+'
3
+ end
4
+ require 'ripper'
5
+ require 'ripper-plus/ripper-plus'
6
+ require 'ripper-plus/scope_stack'
7
+ require 'ripper-plus/transformer'
@@ -0,0 +1,13 @@
1
+ # Top-level module for Ripper Plus. Provides global methods for
2
+ # getting a RipperPlus AST for a given input program.
3
+ module RipperPlus
4
+ # Parses the given Ruby code into a RipperPlus AST.
5
+ def self.sexp(text)
6
+ for_ripper_ast(Ripper.sexp(text))
7
+ end
8
+
9
+ # Transforms the provided Ripper AST into a RipperPlus AST.
10
+ def self.for_ripper_ast(tree)
11
+ Transformer.transform(tree)
12
+ end
13
+ end
@@ -0,0 +1,54 @@
1
+ module RipperPlus
2
+ # internal class that manages the current scopes.
3
+ class ScopeStack
4
+ SCOPE_BLOCKER_9000 = :scope_block
5
+ def initialize
6
+ # simplifies algorithm to have the scope blocker be the stack base
7
+ @stack = [SCOPE_BLOCKER_9000, Set.new]
8
+ end
9
+
10
+ def inspect
11
+ middle = @stack.map do |scope|
12
+ if SCOPE_BLOCKER_9000 == scope
13
+ '||'
14
+ else
15
+ "[ #{scope.to_a.sort.join(', ')} ]"
16
+ end
17
+ end.join(' ')
18
+ "< #{middle} >"
19
+ end
20
+
21
+ def add_variable(var)
22
+ @stack.last << var
23
+ end
24
+
25
+ # An open scope permits reference to local variables in enclosing scopes.
26
+ def with_open_scope
27
+ @stack.push(Set.new)
28
+ yield
29
+ ensure
30
+ @stack.pop # pops open scope
31
+ end
32
+
33
+ # An open scope denies reference to local variables in enclosing scopes.
34
+ def with_closed_scope
35
+ @stack.push(SCOPE_BLOCKER_9000)
36
+ @stack.push(Set.new)
37
+ yield
38
+ ensure
39
+ @stack.pop # pop closed scope
40
+ @stack.pop # pop scope blocker
41
+ end
42
+
43
+ # Checks if the given variable is in scope.
44
+ def has_variable?(var)
45
+ @stack.reverse_each do |scope|
46
+ if SCOPE_BLOCKER_9000 == scope
47
+ return false
48
+ elsif scope.include?(var)
49
+ return true
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,201 @@
1
+ require 'set'
2
+ module RipperPlus
3
+ # Transforms a 1.9.2 Ripper AST into a RipperPlus AST. The only
4
+ # change as a result of this transformation is that the nodes for
5
+ # local variable references and zcalls (bareword calls to self)
6
+ # have different node types:
7
+ #
8
+ # def foo(x)
9
+ # y
10
+ # y = x
11
+ # end
12
+ #
13
+ # becomes
14
+ #
15
+ # [:def,
16
+ # [:@ident, "foo", [1, 4]],
17
+ # [:paren, [:params, [[:@ident, "x", [1, 8]]], nil, nil, nil, nil]],
18
+ # [:bodystmt,
19
+ # [[:zcall, [:@ident, "y", [2, 2]]],
20
+ # [:assign,
21
+ # [:var_field, [:@ident, "y", [3, 2]]],
22
+ # [:var_ref, [:@ident, "x", [3, 6]]]]],
23
+ # nil, nil, nil]]
24
+ #
25
+ # while:
26
+ #
27
+ # def foo(x)
28
+ # y = x
29
+ # y
30
+ # end
31
+ #
32
+ # becomes
33
+ #
34
+ # [:def,
35
+ # [:@ident, "foo", [1, 4]],
36
+ # [:paren, [:params, [[:@ident, "x", [1, 8]]], nil, nil, nil, nil]],
37
+ # [:bodystmt,
38
+ # [[:assign,
39
+ # [:var_field, [:@ident, "y", [2, 2]]],
40
+ # [:var_ref, [:@ident, "x", [2, 6]]]],
41
+ # [:var_ref, [:@ident, "y", [3, 2]]]],
42
+ # nil, nil, nil]]
43
+ module Transformer
44
+ extend self
45
+
46
+ # Transforms the given AST into a RipperPlus AST.
47
+ def transform(root)
48
+ new_copy = clone_sexp(root)
49
+ scope_stack = ScopeStack.new
50
+ transform_tree(new_copy, scope_stack)
51
+ new_copy
52
+ end
53
+
54
+ # Transforms the given tree into a RipperPlus AST.
55
+ def transform_tree(tree, scope_stack)
56
+ case tree[0]
57
+ when :assign, :massign
58
+ lhs, rhs = tree[1..2]
59
+ add_variables_from_lhs(lhs, scope_stack)
60
+ transform_tree(rhs, scope_stack)
61
+ when :for
62
+ vars, iterated, body = tree[1..3]
63
+ add_variables_from_lhs(vars, scope_stack)
64
+ transform_tree(iterated, scope_stack)
65
+ transform_tree(body, scope_stack)
66
+ when :var_ref
67
+ if tree[1][0] == :@ident && !scope_stack.has_variable?(tree[1][1])
68
+ tree[0] = :zcall
69
+ end
70
+ when :class
71
+ superclass, body = tree[2..3]
72
+ transform_tree(superclass, scope_stack) if superclass # superclass node
73
+ scope_stack.with_closed_scope do
74
+ transform_tree(body, scope_stack)
75
+ end
76
+ when :module
77
+ scope_stack.with_closed_scope do
78
+ transform_tree(tree[2], scope_stack) # body
79
+ end
80
+ when :sclass
81
+ singleton, body = tree[1..2]
82
+ transform_tree(singleton, scope_stack)
83
+ scope_stack.with_closed_scope do
84
+ transform_tree(body, scope_stack)
85
+ end
86
+ when :def
87
+ scope_stack.with_closed_scope do
88
+ param_node = tree[2]
89
+ body = tree[3]
90
+ transform_params(param_node, scope_stack)
91
+ transform_tree(body, scope_stack)
92
+ end
93
+ when :defs
94
+ transform_tree(tree[1], scope_stack) # singleton could be a method call!
95
+ scope_stack.with_closed_scope do
96
+ param_node = tree[4]
97
+ body = tree[5]
98
+ transform_params(param_node, scope_stack)
99
+ transform_tree(body, scope_stack)
100
+ end
101
+ when :method_add_block
102
+ call, block = tree[1..2]
103
+ # first transform the call
104
+ transform_tree(call, scope_stack)
105
+ # then transform the block
106
+ block_args, block_body = block[1..2]
107
+ scope_stack.with_open_scope do
108
+ if block_args
109
+ transform_params(block_args[1], scope_stack)
110
+ if block_args[2]
111
+ block_args[2].each { |var| add_variables_from_lhs(var, scope_stack) }
112
+ end
113
+ end
114
+ transform_tree(block_body, scope_stack)
115
+ end
116
+ when :if_mod, :unless_mod, :while_mod, :until_mod, :rescue_mod
117
+ # The AST is the reverse of the parse order for these nodes.
118
+ transform_tree(tree[2], scope_stack)
119
+ transform_tree(tree[1], scope_stack)
120
+ else
121
+ transform_in_order(tree, scope_stack)
122
+ end
123
+ end
124
+
125
+ def add_variables_from_lhs(lhs, scope_stack)
126
+ case lhs[0]
127
+ when :@ident
128
+ scope_stack.add_variable(lhs[1])
129
+ when Array
130
+ lhs.each { |var| add_variables_from_lhs(var, scope_stack) }
131
+ when :mlhs_paren, :var_field, :rest_param, :blockarg
132
+ add_variables_from_lhs(lhs[1], scope_stack)
133
+ when :mlhs_add_star
134
+ pre_star, star, post_star = lhs[1..3]
135
+ pre_star.each { |var| add_variables_from_lhs(var, scope_stack) }
136
+ if star
137
+ add_variables_from_lhs(star, scope_stack)
138
+ end
139
+ if post_star
140
+ post_star.each { |var| add_variables_from_lhs(var, scope_stack) }
141
+ end
142
+ end
143
+ end
144
+
145
+ # If this node's subtrees are ordered as they are lexically, as most are,
146
+ # transform each subtree in order.
147
+ def transform_in_order(tree, scope_stack)
148
+ # some nodes have no type: include the first element in this case
149
+ range = Symbol === tree[0] ? 1..-1 : 0..-1
150
+ tree[range].each do |subtree|
151
+ # obviously don't transform literals or token locations
152
+ if Array === subtree && !(Fixnum === subtree[0])
153
+ transform_tree(subtree, scope_stack)
154
+ end
155
+ end
156
+ end
157
+
158
+ # Transforms a parameter list, and adds the new variables to current scope.
159
+ # Used by both block args and method args.
160
+ def transform_params(param_node, scope_stack)
161
+ param_node = param_node[1] if param_node[0] == :paren
162
+ if param_node
163
+ positional_1, optional, rest, positional_2, block = param_node[1..5]
164
+ if positional_1
165
+ positional_1.each { |var| add_variables_from_lhs(var, scope_stack) }
166
+ end
167
+ if optional
168
+ optional.each do |var, value|
169
+ # MUST walk value first. (def foo(y=y); end) == (def foo(y=y()); end)
170
+ transform_tree(value, scope_stack)
171
+ add_variables_from_lhs(var, scope_stack)
172
+ end
173
+ end
174
+ if rest && rest[1]
175
+ add_variables_from_lhs(rest, scope_stack)
176
+ end
177
+ if positional_2
178
+ positional_2.each { |var| add_variables_from_lhs(var, scope_stack) }
179
+ end
180
+ if block
181
+ add_variables_from_lhs(block, scope_stack)
182
+ end
183
+ end
184
+ end
185
+
186
+ # Deep-copies the sexp. I wish Array#clone did deep copies...
187
+ def clone_sexp(node)
188
+ node.map do |part|
189
+ # guess: arrays most common, then un-dupables, then dup-ables (strings only)
190
+ if Array === part
191
+ clone_sexp(part)
192
+ elsif [Symbol, Fixnum, TrueClass, FalseClass, NilClass].any? { |k| k === part }
193
+ part
194
+ else
195
+ part.dup
196
+ end
197
+ end
198
+ end
199
+ private :clone_sexp
200
+ end
201
+ end
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "RipperPlus" do
4
+
5
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe RipperPlus::ScopeStack do
4
+ before do
5
+ @stack = RipperPlus::ScopeStack.new
6
+ end
7
+
8
+ it 'can see variables added at top level' do
9
+ @stack.should_not have_variable(:hello)
10
+ @stack.add_variable :hello
11
+ @stack.should have_variable(:hello)
12
+ end
13
+
14
+ it 'can not see variables added beyond a closed scope' do
15
+ @stack.add_variable :hello
16
+ @stack.with_closed_scope do
17
+ @stack.should_not have_variable(:hello)
18
+ end
19
+ @stack.should have_variable(:hello)
20
+ end
21
+
22
+ it 'can see variables added beyond an open scope' do
23
+ @stack.add_variable :hello
24
+ @stack.with_open_scope do
25
+ @stack.should have_variable(:hello)
26
+ end
27
+ @stack.should have_variable(:hello)
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'ripper-plus'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec::Matchers.define :transform_to do |output|
11
+ match do |input|
12
+ RipperPlus::Transformer.transform(input) == output
13
+ end
14
+ end
15
+
16
+ RSpec.configure do |config|
17
+
18
+ end
@@ -0,0 +1,467 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe RipperPlus::Transformer do
4
+ it 'should transform a simple zcall in a method' do
5
+ input_tree =
6
+ [:program,
7
+ [[:def,
8
+ [:@ident, "foo", [1, 4]],
9
+ [:paren, [:params, [[:@ident, "x", [1, 8]]], nil, nil, nil, nil]],
10
+ [:bodystmt,
11
+ [[:void_stmt],
12
+ [:var_ref, [:@ident, "y", [1, 12]]],
13
+ [:assign,
14
+ [:var_field, [:@ident, "y", [1, 15]]],
15
+ [:var_ref, [:@ident, "x", [1, 19]]]]],
16
+ nil, nil, nil]]]]
17
+ output_tree =
18
+ [:program,
19
+ [[:def,
20
+ [:@ident, "foo", [1, 4]],
21
+ [:paren, [:params, [[:@ident, "x", [1, 8]]], nil, nil, nil, nil]],
22
+ [:bodystmt,
23
+ [[:void_stmt],
24
+ [:zcall, [:@ident, "y", [1, 12]]],
25
+ [:assign,
26
+ [:var_field, [:@ident, "y", [1, 15]]],
27
+ [:var_ref, [:@ident, "x", [1, 19]]]]],
28
+ nil, nil, nil]]]]
29
+ input_tree.should transform_to(output_tree)
30
+ end
31
+
32
+ it 'should respect argument order in method definitions' do
33
+ input_tree =
34
+ [:program,
35
+ [[:def,
36
+ [:@ident, "foo", [1, 4]],
37
+ [:paren,
38
+ [:params,
39
+ [[:@ident, "x", [1, 8]]],
40
+ [[[:@ident, "y", [1, 11]], [:var_ref, [:@ident, "z", [1, 13]]]],
41
+ [[:@ident, "z", [1, 16]], [:var_ref, [:@ident, "y", [1, 18]]]]],
42
+ nil,
43
+ nil,
44
+ nil]],
45
+ [:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
46
+ output_tree =
47
+ [:program,
48
+ [[:def,
49
+ [:@ident, "foo", [1, 4]],
50
+ [:paren,
51
+ [:params,
52
+ [[:@ident, "x", [1, 8]]],
53
+ [[[:@ident, "y", [1, 11]], [:zcall, [:@ident, "z", [1, 13]]]],
54
+ [[:@ident, "z", [1, 16]], [:var_ref, [:@ident, "y", [1, 18]]]]],
55
+ nil,
56
+ nil,
57
+ nil]],
58
+ [:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
59
+ input_tree.should transform_to(output_tree)
60
+ end
61
+
62
+ it 'should respect argument order in block argument definitions' do
63
+ input_tree =
64
+ [:program,
65
+ [[:method_add_block,
66
+ [:zsuper],
67
+ [:do_block,
68
+ [:block_var,
69
+ [:params,
70
+ [[:@ident, "x", [1, 10]]],
71
+ [[[:@ident, "y", [1, 13]], [:var_ref, [:@ident, "x", [1, 15]]]],
72
+ [[:@ident, "z", [1, 18]], [:var_ref, [:@ident, "a", [1, 20]]]]],
73
+ [:rest_param, [:@ident, "a", [1, 24]]],
74
+ nil,
75
+ nil],
76
+ nil],
77
+ [[:void_stmt],
78
+ [:command,
79
+ [:@ident, "p", [1, 28]],
80
+ [:args_add_block,
81
+ [[:var_ref, [:@ident, "x", [1, 30]]],
82
+ [:var_ref, [:@ident, "y", [1, 33]]],
83
+ [:var_ref, [:@ident, "z", [1, 36]]],
84
+ [:var_ref, [:@ident, "a", [1, 39]]]],
85
+ false]]]]]]]
86
+ output_tree =
87
+ [:program,
88
+ [[:method_add_block,
89
+ [:zsuper],
90
+ [:do_block,
91
+ [:block_var,
92
+ [:params,
93
+ [[:@ident, "x", [1, 10]]],
94
+ [[[:@ident, "y", [1, 13]], [:var_ref, [:@ident, "x", [1, 15]]]],
95
+ [[:@ident, "z", [1, 18]], [:zcall, [:@ident, "a", [1, 20]]]]],
96
+ [:rest_param, [:@ident, "a", [1, 24]]],
97
+ nil,
98
+ nil],
99
+ nil],
100
+ [[:void_stmt],
101
+ [:command,
102
+ [:@ident, "p", [1, 28]],
103
+ [:args_add_block,
104
+ [[:var_ref, [:@ident, "x", [1, 30]]],
105
+ [:var_ref, [:@ident, "y", [1, 33]]],
106
+ [:var_ref, [:@ident, "z", [1, 36]]],
107
+ [:var_ref, [:@ident, "a", [1, 39]]]],
108
+ false]]]]]]]
109
+ input_tree.should transform_to(output_tree)
110
+ end
111
+
112
+ it 'should transform singleton names in singleton method definitions' do
113
+ input_tree =
114
+ [:program,
115
+ [[:defs,
116
+ [:var_ref, [:@ident, "foo", [1, 4]]],
117
+ [:@period, ".", [1, 7]],
118
+ [:@ident, "silly", [1, 8]],
119
+ [:paren, [:params, nil, nil, nil, nil, nil]],
120
+ [:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
121
+ output_tree =
122
+ [:program,
123
+ [[:defs,
124
+ [:zcall, [:@ident, "foo", [1, 4]]],
125
+ [:@period, ".", [1, 7]],
126
+ [:@ident, "silly", [1, 8]],
127
+ [:paren, [:params, nil, nil, nil, nil, nil]],
128
+ [:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
129
+ input_tree.should transform_to(output_tree)
130
+ end
131
+
132
+ it 'should not transform singleton names when an appropriate local variable exists' do
133
+ input_tree =
134
+ [:program,
135
+ [[:assign,
136
+ [:var_field, [:@ident, "foo", [1, 0]]],
137
+ [:var_ref, [:@kw, "self", [1, 6]]]],
138
+ [:defs,
139
+ [:var_ref, [:@ident, "foo", [1, 16]]],
140
+ [:@period, ".", [1, 19]],
141
+ [:@ident, "silly", [1, 20]],
142
+ [:params, nil, nil, nil, nil, nil],
143
+ [:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
144
+ input_tree.should transform_to(input_tree)
145
+ end
146
+
147
+ it 'does not transform the tricky x = 5 unless defined?(x) case' do
148
+ input_tree =
149
+ [:program,
150
+ [[:unless_mod,
151
+ [:defined, [:var_ref, [:@ident, "x", [1, 22]]]],
152
+ [:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]]]]]
153
+ input_tree.should transform_to(input_tree)
154
+ end
155
+
156
+ it 'finds all LHS vars before transforming an MLHS' do
157
+ input_tree =
158
+ [:program,
159
+ [[:massign,
160
+ [[:@ident, "a", [1, 0]],
161
+ [:@const, "B", [1, 3]],
162
+ [:mlhs_paren,
163
+ [:mlhs_add_star,
164
+ [[:@gvar, "$c", [1, 7]]],
165
+ [:@ident, "rest", [1, 12]],
166
+ [[:@ident, "d", [1, 18]], [:@ident, "e", [1, 21]]]]]],
167
+ [:method_add_arg,
168
+ [:fcall, [:@ident, "foo", [1, 26]]],
169
+ [:arg_paren,
170
+ [:args_add_block,
171
+ [[:var_ref, [:@ident, "a", [1, 30]]],
172
+ [:var_ref, [:@ident, "c", [1, 33]]],
173
+ [:var_ref, [:@ident, "d", [1, 36]]]],
174
+ false]]]]]]
175
+ output_tree =
176
+ [:program,
177
+ [[:massign,
178
+ [[:@ident, "a", [1, 0]],
179
+ [:@const, "B", [1, 3]],
180
+ [:mlhs_paren,
181
+ [:mlhs_add_star,
182
+ [[:@gvar, "$c", [1, 7]]],
183
+ [:@ident, "rest", [1, 12]],
184
+ [[:@ident, "d", [1, 18]], [:@ident, "e", [1, 21]]]]]],
185
+ [:method_add_arg,
186
+ [:fcall, [:@ident, "foo", [1, 26]]],
187
+ [:arg_paren,
188
+ [:args_add_block,
189
+ [[:var_ref, [:@ident, "a", [1, 30]]],
190
+ [:zcall, [:@ident, "c", [1, 33]]],
191
+ [:var_ref, [:@ident, "d", [1, 36]]]],
192
+ false]]]]]]
193
+ input_tree.should transform_to(output_tree)
194
+ end
195
+
196
+ it 'creates for-loop MLHS vars before transforming the iteratee' do
197
+ input_tree =
198
+ [:program,
199
+ [[:for,
200
+ [[:@ident, "a", [1, 4]],
201
+ [:@ident, "b", [1, 7]],
202
+ [:mlhs_paren,
203
+ [[:@ident, "c", [1, 11]],
204
+ [:mlhs_paren,
205
+ [:mlhs_add_star,
206
+ [],
207
+ [:@ident, "d", [1, 16]],
208
+ [[:@ident, "e", [1, 19]]]]]]]],
209
+ [:method_add_arg,
210
+ [:fcall, [:@ident, "foo", [1, 26]]],
211
+ [:arg_paren,
212
+ [:args_add_block,
213
+ [[:var_ref, [:@ident, "a", [1, 30]]],
214
+ [:var_ref, [:@ident, "b", [1, 33]]],
215
+ [:var_ref, [:@ident, "c", [1, 36]]],
216
+ [:var_ref, [:@ident, "d", [1, 39]]],
217
+ [:var_ref, [:@ident, "e", [1, 42]]],
218
+ [:var_ref, [:@ident, "f", [1, 45]]],
219
+ [:var_ref, [:@ident, "g", [1, 48]]]],
220
+ false]]],
221
+ [[:void_stmt]]]]]
222
+ output_tree =
223
+ [:program,
224
+ [[:for,
225
+ [[:@ident, "a", [1, 4]],
226
+ [:@ident, "b", [1, 7]],
227
+ [:mlhs_paren,
228
+ [[:@ident, "c", [1, 11]],
229
+ [:mlhs_paren,
230
+ [:mlhs_add_star,
231
+ [],
232
+ [:@ident, "d", [1, 16]],
233
+ [[:@ident, "e", [1, 19]]]]]]]],
234
+ [:method_add_arg,
235
+ [:fcall, [:@ident, "foo", [1, 26]]],
236
+ [:arg_paren,
237
+ [:args_add_block,
238
+ [[:var_ref, [:@ident, "a", [1, 30]]],
239
+ [:var_ref, [:@ident, "b", [1, 33]]],
240
+ [:var_ref, [:@ident, "c", [1, 36]]],
241
+ [:var_ref, [:@ident, "d", [1, 39]]],
242
+ [:var_ref, [:@ident, "e", [1, 42]]],
243
+ [:zcall, [:@ident, "f", [1, 45]]],
244
+ [:zcall, [:@ident, "g", [1, 48]]]],
245
+ false]]],
246
+ [[:void_stmt]]]]]
247
+ input_tree.should transform_to(output_tree)
248
+ end
249
+
250
+ it 'should transform respecting subassignments in block arguments' do
251
+ input_tree =
252
+ [:program,
253
+ [[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "10", [1, 4]]],
254
+ [:method_add_block,
255
+ [:call,
256
+ [:top_const_ref, [:@const, "ARR", [1, 10]]],
257
+ :".",
258
+ [:@ident, "each", [1, 14]]],
259
+ [:brace_block,
260
+ [:block_var,
261
+ [:params,
262
+ [[:@ident, "y", [1, 22]],
263
+ [:mlhs_paren,
264
+ [[:mlhs_paren, [:@ident, "z", [1, 26]]],
265
+ [:mlhs_paren, [:@ident, "a", [1, 29]]]]]],
266
+ [[[:@ident, "k", [1, 33]], [:var_ref, [:@ident, "z", [1, 37]]]],
267
+ [[:@ident, "j", [1, 40]], [:var_ref, [:@ident, "d", [1, 44]]]]],
268
+ [:rest_param, [:@ident, "r", [1, 48]]],
269
+ nil,
270
+ [:blockarg, [:@ident, "blk", [1, 52]]]],
271
+ [[:@ident, "local", [1, 57]]]],
272
+ [[:command,
273
+ [:@ident, "p", [1, 64]],
274
+ [:args_add_block,
275
+ [[:var_ref, [:@ident, "x", [1, 66]]],
276
+ [:var_ref, [:@ident, "y", [1, 69]]],
277
+ [:var_ref, [:@ident, "z", [1, 72]]],
278
+ [:var_ref, [:@ident, "a", [1, 75]]],
279
+ [:var_ref, [:@ident, "k", [1, 78]]],
280
+ [:var_ref, [:@ident, "j", [1, 81]]],
281
+ [:var_ref, [:@ident, "r", [1, 84]]],
282
+ [:var_ref, [:@ident, "blk", [1, 87]]],
283
+ [:var_ref, [:@ident, "local", [1, 92]]],
284
+ [:var_ref, [:@ident, "foo", [1, 99]]]],
285
+ false]]]]]]]
286
+ output_tree =
287
+ [:program,
288
+ [[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "10", [1, 4]]],
289
+ [:method_add_block,
290
+ [:call,
291
+ [:top_const_ref, [:@const, "ARR", [1, 10]]],
292
+ :".",
293
+ [:@ident, "each", [1, 14]]],
294
+ [:brace_block,
295
+ [:block_var,
296
+ [:params,
297
+ [[:@ident, "y", [1, 22]],
298
+ [:mlhs_paren,
299
+ [[:mlhs_paren, [:@ident, "z", [1, 26]]],
300
+ [:mlhs_paren, [:@ident, "a", [1, 29]]]]]],
301
+ [[[:@ident, "k", [1, 33]], [:var_ref, [:@ident, "z", [1, 37]]]],
302
+ [[:@ident, "j", [1, 40]], [:zcall, [:@ident, "d", [1, 44]]]]],
303
+ [:rest_param, [:@ident, "r", [1, 48]]],
304
+ nil,
305
+ [:blockarg, [:@ident, "blk", [1, 52]]]],
306
+ [[:@ident, "local", [1, 57]]]],
307
+ [[:command,
308
+ [:@ident, "p", [1, 64]],
309
+ [:args_add_block,
310
+ [[:var_ref, [:@ident, "x", [1, 66]]],
311
+ [:var_ref, [:@ident, "y", [1, 69]]],
312
+ [:var_ref, [:@ident, "z", [1, 72]]],
313
+ [:var_ref, [:@ident, "a", [1, 75]]],
314
+ [:var_ref, [:@ident, "k", [1, 78]]],
315
+ [:var_ref, [:@ident, "j", [1, 81]]],
316
+ [:var_ref, [:@ident, "r", [1, 84]]],
317
+ [:var_ref, [:@ident, "blk", [1, 87]]],
318
+ [:var_ref, [:@ident, "local", [1, 92]]],
319
+ [:zcall, [:@ident, "foo", [1, 99]]]],
320
+ false]]]]]]]
321
+ input_tree.should transform_to(output_tree)
322
+ end
323
+
324
+ it 'does not let block variables escape into enclosing scopes' do
325
+ input_tree =
326
+ [:program,
327
+ [[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
328
+ [:method_add_block,
329
+ [:call, [:array, nil], :".", [:@ident, "each", [1, 10]]],
330
+ [:brace_block,
331
+ [:block_var,
332
+ [:params, [[:@ident, "y", [1, 18]]], nil, nil, nil, nil],
333
+ [[:@ident, "z", [1, 20]], [:@ident, "x", [1, 23]]]],
334
+ [[:var_ref, [:@ident, "x", [1, 26]]],
335
+ [:var_ref, [:@ident, "y", [1, 28]]],
336
+ [:var_ref, [:@ident, "z", [1, 30]]]]]],
337
+ [:var_ref, [:@ident, "x", [1, 36]]],
338
+ [:var_ref, [:@ident, "y", [1, 39]]],
339
+ [:var_ref, [:@ident, "z", [1, 42]]]]]
340
+ output_tree =
341
+ [:program,
342
+ [[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
343
+ [:method_add_block,
344
+ [:call, [:array, nil], :".", [:@ident, "each", [1, 10]]],
345
+ [:brace_block,
346
+ [:block_var,
347
+ [:params, [[:@ident, "y", [1, 18]]], nil, nil, nil, nil],
348
+ [[:@ident, "z", [1, 20]], [:@ident, "x", [1, 23]]]],
349
+ [[:var_ref, [:@ident, "x", [1, 26]]],
350
+ [:var_ref, [:@ident, "y", [1, 28]]],
351
+ [:var_ref, [:@ident, "z", [1, 30]]]]]],
352
+ [:var_ref, [:@ident, "x", [1, 36]]],
353
+ [:zcall, [:@ident, "y", [1, 39]]],
354
+ [:zcall, [:@ident, "z", [1, 42]]]]]
355
+ input_tree.should transform_to(output_tree)
356
+ end
357
+
358
+ it 'creates closed scopes when classes are created' do
359
+ input_tree =
360
+ [:program,
361
+ [[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
362
+ [:class,
363
+ [:const_ref, [:@const, "Foo", [1, 13]]],
364
+ nil,
365
+ [:bodystmt, [[:var_ref, [:@ident, "x", [1, 18]]]], nil, nil, nil]],
366
+ [:var_ref, [:@ident, "x", [1, 26]]]]]
367
+ output_tree =
368
+ [:program,
369
+ [[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
370
+ [:class,
371
+ [:const_ref, [:@const, "Foo", [1, 13]]],
372
+ nil,
373
+ [:bodystmt, [[:zcall, [:@ident, "x", [1, 18]]]], nil, nil, nil]],
374
+ [:var_ref, [:@ident, "x", [1, 26]]]]]
375
+ input_tree.should transform_to(output_tree)
376
+ end
377
+
378
+ it 'uses the previous scope for superclass specification' do
379
+ input_tree =
380
+ [:program,
381
+ [[:assign,
382
+ [:var_field, [:@ident, "x", [1, 0]]],
383
+ [:var_ref, [:@const, "String", [1, 4]]]],
384
+ [:class,
385
+ [:const_ref, [:@const, "A", [1, 18]]],
386
+ [:var_ref, [:@ident, "x", [1, 22]]],
387
+ [:bodystmt, [[:var_ref, [:@ident, "x", [1, 25]]]], nil, nil, nil]]]]
388
+ output_tree =
389
+ [:program,
390
+ [[:assign,
391
+ [:var_field, [:@ident, "x", [1, 0]]],
392
+ [:var_ref, [:@const, "String", [1, 4]]]],
393
+ [:class,
394
+ [:const_ref, [:@const, "A", [1, 18]]],
395
+ [:var_ref, [:@ident, "x", [1, 22]]],
396
+ [:bodystmt, [[:zcall, [:@ident, "x", [1, 25]]]], nil, nil, nil]]]]
397
+ input_tree.should transform_to output_tree
398
+ end
399
+
400
+ it 'creates closed scopes when modules are created' do
401
+ input_tree =
402
+ [:program,
403
+ [[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
404
+ [:module,
405
+ [:const_ref, [:@const, "Foo", [1, 14]]],
406
+ [:bodystmt,
407
+ [[:void_stmt], [:var_ref, [:@ident, "x", [1, 19]]]],
408
+ nil,
409
+ nil,
410
+ nil]],
411
+ [:var_ref, [:@ident, "x", [1, 27]]]]]
412
+ output_tree =
413
+ [:program,
414
+ [[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
415
+ [:module,
416
+ [:const_ref, [:@const, "Foo", [1, 14]]],
417
+ [:bodystmt,
418
+ [[:void_stmt], [:zcall, [:@ident, "x", [1, 19]]]],
419
+ nil,
420
+ nil,
421
+ nil]],
422
+ [:var_ref, [:@ident, "x", [1, 27]]]]]
423
+ input_tree.should transform_to output_tree
424
+ end
425
+
426
+ it 'creates closed scopes when singleton classes are opened' do
427
+ input_tree =
428
+ [:program,
429
+ [[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
430
+ [:sclass,
431
+ [:var_ref, [:@const, "String", [1, 16]]],
432
+ [:bodystmt, [[:var_ref, [:@ident, "x", [1, 24]]]], nil, nil, nil]]]]
433
+ output_tree =
434
+ [:program,
435
+ [[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
436
+ [:sclass,
437
+ [:var_ref, [:@const, "String", [1, 16]]],
438
+ [:bodystmt, [[:zcall, [:@ident, "x", [1, 24]]]], nil, nil, nil]]]]
439
+ input_tree.should transform_to output_tree
440
+ end
441
+
442
+ it 'refers to the enclosing environment to determine the singleton to open' do
443
+ input_tree =
444
+ [:program,
445
+ [[:assign,
446
+ [:var_field, [:@ident, "x", [1, 0]]],
447
+ [:string_literal, [:string_content, [:@tstring_content, "hi", [1, 5]]]]],
448
+ [:sclass,
449
+ [:var_ref, [:@ident, "x", [1, 19]]],
450
+ [:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
451
+ input_tree.should transform_to input_tree
452
+ end
453
+
454
+ it 'refers to the enclosing environment to determine the singleton to open - resulting in zcall' do
455
+ input_tree =
456
+ [:program,
457
+ [[:sclass,
458
+ [:var_ref, [:@ident, "x", [1, 19]]],
459
+ [:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
460
+ output_tree =
461
+ [:program,
462
+ [[:sclass,
463
+ [:zcall, [:@ident, "x", [1, 19]]],
464
+ [:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
465
+ input_tree.should transform_to output_tree
466
+ end
467
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ripper-plus
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 1.0.0
6
+ platform: ruby
7
+ authors:
8
+ - Michael Edgar
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-27 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &id001 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 2.3.0
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: yard
28
+ requirement: &id002 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: bundler
39
+ requirement: &id003 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 1.0.0
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: jeweler
50
+ requirement: &id004 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 1.5.2
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: rcov
61
+ requirement: &id005 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *id005
70
+ description: |-
71
+ Ripper is the Ruby parser library packaged
72
+ with Ruby 1.9. While quite complete, it has some quirks that can
73
+ make use frustrating. This gem intends to correct them.
74
+ email: michael.j.edgar@dartmouth.edu
75
+ executables: []
76
+
77
+ extensions: []
78
+
79
+ extra_rdoc_files:
80
+ - LICENSE.txt
81
+ - README.md
82
+ files:
83
+ - .document
84
+ - .rspec
85
+ - Gemfile
86
+ - LICENSE.txt
87
+ - README.md
88
+ - Rakefile
89
+ - VERSION
90
+ - lib/ripper-plus.rb
91
+ - lib/ripper-plus/ripper-plus.rb
92
+ - lib/ripper-plus/scope_stack.rb
93
+ - lib/ripper-plus/transformer.rb
94
+ - spec/ripper-plus_spec.rb
95
+ - spec/scope_stack_spec.rb
96
+ - spec/spec_helper.rb
97
+ - spec/transformer_spec.rb
98
+ homepage: http://github.com/michaeledgar/ripper-plus
99
+ licenses:
100
+ - MIT
101
+ post_install_message:
102
+ rdoc_options: []
103
+
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ hash: 505813799173151565
112
+ segments:
113
+ - 0
114
+ version: "0"
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: "0"
121
+ requirements: []
122
+
123
+ rubyforge_project:
124
+ rubygems_version: 1.7.2
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Parses Ruby code into an improved Ripper AST format
128
+ test_files:
129
+ - spec/ripper-plus_spec.rb
130
+ - spec/scope_stack_spec.rb
131
+ - spec/spec_helper.rb
132
+ - spec/transformer_spec.rb