irb 1.7.1 → 1.13.2

Sign up to get free protection for your applications and to get access to all the features.
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)