irb 1.6.4 → 1.7.1

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.
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+ module IRB
3
+ 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_unquoted_symbol
16
+ unless IGNORE_TOKENS.include?(t.event)
17
+ opens.pop
18
+ skip = true
19
+ end
20
+ when :in_lambda_head
21
+ opens.pop if t.event == :on_tlambeg || (t.event == :on_kw && t.tok == 'do')
22
+ when :in_method_head
23
+ unless IGNORE_TOKENS.include?(t.event)
24
+ next_args = []
25
+ body = nil
26
+ if args.include?(:receiver)
27
+ case t.event
28
+ when :on_lparen, :on_ivar, :on_gvar, :on_cvar
29
+ # def (receiver). | def @ivar. | def $gvar. | def @@cvar.
30
+ next_args << :dot
31
+ when :on_kw
32
+ case t.tok
33
+ when 'self', 'true', 'false', 'nil'
34
+ # def self(arg) | def self.
35
+ next_args.push(:arg, :dot)
36
+ else
37
+ # def if(arg)
38
+ skip = true
39
+ next_args << :arg
40
+ end
41
+ when :on_op, :on_backtick
42
+ # def +(arg)
43
+ skip = true
44
+ next_args << :arg
45
+ when :on_ident, :on_const
46
+ # def a(arg) | def a.
47
+ next_args.push(:arg, :dot)
48
+ end
49
+ end
50
+ if args.include?(:dot)
51
+ # def receiver.name
52
+ next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::')
53
+ end
54
+ if args.include?(:name)
55
+ if %i[on_ident on_const on_op on_kw on_backtick].include?(t.event)
56
+ # def name(arg) | def receiver.name(arg)
57
+ next_args << :arg
58
+ skip = true
59
+ end
60
+ end
61
+ if args.include?(:arg)
62
+ case t.event
63
+ when :on_nl, :on_semicolon
64
+ # def recever.f;
65
+ body = :normal
66
+ when :on_lparen
67
+ # def recever.f()
68
+ next_args << :eq
69
+ else
70
+ if t.event == :on_op && t.tok == '='
71
+ # def receiver.f =
72
+ body = :oneliner
73
+ else
74
+ # def recever.f arg
75
+ next_args << :arg_without_paren
76
+ end
77
+ end
78
+ end
79
+ if args.include?(:eq)
80
+ if t.event == :on_op && t.tok == '='
81
+ body = :oneliner
82
+ else
83
+ body = :normal
84
+ end
85
+ end
86
+ if args.include?(:arg_without_paren)
87
+ if %i[on_semicolon on_nl].include?(t.event)
88
+ # def f a;
89
+ body = :normal
90
+ else
91
+ # def f a, b
92
+ next_args << :arg_without_paren
93
+ end
94
+ end
95
+ if body == :oneliner
96
+ opens.pop
97
+ elsif body
98
+ opens[-1] = [last_tok, nil]
99
+ else
100
+ opens[-1] = [last_tok, :in_method_head, next_args]
101
+ end
102
+ end
103
+ when :in_for_while_until_condition
104
+ if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do')
105
+ skip = true if t.event == :on_kw && t.tok == 'do'
106
+ opens[-1] = [last_tok, nil]
107
+ end
108
+ end
109
+
110
+ unless skip
111
+ case t.event
112
+ when :on_kw
113
+ case t.tok
114
+ when 'begin', 'class', 'module', 'do', 'case'
115
+ opens << [t, nil]
116
+ when 'end'
117
+ opens.pop
118
+ when 'def'
119
+ opens << [t, :in_method_head, [:receiver, :name]]
120
+ when 'if', 'unless'
121
+ unless t.state.allbits?(Ripper::EXPR_LABEL)
122
+ opens << [t, nil]
123
+ end
124
+ when 'while', 'until'
125
+ unless t.state.allbits?(Ripper::EXPR_LABEL)
126
+ opens << [t, :in_for_while_until_condition]
127
+ end
128
+ when 'ensure', 'rescue'
129
+ unless t.state.allbits?(Ripper::EXPR_LABEL)
130
+ opens.pop
131
+ opens << [t, nil]
132
+ end
133
+ when 'elsif', 'else', 'when'
134
+ opens.pop
135
+ opens << [t, nil]
136
+ when 'for'
137
+ opens << [t, :in_for_while_until_condition]
138
+ when 'in'
139
+ if last_tok&.event == :on_kw && %w[case in].include?(last_tok.tok) && first_token_on_line
140
+ opens.pop
141
+ opens << [t, nil]
142
+ end
143
+ end
144
+ when :on_tlambda
145
+ opens << [t, :in_lambda_head]
146
+ when :on_lparen, :on_lbracket, :on_lbrace, :on_tlambeg, :on_embexpr_beg, :on_embdoc_beg
147
+ opens << [t, nil]
148
+ when :on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end, :on_embdoc_end
149
+ opens.pop
150
+ when :on_heredoc_beg
151
+ pending_heredocs << t
152
+ when :on_heredoc_end
153
+ opens.pop
154
+ when :on_backtick
155
+ opens << [t, nil] if t.state.allbits?(Ripper::EXPR_BEG)
156
+ when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg
157
+ opens << [t, nil]
158
+ when :on_tstring_end, :on_regexp_end, :on_label_end
159
+ opens.pop
160
+ when :on_symbeg
161
+ if t.tok == ':'
162
+ opens << [t, :in_unquoted_symbol]
163
+ else
164
+ opens << [t, nil]
165
+ end
166
+ end
167
+ end
168
+ if t.event == :on_nl || t.event == :on_semicolon
169
+ first_token_on_line = true
170
+ elsif t.event != :on_sp
171
+ first_token_on_line = false
172
+ end
173
+ if pending_heredocs.any? && t.tok.include?("\n")
174
+ pending_heredocs.reverse_each { |t| opens << [t, nil] }
175
+ pending_heredocs = []
176
+ end
177
+ yield t, opens if block_given?
178
+ end
179
+ opens.map(&:first) + pending_heredocs.reverse
180
+ end
181
+
182
+ def self.open_tokens(tokens)
183
+ # scan_opens without block will return a list of open tokens at last token position
184
+ scan_opens(tokens)
185
+ end
186
+
187
+ # Calculates token information [line_tokens, prev_opens, next_opens, min_depth] for each line.
188
+ # Example code
189
+ # ["hello
190
+ # world"+(
191
+ # First line
192
+ # line_tokens: [[lbracket, '['], [tstring_beg, '"'], [tstring_content("hello\nworld"), "hello\n"]]
193
+ # prev_opens: []
194
+ # next_tokens: [lbracket, tstring_beg]
195
+ # min_depth: 0 (minimum at beginning of line)
196
+ # Second line
197
+ # line_tokens: [[tstring_content("hello\nworld"), "world"], [tstring_end, '"'], [op, '+'], [lparen, '(']]
198
+ # prev_opens: [lbracket, tstring_beg]
199
+ # next_tokens: [lbracket, lparen]
200
+ # min_depth: 1 (minimum just after tstring_end)
201
+ def self.parse_by_line(tokens)
202
+ line_tokens = []
203
+ prev_opens = []
204
+ min_depth = 0
205
+ output = []
206
+ last_opens = scan_opens(tokens) do |t, opens|
207
+ depth = t == opens.last&.first ? opens.size - 1 : opens.size
208
+ min_depth = depth if depth < min_depth
209
+ if t.tok.include?("\n")
210
+ t.tok.each_line do |line|
211
+ line_tokens << [t, line]
212
+ next if line[-1] != "\n"
213
+ next_opens = opens.map(&:first)
214
+ output << [line_tokens, prev_opens, next_opens, min_depth]
215
+ prev_opens = next_opens
216
+ min_depth = prev_opens.size
217
+ line_tokens = []
218
+ end
219
+ else
220
+ line_tokens << [t, t.tok]
221
+ end
222
+ end
223
+ output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any?
224
+ output
225
+ end
226
+ end
227
+ end