irb 1.7.1 → 1.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.document +1 -1
- data/Gemfile +10 -1
- data/README.md +265 -20
- data/Rakefile +13 -10
- data/doc/irb/irb.rd.ja +1 -3
- data/irb.gemspec +2 -1
- data/lib/irb/cmd/nop.rb +3 -52
- data/lib/irb/color.rb +4 -2
- data/lib/irb/command/backtrace.rb +17 -0
- data/lib/irb/command/base.rb +62 -0
- data/lib/irb/command/break.rb +17 -0
- data/lib/irb/command/catch.rb +17 -0
- data/lib/irb/command/chws.rb +40 -0
- data/lib/irb/command/context.rb +16 -0
- data/lib/irb/{cmd → command}/continue.rb +3 -3
- data/lib/irb/command/debug.rb +71 -0
- data/lib/irb/{cmd → command}/delete.rb +3 -3
- data/lib/irb/command/disable_irb.rb +19 -0
- data/lib/irb/command/edit.rb +63 -0
- data/lib/irb/command/exit.rb +18 -0
- data/lib/irb/{cmd → command}/finish.rb +3 -3
- data/lib/irb/command/force_exit.rb +18 -0
- data/lib/irb/command/help.rb +83 -0
- data/lib/irb/command/history.rb +45 -0
- data/lib/irb/command/info.rb +17 -0
- data/lib/irb/command/internal_helpers.rb +27 -0
- data/lib/irb/{cmd → command}/irb_info.rb +7 -7
- data/lib/irb/{cmd → command}/load.rb +23 -8
- data/lib/irb/{cmd → command}/ls.rb +42 -19
- data/lib/irb/{cmd → command}/measure.rb +18 -17
- data/lib/irb/{cmd → command}/next.rb +3 -3
- data/lib/irb/command/pushws.rb +65 -0
- data/lib/irb/command/show_doc.rb +51 -0
- data/lib/irb/command/show_source.rb +74 -0
- data/lib/irb/{cmd → command}/step.rb +3 -3
- data/lib/irb/command/subirb.rb +123 -0
- data/lib/irb/{cmd → command}/whereami.rb +3 -5
- data/lib/irb/command.rb +23 -0
- data/lib/irb/completion.rb +133 -102
- data/lib/irb/context.rb +182 -66
- data/lib/irb/debug/ui.rb +103 -0
- data/lib/irb/{cmd/debug.rb → debug.rb} +53 -59
- data/lib/irb/default_commands.rb +265 -0
- data/lib/irb/easter-egg.rb +16 -6
- data/lib/irb/ext/change-ws.rb +6 -8
- data/lib/irb/ext/{history.rb → eval_history.rb} +7 -7
- data/lib/irb/ext/loader.rb +4 -4
- data/lib/irb/ext/multi-irb.rb +5 -5
- data/lib/irb/ext/tracer.rb +12 -51
- data/lib/irb/ext/use-loader.rb +6 -8
- data/lib/irb/ext/workspaces.rb +10 -34
- data/lib/irb/frame.rb +1 -1
- data/lib/irb/help.rb +3 -3
- data/lib/irb/helper_method/base.rb +16 -0
- data/lib/irb/helper_method/conf.rb +11 -0
- data/lib/irb/helper_method.rb +29 -0
- data/lib/irb/{ext/save-history.rb → history.rb} +20 -58
- data/lib/irb/init.rb +154 -58
- data/lib/irb/input-method.rb +238 -203
- data/lib/irb/inspector.rb +3 -3
- data/lib/irb/lc/error.rb +1 -11
- data/lib/irb/lc/help-message +4 -0
- data/lib/irb/lc/ja/error.rb +1 -11
- data/lib/irb/lc/ja/help-message +13 -0
- data/lib/irb/locale.rb +2 -2
- data/lib/irb/nesting_parser.rb +13 -3
- data/lib/irb/notifier.rb +1 -1
- data/lib/irb/output-method.rb +2 -8
- data/lib/irb/pager.rb +91 -0
- data/lib/irb/ruby-lex.rb +391 -471
- data/lib/irb/ruby_logo.aa +43 -0
- data/lib/irb/source_finder.rb +139 -0
- data/lib/irb/statement.rb +80 -0
- data/lib/irb/version.rb +3 -3
- data/lib/irb/workspace.rb +24 -4
- data/lib/irb/ws-for-case-2.rb +1 -1
- data/lib/irb/xmp.rb +3 -3
- data/lib/irb.rb +1232 -604
- data/man/irb.1 +7 -0
- metadata +60 -32
- data/lib/irb/cmd/backtrace.rb +0 -21
- data/lib/irb/cmd/break.rb +0 -21
- data/lib/irb/cmd/catch.rb +0 -21
- data/lib/irb/cmd/chws.rb +0 -36
- data/lib/irb/cmd/edit.rb +0 -61
- data/lib/irb/cmd/help.rb +0 -23
- data/lib/irb/cmd/info.rb +0 -21
- data/lib/irb/cmd/pushws.rb +0 -45
- data/lib/irb/cmd/show_cmds.rb +0 -39
- data/lib/irb/cmd/show_doc.rb +0 -48
- data/lib/irb/cmd/show_source.rb +0 -113
- data/lib/irb/cmd/subirb.rb +0 -66
- data/lib/irb/extend-command.rb +0 -356
- data/lib/irb/src_encoding.rb +0 -7
data/lib/irb/ruby-lex.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# frozen_string_literal:
|
1
|
+
# frozen_string_literal: true
|
2
2
|
#
|
3
3
|
# irb/ruby-lex.rb - ruby lexcal analyzer
|
4
4
|
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
|
@@ -8,547 +8,467 @@ require "ripper"
|
|
8
8
|
require "jruby" if RUBY_ENGINE == "jruby"
|
9
9
|
require_relative "nesting_parser"
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
module IRB
|
12
|
+
# :stopdoc:
|
13
|
+
class RubyLex
|
14
|
+
ASSIGNMENT_NODE_TYPES = [
|
15
|
+
# Local, instance, global, class, constant, instance, and index assignment:
|
16
|
+
# "foo = bar",
|
17
|
+
# "@foo = bar",
|
18
|
+
# "$foo = bar",
|
19
|
+
# "@@foo = bar",
|
20
|
+
# "::Foo = bar",
|
21
|
+
# "a::Foo = bar",
|
22
|
+
# "Foo = bar"
|
23
|
+
# "foo.bar = 1"
|
24
|
+
# "foo[1] = bar"
|
25
|
+
:assign,
|
26
|
+
|
27
|
+
# Operation assignment:
|
28
|
+
# "foo += bar"
|
29
|
+
# "foo -= bar"
|
30
|
+
# "foo ||= bar"
|
31
|
+
# "foo &&= bar"
|
32
|
+
:opassign,
|
33
|
+
|
34
|
+
# Multiple assignment:
|
35
|
+
# "foo, bar = 1, 2
|
36
|
+
:massign,
|
37
|
+
]
|
38
|
+
|
39
|
+
class TerminateLineInput < StandardError
|
40
|
+
def initialize
|
41
|
+
super("Terminate Line Input")
|
42
|
+
end
|
17
43
|
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def initialize(context)
|
21
|
-
@context = context
|
22
|
-
@line_no = 1
|
23
|
-
@prompt = nil
|
24
|
-
end
|
25
44
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
45
|
+
def self.compile_with_errors_suppressed(code, line_no: 1)
|
46
|
+
begin
|
47
|
+
result = yield code, line_no
|
48
|
+
rescue ArgumentError
|
49
|
+
# Ruby can issue an error for the code if there is an
|
50
|
+
# incomplete magic comment for encoding in it. Force an
|
51
|
+
# expression with a new line before the code in this
|
52
|
+
# case to prevent magic comment handling. To make sure
|
53
|
+
# line numbers in the lexed code remain the same,
|
54
|
+
# decrease the line number by one.
|
55
|
+
code = ";\n#{code}"
|
56
|
+
line_no -= 1
|
57
|
+
result = yield code, line_no
|
58
|
+
end
|
59
|
+
result
|
39
60
|
end
|
40
|
-
result
|
41
|
-
end
|
42
|
-
|
43
|
-
def single_line_command?(code)
|
44
|
-
command = code.split(/\s/, 2).first
|
45
|
-
@context.symbol_alias?(command) || @context.transform_args?(command)
|
46
|
-
end
|
47
61
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
Reline.delete_text
|
61
|
-
rest.bytes.reverse_each do |c|
|
62
|
-
Reline.ungetc(c)
|
63
|
-
end
|
64
|
-
true
|
65
|
-
else
|
66
|
-
false
|
67
|
-
end
|
68
|
-
else
|
69
|
-
# Accept any single-line input for symbol aliases or commands that transform args
|
70
|
-
next true if single_line_command?(code)
|
62
|
+
ERROR_TOKENS = [
|
63
|
+
:on_parse_error,
|
64
|
+
:compile_error,
|
65
|
+
:on_assign_error,
|
66
|
+
:on_alias_error,
|
67
|
+
:on_class_name_error,
|
68
|
+
:on_param_error
|
69
|
+
]
|
70
|
+
|
71
|
+
def self.generate_local_variables_assign_code(local_variables)
|
72
|
+
"#{local_variables.join('=')}=nil;" unless local_variables.empty?
|
73
|
+
end
|
71
74
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
+
# Some part of the code is not included in Ripper's token.
|
76
|
+
# Example: DATA part, token after heredoc_beg when heredoc has unclosed embexpr.
|
77
|
+
# With interpolated tokens, tokens.map(&:tok).join will be equal to code.
|
78
|
+
def self.interpolate_ripper_ignored_tokens(code, tokens)
|
79
|
+
line_positions = [0]
|
80
|
+
code.lines.each do |line|
|
81
|
+
line_positions << line_positions.last + line.bytesize
|
75
82
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
continue = should_continue?(tokens_until_line)
|
89
|
-
prompt(next_opens, continue, line_num_offset)
|
83
|
+
prev_byte_pos = 0
|
84
|
+
interpolated = []
|
85
|
+
prev_line = 1
|
86
|
+
tokens.each do |t|
|
87
|
+
line, col = t.pos
|
88
|
+
byte_pos = line_positions[line - 1] + col
|
89
|
+
if prev_byte_pos < byte_pos
|
90
|
+
tok = code.byteslice(prev_byte_pos...byte_pos)
|
91
|
+
pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]]
|
92
|
+
interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0)
|
93
|
+
prev_line += tok.count("\n")
|
90
94
|
end
|
95
|
+
interpolated << t
|
96
|
+
prev_byte_pos = byte_pos + t.tok.bytesize
|
97
|
+
prev_line += t.tok.count("\n")
|
91
98
|
end
|
92
|
-
|
93
|
-
|
94
|
-
if @io.respond_to?(:auto_indent) and @context.auto_indent_mode
|
95
|
-
@io.auto_indent do |lines, line_index, byte_pointer, is_newline|
|
96
|
-
next nil if lines == [nil] # Workaround for exit IRB with CTRL+d
|
97
|
-
next nil if !is_newline && lines[line_index]&.byteslice(0, byte_pointer)&.match?(/\A\s*\z/)
|
98
|
-
|
99
|
-
code = lines[0..line_index].map { |l| "#{l}\n" }.join
|
100
|
-
tokens = self.class.ripper_lex_without_warning(code, context: @context)
|
101
|
-
process_indent_level(tokens, lines, line_index, is_newline)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def set_prompt(&block)
|
107
|
-
@prompt = block
|
108
|
-
end
|
109
|
-
|
110
|
-
ERROR_TOKENS = [
|
111
|
-
:on_parse_error,
|
112
|
-
:compile_error,
|
113
|
-
:on_assign_error,
|
114
|
-
:on_alias_error,
|
115
|
-
:on_class_name_error,
|
116
|
-
:on_param_error
|
117
|
-
]
|
118
|
-
|
119
|
-
def self.generate_local_variables_assign_code(local_variables)
|
120
|
-
"#{local_variables.join('=')}=nil;" unless local_variables.empty?
|
121
|
-
end
|
122
|
-
|
123
|
-
# Some part of the code is not included in Ripper's token.
|
124
|
-
# Example: DATA part, token after heredoc_beg when heredoc has unclosed embexpr.
|
125
|
-
# With interpolated tokens, tokens.map(&:tok).join will be equal to code.
|
126
|
-
def self.interpolate_ripper_ignored_tokens(code, tokens)
|
127
|
-
line_positions = [0]
|
128
|
-
code.lines.each do |line|
|
129
|
-
line_positions << line_positions.last + line.bytesize
|
130
|
-
end
|
131
|
-
prev_byte_pos = 0
|
132
|
-
interpolated = []
|
133
|
-
prev_line = 1
|
134
|
-
tokens.each do |t|
|
135
|
-
line, col = t.pos
|
136
|
-
byte_pos = line_positions[line - 1] + col
|
137
|
-
if prev_byte_pos < byte_pos
|
138
|
-
tok = code.byteslice(prev_byte_pos...byte_pos)
|
99
|
+
if prev_byte_pos < code.bytesize
|
100
|
+
tok = code.byteslice(prev_byte_pos..)
|
139
101
|
pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]]
|
140
102
|
interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0)
|
141
|
-
prev_line += tok.count("\n")
|
142
103
|
end
|
143
|
-
interpolated
|
144
|
-
prev_byte_pos = byte_pos + t.tok.bytesize
|
145
|
-
prev_line += t.tok.count("\n")
|
104
|
+
interpolated
|
146
105
|
end
|
147
|
-
if prev_byte_pos < code.bytesize
|
148
|
-
tok = code.byteslice(prev_byte_pos..)
|
149
|
-
pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]]
|
150
|
-
interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0)
|
151
|
-
end
|
152
|
-
interpolated
|
153
|
-
end
|
154
106
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
107
|
+
def self.ripper_lex_without_warning(code, local_variables: [])
|
108
|
+
verbose, $VERBOSE = $VERBOSE, nil
|
109
|
+
lvars_code = generate_local_variables_assign_code(local_variables)
|
110
|
+
original_code = code
|
111
|
+
if lvars_code
|
112
|
+
code = "#{lvars_code}\n#{code}"
|
113
|
+
line_no = 0
|
114
|
+
else
|
115
|
+
line_no = 1
|
116
|
+
end
|
165
117
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
118
|
+
compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no|
|
119
|
+
lexer = Ripper::Lexer.new(inner_code, '-', line_no)
|
120
|
+
tokens = []
|
121
|
+
lexer.scan.each do |t|
|
122
|
+
next if t.pos.first == 0
|
123
|
+
prev_tk = tokens.last
|
124
|
+
position_overlapped = prev_tk && t.pos[0] == prev_tk.pos[0] && t.pos[1] < prev_tk.pos[1] + prev_tk.tok.bytesize
|
125
|
+
if position_overlapped
|
126
|
+
tokens[-1] = t if ERROR_TOKENS.include?(prev_tk.event) && !ERROR_TOKENS.include?(t.event)
|
127
|
+
else
|
128
|
+
tokens << t
|
129
|
+
end
|
177
130
|
end
|
131
|
+
interpolate_ripper_ignored_tokens(original_code, tokens)
|
178
132
|
end
|
179
|
-
|
133
|
+
ensure
|
134
|
+
$VERBOSE = verbose
|
180
135
|
end
|
181
|
-
ensure
|
182
|
-
$VERBOSE = verbose
|
183
|
-
end
|
184
|
-
|
185
|
-
def prompt(opens, continue, line_num_offset)
|
186
|
-
ltype = ltype_from_open_tokens(opens)
|
187
|
-
indent_level = calc_indent_level(opens)
|
188
|
-
@prompt&.call(ltype, indent_level, opens.any? || continue, @line_no + line_num_offset)
|
189
|
-
end
|
190
|
-
|
191
|
-
def check_code_state(code)
|
192
|
-
check_target_code = code.gsub(/\s*\z/, '').concat("\n")
|
193
|
-
tokens = self.class.ripper_lex_without_warning(check_target_code, context: @context)
|
194
|
-
opens = IRB::NestingParser.open_tokens(tokens)
|
195
|
-
[tokens, opens, code_terminated?(code, tokens, opens)]
|
196
|
-
end
|
197
136
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
when :recoverable_error
|
203
|
-
false
|
204
|
-
when :other_error
|
205
|
-
opens.empty? && !should_continue?(tokens)
|
206
|
-
when :valid
|
207
|
-
!should_continue?(tokens)
|
137
|
+
def check_code_state(code, local_variables:)
|
138
|
+
tokens = self.class.ripper_lex_without_warning(code, local_variables: local_variables)
|
139
|
+
opens = NestingParser.open_tokens(tokens)
|
140
|
+
[tokens, opens, code_terminated?(code, tokens, opens, local_variables: local_variables)]
|
208
141
|
end
|
209
|
-
end
|
210
|
-
|
211
|
-
def save_prompt_to_context_io(opens, continue, line_num_offset)
|
212
|
-
# Implicitly saves prompt string to `@context.io.prompt`. This will be used in the next `@input.call`.
|
213
|
-
prompt(opens, continue, line_num_offset)
|
214
|
-
end
|
215
142
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
line = @input.call
|
227
|
-
unless line
|
228
|
-
return code.empty? ? nil : code
|
143
|
+
def code_terminated?(code, tokens, opens, local_variables:)
|
144
|
+
case check_code_syntax(code, local_variables: local_variables)
|
145
|
+
when :unrecoverable_error
|
146
|
+
true
|
147
|
+
when :recoverable_error
|
148
|
+
false
|
149
|
+
when :other_error
|
150
|
+
opens.empty? && !should_continue?(tokens)
|
151
|
+
when :valid
|
152
|
+
!should_continue?(tokens)
|
229
153
|
end
|
230
|
-
|
231
|
-
code << line
|
232
|
-
# Accept any single-line input for symbol aliases or commands that transform args
|
233
|
-
return code if single_line_command?(code)
|
234
|
-
|
235
|
-
tokens, opens, terminated = check_code_state(code)
|
236
|
-
return code if terminated
|
237
|
-
|
238
|
-
line_offset += 1
|
239
|
-
continue = should_continue?(tokens)
|
240
|
-
save_prompt_to_context_io(opens, continue, line_offset)
|
241
154
|
end
|
242
|
-
end
|
243
155
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
break unless code
|
156
|
+
def assignment_expression?(code, local_variables:)
|
157
|
+
# Try to parse the code and check if the last of possibly multiple
|
158
|
+
# expressions is an assignment type.
|
248
159
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
160
|
+
# If the expression is invalid, Ripper.sexp should return nil which will
|
161
|
+
# result in false being returned. Any valid expression should return an
|
162
|
+
# s-expression where the second element of the top level array is an
|
163
|
+
# array of parsed expressions. The first element of each expression is the
|
164
|
+
# expression's type.
|
165
|
+
verbose, $VERBOSE = $VERBOSE, nil
|
166
|
+
code = "#{RubyLex.generate_local_variables_assign_code(local_variables) || 'nil;'}\n#{code}"
|
167
|
+
# Get the last node_type of the line. drop(1) is to ignore the local_variables_assign_code part.
|
168
|
+
node_type = Ripper.sexp(code)&.dig(1)&.drop(1)&.dig(-1, 0)
|
169
|
+
ASSIGNMENT_NODE_TYPES.include?(node_type)
|
170
|
+
ensure
|
171
|
+
$VERBOSE = verbose
|
255
172
|
end
|
256
|
-
end
|
257
173
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
174
|
+
def should_continue?(tokens)
|
175
|
+
# Look at the last token and check if IRB need to continue reading next line.
|
176
|
+
# Example code that should continue: `a\` `a +` `a.`
|
177
|
+
# Trailing spaces, newline, comments are skipped
|
178
|
+
return true if tokens.last&.event == :on_sp && tokens.last.tok == "\\\n"
|
179
|
+
|
180
|
+
tokens.reverse_each do |token|
|
181
|
+
case token.event
|
182
|
+
when :on_sp, :on_nl, :on_ignored_nl, :on_comment, :on_embdoc_beg, :on_embdoc, :on_embdoc_end
|
183
|
+
# Skip
|
184
|
+
when :on_regexp_end, :on_heredoc_end, :on_semicolon
|
185
|
+
# State is EXPR_BEG but should not continue
|
186
|
+
return false
|
187
|
+
else
|
188
|
+
# Endless range should not continue
|
189
|
+
return false if token.event == :on_op && token.tok.match?(/\A\.\.\.?\z/)
|
274
190
|
|
275
|
-
|
276
|
-
|
191
|
+
# EXPR_DOT and most of the EXPR_BEG should continue
|
192
|
+
return token.state.anybits?(Ripper::EXPR_BEG | Ripper::EXPR_DOT)
|
193
|
+
end
|
277
194
|
end
|
195
|
+
false
|
278
196
|
end
|
279
|
-
false
|
280
|
-
end
|
281
197
|
|
282
|
-
|
283
|
-
|
284
|
-
|
198
|
+
def check_code_syntax(code, local_variables:)
|
199
|
+
lvars_code = RubyLex.generate_local_variables_assign_code(local_variables)
|
200
|
+
code = "#{lvars_code}\n#{code}"
|
285
201
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
202
|
+
begin # check if parser error are available
|
203
|
+
verbose, $VERBOSE = $VERBOSE, nil
|
204
|
+
case RUBY_ENGINE
|
205
|
+
when 'ruby'
|
206
|
+
self.class.compile_with_errors_suppressed(code) do |inner_code, line_no|
|
207
|
+
RubyVM::InstructionSequence.compile(inner_code, nil, nil, line_no)
|
208
|
+
end
|
209
|
+
when 'jruby'
|
210
|
+
JRuby.compile_ir(code)
|
211
|
+
else
|
212
|
+
catch(:valid) do
|
213
|
+
eval("BEGIN { throw :valid, true }\n#{code}")
|
214
|
+
false
|
215
|
+
end
|
292
216
|
end
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
217
|
+
rescue EncodingError
|
218
|
+
# This is for a hash with invalid encoding symbol, {"\xAE": 1}
|
219
|
+
:unrecoverable_error
|
220
|
+
rescue SyntaxError => e
|
221
|
+
case e.message
|
222
|
+
when /unexpected keyword_end/
|
223
|
+
# "syntax error, unexpected keyword_end"
|
224
|
+
#
|
225
|
+
# example:
|
226
|
+
# if (
|
227
|
+
# end
|
228
|
+
#
|
229
|
+
# example:
|
230
|
+
# end
|
231
|
+
return :unrecoverable_error
|
232
|
+
when /unexpected '\.'/
|
233
|
+
# "syntax error, unexpected '.'"
|
234
|
+
#
|
235
|
+
# example:
|
236
|
+
# .
|
237
|
+
return :unrecoverable_error
|
238
|
+
when /unexpected tREGEXP_BEG/
|
239
|
+
# "syntax error, unexpected tREGEXP_BEG, expecting keyword_do or '{' or '('"
|
240
|
+
#
|
241
|
+
# example:
|
242
|
+
# method / f /
|
243
|
+
return :unrecoverable_error
|
244
|
+
when /unterminated (?:string|regexp) meets end of file/
|
245
|
+
# "unterminated regexp meets end of file"
|
246
|
+
#
|
247
|
+
# example:
|
248
|
+
# /
|
249
|
+
#
|
250
|
+
# "unterminated string meets end of file"
|
251
|
+
#
|
252
|
+
# example:
|
253
|
+
# '
|
254
|
+
return :recoverable_error
|
255
|
+
when /unexpected end-of-input/
|
256
|
+
# "syntax error, unexpected end-of-input, expecting keyword_end"
|
257
|
+
#
|
258
|
+
# example:
|
259
|
+
# if true
|
260
|
+
# hoge
|
261
|
+
# if false
|
262
|
+
# fuga
|
263
|
+
# end
|
264
|
+
return :recoverable_error
|
265
|
+
else
|
266
|
+
return :other_error
|
299
267
|
end
|
268
|
+
ensure
|
269
|
+
$VERBOSE = verbose
|
300
270
|
end
|
301
|
-
|
302
|
-
# This is for a hash with invalid encoding symbol, {"\xAE": 1}
|
303
|
-
:unrecoverable_error
|
304
|
-
rescue SyntaxError => e
|
305
|
-
case e.message
|
306
|
-
when /unterminated (?:string|regexp) meets end of file/
|
307
|
-
# "unterminated regexp meets end of file"
|
308
|
-
#
|
309
|
-
# example:
|
310
|
-
# /
|
311
|
-
#
|
312
|
-
# "unterminated string meets end of file"
|
313
|
-
#
|
314
|
-
# example:
|
315
|
-
# '
|
316
|
-
return :recoverable_error
|
317
|
-
when /syntax error, unexpected end-of-input/
|
318
|
-
# "syntax error, unexpected end-of-input, expecting keyword_end"
|
319
|
-
#
|
320
|
-
# example:
|
321
|
-
# if true
|
322
|
-
# hoge
|
323
|
-
# if false
|
324
|
-
# fuga
|
325
|
-
# end
|
326
|
-
return :recoverable_error
|
327
|
-
when /syntax error, unexpected keyword_end/
|
328
|
-
# "syntax error, unexpected keyword_end"
|
329
|
-
#
|
330
|
-
# example:
|
331
|
-
# if (
|
332
|
-
# end
|
333
|
-
#
|
334
|
-
# example:
|
335
|
-
# end
|
336
|
-
return :unrecoverable_error
|
337
|
-
when /syntax error, unexpected '\.'/
|
338
|
-
# "syntax error, unexpected '.'"
|
339
|
-
#
|
340
|
-
# example:
|
341
|
-
# .
|
342
|
-
return :unrecoverable_error
|
343
|
-
when /unexpected tREGEXP_BEG/
|
344
|
-
# "syntax error, unexpected tREGEXP_BEG, expecting keyword_do or '{' or '('"
|
345
|
-
#
|
346
|
-
# example:
|
347
|
-
# method / f /
|
348
|
-
return :unrecoverable_error
|
349
|
-
else
|
350
|
-
return :other_error
|
351
|
-
end
|
352
|
-
ensure
|
353
|
-
$VERBOSE = verbose
|
271
|
+
:valid
|
354
272
|
end
|
355
|
-
:valid
|
356
|
-
end
|
357
273
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
274
|
+
def calc_indent_level(opens)
|
275
|
+
indent_level = 0
|
276
|
+
opens.each_with_index do |t, index|
|
277
|
+
case t.event
|
278
|
+
when :on_heredoc_beg
|
279
|
+
if opens[index + 1]&.event != :on_heredoc_beg
|
280
|
+
if t.tok.match?(/^<<[~-]/)
|
281
|
+
indent_level += 1
|
282
|
+
else
|
283
|
+
indent_level = 0
|
284
|
+
end
|
368
285
|
end
|
286
|
+
when :on_tstring_beg, :on_regexp_beg, :on_symbeg, :on_backtick
|
287
|
+
# No indent: "", //, :"", ``
|
288
|
+
# Indent: %(), %r(), %i(), %x()
|
289
|
+
indent_level += 1 if t.tok.start_with? '%'
|
290
|
+
when :on_embdoc_beg
|
291
|
+
indent_level = 0
|
292
|
+
else
|
293
|
+
indent_level += 1 unless t.tok == 'alias' || t.tok == 'undef'
|
369
294
|
end
|
370
|
-
when :on_tstring_beg, :on_regexp_beg, :on_symbeg, :on_backtick
|
371
|
-
# can be indented if t.tok starts with `%`
|
372
|
-
when :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_embexpr_beg
|
373
|
-
# can be indented but not indented in current implementation
|
374
|
-
when :on_embdoc_beg
|
375
|
-
indent_level = 0
|
376
|
-
else
|
377
|
-
indent_level += 1
|
378
295
|
end
|
296
|
+
indent_level
|
379
297
|
end
|
380
|
-
indent_level
|
381
|
-
end
|
382
298
|
|
383
|
-
|
299
|
+
FREE_INDENT_TOKENS = %i[on_tstring_beg on_backtick on_regexp_beg on_symbeg]
|
384
300
|
|
385
|
-
|
386
|
-
|
387
|
-
|
301
|
+
def free_indent_token?(token)
|
302
|
+
FREE_INDENT_TOKENS.include?(token&.event)
|
303
|
+
end
|
388
304
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
305
|
+
# Calculates the difference of pasted code's indent and indent calculated from tokens
|
306
|
+
def indent_difference(lines, line_results, line_index)
|
307
|
+
loop do
|
308
|
+
_tokens, prev_opens, _next_opens, min_depth = line_results[line_index]
|
309
|
+
open_token = prev_opens.last
|
310
|
+
if !open_token || (open_token.event != :on_heredoc_beg && !free_indent_token?(open_token))
|
311
|
+
# If the leading whitespace is an indent, return the difference
|
312
|
+
indent_level = calc_indent_level(prev_opens.take(min_depth))
|
313
|
+
calculated_indent = 2 * indent_level
|
314
|
+
actual_indent = lines[line_index][/^ */].size
|
315
|
+
return actual_indent - calculated_indent
|
316
|
+
elsif open_token.event == :on_heredoc_beg && open_token.tok.match?(/^<<[^-~]/)
|
317
|
+
return 0
|
318
|
+
end
|
319
|
+
# If the leading whitespace is not an indent but part of a multiline token
|
320
|
+
# Calculate base_indent of the multiline token's beginning line
|
321
|
+
line_index = open_token.pos[0] - 1
|
402
322
|
end
|
403
|
-
# If the leading whitespace is not an indent but part of a multiline token
|
404
|
-
# Calculate base_indent of the multiline token's beginning line
|
405
|
-
line_index = open_token.pos[0] - 1
|
406
323
|
end
|
407
|
-
end
|
408
324
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
325
|
+
def process_indent_level(tokens, lines, line_index, is_newline)
|
326
|
+
line_results = NestingParser.parse_by_line(tokens)
|
327
|
+
result = line_results[line_index]
|
328
|
+
if result
|
329
|
+
_tokens, prev_opens, next_opens, min_depth = result
|
330
|
+
else
|
331
|
+
# When last line is empty
|
332
|
+
prev_opens = next_opens = line_results.last[2]
|
333
|
+
min_depth = next_opens.size
|
334
|
+
end
|
419
335
|
|
420
|
-
|
421
|
-
|
422
|
-
|
336
|
+
# To correctly indent line like `end.map do`, we use shortest open tokens on each line for indent calculation.
|
337
|
+
# Shortest open tokens can be calculated by `opens.take(min_depth)`
|
338
|
+
indent = 2 * calc_indent_level(prev_opens.take(min_depth))
|
423
339
|
|
424
|
-
|
340
|
+
preserve_indent = lines[line_index - (is_newline ? 1 : 0)][/^ */].size
|
425
341
|
|
426
|
-
|
427
|
-
|
342
|
+
prev_open_token = prev_opens.last
|
343
|
+
next_open_token = next_opens.last
|
428
344
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
else
|
436
|
-
base_indent = 0
|
437
|
-
end
|
438
|
-
|
439
|
-
if free_indent_token?(prev_open_token)
|
440
|
-
if is_newline && prev_open_token.pos[0] == line_index
|
441
|
-
# First newline inside free-indent token
|
442
|
-
base_indent + indent
|
443
|
-
else
|
444
|
-
# Accept any number of indent inside free-indent token
|
445
|
-
preserve_indent
|
446
|
-
end
|
447
|
-
elsif prev_open_token&.event == :on_embdoc_beg || next_open_token&.event == :on_embdoc_beg
|
448
|
-
if prev_open_token&.event == next_open_token&.event
|
449
|
-
# Accept any number of indent inside embdoc content
|
450
|
-
preserve_indent
|
345
|
+
# Calculates base indent for pasted code on the line where prev_open_token is located
|
346
|
+
# irb(main):001:1* if a # base_indent is 2, indent calculated from tokens is 0
|
347
|
+
# irb(main):002:1* if b # base_indent is 6, indent calculated from tokens is 2
|
348
|
+
# irb(main):003:0> c # base_indent is 6, indent calculated from tokens is 4
|
349
|
+
if prev_open_token
|
350
|
+
base_indent = [0, indent_difference(lines, line_results, prev_open_token.pos[0] - 1)].max
|
451
351
|
else
|
452
|
-
|
453
|
-
0
|
352
|
+
base_indent = 0
|
454
353
|
end
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
tok.match?(/^<<[-~]/) ? base_indent + indent : indent
|
461
|
-
elsif tok.match?(/^<<~/)
|
462
|
-
# Accept extra indent spaces inside `<<~` heredoc
|
463
|
-
[base_indent + indent, preserve_indent].max
|
354
|
+
|
355
|
+
if free_indent_token?(prev_open_token)
|
356
|
+
if is_newline && prev_open_token.pos[0] == line_index
|
357
|
+
# First newline inside free-indent token
|
358
|
+
base_indent + indent
|
464
359
|
else
|
465
|
-
# Accept any number of indent inside
|
360
|
+
# Accept any number of indent inside free-indent token
|
361
|
+
preserve_indent
|
362
|
+
end
|
363
|
+
elsif prev_open_token&.event == :on_embdoc_beg || next_open_token&.event == :on_embdoc_beg
|
364
|
+
if prev_open_token&.event == next_open_token&.event
|
365
|
+
# Accept any number of indent inside embdoc content
|
466
366
|
preserve_indent
|
367
|
+
else
|
368
|
+
# =begin or =end
|
369
|
+
0
|
370
|
+
end
|
371
|
+
elsif prev_open_token&.event == :on_heredoc_beg
|
372
|
+
tok = prev_open_token.tok
|
373
|
+
if prev_opens.size <= next_opens.size
|
374
|
+
if is_newline && lines[line_index].empty? && line_results[line_index - 1][1].last != next_open_token
|
375
|
+
# First line in heredoc
|
376
|
+
tok.match?(/^<<[-~]/) ? base_indent + indent : indent
|
377
|
+
elsif tok.match?(/^<<~/)
|
378
|
+
# Accept extra indent spaces inside `<<~` heredoc
|
379
|
+
[base_indent + indent, preserve_indent].max
|
380
|
+
else
|
381
|
+
# Accept any number of indent inside other heredoc
|
382
|
+
preserve_indent
|
383
|
+
end
|
384
|
+
else
|
385
|
+
# Heredoc close
|
386
|
+
prev_line_indent_level = calc_indent_level(prev_opens)
|
387
|
+
tok.match?(/^<<[~-]/) ? base_indent + 2 * (prev_line_indent_level - 1) : 0
|
467
388
|
end
|
468
389
|
else
|
469
|
-
|
470
|
-
prev_line_indent_level = calc_indent_level(prev_opens)
|
471
|
-
tok.match?(/^<<[~-]/) ? base_indent + 2 * (prev_line_indent_level - 1) : 0
|
390
|
+
base_indent + indent
|
472
391
|
end
|
473
|
-
else
|
474
|
-
base_indent + indent
|
475
392
|
end
|
476
|
-
end
|
477
393
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
394
|
+
LTYPE_TOKENS = %i[
|
395
|
+
on_heredoc_beg on_tstring_beg
|
396
|
+
on_regexp_beg on_symbeg on_backtick
|
397
|
+
on_symbols_beg on_qsymbols_beg
|
398
|
+
on_words_beg on_qwords_beg
|
399
|
+
]
|
484
400
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
end
|
489
|
-
return nil unless start_token
|
490
|
-
|
491
|
-
case start_token&.event
|
492
|
-
when :on_tstring_beg
|
493
|
-
case start_token&.tok
|
494
|
-
when ?" then ?"
|
495
|
-
when /^%.$/ then ?"
|
496
|
-
when /^%Q.$/ then ?"
|
497
|
-
when ?' then ?'
|
498
|
-
when /^%q.$/ then ?'
|
401
|
+
def ltype_from_open_tokens(opens)
|
402
|
+
start_token = opens.reverse_each.find do |tok|
|
403
|
+
LTYPE_TOKENS.include?(tok.event)
|
499
404
|
end
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
else
|
511
|
-
nil
|
512
|
-
end
|
513
|
-
end
|
514
|
-
|
515
|
-
def check_termination_in_prev_line(code)
|
516
|
-
tokens = self.class.ripper_lex_without_warning(code, context: @context)
|
517
|
-
past_first_newline = false
|
518
|
-
index = tokens.rindex do |t|
|
519
|
-
# traverse first token before last line
|
520
|
-
if past_first_newline
|
521
|
-
if t.tok.include?("\n")
|
522
|
-
true
|
405
|
+
return nil unless start_token
|
406
|
+
|
407
|
+
case start_token&.event
|
408
|
+
when :on_tstring_beg
|
409
|
+
case start_token&.tok
|
410
|
+
when ?" then ?"
|
411
|
+
when /^%.$/ then ?"
|
412
|
+
when /^%Q.$/ then ?"
|
413
|
+
when ?' then ?'
|
414
|
+
when /^%q.$/ then ?'
|
523
415
|
end
|
524
|
-
|
525
|
-
|
526
|
-
|
416
|
+
when :on_regexp_beg then ?/
|
417
|
+
when :on_symbeg then ?:
|
418
|
+
when :on_backtick then ?`
|
419
|
+
when :on_qwords_beg then ?]
|
420
|
+
when :on_words_beg then ?]
|
421
|
+
when :on_qsymbols_beg then ?]
|
422
|
+
when :on_symbols_beg then ?]
|
423
|
+
when :on_heredoc_beg
|
424
|
+
start_token&.tok =~ /<<[-~]?(['"`])\w+\1/
|
425
|
+
$1 || ?"
|
527
426
|
else
|
528
|
-
|
427
|
+
nil
|
529
428
|
end
|
530
429
|
end
|
531
430
|
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
431
|
+
def check_termination_in_prev_line(code, local_variables:)
|
432
|
+
tokens = self.class.ripper_lex_without_warning(code, local_variables: local_variables)
|
433
|
+
past_first_newline = false
|
434
|
+
index = tokens.rindex do |t|
|
435
|
+
# traverse first token before last line
|
436
|
+
if past_first_newline
|
437
|
+
if t.tok.include?("\n")
|
438
|
+
true
|
439
|
+
end
|
440
|
+
elsif t.tok.include?("\n")
|
441
|
+
past_first_newline = true
|
442
|
+
false
|
443
|
+
else
|
444
|
+
false
|
539
445
|
end
|
540
446
|
end
|
541
447
|
|
542
|
-
if
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
448
|
+
if index
|
449
|
+
first_token = nil
|
450
|
+
last_line_tokens = tokens[(index + 1)..(tokens.size - 1)]
|
451
|
+
last_line_tokens.each do |t|
|
452
|
+
unless [:on_sp, :on_ignored_sp, :on_comment].include?(t.event)
|
453
|
+
first_token = t
|
454
|
+
break
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
if first_token && first_token.state != Ripper::EXPR_DOT
|
459
|
+
tokens_without_last_line = tokens[0..index]
|
460
|
+
code_without_last_line = tokens_without_last_line.map(&:tok).join
|
461
|
+
opens_without_last_line = NestingParser.open_tokens(tokens_without_last_line)
|
462
|
+
if code_terminated?(code_without_last_line, tokens_without_last_line, opens_without_last_line, local_variables: local_variables)
|
463
|
+
return last_line_tokens.map(&:tok).join
|
464
|
+
end
|
548
465
|
end
|
549
466
|
end
|
467
|
+
false
|
550
468
|
end
|
551
|
-
false
|
552
469
|
end
|
470
|
+
# :startdoc:
|
553
471
|
end
|
554
|
-
|
472
|
+
|
473
|
+
RubyLex = IRB::RubyLex
|
474
|
+
Object.deprecate_constant(:RubyLex)
|