irb 1.15.2 → 1.17.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.
@@ -65,6 +65,7 @@ module IRB
65
65
  @current_job = irb
66
66
  th.run
67
67
  Thread.stop
68
+ Thread.pass
68
69
  @current_job = irb(Thread.current)
69
70
  end
70
71
 
@@ -220,6 +221,7 @@ module IRB
220
221
  end
221
222
  end
222
223
  Thread.stop
224
+ Thread.pass
223
225
  @JobManager.current_job = @JobManager.irb(Thread.current)
224
226
  end
225
227
 
@@ -223,7 +223,7 @@ module IRB
223
223
  Readline.input = @stdin
224
224
  Readline.output = @stdout
225
225
  if l = Readline.readline(@prompt, false)
226
- Readline::HISTORY.push(l) if !l.empty?
226
+ Readline::HISTORY.push(l) if !l.empty? && l != Readline::HISTORY.to_a.last
227
227
  @line[@line_no += 1] = l + "\n"
228
228
  else
229
229
  @eof = true
@@ -480,7 +480,7 @@ module IRB
480
480
  Reline.prompt_proc = @prompt_proc
481
481
  Reline.auto_indent_proc = @auto_indent_proc if @auto_indent_proc
482
482
  if l = Reline.readmultiline(@prompt, false, &@check_termination_proc)
483
- Reline::HISTORY.push(l) if !l.empty?
483
+ Reline::HISTORY.push(l) if !l.empty? && l != Reline::HISTORY.to_a.last
484
484
  @line[@line_no += 1] = l + "\n"
485
485
  else
486
486
  @eof = true
data/lib/irb/inspector.rb CHANGED
@@ -46,7 +46,7 @@ module IRB # :nodoc:
46
46
  # Determines the inspector to use where +inspector+ is one of the keys passed
47
47
  # during inspector definition.
48
48
  def keys_with_inspector(inspector)
49
- INSPECTORS.select{|k, v| v == inspector}.collect{|k, v| k}
49
+ INSPECTORS.filter_map {|k, v| k if v == inspector}
50
50
  end
51
51
 
52
52
  # Example
@@ -6,10 +6,10 @@ Usage: irb.rb [options] [programfile] [arguments]
6
6
  -U Set external and internal encodings to UTF-8.
7
7
  -E ex[:in] Set default external (ex) and internal (in) encodings
8
8
  (same as 'ruby -E').
9
- -w Suppress warnings (same as 'ruby -w').
9
+ -w Enable warnings (same as 'ruby -w' or 'ruby -W1').
10
10
  -W[level=2] Set warning level: 0=silence, 1=medium, 2=verbose
11
11
  (same as 'ruby -W').
12
- --context-mode n Set n[0-4] to method to create Binding Object,
12
+ --context-mode n Set n[0-5] to method to create Binding Object,
13
13
  when new workspace was created.
14
14
  --extra-doc-dir Add an extra doc dir for the doc dialog.
15
15
  --echo Show result (default).
@@ -8,7 +8,7 @@ Usage: irb.rb [options] [programfile] [arguments]
8
8
  -w ruby -w と同じ.
9
9
  -W[level=2] ruby -W と同じ.
10
10
  --context-mode n 新しいワークスペースを作成した時に関連する Binding
11
- オブジェクトの作成方法を 0 から 4 のいずれかに設定する.
11
+ オブジェクトの作成方法を 0 から 5 のいずれかに設定する.
12
12
  --extra-doc-dir 指定したディレクトリのドキュメントを追加で読み込む.
13
13
  --echo 実行結果を表示する(デフォルト).
14
14
  --noecho 実行結果を表示しない.
@@ -1,238 +1,386 @@
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
+ open_location(node.location, type, node.opening)
226
+ if node.closing && node.closing != ''
227
+ # Closing of `"#{\n` is "\n". We need to treat it as not-closed.
228
+ close_location_start(node.closing_loc) if node.opening.match?(/\n\z/) || node.closing != "\n"
183
229
  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
230
+ end
231
+ end
232
+
233
+ def visit_embedded_statements_node(node)
234
+ super
235
+ open_location(node.location, :on_embexpr_beg, '#{')
236
+ close_closing_loc(node)
237
+ end
238
+
239
+ def visit_interpolated_string_node(node)
240
+ super
241
+ heredoc_string_like(node, :on_tstring_beg)
242
+ end
243
+ alias visit_string_node visit_interpolated_string_node
244
+
245
+ def visit_interpolated_x_string_node(node)
246
+ super
247
+ heredoc_string_like(node, :on_backtick)
248
+ end
249
+ alias visit_x_string_node visit_interpolated_x_string_node
250
+
251
+ def visit_symbol_node(node)
252
+ super
253
+ unless node.opening.nil? || node.opening.empty? || node.opening == ':'
254
+ # :"sym" or %s[sym]
255
+ open_location(node.location, :on_symbeg, node.opening)
256
+ close_closing_loc(node)
257
+ end
258
+ end
259
+ alias visit_interpolated_symbol_node visit_symbol_node
260
+
261
+ def visit_regular_expression_node(node)
262
+ super
263
+ open_location(node.location, :on_regexp_beg, node.opening)
264
+ close_closing_loc(node)
265
+ end
266
+ alias visit_interpolated_regular_expression_node visit_regular_expression_node
267
+
268
+ def visit_parentheses_node(node)
269
+ super
270
+ open_location(node.location, :on_lparen, '(')
271
+ close_closing_loc(node)
272
+ end
273
+
274
+ def visit_call_node(node)
275
+ super
276
+ type =
277
+ case node.opening
278
+ when '('
279
+ :on_lparen
280
+ when '['
281
+ :on_lbracket
187
282
  end
188
- yield t, opens if block_given?
283
+
284
+ if type
285
+ open_location(node.opening_loc, type, node.opening)
286
+ close_closing_loc(node)
287
+ end
288
+ end
289
+
290
+ def visit_block_parameters_node(node)
291
+ super
292
+ if node.opening == '('
293
+ open_location(node.location, :on_lparen, '(')
294
+ close_closing_loc(node)
295
+ end
296
+ end
297
+
298
+ def visit_lambda_node(node)
299
+ super
300
+ open_location(node.opening_loc, :on_tlambeg, node.opening)
301
+ close_closing_loc(node)
302
+ end
303
+
304
+ def visit_super_node(node)
305
+ super
306
+ if node.lparen
307
+ open_location(node.lparen_loc, :on_lparen, '(')
308
+ close_location(node.rparen_loc) if node.rparen == ')'
309
+ end
310
+ end
311
+ alias visit_yield_node visit_super_node
312
+ alias visit_defined_node visit_super_node
313
+
314
+ def visit_def_node(node)
315
+ super
316
+ open_location(node.location, :on_kw, 'def')
317
+ if node.lparen == '('
318
+ open_location(node.lparen_loc, :on_lparen, '(')
319
+ close_location(node.rparen_loc) if node.rparen == ')'
189
320
  end
190
- opens.map(&:first) + pending_heredocs.reverse
321
+ if node.equal
322
+ close_location(node.equal_loc)
323
+ else
324
+ close_end_keyword_loc(node)
325
+ end
326
+ end
327
+
328
+ def visit_class_node(node)
329
+ super
330
+ open_location(node.location, :on_kw, 'class')
331
+ close_end_keyword_loc(node)
332
+ end
333
+ alias visit_singleton_class_node visit_class_node
334
+
335
+ def visit_module_node(node)
336
+ super
337
+ open_location(node.location, :on_kw, 'module')
338
+ close_end_keyword_loc(node)
191
339
  end
340
+ end
341
+
342
+ class << self
192
343
 
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)
344
+ # Return a list of open nesting elements at EOF position
345
+ def open_nestings(parse_lex_result)
346
+ parse_by_line(parse_lex_result).last[1]
196
347
  end
197
348
 
198
- # Calculates token information [line_tokens, prev_opens, next_opens, min_depth] for each line.
349
+ # Calculates nesting information [prev_opens, next_opens, min_depth] for each line.
199
350
  # Example code
200
351
  # ["hello
201
352
  # world"+(
202
353
  # 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)
354
+ # prev_opens: []
355
+ # next_opens: [lbracket, tstring_beg]
356
+ # min_depth: 0 (minimum at beginning of line)
207
357
  # 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]
358
+ # prev_opens: [lbracket, tstring_beg]
359
+ # next_opens: [lbracket, lparen]
360
+ # min_depth: 1 (minimum just after tstring_end)
361
+
362
+ def parse_by_line(parse_lex_result)
363
+ visitor = NestingVisitor.new
364
+ node, tokens = parse_lex_result.value
365
+ node.accept(visitor)
366
+ tokens.each do |token,|
367
+ case token.type
368
+ when :EMBDOC_BEGIN
369
+ visitor.open_location(token.location, :on_embdoc_beg, '=begin')
370
+ when :EMBDOC_END
371
+ visitor.close_location_start(token.location)
232
372
  end
233
373
  end
234
- output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any?
235
- output
374
+ nestings = visitor.nestings
375
+ last_nesting = nestings.last || []
376
+
377
+ num_lines = parse_lex_result.source.source.lines.size
378
+ num_lines.times.map do |i|
379
+ prev_opens = i == 0 ? [] : nestings[i - 1] || last_nesting
380
+ opens = nestings[i] || last_nesting
381
+ min_depth = prev_opens.zip(opens).take_while { |s, e| s == e }.size
382
+ [prev_opens, opens, min_depth]
383
+ end
236
384
  end
237
385
  end
238
386
  end