sourcify 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/HISTORY.txt +4 -0
- data/LICENSE +20 -0
- data/README.rdoc +154 -0
- data/Rakefile +114 -0
- data/VERSION +1 -0
- data/lib/sourcify.rb +12 -0
- data/lib/sourcify/proc.rb +53 -0
- data/lib/sourcify/proc/counter.rb +41 -0
- data/lib/sourcify/proc/lexer.rb +40 -0
- data/lib/sourcify/proc/lexer18.rb +224 -0
- data/lib/sourcify/proc/lexer19.rb +195 -0
- data/lib/sourcify/proc/parser.rb +74 -0
- data/sourcify.gemspec +109 -0
- data/spec/proc/19x_extras.rb +27 -0
- data/spec/proc/readme +5 -0
- data/spec/proc/to_sexp_variables_spec.rb +146 -0
- data/spec/proc/to_source_from_braced_block_w_nested_braced_block_spec.rb +33 -0
- data/spec/proc/to_source_from_braced_block_w_nested_hash_spec.rb +34 -0
- data/spec/proc/to_source_from_braced_block_wo_nesting_complication_spec.rb +46 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_begin_spec.rb +35 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_case_spec.rb +35 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_class_spec.rb +89 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_do_end_block_spec.rb +33 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_for_spec.rb +132 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_if_spec.rb +73 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_method_spec.rb +33 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_module_spec.rb +49 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_unless_spec.rb +73 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_until_spec.rb +176 -0
- data/spec/proc/to_source_from_do_end_block_w_nested_while_spec.rb +176 -0
- data/spec/proc/to_source_from_do_end_block_wo_nesting_complication_spec.rb +46 -0
- data/spec/proc/to_source_from_multi_blocks_w_many_matches_spec.rb +73 -0
- data/spec/proc/to_source_from_multi_blocks_w_single_match_spec.rb +31 -0
- data/spec/proc/to_source_from_multi_do_end_blocks_w_single_match_spec.rb +31 -0
- data/spec/proc/to_source_magic_file_var_spec.rb +127 -0
- data/spec/proc/to_source_magic_line_var_spec.rb +127 -0
- data/spec/proc/to_source_variables_spec.rb +29 -0
- data/spec/spec_helper.rb +41 -0
- metadata +159 -0
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'irb/ruby-lex'
|
2
|
+
require 'irb/ruby-token'
|
3
|
+
|
4
|
+
module Sourcify
|
5
|
+
module Proc
|
6
|
+
|
7
|
+
class Lexer18
|
8
|
+
|
9
|
+
# Implementation of this class has been inspired by the discussion at
|
10
|
+
# http://www.justskins.com/forums/breaking-ruby-code-into-117453.html
|
11
|
+
|
12
|
+
include Lexer::Commons
|
13
|
+
|
14
|
+
def initialize(io, file, line)
|
15
|
+
@file, @line, @io, @pos = file, line, io, io.pos
|
16
|
+
@lex = RubyLex.new
|
17
|
+
@lex.set_input(@io)
|
18
|
+
@lex.get_readed
|
19
|
+
end
|
20
|
+
|
21
|
+
def lex
|
22
|
+
(@tokens = []).extend(Extensions)
|
23
|
+
@magic_lines = []
|
24
|
+
|
25
|
+
while @tk = @lex.token
|
26
|
+
tkc = @tk.class.to_s.sub(/\ARubyToken::/, '').downcase.to_sym
|
27
|
+
@tokens << [@tk.line_no, @tk.char_no, tkc]
|
28
|
+
post_qstring if @qstring_data
|
29
|
+
send(:"on_#{tkc}") rescue NoMethodError
|
30
|
+
@lex.get_readed if tkc == :tknl
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_tknl
|
35
|
+
raise EndOfLine unless @results.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_tk__line__
|
39
|
+
@magic_lines << [@tk.seek, @tk.line_no + @line]
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_tkdstring
|
43
|
+
@qstring_data = [@tk.seek, @tk.line_no + @line]
|
44
|
+
end
|
45
|
+
|
46
|
+
alias_method :on_tkstring, :on_tkdstring # heredoc
|
47
|
+
alias_method :on_tkdregexp, :on_tkdstring # regexp
|
48
|
+
alias_method :on_tkdxstring, :on_tkdstring # ` command
|
49
|
+
|
50
|
+
def post_qstring
|
51
|
+
seek, line = @qstring_data
|
52
|
+
@magic_lines << [(seek .. @tk.seek), line]
|
53
|
+
@qstring_data = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_tkdo
|
57
|
+
if !@do_end_counter.started?
|
58
|
+
@do_end_counter.marker = @tk.seek
|
59
|
+
@do_end_counter.increment_start
|
60
|
+
elsif @tokens.same_as_curr_line.keywords(:tkfor, :tkwhile, :tkuntil).empty?
|
61
|
+
# It is possible for a 'for', 'while' or 'until' to have an attached 'do',
|
62
|
+
# for such a case, we want to skip it
|
63
|
+
@do_end_counter.increment_start
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def on_tkend
|
68
|
+
if @do_end_counter.started? && @do_end_counter.increment_end.telly?
|
69
|
+
@result = grab_result_and_reset_lex(@do_end_counter.marker, 3)
|
70
|
+
@is_multiline_block = @tokens.multiline?
|
71
|
+
raise EndOfBlock
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_tkclass
|
76
|
+
# Pretty straightforward for these, each of them will consume an 'end' close it
|
77
|
+
@do_end_counter.increment_start if @do_end_counter.started?
|
78
|
+
end
|
79
|
+
|
80
|
+
# These work the same as 'class', the exception is 'for', which can have an optional
|
81
|
+
# 'do' attached:
|
82
|
+
# * for a in [1,2] do ... end
|
83
|
+
# * for a in [1,2] \n ... end
|
84
|
+
%w{def module begin case for}.each{|kw| alias_method :"on_tk#{kw}", :on_tkclass }
|
85
|
+
|
86
|
+
def on_tkwhile
|
87
|
+
# This has optional trailing 'do', and can work as a modifier as well, eg:
|
88
|
+
# * while true do ... end # => 'do' must be on the same line as 'while'
|
89
|
+
# * while true \n ... end
|
90
|
+
# * ... while true # => 'while' is pre-pended with non-spaces
|
91
|
+
if @do_end_counter.started? && @tokens.start_of_line?
|
92
|
+
@do_end_counter.increment_start
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# These work exactly the same as 'while'.
|
97
|
+
%w{until if unless}.each{|kw| alias_method :"on_tk#{kw}", :on_tkwhile }
|
98
|
+
|
99
|
+
def on_tklbrace
|
100
|
+
unless @do_end_counter.started?
|
101
|
+
@braced_counter.marker = @tk.seek unless @braced_counter.started?
|
102
|
+
@braced_counter.increment_start
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def on_tkrbrace
|
107
|
+
if @braced_counter.started? && @braced_counter.increment_end.telly?
|
108
|
+
@result = grab_result_and_reset_lex(@braced_counter.marker, 1)
|
109
|
+
@is_multiline_block = @tokens.multiline?
|
110
|
+
raise EndOfBlock
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
alias_method :on_tkflbrace, :on_tklbrace
|
115
|
+
alias_method :on_tkfrbrace, :on_tkrbrace
|
116
|
+
|
117
|
+
def on_tkassoc
|
118
|
+
if @braced_counter.started? && @braced_counter[:start] == 1
|
119
|
+
@braced_counter.decrement_start
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def on_tkgt
|
124
|
+
on_tkassoc if @tokens[-2 .. -1].map(&:last) == [:tkassign, :tkgt]
|
125
|
+
end
|
126
|
+
|
127
|
+
def grab_result_and_reset_lex(marker, offset)
|
128
|
+
@io.seek(@pos+marker)
|
129
|
+
diff = @tk.seek - marker + offset
|
130
|
+
result = replace_magic_lines(@io.read(diff), marker)
|
131
|
+
@io.seek(@pos + diff)
|
132
|
+
@lex.set_input(@io)
|
133
|
+
@lex.get_readed
|
134
|
+
result
|
135
|
+
end
|
136
|
+
|
137
|
+
def replace_magic_lines(result, marker, offset = 0)
|
138
|
+
@magic_lines.inject(result) do |rs, (pos,val)|
|
139
|
+
meth = :"replace_magic_line_by_#{pos.class.to_s.downcase}"
|
140
|
+
n_rs = send(meth, rs, marker + offset, pos, val)
|
141
|
+
offset = result.length - n_rs.length
|
142
|
+
n_rs
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def replace_magic_line_by_fixnum(rs, offset, pos, val)
|
147
|
+
m = rs.match(/^(.{#{pos - offset}})__LINE__(.*)$/m)
|
148
|
+
m[1] + val.pred.to_s + m[2]
|
149
|
+
end
|
150
|
+
|
151
|
+
def replace_magic_line_by_range(rs, offset, pos, lineno)
|
152
|
+
@io.seek(@pos + pos.begin)
|
153
|
+
subject = @io.read(pos.end - pos.begin)
|
154
|
+
return rs if %w{' %q %w}.any?{|q| subject.start_with?(q) }
|
155
|
+
prepend, append = rs.match(/^(.*?)#{Regexp.quote(subject)}(.*)$/m)[1..2]
|
156
|
+
middle = subject.split("\n").each_with_index do |line, i|
|
157
|
+
line.gsub!(pattern = /(.*?\#\{)__LINE__(\})/) do |s|
|
158
|
+
(m = s.match(pattern)[1..2])[0].end_with?('\\#{') ?
|
159
|
+
s : m.insert(1, (lineno + i).pred.to_s).join
|
160
|
+
end
|
161
|
+
end.join("\n")
|
162
|
+
prepend + middle + append
|
163
|
+
end
|
164
|
+
|
165
|
+
# Ease working with the hybrid token set collected from RubyLex
|
166
|
+
module Extensions
|
167
|
+
|
168
|
+
ROW, COL, TYP = 0, 1, 2
|
169
|
+
|
170
|
+
def same_as_curr_line
|
171
|
+
same_line(curr_line)
|
172
|
+
end
|
173
|
+
|
174
|
+
def multiline?
|
175
|
+
self[0][ROW] != self[-1][ROW]
|
176
|
+
end
|
177
|
+
|
178
|
+
def curr_line
|
179
|
+
curr[ROW]
|
180
|
+
end
|
181
|
+
|
182
|
+
def curr
|
183
|
+
self[-1]
|
184
|
+
end
|
185
|
+
|
186
|
+
def same_line(line)
|
187
|
+
(
|
188
|
+
# ignore the current node
|
189
|
+
self[0..-2].reverse.take_while do |e|
|
190
|
+
if e[TYP] == :tsemi
|
191
|
+
false
|
192
|
+
elsif e[ROW] == line
|
193
|
+
true
|
194
|
+
elsif e[ROW] == line.pred && e[TYP] != :tknl
|
195
|
+
line -= 1
|
196
|
+
true
|
197
|
+
end
|
198
|
+
end.reverse
|
199
|
+
).extend(Extensions)
|
200
|
+
end
|
201
|
+
|
202
|
+
def keywords(*types)
|
203
|
+
(
|
204
|
+
types = [types].flatten
|
205
|
+
select{|e| types.include?(e[TYP]) }
|
206
|
+
).extend(Extensions)
|
207
|
+
end
|
208
|
+
|
209
|
+
def non_spaces(*types)
|
210
|
+
(
|
211
|
+
types = [types].flatten
|
212
|
+
reject{|e| types.empty? or types.include?(e[TYP]) }
|
213
|
+
).extend(Extensions)
|
214
|
+
end
|
215
|
+
|
216
|
+
def start_of_line?
|
217
|
+
same_as_curr_line.non_spaces.empty?
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'ripper'
|
2
|
+
|
3
|
+
module Sourcify
|
4
|
+
module Proc
|
5
|
+
class Lexer19 < Ripper::Lexer
|
6
|
+
|
7
|
+
include Lexer::Commons
|
8
|
+
|
9
|
+
def on_nl(token)
|
10
|
+
super.tap do |rs|
|
11
|
+
raise EndOfLine unless @results.empty?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :on_ignored_nl, :on_nl
|
16
|
+
|
17
|
+
def on_kw(token)
|
18
|
+
super.tap do |rs|
|
19
|
+
send(:"on_kw_#{token}", rs) rescue NoMethodError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_kw_class(rs)
|
24
|
+
# Pretty straightforward for these, each of them will consume an 'end' close it
|
25
|
+
@do_end_counter.increment_start if @do_end_counter.started?
|
26
|
+
end
|
27
|
+
|
28
|
+
# These work the same as 'class', the exception is 'for', which can have an optional
|
29
|
+
# 'do' attached:
|
30
|
+
# * for a in [1,2] do ... end
|
31
|
+
# * for a in [1,2] \n ... end
|
32
|
+
%w{def module begin case for}.each{|kw| alias_method :"on_kw_#{kw}", :on_kw_class }
|
33
|
+
|
34
|
+
def on_kw_while(rs)
|
35
|
+
# This has optional trailing 'do', and can work as a modifier as well, eg:
|
36
|
+
# * while true do ... end # => 'do' must be on the same line as 'while'
|
37
|
+
# * while true \n ... end
|
38
|
+
# * ... while true # => 'while' is pre-pended with non-spaces
|
39
|
+
if @do_end_counter.started? && (rs.start_of_line? or rs.within_block?)
|
40
|
+
@do_end_counter.increment_start
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# These work exactly the same as 'while'.
|
45
|
+
%w{until if unless}.each{|kw| alias_method :"on_kw_#{kw}", :on_kw_while }
|
46
|
+
|
47
|
+
def on_kw_do(rs)
|
48
|
+
if !@do_end_counter.started?
|
49
|
+
rs.extend(Extensions) unless rs.respond_to?(:curr)
|
50
|
+
@do_end_counter.marker = rs.curr
|
51
|
+
@do_end_counter.increment_start
|
52
|
+
elsif rs.same_as_curr_line.keywords(%w{for while until}).empty?
|
53
|
+
# It is possible for a 'for', 'while' or 'until' to have an attached 'do',
|
54
|
+
# for such a case, we want to skip it
|
55
|
+
@do_end_counter.increment_start
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def on_kw_end(rs)
|
60
|
+
if @do_end_counter.started? && @do_end_counter.increment_end.telly?
|
61
|
+
@result = rs.to_code(@do_end_counter.marker)
|
62
|
+
@is_multiline_block = rs.multiline?
|
63
|
+
raise EndOfBlock
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def on_lbrace(token)
|
68
|
+
super.tap do |rs|
|
69
|
+
unless @do_end_counter.started?
|
70
|
+
rs.extend(Extensions) unless rs.respond_to?(:curr)
|
71
|
+
@braced_counter.marker = rs.curr unless @braced_counter.started?
|
72
|
+
@braced_counter.increment_start
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def on_rbrace(token)
|
78
|
+
super.tap do |rs|
|
79
|
+
if @braced_counter.started? && @braced_counter.increment_end.telly?
|
80
|
+
@result = rs.to_code(@braced_counter.marker)
|
81
|
+
@is_multiline_block = rs.multiline?
|
82
|
+
raise EndOfBlock
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def on_embexpr_beg(token)
|
88
|
+
super.tap do |rs|
|
89
|
+
@braced_counter.increment_start if @braced_counter.started?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def on_op(token)
|
94
|
+
super.tap do |rs|
|
95
|
+
if @braced_counter.started? && token == '=>' && @braced_counter[:start] == 1
|
96
|
+
@braced_counter.decrement_start
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def on_label(token)
|
102
|
+
super.tap do |rs|
|
103
|
+
if @braced_counter.started? && @braced_counter[:start] == 1
|
104
|
+
@braced_counter.decrement_start
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Ease working with the result set generated by Ripper
|
110
|
+
module Extensions
|
111
|
+
|
112
|
+
POS, TYP, VAL = 0, 1, 2
|
113
|
+
ROW, COL= 0, 1
|
114
|
+
|
115
|
+
def same_as_curr_line
|
116
|
+
same_line(curr_line)
|
117
|
+
end
|
118
|
+
|
119
|
+
def multiline?
|
120
|
+
self[0][POS][ROW] != self[-1][POS][ROW]
|
121
|
+
end
|
122
|
+
|
123
|
+
def curr_line
|
124
|
+
curr[POS][ROW]
|
125
|
+
end
|
126
|
+
|
127
|
+
def curr
|
128
|
+
self[-1]
|
129
|
+
end
|
130
|
+
|
131
|
+
def same_line(line)
|
132
|
+
(
|
133
|
+
# ignore the current node
|
134
|
+
self[0..-2].reverse.take_while do |e|
|
135
|
+
if e[TYP] == :on_semicolon && e[VAL] == ';'
|
136
|
+
false
|
137
|
+
elsif e[POS][ROW] == line
|
138
|
+
true
|
139
|
+
elsif e[TYP] == :on_sp && e[VAL] == "\\\n"
|
140
|
+
line -= 1
|
141
|
+
true
|
142
|
+
end
|
143
|
+
end.reverse
|
144
|
+
).extend(Extensions)
|
145
|
+
end
|
146
|
+
|
147
|
+
def keywords(*types)
|
148
|
+
(
|
149
|
+
types = [types].flatten.map(&:to_s)
|
150
|
+
select{|e| e[TYP] == :on_kw && (types.empty? or types.include?(e[VAL])) }
|
151
|
+
).extend(Extensions)
|
152
|
+
end
|
153
|
+
|
154
|
+
def non_spaces(*types)
|
155
|
+
(
|
156
|
+
types = [types].flatten
|
157
|
+
reject{|e| e[TYP] == :on_sp && (types.empty? or types.include?(e[VAL])) }
|
158
|
+
).extend(Extensions)
|
159
|
+
end
|
160
|
+
|
161
|
+
def start_of_line?
|
162
|
+
same_as_curr_line.non_spaces.empty?
|
163
|
+
end
|
164
|
+
|
165
|
+
def within_block?
|
166
|
+
same_as_curr_line.non_spaces[-1][TYP] == :on_lparen
|
167
|
+
end
|
168
|
+
|
169
|
+
def to_code(marker)
|
170
|
+
heredoc_beg = false # fixing mysteriously missing newline after :on_heredoc_begin
|
171
|
+
self[index(marker) .. -1].map do |e|
|
172
|
+
if e[TYP] == :on_heredoc_beg
|
173
|
+
heredoc_beg = true
|
174
|
+
e[VAL]
|
175
|
+
elsif heredoc_beg && e[TYP] != :on_nl
|
176
|
+
heredoc_beg = false
|
177
|
+
"\n" + e[VAL]
|
178
|
+
else
|
179
|
+
heredoc_beg = false
|
180
|
+
if e[TYP] == :on_label
|
181
|
+
':%s => ' % e[VAL][0..-2]
|
182
|
+
elsif e[TYP] == :on_kw && e[VAL] == '__LINE__'
|
183
|
+
e[POS][ROW]
|
184
|
+
else
|
185
|
+
e[VAL]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end.join
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'sourcify/proc/lexer'
|
2
|
+
require 'sourcify/proc/counter'
|
3
|
+
|
4
|
+
module Sourcify
|
5
|
+
module Proc
|
6
|
+
class Parser
|
7
|
+
|
8
|
+
RUBY_PARSER = RubyParser.new
|
9
|
+
RUBY_2_RUBY = Ruby2Ruby.new
|
10
|
+
|
11
|
+
def initialize(_proc)
|
12
|
+
@binding, @arity = _proc.binding, _proc.arity
|
13
|
+
@file, @line = _proc.source_location
|
14
|
+
end
|
15
|
+
|
16
|
+
def source
|
17
|
+
RUBY_2_RUBY.process(sexp)
|
18
|
+
end
|
19
|
+
|
20
|
+
def sexp
|
21
|
+
@sexp ||= (
|
22
|
+
raw_sexp = RUBY_PARSER.parse(raw_source, @file)
|
23
|
+
Sexp.from_array(replace_with_lvars(raw_sexp.to_a))
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def raw_source
|
30
|
+
@raw_source ||= (
|
31
|
+
frags = Sourcify::Proc::Lexer.new(raw_source_io, @file, @line).work.
|
32
|
+
select{|frag| eval('proc ' + frag).arity == @arity }
|
33
|
+
raise MultipleMatchingProcsPerLineError if frags.size > 1
|
34
|
+
'proc %s' % frags[0]
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def raw_source_io
|
39
|
+
File.open(@file, 'r') do |fh|
|
40
|
+
fh.extend(File::Tail).forward(@line.pred)
|
41
|
+
StringIO.new(fh.readlines.join, 'r')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def replace_with_lvars(array)
|
46
|
+
return array if [:class, :sclass, :defn, :module].include?(array[0])
|
47
|
+
array.map do |e|
|
48
|
+
if e.is_a?(Array)
|
49
|
+
no_arg_method_call_or_lvar(e) or replace_with_lvars(e)
|
50
|
+
else
|
51
|
+
e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def no_arg_method_call_or_lvar(e)
|
57
|
+
if represents_no_arg_call?(e)
|
58
|
+
has_as_local_var?(var = e[2]) ? [:lvar, var] : e
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def represents_no_arg_call?(e)
|
63
|
+
e.size == 4 && e[0..1] == [:call, nil] &&
|
64
|
+
e[3] == [:arglist] && (var = e[2]).is_a?(Symbol)
|
65
|
+
end
|
66
|
+
|
67
|
+
def has_as_local_var?(var)
|
68
|
+
qvar = (@q ||= (RUBY_VERSION.include?('1.9.') ? ":%s" : "'%s'")) % var
|
69
|
+
@binding.eval("local_variables.include?(#{qvar})")
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|