irb 1.14.0 → 1.18.0

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +5 -0
  3. data/CONTRIBUTING.md +52 -0
  4. data/EXTEND_IRB.md +3 -0
  5. data/Gemfile +11 -1
  6. data/README.md +13 -293
  7. data/doc/COMMAND_LINE_OPTIONS.md +69 -0
  8. data/doc/COMPARED_WITH_PRY.md +22 -0
  9. data/doc/Configurations.md +274 -0
  10. data/doc/EXTEND_IRB.md +122 -0
  11. data/doc/Index.md +705 -0
  12. data/doc/irb/irb.rd.ja +1 -1
  13. data/lib/irb/color.rb +251 -162
  14. data/lib/irb/color_printer.rb +10 -9
  15. data/lib/irb/command/base.rb +46 -13
  16. data/lib/irb/command/copy.rb +83 -0
  17. data/lib/irb/command/debug.rb +8 -6
  18. data/lib/irb/command/help.rb +1 -1
  19. data/lib/irb/command/history.rb +1 -1
  20. data/lib/irb/command/internal_helpers.rb +7 -4
  21. data/lib/irb/command/ls.rb +29 -15
  22. data/lib/irb/completion.rb +81 -57
  23. data/lib/irb/context.rb +132 -53
  24. data/lib/irb/debug/ui.rb +2 -4
  25. data/lib/irb/debug.rb +9 -12
  26. data/lib/irb/default_commands.rb +19 -8
  27. data/lib/irb/easter-egg.rb +13 -7
  28. data/lib/irb/ext/multi-irb.rb +2 -0
  29. data/lib/irb/history.rb +84 -55
  30. data/lib/irb/init.rb +7 -2
  31. data/lib/irb/input-method.rb +167 -130
  32. data/lib/irb/inspector.rb +42 -36
  33. data/lib/irb/lc/help-message +2 -2
  34. data/lib/irb/lc/ja/help-message +4 -4
  35. data/lib/irb/nesting_parser.rb +364 -213
  36. data/lib/irb/pager.rb +137 -7
  37. data/lib/irb/ruby-lex.rb +230 -302
  38. data/lib/irb/ruby_logo.aa +79 -37
  39. data/lib/irb/source_finder.rb +15 -22
  40. data/lib/irb/startup_message.rb +83 -0
  41. data/lib/irb/statement.rb +21 -0
  42. data/lib/irb/version.rb +2 -2
  43. data/lib/irb/workspace.rb +22 -31
  44. data/lib/irb.rb +148 -986
  45. data/man/irb.1 +37 -2
  46. metadata +42 -12
  47. data/.document +0 -4
  48. data/Rakefile +0 -54
  49. data/bin/console +0 -6
  50. data/bin/setup +0 -6
  51. data/irb.gemspec +0 -46
@@ -1,237 +1,388 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'prism'
4
+
2
5
  module IRB
3
6
  module NestingParser
4
- IGNORE_TOKENS = %i[on_sp on_ignored_nl on_comment on_embdoc_beg on_embdoc on_embdoc_end]
5
-
6
- # Scan each token and call the given block with array of token and other information for parsing
7
- def self.scan_opens(tokens)
8
- opens = []
9
- pending_heredocs = []
10
- first_token_on_line = true
11
- tokens.each do |t|
12
- skip = false
13
- last_tok, state, args = opens.last
14
- case state
15
- when :in_alias_undef
16
- skip = t.event == :on_kw
17
- when :in_unquoted_symbol
18
- unless IGNORE_TOKENS.include?(t.event)
19
- opens.pop
20
- skip = true
21
- end
22
- when :in_lambda_head
23
- opens.pop if t.event == :on_tlambeg || (t.event == :on_kw && t.tok == 'do')
24
- when :in_method_head
25
- unless IGNORE_TOKENS.include?(t.event)
26
- next_args = []
27
- body = nil
28
- if args.include?(:receiver)
29
- case t.event
30
- when :on_lparen, :on_ivar, :on_gvar, :on_cvar
31
- # def (receiver). | def @ivar. | def $gvar. | def @@cvar.
32
- next_args << :dot
33
- when :on_kw
34
- case t.tok
35
- when 'self', 'true', 'false', 'nil'
36
- # def self(arg) | def self.
37
- next_args.push(:arg, :dot)
38
- else
39
- # def if(arg)
40
- skip = true
41
- next_args << :arg
42
- end
43
- when :on_op, :on_backtick
44
- # def +(arg)
45
- skip = true
46
- next_args << :arg
47
- when :on_ident, :on_const
48
- # def a(arg) | def a.
49
- next_args.push(:arg, :dot)
50
- end
51
- end
52
- if args.include?(:dot)
53
- # def receiver.name
54
- next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::')
55
- end
56
- if args.include?(:name)
57
- if %i[on_ident on_const on_op on_kw on_backtick].include?(t.event)
58
- # def name(arg) | def receiver.name(arg)
59
- next_args << :arg
60
- skip = true
61
- end
62
- end
63
- if args.include?(:arg)
64
- case t.event
65
- when :on_nl, :on_semicolon
66
- # def receiver.f;
67
- body = :normal
68
- when :on_lparen
69
- # def receiver.f()
70
- next_args << :eq
71
- else
72
- if t.event == :on_op && t.tok == '='
73
- # def receiver.f =
74
- body = :oneliner
75
- else
76
- # def receiver.f arg
77
- next_args << :arg_without_paren
78
- end
79
- end
80
- end
81
- if args.include?(:eq)
82
- if t.event == :on_op && t.tok == '='
83
- body = :oneliner
84
- else
85
- body = :normal
86
- end
87
- end
88
- if args.include?(:arg_without_paren)
89
- if %i[on_semicolon on_nl].include?(t.event)
90
- # def f a;
91
- body = :normal
92
- else
93
- # def f a, b
94
- next_args << :arg_without_paren
95
- end
96
- end
97
- if body == :oneliner
98
- opens.pop
99
- elsif body
100
- opens[-1] = [last_tok, nil]
7
+ NestingElem = Struct.new(:pos, :event, :tok)
8
+
9
+ class NestingVisitor < Prism::Visitor
10
+ def initialize
11
+ # Array of [column, priority(+1/-1), NestingElem(open) or nil(close)] per line.
12
+ # priority is +1 for open, -1 for close so that close comes before open when sorted.
13
+ # Example:
14
+ # if cond
15
+ # else
16
+ # end
17
+ # `else` closes `if` at column 0 first and then opens `else` at column 0 next.
18
+ @lines = []
19
+
20
+ # Array of open heredoc NestingElem per line
21
+ @heredocs = []
22
+ end
23
+
24
+ def nestings
25
+ size = [@lines.size, @heredocs.size].max
26
+ nesting = []
27
+ size.times.map do |line_index|
28
+ @lines[line_index]&.sort_by { |col, pri| [col, pri] }&.each do |col, pri, elem|
29
+ if elem
30
+ nesting << elem
101
31
  else
102
- opens[-1] = [last_tok, :in_method_head, next_args]
32
+ nesting.pop
103
33
  end
104
34
  end
105
- when :in_for_while_until_condition
106
- if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do')
107
- skip = true if t.event == :on_kw && t.tok == 'do'
108
- opens[-1] = [last_tok, nil]
35
+ @heredocs[line_index]&.sort_by { |elem| elem.pos[1] }&.reverse_each do |elem|
36
+ nesting << elem
109
37
  end
38
+ nesting.dup
110
39
  end
40
+ end
111
41
 
112
- unless skip
113
- case t.event
114
- when :on_kw
115
- case t.tok
116
- when 'begin', 'class', 'module', 'do', 'case'
117
- opens << [t, nil]
118
- when 'end'
119
- opens.pop
120
- when 'def'
121
- opens << [t, :in_method_head, [:receiver, :name]]
122
- when 'if', 'unless'
123
- unless t.state.allbits?(Ripper::EXPR_LABEL)
124
- opens << [t, nil]
125
- end
126
- when 'while', 'until'
127
- unless t.state.allbits?(Ripper::EXPR_LABEL)
128
- opens << [t, :in_for_while_until_condition]
129
- end
130
- when 'ensure', 'rescue'
131
- unless t.state.allbits?(Ripper::EXPR_LABEL)
132
- opens.pop
133
- opens << [t, nil]
134
- end
135
- when 'alias'
136
- opens << [t, :in_alias_undef, 2]
137
- when 'undef'
138
- opens << [t, :in_alias_undef, 1]
139
- when 'elsif', 'else', 'when'
140
- opens.pop
141
- opens << [t, nil]
142
- when 'for'
143
- opens << [t, :in_for_while_until_condition]
144
- when 'in'
145
- if last_tok&.event == :on_kw && %w[case in].include?(last_tok.tok) && first_token_on_line
146
- opens.pop
147
- opens << [t, nil]
148
- end
149
- end
150
- when :on_tlambda
151
- opens << [t, :in_lambda_head]
152
- when :on_lparen, :on_lbracket, :on_lbrace, :on_tlambeg, :on_embexpr_beg, :on_embdoc_beg
153
- opens << [t, nil]
154
- when :on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end, :on_embdoc_end
155
- opens.pop
156
- when :on_heredoc_beg
157
- pending_heredocs << t
158
- when :on_heredoc_end
159
- opens.pop
160
- when :on_backtick
161
- opens << [t, nil] if t.state.allbits?(Ripper::EXPR_BEG)
162
- when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg
163
- opens << [t, nil]
164
- when :on_tstring_end, :on_regexp_end, :on_label_end
165
- opens.pop
166
- when :on_symbeg
167
- if t.tok == ':'
168
- opens << [t, :in_unquoted_symbol]
169
- else
170
- opens << [t, nil]
171
- end
42
+ def heredoc_open(node)
43
+ elem = NestingElem.new([node.location.start_line, node.location.start_column], :on_heredoc_beg, node.opening)
44
+ (@heredocs[node.location.start_line - 1] ||= []) << elem
45
+ end
46
+
47
+ def open(line, column, elem)
48
+ (@lines[line - 1] ||= []) << [column, +1, elem]
49
+ end
50
+
51
+ def close(line, column)
52
+ (@lines[line - 1] ||= []) << [column, -1]
53
+ end
54
+
55
+ # Checks if a node (if, while, etc) is a modifier form that does not need `end` closing.
56
+ # modifier node: `a if b`, non-modifier node: `if a; b; end`
57
+ def modifier_node?(node, keyword_loc)
58
+ !(keyword_loc && node.location.start_line == keyword_loc.start_line && node.location.start_column == keyword_loc.start_column)
59
+ end
60
+
61
+ def open_location(location, type, tok)
62
+ open(location.start_line, location.start_column, NestingElem.new([location.start_line, location.start_column], type, tok))
63
+ end
64
+
65
+ def close_location(location)
66
+ close(location.end_line, location.end_column)
67
+ end
68
+
69
+ def close_location_start(location)
70
+ close(location.start_line, location.start_column)
71
+ end
72
+
73
+ def close_end_keyword_loc(node)
74
+ close_location(node.end_keyword_loc) if node.end_keyword == 'end'
75
+ end
76
+
77
+ def close_closing_loc(node)
78
+ close_location(node.closing_loc) if node.closing_loc && !node.closing.empty?
79
+ end
80
+
81
+ def visit_for_node(node)
82
+ super
83
+ open_location(node.location, :on_kw, 'for')
84
+ close_end_keyword_loc(node)
85
+ end
86
+
87
+ def visit_while_node(node)
88
+ super
89
+ return if modifier_node?(node, node.keyword_loc)
90
+
91
+ open_location(node.location, :on_kw, 'while')
92
+ close_closing_loc(node)
93
+ end
94
+
95
+ def visit_until_node(node)
96
+ super
97
+ return if modifier_node?(node, node.keyword_loc)
98
+
99
+ open_location(node.location, :on_kw, 'until')
100
+ close_closing_loc(node)
101
+ end
102
+
103
+ def visit_if_node(node)
104
+ super
105
+ return if !node.if_keyword || modifier_node?(node, node.if_keyword_loc)
106
+
107
+ open_location(node.location, :on_kw, node.if_keyword)
108
+ if node.subsequent
109
+ close_location_start(node.subsequent.location)
110
+ else
111
+ close_end_keyword_loc(node)
112
+ end
113
+ end
114
+
115
+ def visit_unless_node(node)
116
+ super
117
+ return if modifier_node?(node, node.keyword_loc)
118
+
119
+ open_location(node.location, :on_kw, 'unless')
120
+ if node.else_clause
121
+ close_location_start(node.else_clause.location)
122
+ else
123
+ close_end_keyword_loc(node)
124
+ end
125
+ end
126
+
127
+ def visit_case_node(node)
128
+ super
129
+ open_location(node.location, :on_kw, 'case')
130
+ if node.else_clause
131
+ close_location_start(node.else_clause.location)
132
+ else
133
+ close_end_keyword_loc(node)
134
+ end
135
+ end
136
+ alias visit_case_match_node visit_case_node
137
+
138
+ def visit_when_node(node)
139
+ super
140
+ close_location_start(node.location)
141
+ open_location(node.location, :on_kw, 'when')
142
+ end
143
+
144
+ def visit_in_node(node)
145
+ super
146
+ close_location_start(node.location)
147
+ open_location(node.location, :on_kw, 'in')
148
+ end
149
+
150
+ def visit_else_node(node)
151
+ super
152
+ if node.else_keyword == 'else'
153
+ open_location(node.location, :on_kw, 'else')
154
+ close_end_keyword_loc(node)
155
+ end
156
+ end
157
+
158
+ def visit_ensure_node(node)
159
+ super
160
+ return if modifier_node?(node, node.ensure_keyword_loc)
161
+
162
+ close_location_start(node.location)
163
+ open_location(node.location, :on_kw, 'ensure')
164
+ end
165
+
166
+ def visit_rescue_node(node)
167
+ super
168
+ return if modifier_node?(node, node.keyword_loc)
169
+
170
+ close_location_start(node.location)
171
+ open_location(node.location, :on_kw, 'rescue')
172
+ end
173
+
174
+ def visit_begin_node(node)
175
+ super
176
+ if node.begin_keyword
177
+ open_location(node.location, :on_kw, 'begin')
178
+ close_end_keyword_loc(node)
179
+ end
180
+ end
181
+
182
+ def visit_block_node(node)
183
+ super
184
+ open_location(node.location, node.opening == '{' ? :on_lbrace : :on_kw, node.opening)
185
+ close_closing_loc(node)
186
+ end
187
+
188
+ def visit_array_node(node)
189
+ super
190
+ type =
191
+ case node.opening
192
+ when nil
193
+ # `x = 1, 2` doesn't have opening
194
+ nil
195
+ when '['
196
+ :bracket
197
+ when /\A%W/
198
+ :on_words_beg
199
+ when /\A%w/
200
+ :on_qwords_beg
201
+ when /\A%I/
202
+ :on_symbols_beg
203
+ when /\A%i/
204
+ :on_qsymbols_beg
205
+ end
206
+
207
+ if type
208
+ open_location(node.location, type, node.opening)
209
+ close_closing_loc(node)
210
+ end
211
+ end
212
+
213
+ def visit_hash_node(node)
214
+ super
215
+ open_location(node.location, :on_lbrace, '{')
216
+ close_closing_loc(node)
217
+ end
218
+
219
+ def heredoc_string_like(node, type)
220
+ if node.opening&.start_with?('<<')
221
+ heredoc_open(node)
222
+ # Heredoc closing contains trailing newline. We need to exclude it
223
+ close_location_start(node.closing_loc) if node.closing_loc && !node.closing.empty?
224
+ elsif node.opening
225
+ return if node.opening == '?' && node.closing.nil? # Character literal has no closing
226
+ open_location(node.location, type, node.opening)
227
+ if node.closing && node.closing != ''
228
+ # Closing of `"#{\n` is "\n". We need to treat it as not-closed.
229
+ close_location_start(node.closing_loc) if node.opening.match?(/\n\z/) || node.closing != "\n"
230
+ end
231
+ end
232
+ end
233
+
234
+ def visit_embedded_statements_node(node)
235
+ super
236
+ open_location(node.location, :on_embexpr_beg, '#{')
237
+ close_closing_loc(node)
238
+ end
239
+
240
+ def visit_interpolated_string_node(node)
241
+ super
242
+ heredoc_string_like(node, :on_tstring_beg)
243
+ end
244
+ alias visit_string_node visit_interpolated_string_node
245
+
246
+ def visit_interpolated_x_string_node(node)
247
+ super
248
+ heredoc_string_like(node, :on_backtick)
249
+ end
250
+ alias visit_x_string_node visit_interpolated_x_string_node
251
+
252
+ def visit_symbol_node(node)
253
+ super
254
+ unless node.opening.nil? || node.opening.empty? || node.opening == ':'
255
+ # :"sym" or %s[sym]
256
+ open_location(node.location, :on_symbeg, node.opening)
257
+ close_closing_loc(node)
258
+ end
259
+ end
260
+ alias visit_interpolated_symbol_node visit_symbol_node
261
+
262
+ def visit_regular_expression_node(node)
263
+ super
264
+ open_location(node.location, :on_regexp_beg, node.opening)
265
+ close_closing_loc(node)
266
+ end
267
+ alias visit_interpolated_regular_expression_node visit_regular_expression_node
268
+
269
+ def visit_parentheses_node(node)
270
+ super
271
+ open_location(node.location, :on_lparen, '(')
272
+ close_closing_loc(node)
273
+ end
274
+
275
+ def visit_call_node(node)
276
+ super
277
+ type =
278
+ case node.opening
279
+ when '('
280
+ :on_lparen
281
+ when '['
282
+ :on_lbracket
172
283
  end
284
+
285
+ if type
286
+ open_location(node.opening_loc, type, node.opening)
287
+ close_closing_loc(node)
288
+ end
289
+ end
290
+
291
+ def visit_block_parameters_node(node)
292
+ super
293
+ if node.opening == '('
294
+ open_location(node.location, :on_lparen, '(')
295
+ close_closing_loc(node)
173
296
  end
174
- if t.event == :on_nl || t.event == :on_semicolon
175
- first_token_on_line = true
176
- elsif t.event != :on_sp
177
- first_token_on_line = false
297
+ end
298
+
299
+ def visit_lambda_node(node)
300
+ super
301
+ open_location(node.opening_loc, :on_tlambeg, node.opening)
302
+ close_closing_loc(node)
303
+ end
304
+
305
+ def visit_super_node(node)
306
+ super
307
+ if node.lparen
308
+ open_location(node.lparen_loc, :on_lparen, '(')
309
+ close_location(node.rparen_loc) if node.rparen == ')'
178
310
  end
179
- if pending_heredocs.any? && t.tok.include?("\n")
180
- pending_heredocs.reverse_each { |t| opens << [t, nil] }
181
- pending_heredocs = []
311
+ end
312
+ alias visit_yield_node visit_super_node
313
+ alias visit_defined_node visit_super_node
314
+
315
+ def visit_def_node(node)
316
+ super
317
+ open_location(node.location, :on_kw, 'def')
318
+ if node.lparen == '('
319
+ open_location(node.lparen_loc, :on_lparen, '(')
320
+ close_location(node.rparen_loc) if node.rparen == ')'
182
321
  end
183
- if opens.last && opens.last[1] == :in_alias_undef && !IGNORE_TOKENS.include?(t.event) && t.event != :on_heredoc_end
184
- tok, state, arg = opens.pop
185
- opens << [tok, state, arg - 1] if arg >= 1
322
+ if node.equal
323
+ close_location(node.equal_loc)
324
+ else
325
+ close_end_keyword_loc(node)
186
326
  end
187
- yield t, opens if block_given?
188
327
  end
189
- opens.map(&:first) + pending_heredocs.reverse
190
- end
191
328
 
192
- def self.open_tokens(tokens)
193
- # scan_opens without block will return a list of open tokens at last token position
194
- scan_opens(tokens)
329
+ def visit_class_node(node)
330
+ super
331
+ open_location(node.location, :on_kw, 'class')
332
+ close_end_keyword_loc(node)
333
+ end
334
+ alias visit_singleton_class_node visit_class_node
335
+
336
+ def visit_module_node(node)
337
+ super
338
+ open_location(node.location, :on_kw, 'module')
339
+ close_end_keyword_loc(node)
340
+ end
195
341
  end
196
342
 
197
- # Calculates token information [line_tokens, prev_opens, next_opens, min_depth] for each line.
198
- # Example code
199
- # ["hello
200
- # world"+(
201
- # First line
202
- # line_tokens: [[lbracket, '['], [tstring_beg, '"'], [tstring_content("hello\nworld"), "hello\n"]]
203
- # prev_opens: []
204
- # next_tokens: [lbracket, tstring_beg]
205
- # min_depth: 0 (minimum at beginning of line)
206
- # Second line
207
- # line_tokens: [[tstring_content("hello\nworld"), "world"], [tstring_end, '"'], [op, '+'], [lparen, '(']]
208
- # prev_opens: [lbracket, tstring_beg]
209
- # next_tokens: [lbracket, lparen]
210
- # min_depth: 1 (minimum just after tstring_end)
211
- def self.parse_by_line(tokens)
212
- line_tokens = []
213
- prev_opens = []
214
- min_depth = 0
215
- output = []
216
- last_opens = scan_opens(tokens) do |t, opens|
217
- depth = t == opens.last&.first ? opens.size - 1 : opens.size
218
- min_depth = depth if depth < min_depth
219
- if t.tok.include?("\n")
220
- t.tok.each_line do |line|
221
- line_tokens << [t, line]
222
- next if line[-1] != "\n"
223
- next_opens = opens.map(&:first)
224
- output << [line_tokens, prev_opens, next_opens, min_depth]
225
- prev_opens = next_opens
226
- min_depth = prev_opens.size
227
- line_tokens = []
343
+ class << self
344
+
345
+ # Return a list of open nesting elements at EOF position
346
+ def open_nestings(parse_lex_result)
347
+ parse_by_line(parse_lex_result).last[1]
348
+ end
349
+
350
+ # Calculates nesting information [prev_opens, next_opens, min_depth] for each line.
351
+ # Example code
352
+ # ["hello
353
+ # world"+(
354
+ # First line
355
+ # prev_opens: []
356
+ # next_opens: [lbracket, tstring_beg]
357
+ # min_depth: 0 (minimum at beginning of line)
358
+ # Second line
359
+ # prev_opens: [lbracket, tstring_beg]
360
+ # next_opens: [lbracket, lparen]
361
+ # min_depth: 1 (minimum just after tstring_end)
362
+
363
+ def parse_by_line(parse_lex_result)
364
+ visitor = NestingVisitor.new
365
+ node, tokens = parse_lex_result.value
366
+ node.accept(visitor)
367
+ tokens.each do |token,|
368
+ case token.type
369
+ when :EMBDOC_BEGIN
370
+ visitor.open_location(token.location, :on_embdoc_beg, '=begin')
371
+ when :EMBDOC_END
372
+ visitor.close_location_start(token.location)
228
373
  end
229
- else
230
- line_tokens << [t, t.tok]
374
+ end
375
+ nestings = visitor.nestings
376
+ last_nesting = nestings.last || []
377
+
378
+ num_lines = parse_lex_result.source.source.lines.size
379
+ num_lines.times.map do |i|
380
+ prev_opens = i == 0 ? [] : nestings[i - 1] || last_nesting
381
+ opens = nestings[i] || last_nesting
382
+ min_depth = prev_opens.zip(opens).take_while { |s, e| s == e }.size
383
+ [prev_opens, opens, min_depth]
231
384
  end
232
385
  end
233
- output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any?
234
- output
235
386
  end
236
387
  end
237
388
  end