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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.document +1 -1
  3. data/Gemfile +10 -1
  4. data/README.md +265 -20
  5. data/Rakefile +13 -10
  6. data/doc/irb/irb.rd.ja +1 -3
  7. data/irb.gemspec +2 -1
  8. data/lib/irb/cmd/nop.rb +3 -52
  9. data/lib/irb/color.rb +4 -2
  10. data/lib/irb/command/backtrace.rb +17 -0
  11. data/lib/irb/command/base.rb +62 -0
  12. data/lib/irb/command/break.rb +17 -0
  13. data/lib/irb/command/catch.rb +17 -0
  14. data/lib/irb/command/chws.rb +40 -0
  15. data/lib/irb/command/context.rb +16 -0
  16. data/lib/irb/{cmd → command}/continue.rb +3 -3
  17. data/lib/irb/command/debug.rb +71 -0
  18. data/lib/irb/{cmd → command}/delete.rb +3 -3
  19. data/lib/irb/command/disable_irb.rb +19 -0
  20. data/lib/irb/command/edit.rb +63 -0
  21. data/lib/irb/command/exit.rb +18 -0
  22. data/lib/irb/{cmd → command}/finish.rb +3 -3
  23. data/lib/irb/command/force_exit.rb +18 -0
  24. data/lib/irb/command/help.rb +83 -0
  25. data/lib/irb/command/history.rb +45 -0
  26. data/lib/irb/command/info.rb +17 -0
  27. data/lib/irb/command/internal_helpers.rb +27 -0
  28. data/lib/irb/{cmd → command}/irb_info.rb +7 -7
  29. data/lib/irb/{cmd → command}/load.rb +23 -8
  30. data/lib/irb/{cmd → command}/ls.rb +42 -19
  31. data/lib/irb/{cmd → command}/measure.rb +18 -17
  32. data/lib/irb/{cmd → command}/next.rb +3 -3
  33. data/lib/irb/command/pushws.rb +65 -0
  34. data/lib/irb/command/show_doc.rb +51 -0
  35. data/lib/irb/command/show_source.rb +74 -0
  36. data/lib/irb/{cmd → command}/step.rb +3 -3
  37. data/lib/irb/command/subirb.rb +123 -0
  38. data/lib/irb/{cmd → command}/whereami.rb +3 -5
  39. data/lib/irb/command.rb +23 -0
  40. data/lib/irb/completion.rb +133 -102
  41. data/lib/irb/context.rb +182 -66
  42. data/lib/irb/debug/ui.rb +103 -0
  43. data/lib/irb/{cmd/debug.rb → debug.rb} +53 -59
  44. data/lib/irb/default_commands.rb +265 -0
  45. data/lib/irb/easter-egg.rb +16 -6
  46. data/lib/irb/ext/change-ws.rb +6 -8
  47. data/lib/irb/ext/{history.rb → eval_history.rb} +7 -7
  48. data/lib/irb/ext/loader.rb +4 -4
  49. data/lib/irb/ext/multi-irb.rb +5 -5
  50. data/lib/irb/ext/tracer.rb +12 -51
  51. data/lib/irb/ext/use-loader.rb +6 -8
  52. data/lib/irb/ext/workspaces.rb +10 -34
  53. data/lib/irb/frame.rb +1 -1
  54. data/lib/irb/help.rb +3 -3
  55. data/lib/irb/helper_method/base.rb +16 -0
  56. data/lib/irb/helper_method/conf.rb +11 -0
  57. data/lib/irb/helper_method.rb +29 -0
  58. data/lib/irb/{ext/save-history.rb → history.rb} +20 -58
  59. data/lib/irb/init.rb +154 -58
  60. data/lib/irb/input-method.rb +238 -203
  61. data/lib/irb/inspector.rb +3 -3
  62. data/lib/irb/lc/error.rb +1 -11
  63. data/lib/irb/lc/help-message +4 -0
  64. data/lib/irb/lc/ja/error.rb +1 -11
  65. data/lib/irb/lc/ja/help-message +13 -0
  66. data/lib/irb/locale.rb +2 -2
  67. data/lib/irb/nesting_parser.rb +13 -3
  68. data/lib/irb/notifier.rb +1 -1
  69. data/lib/irb/output-method.rb +2 -8
  70. data/lib/irb/pager.rb +91 -0
  71. data/lib/irb/ruby-lex.rb +391 -471
  72. data/lib/irb/ruby_logo.aa +43 -0
  73. data/lib/irb/source_finder.rb +139 -0
  74. data/lib/irb/statement.rb +80 -0
  75. data/lib/irb/version.rb +3 -3
  76. data/lib/irb/workspace.rb +24 -4
  77. data/lib/irb/ws-for-case-2.rb +1 -1
  78. data/lib/irb/xmp.rb +3 -3
  79. data/lib/irb.rb +1232 -604
  80. data/man/irb.1 +7 -0
  81. metadata +60 -32
  82. data/lib/irb/cmd/backtrace.rb +0 -21
  83. data/lib/irb/cmd/break.rb +0 -21
  84. data/lib/irb/cmd/catch.rb +0 -21
  85. data/lib/irb/cmd/chws.rb +0 -36
  86. data/lib/irb/cmd/edit.rb +0 -61
  87. data/lib/irb/cmd/help.rb +0 -23
  88. data/lib/irb/cmd/info.rb +0 -21
  89. data/lib/irb/cmd/pushws.rb +0 -45
  90. data/lib/irb/cmd/show_cmds.rb +0 -39
  91. data/lib/irb/cmd/show_doc.rb +0 -48
  92. data/lib/irb/cmd/show_source.rb +0 -113
  93. data/lib/irb/cmd/subirb.rb +0 -66
  94. data/lib/irb/extend-command.rb +0 -356
  95. data/lib/irb/src_encoding.rb +0 -7
data/lib/irb/ruby-lex.rb CHANGED
@@ -1,4 +1,4 @@
1
- # frozen_string_literal: false
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
- # :stopdoc:
12
- class RubyLex
13
-
14
- class TerminateLineInput < StandardError
15
- def initialize
16
- super("Terminate Line Input")
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
- def self.compile_with_errors_suppressed(code, line_no: 1)
27
- begin
28
- result = yield code, line_no
29
- rescue ArgumentError
30
- # Ruby can issue an error for the code if there is an
31
- # incomplete magic comment for encoding in it. Force an
32
- # expression with a new line before the code in this
33
- # case to prevent magic comment handling. To make sure
34
- # line numbers in the lexed code remain the same,
35
- # decrease the line number by one.
36
- code = ";\n#{code}"
37
- line_no -= 1
38
- result = yield code, line_no
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
- # io functions
49
- def set_input(&block)
50
- @input = block
51
- end
52
-
53
- def configure_io(io)
54
- @io = io
55
- if @io.respond_to?(:check_termination)
56
- @io.check_termination do |code|
57
- if Reline::IOGate.in_pasting?
58
- rest = check_termination_in_prev_line(code)
59
- if rest
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
- _tokens, _opens, terminated = check_code_state(code)
73
- terminated
74
- end
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
- end
77
- if @io.respond_to?(:dynamic_prompt)
78
- @io.dynamic_prompt do |lines|
79
- lines << '' if lines.empty?
80
- tokens = self.class.ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join, context: @context)
81
- line_results = IRB::NestingParser.parse_by_line(tokens)
82
- tokens_until_line = []
83
- line_results.map.with_index do |(line_tokens, _prev_opens, next_opens, _min_depth), line_num_offset|
84
- line_tokens.each do |token, _s|
85
- # Avoid appending duplicated token. Tokens that include "\n" like multiline tstring_content can exist in multiple lines.
86
- tokens_until_line << token if token != tokens_until_line.last
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
- end
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 << t
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
- def self.ripper_lex_without_warning(code, context: nil)
156
- verbose, $VERBOSE = $VERBOSE, nil
157
- lvars_code = generate_local_variables_assign_code(context&.local_variables || [])
158
- original_code = code
159
- if lvars_code
160
- code = "#{lvars_code}\n#{code}"
161
- line_no = 0
162
- else
163
- line_no = 1
164
- end
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
- compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no|
167
- lexer = Ripper::Lexer.new(inner_code, '-', line_no)
168
- tokens = []
169
- lexer.scan.each do |t|
170
- next if t.pos.first == 0
171
- prev_tk = tokens.last
172
- position_overlapped = prev_tk && t.pos[0] == prev_tk.pos[0] && t.pos[1] < prev_tk.pos[1] + prev_tk.tok.bytesize
173
- if position_overlapped
174
- tokens[-1] = t if ERROR_TOKENS.include?(prev_tk.event) && !ERROR_TOKENS.include?(t.event)
175
- else
176
- tokens << t
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
- interpolate_ripper_ignored_tokens(original_code, tokens)
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
- def code_terminated?(code, tokens, opens)
199
- case check_code_syntax(code)
200
- when :unrecoverable_error
201
- true
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
- def readmultiline
217
- save_prompt_to_context_io([], false, 0)
218
-
219
- # multiline
220
- return @input.call if @io.respond_to?(:check_termination)
221
-
222
- # nomultiline
223
- code = ''
224
- line_offset = 0
225
- loop do
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
- def each_top_level_statement
245
- loop do
246
- code = readmultiline
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
- if code != "\n"
250
- code.force_encoding(@io.encoding)
251
- yield code, @line_no
252
- end
253
- @line_no += code.count("\n")
254
- rescue TerminateLineInput
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
- def should_continue?(tokens)
259
- # Look at the last token and check if IRB need to continue reading next line.
260
- # Example code that should continue: `a\` `a +` `a.`
261
- # Trailing spaces, newline, comments are skipped
262
- return true if tokens.last&.event == :on_sp && tokens.last.tok == "\\\n"
263
-
264
- tokens.reverse_each do |token|
265
- case token.event
266
- when :on_sp, :on_nl, :on_ignored_nl, :on_comment, :on_embdoc_beg, :on_embdoc, :on_embdoc_end
267
- # Skip
268
- when :on_regexp_end, :on_heredoc_end, :on_semicolon
269
- # State is EXPR_BEG but should not continue
270
- return false
271
- else
272
- # Endless range should not continue
273
- return false if token.event == :on_op && token.tok.match?(/\A\.\.\.?\z/)
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
- # EXPR_DOT and most of the EXPR_BEG should continue
276
- return token.state.anybits?(Ripper::EXPR_BEG | Ripper::EXPR_DOT)
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
- def check_code_syntax(code)
283
- lvars_code = RubyLex.generate_local_variables_assign_code(@context.local_variables)
284
- code = "#{lvars_code}\n#{code}"
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
- begin # check if parser error are available
287
- verbose, $VERBOSE = $VERBOSE, nil
288
- case RUBY_ENGINE
289
- when 'ruby'
290
- self.class.compile_with_errors_suppressed(code) do |inner_code, line_no|
291
- RubyVM::InstructionSequence.compile(inner_code, nil, nil, line_no)
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
- when 'jruby'
294
- JRuby.compile_ir(code)
295
- else
296
- catch(:valid) do
297
- eval("BEGIN { throw :valid, true }\n#{code}")
298
- false
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
- rescue EncodingError
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
- def calc_indent_level(opens)
359
- indent_level = 0
360
- opens.each_with_index do |t, index|
361
- case t.event
362
- when :on_heredoc_beg
363
- if opens[index + 1]&.event != :on_heredoc_beg
364
- if t.tok.match?(/^<<[~-]/)
365
- indent_level += 1
366
- else
367
- indent_level = 0
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
- FREE_INDENT_TOKENS = %i[on_tstring_beg on_backtick on_regexp_beg on_symbeg]
299
+ FREE_INDENT_TOKENS = %i[on_tstring_beg on_backtick on_regexp_beg on_symbeg]
384
300
 
385
- def free_indent_token?(token)
386
- FREE_INDENT_TOKENS.include?(token&.event)
387
- end
301
+ def free_indent_token?(token)
302
+ FREE_INDENT_TOKENS.include?(token&.event)
303
+ end
388
304
 
389
- # Calculates the difference of pasted code's indent and indent calculated from tokens
390
- def indent_difference(lines, line_results, line_index)
391
- loop do
392
- _tokens, prev_opens, _next_opens, min_depth = line_results[line_index]
393
- open_token = prev_opens.last
394
- if !open_token || (open_token.event != :on_heredoc_beg && !free_indent_token?(open_token))
395
- # If the leading whitespace is an indent, return the difference
396
- indent_level = calc_indent_level(prev_opens.take(min_depth))
397
- calculated_indent = 2 * indent_level
398
- actual_indent = lines[line_index][/^ */].size
399
- return actual_indent - calculated_indent
400
- elsif open_token.event == :on_heredoc_beg && open_token.tok.match?(/^<<[^-~]/)
401
- return 0
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
- def process_indent_level(tokens, lines, line_index, is_newline)
410
- line_results = IRB::NestingParser.parse_by_line(tokens)
411
- result = line_results[line_index]
412
- if result
413
- _tokens, prev_opens, next_opens, min_depth = result
414
- else
415
- # When last line is empty
416
- prev_opens = next_opens = line_results.last[2]
417
- min_depth = next_opens.size
418
- end
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
- # To correctly indent line like `end.map do`, we use shortest open tokens on each line for indent calculation.
421
- # Shortest open tokens can be calculated by `opens.take(min_depth)`
422
- indent = 2 * calc_indent_level(prev_opens.take(min_depth))
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
- preserve_indent = lines[line_index - (is_newline ? 1 : 0)][/^ */].size
340
+ preserve_indent = lines[line_index - (is_newline ? 1 : 0)][/^ */].size
425
341
 
426
- prev_open_token = prev_opens.last
427
- next_open_token = next_opens.last
342
+ prev_open_token = prev_opens.last
343
+ next_open_token = next_opens.last
428
344
 
429
- # Calculates base indent for pasted code on the line where prev_open_token is located
430
- # irb(main):001:1* if a # base_indent is 2, indent calculated from tokens is 0
431
- # irb(main):002:1* if b # base_indent is 6, indent calculated from tokens is 2
432
- # irb(main):003:0> c # base_indent is 6, indent calculated from tokens is 4
433
- if prev_open_token
434
- base_indent = [0, indent_difference(lines, line_results, prev_open_token.pos[0] - 1)].max
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
- # =begin or =end
453
- 0
352
+ base_indent = 0
454
353
  end
455
- elsif prev_open_token&.event == :on_heredoc_beg
456
- tok = prev_open_token.tok
457
- if prev_opens.size <= next_opens.size
458
- if is_newline && lines[line_index].empty? && line_results[line_index - 1][1].last != next_open_token
459
- # First line in heredoc
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 other heredoc
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
- # Heredoc close
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
- LTYPE_TOKENS = %i[
479
- on_heredoc_beg on_tstring_beg
480
- on_regexp_beg on_symbeg on_backtick
481
- on_symbols_beg on_qsymbols_beg
482
- on_words_beg on_qwords_beg
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
- def ltype_from_open_tokens(opens)
486
- start_token = opens.reverse_each.find do |tok|
487
- LTYPE_TOKENS.include?(tok.event)
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
- when :on_regexp_beg then ?/
501
- when :on_symbeg then ?:
502
- when :on_backtick then ?`
503
- when :on_qwords_beg then ?]
504
- when :on_words_beg then ?]
505
- when :on_qsymbols_beg then ?]
506
- when :on_symbols_beg then ?]
507
- when :on_heredoc_beg
508
- start_token&.tok =~ /<<[-~]?(['"`])\w+\1/
509
- $1 || ?"
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
- elsif t.tok.include?("\n")
525
- past_first_newline = true
526
- false
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
- false
427
+ nil
529
428
  end
530
429
  end
531
430
 
532
- if index
533
- first_token = nil
534
- last_line_tokens = tokens[(index + 1)..(tokens.size - 1)]
535
- last_line_tokens.each do |t|
536
- unless [:on_sp, :on_ignored_sp, :on_comment].include?(t.event)
537
- first_token = t
538
- break
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 first_token && first_token.state != Ripper::EXPR_DOT
543
- tokens_without_last_line = tokens[0..index]
544
- code_without_last_line = tokens_without_last_line.map(&:tok).join
545
- opens_without_last_line = IRB::NestingParser.open_tokens(tokens_without_last_line)
546
- if code_terminated?(code_without_last_line, tokens_without_last_line, opens_without_last_line)
547
- return last_line_tokens.map(&:tok).join
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
- # :startdoc:
472
+
473
+ RubyLex = IRB::RubyLex
474
+ Object.deprecate_constant(:RubyLex)