irb 1.16.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.
@@ -1,238 +1,387 @@
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]
7
+ NestingElem = Struct.new(:pos, :event, :tok)
5
8
 
6
- class << self
7
- # Scan each token and call the given block with array of token and other information for parsing
8
- def scan_opens(tokens)
9
- opens = []
10
- pending_heredocs = []
11
- first_token_on_line = true
12
- tokens.each do |t|
13
- skip = false
14
- last_tok, state, args = opens.last
15
- case state
16
- when :in_alias_undef
17
- skip = t.event == :on_kw
18
- when :in_unquoted_symbol
19
- unless IGNORE_TOKENS.include?(t.event)
20
- opens.pop
21
- skip = true
22
- end
23
- when :in_lambda_head
24
- opens.pop if t.event == :on_tlambeg || (t.event == :on_kw && t.tok == 'do')
25
- when :in_method_head
26
- unless IGNORE_TOKENS.include?(t.event)
27
- next_args = []
28
- body = nil
29
- if args.include?(:receiver)
30
- case t.event
31
- when :on_lparen, :on_ivar, :on_gvar, :on_cvar
32
- # def (receiver). | def @ivar. | def $gvar. | def @@cvar.
33
- next_args << :dot
34
- when :on_kw
35
- case t.tok
36
- when 'self', 'true', 'false', 'nil'
37
- # def self(arg) | def self.
38
- next_args.push(:arg, :dot)
39
- else
40
- # def if(arg)
41
- skip = true
42
- next_args << :arg
43
- end
44
- when :on_op, :on_backtick
45
- # def +(arg)
46
- skip = true
47
- next_args << :arg
48
- when :on_ident, :on_const
49
- # def a(arg) | def a.
50
- next_args.push(:arg, :dot)
51
- end
52
- end
53
- if args.include?(:dot)
54
- # def receiver.name
55
- next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::')
56
- end
57
- if args.include?(:name)
58
- if %i[on_ident on_const on_op on_kw on_backtick].include?(t.event)
59
- # def name(arg) | def receiver.name(arg)
60
- next_args << :arg
61
- skip = true
62
- end
63
- end
64
- if args.include?(:arg)
65
- case t.event
66
- when :on_nl, :on_semicolon
67
- # def receiver.f;
68
- body = :normal
69
- when :on_lparen
70
- # def receiver.f()
71
- next_args << :eq
72
- else
73
- if t.event == :on_op && t.tok == '='
74
- # def receiver.f =
75
- body = :oneliner
76
- else
77
- # def receiver.f arg
78
- next_args << :arg_without_paren
79
- end
80
- end
81
- end
82
- if args.include?(:eq)
83
- if t.event == :on_op && t.tok == '='
84
- body = :oneliner
85
- else
86
- body = :normal
87
- end
88
- end
89
- if args.include?(:arg_without_paren)
90
- if %i[on_semicolon on_nl].include?(t.event)
91
- # def f a;
92
- body = :normal
93
- else
94
- # def f a, b
95
- next_args << :arg_without_paren
96
- end
97
- end
98
- if body == :oneliner
99
- opens.pop
100
- elsif body
101
- opens[-1] = [last_tok, nil]
102
- else
103
- opens[-1] = [last_tok, :in_method_head, next_args]
104
- end
105
- end
106
- when :in_for_while_until_condition
107
- if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do')
108
- skip = true if t.event == :on_kw && t.tok == 'do'
109
- opens[-1] = [last_tok, nil]
110
- end
111
- end
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 = []
112
19
 
113
- unless skip
114
- case t.event
115
- when :on_kw
116
- case t.tok
117
- when 'begin', 'class', 'module', 'do', 'case'
118
- opens << [t, nil]
119
- when 'end'
120
- opens.pop
121
- when 'def'
122
- opens << [t, :in_method_head, [:receiver, :name]]
123
- when 'if', 'unless'
124
- unless t.state.allbits?(Ripper::EXPR_LABEL)
125
- opens << [t, nil]
126
- end
127
- when 'while', 'until'
128
- unless t.state.allbits?(Ripper::EXPR_LABEL)
129
- opens << [t, :in_for_while_until_condition]
130
- end
131
- when 'ensure', 'rescue'
132
- unless t.state.allbits?(Ripper::EXPR_LABEL)
133
- opens.pop
134
- opens << [t, nil]
135
- end
136
- when 'alias'
137
- opens << [t, :in_alias_undef, 2]
138
- when 'undef'
139
- opens << [t, :in_alias_undef, 1]
140
- when 'elsif', 'else', 'when'
141
- opens.pop
142
- opens << [t, nil]
143
- when 'for'
144
- opens << [t, :in_for_while_until_condition]
145
- when 'in'
146
- if last_tok&.event == :on_kw && %w[case in].include?(last_tok.tok) && first_token_on_line
147
- opens.pop
148
- opens << [t, nil]
149
- end
150
- end
151
- when :on_tlambda
152
- opens << [t, :in_lambda_head]
153
- when :on_lparen, :on_lbracket, :on_lbrace, :on_tlambeg, :on_embexpr_beg, :on_embdoc_beg
154
- opens << [t, nil]
155
- when :on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end, :on_embdoc_end
156
- opens.pop
157
- when :on_heredoc_beg
158
- pending_heredocs << t
159
- when :on_heredoc_end
160
- opens.pop
161
- when :on_backtick
162
- opens << [t, nil] unless t.state == Ripper::EXPR_ARG
163
- when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg
164
- opens << [t, nil]
165
- when :on_tstring_end, :on_regexp_end, :on_label_end
166
- opens.pop
167
- when :on_symbeg
168
- if t.tok == ':'
169
- opens << [t, :in_unquoted_symbol]
170
- else
171
- opens << [t, nil]
172
- end
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
31
+ else
32
+ nesting.pop
173
33
  end
174
34
  end
175
- if t.event == :on_nl || t.event == :on_semicolon
176
- first_token_on_line = true
177
- elsif t.event != :on_sp
178
- first_token_on_line = false
35
+ @heredocs[line_index]&.sort_by { |elem| elem.pos[1] }&.reverse_each do |elem|
36
+ nesting << elem
37
+ end
38
+ nesting.dup
39
+ end
40
+ end
41
+
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
179
205
  end
180
- if pending_heredocs.any? && t.tok.include?("\n")
181
- pending_heredocs.reverse_each { |t| opens << [t, nil] }
182
- pending_heredocs = []
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"
183
230
  end
184
- if opens.last && opens.last[1] == :in_alias_undef && !IGNORE_TOKENS.include?(t.event) && t.event != :on_heredoc_end
185
- tok, state, arg = opens.pop
186
- opens << [tok, state, arg - 1] if arg >= 1
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
187
283
  end
188
- yield t, opens if block_given?
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)
296
+ end
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 == ')'
310
+ end
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 == ')'
189
321
  end
190
- opens.map(&:first) + pending_heredocs.reverse
322
+ if node.equal
323
+ close_location(node.equal_loc)
324
+ else
325
+ close_end_keyword_loc(node)
326
+ end
327
+ end
328
+
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)
191
340
  end
341
+ end
342
+
343
+ class << self
192
344
 
193
- def open_tokens(tokens)
194
- # scan_opens without block will return a list of open tokens at last token position
195
- scan_opens(tokens)
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]
196
348
  end
197
349
 
198
- # Calculates token information [line_tokens, prev_opens, next_opens, min_depth] for each line.
350
+ # Calculates nesting information [prev_opens, next_opens, min_depth] for each line.
199
351
  # Example code
200
352
  # ["hello
201
353
  # world"+(
202
354
  # First line
203
- # line_tokens: [[lbracket, '['], [tstring_beg, '"'], [tstring_content("hello\nworld"), "hello\n"]]
204
- # prev_opens: []
205
- # next_tokens: [lbracket, tstring_beg]
206
- # min_depth: 0 (minimum at beginning of line)
355
+ # prev_opens: []
356
+ # next_opens: [lbracket, tstring_beg]
357
+ # min_depth: 0 (minimum at beginning of line)
207
358
  # Second line
208
- # line_tokens: [[tstring_content("hello\nworld"), "world"], [tstring_end, '"'], [op, '+'], [lparen, '(']]
209
- # prev_opens: [lbracket, tstring_beg]
210
- # next_tokens: [lbracket, lparen]
211
- # min_depth: 1 (minimum just after tstring_end)
212
- def parse_by_line(tokens)
213
- line_tokens = []
214
- prev_opens = []
215
- min_depth = 0
216
- output = []
217
- last_opens = scan_opens(tokens) do |t, opens|
218
- depth = t == opens.last&.first ? opens.size - 1 : opens.size
219
- min_depth = depth if depth < min_depth
220
- if t.tok.include?("\n")
221
- t.tok.each_line do |line|
222
- line_tokens << [t, line]
223
- next if line[-1] != "\n"
224
- next_opens = opens.map(&:first)
225
- output << [line_tokens, prev_opens, next_opens, min_depth]
226
- prev_opens = next_opens
227
- min_depth = prev_opens.size
228
- line_tokens = []
229
- end
230
- else
231
- line_tokens << [t, t.tok]
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)
232
373
  end
233
374
  end
234
- output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any?
235
- output
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]
384
+ end
236
385
  end
237
386
  end
238
387
  end
data/lib/irb/pager.rb CHANGED
@@ -42,6 +42,14 @@ module IRB
42
42
  # SIGTERM not supported (windows)
43
43
  Process.kill("KILL", pid)
44
44
  end
45
+
46
+ begin
47
+ # Wait for the pager process to terminate.
48
+ # Reading next input from Reline before the pager process is fully terminated
49
+ # may cause issues like raw/cooked mode not being controlled properly.
50
+ Process.waitpid(pid) if pid
51
+ rescue Errno::ECHILD, Errno::ESRCH
52
+ end
45
53
  rescue Errno::ESRCH
46
54
  # Pager process already terminated
47
55
  end