rbs-inline 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,765 @@
1
+ # rbs_inline: enabled
2
+
3
+ module RBS
4
+ module Inline
5
+ class AnnotationParser
6
+ class ParsingResult
7
+ attr_reader :comments #:: Array[Prism::Comment]
8
+ attr_reader :annotations #:: Array[AST::Annotations::t]
9
+ attr_reader :first_comment_offset #:: Integer
10
+
11
+ # @rbs first_comment: Prism::Comment
12
+ def initialize(first_comment)
13
+ @comments = [first_comment]
14
+ @annotations = []
15
+ content = first_comment.location.slice
16
+ index = content.index(/[^#\s]/) || content.size
17
+ @first_comment_offset = index
18
+ end
19
+
20
+ # @rbs returns Range[Integer]
21
+ def line_range
22
+ first = comments.first or raise
23
+ last = comments.last or raise
24
+
25
+ first.location.start_line .. last.location.end_line
26
+ end
27
+
28
+ # @rbs returns Prism::Comment
29
+ def last_comment
30
+ comments.last or raise
31
+ end
32
+
33
+ # @rbs comment: Prism::Comment
34
+ # @rbs returns self?
35
+ def add_comment(comment)
36
+ if last_comment.location.end_line + 1 == comment.location.start_line
37
+ if last_comment.location.start_column == comment.location.start_column
38
+ if prefix = comment.location.start_line_slice[..comment.location.start_column]
39
+ prefix.strip!
40
+ if prefix.empty?
41
+ comments << comment
42
+ self
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # @rbs returns Array[[String, Prism::Comment]]
50
+ def lines
51
+ comments.map do |comment|
52
+ slice = comment.location.slice
53
+ index = slice.index(/[^#\s]/) || slice.size
54
+ string = if index > first_comment_offset
55
+ slice[first_comment_offset..] || ""
56
+ else
57
+ slice[index..] || ""
58
+ end
59
+ [string, comment]
60
+ end
61
+ end
62
+
63
+ # @rbs returns String
64
+ def content
65
+ lines.map(&:first).join("\n")
66
+ end
67
+ end
68
+
69
+ attr_reader :input #:: Array[Prism::Comment]
70
+
71
+ # @rbs input: Array[Prism::Comment]
72
+ def initialize(input)
73
+ @input = input
74
+ end
75
+
76
+ # @rbs input: Array[Prism::Comment]
77
+ # @rbs returns Array[ParsingResult]
78
+ def self.parse(input)
79
+ new(input).parse
80
+ end
81
+
82
+ # @rbs returns Array[ParsingResult]
83
+ def parse
84
+ results = [] #: Array[ParsingResult]
85
+
86
+ first_comment, *rest = input
87
+ first_comment or return results
88
+
89
+ result = ParsingResult.new(first_comment)
90
+ results << result
91
+
92
+ rest.each do |comment|
93
+ unless result.add_comment(comment)
94
+ result = ParsingResult.new(comment)
95
+ results << result
96
+ end
97
+ end
98
+
99
+ results.each do |result|
100
+ each_annotation_paragraph(result) do |comments|
101
+ if annot = parse_annotation(AST::CommentLines.new(comments))
102
+ result.annotations << annot
103
+ end
104
+ end
105
+ end
106
+
107
+ results
108
+ end
109
+
110
+ private
111
+
112
+ # @rbs result: ParsingResult
113
+ # @rbs block: ^(Array[Prism::Comment]) -> void
114
+ # @rbs returns void
115
+ def each_annotation_paragraph(result, &block)
116
+ lines = result.lines
117
+
118
+ while true
119
+ line, comment = lines.shift
120
+ break unless line && comment
121
+
122
+ next_line, next_comment = lines.first
123
+
124
+ possible_annotation = false
125
+ possible_annotation ||= line.start_with?('@rbs', '@rbs!')
126
+ possible_annotation ||= comment.location.slice.start_with?("#::", "#[") # No leading whitespace is allowed
127
+
128
+ if possible_annotation
129
+ line_offset = line.index(/\S/) || raise
130
+
131
+ comments = [comment]
132
+
133
+ while true
134
+ break unless next_line && next_comment
135
+ next_offset = next_line.index(/\S/) || 0
136
+ break unless next_offset > line_offset
137
+
138
+ comments << next_comment
139
+ lines.shift
140
+
141
+ next_line, next_comment = lines.first
142
+ end
143
+
144
+ yield comments
145
+ end
146
+ end
147
+ end
148
+
149
+ class Tokenizer
150
+ attr_reader :scanner #:: StringScanner
151
+ attr_reader :current_token #:: token?
152
+
153
+ KEYWORDS = {
154
+ "returns" => :kRETURNS,
155
+ "inherits" => :kINHERITS,
156
+ "as" => :kAS,
157
+ "override" => :kOVERRIDE,
158
+ "use" => :kUSE,
159
+ "module-self" => :kMODULESELF,
160
+ "generic" => :kGENERIC,
161
+ "in" => :kIN,
162
+ "out" => :kOUT,
163
+ "unchecked" => :kUNCHECKED,
164
+ "self" => :kSELF,
165
+ "skip" => :kSKIP,
166
+ "yields" => :kYIELDS,
167
+ } #:: Hash[String, Symbol]
168
+ KW_RE = /#{Regexp.union(KEYWORDS.keys)}\b/
169
+
170
+ PUNCTS = {
171
+ "[optional]" => :kOPTIONAL,
172
+ "::" => :kCOLON2,
173
+ ":" => :kCOLON,
174
+ "[" => :kLBRACKET,
175
+ "]" => :kRBRACKET,
176
+ "," => :kCOMMA,
177
+ "*" => :kSTAR,
178
+ "--" => :kMINUS2,
179
+ "<" => :kLT,
180
+ "." => :kDOT,
181
+ } #:: Hash[String, Symbol]
182
+ PUNCTS_RE = Regexp.union(PUNCTS.keys) #:: Regexp
183
+
184
+ # @rbs scanner: StringScanner
185
+ # @rbs returns void
186
+ def initialize(scanner)
187
+ @scanner = scanner
188
+ @current_token = nil
189
+ end
190
+
191
+ # @rbs tree: AST::Tree
192
+ # @rbs returns token?
193
+ def advance(tree)
194
+ last = current_token
195
+
196
+ case
197
+ when s = scanner.scan(/\s+/)
198
+ tree << [:tWHITESPACE, s] if tree
199
+ advance(tree)
200
+ when s = scanner.scan(/@rbs!/)
201
+ @current_token = [:kRBSE, s]
202
+ when s = scanner.scan(/@rbs\b/)
203
+ @current_token = [:kRBS, s]
204
+ when s = scanner.scan(PUNCTS_RE)
205
+ @current_token = [PUNCTS.fetch(s), s]
206
+ when s = scanner.scan(KW_RE)
207
+ @current_token = [KEYWORDS.fetch(s), s]
208
+ when s = scanner.scan(/[A-Z]\w*/)
209
+ @current_token = [:tUIDENT, s]
210
+ when s = scanner.scan(/_[A-Z]\w*/)
211
+ @current_token = [:tIFIDENT, s]
212
+ when s = scanner.scan(/[a-z]\w*/)
213
+ @current_token = [:tLVAR, s]
214
+ when s = scanner.scan(/![a-z]\w*/)
215
+ @current_token = [:tELVAR, s]
216
+ when s = scanner.scan(/@\w+/)
217
+ @current_token = [:tATIDENT, s]
218
+ when s = scanner.scan(/%a\{[^}]+\}/)
219
+ @current_token = [:tANNOTATION, s]
220
+ when s = scanner.scan(/%a\[[^\]]+\]/)
221
+ @current_token = [:tANNOTATION, s]
222
+ when s = scanner.scan(/%a\([^)]+\)/)
223
+ @current_token = [:tANNOTATION, s]
224
+ else
225
+ @current_token = nil
226
+ end
227
+
228
+ last
229
+ end
230
+
231
+ # Consume given token type and inserts the token to the tree or `nil`
232
+ #
233
+ # @rbs type: Array[Symbol]
234
+ # @rbs tree: AST::Tree
235
+ # @rbs returns void
236
+ def consume_token(*types, tree:)
237
+ if type?(*types)
238
+ tree << advance(tree)
239
+ else
240
+ tree << nil
241
+ end
242
+ end
243
+
244
+ # Consume given token type and inserts the token to the tree or raise
245
+ #
246
+ # @rbs type: Array[Symbol]
247
+ # @rbs tree: AST::Tree
248
+ # @rbs returns void
249
+ def consume_token!(*types, tree:)
250
+ type!(*types)
251
+ tree << advance(tree)
252
+ end
253
+
254
+ # Test if current token has specified `type`
255
+ #
256
+ # @rbs type: Array[Symbol]
257
+ # @rbs returns bool
258
+ def type?(*type)
259
+ type.any? { current_token && current_token[0] == _1 }
260
+ end
261
+
262
+ # Ensure current token is one of the specified in types
263
+ #
264
+ # @rbs types: Array[Symbol]
265
+ # @rbs returns void
266
+ def type!(*types)
267
+ raise "Unexpected token: #{current_token&.[](0)}, where expected token: #{types.join(",")}" unless type?(*types)
268
+ end
269
+
270
+ # Reset the current_token to incoming comment `--`
271
+ #
272
+ # Reset to the end of the input if `--` token cannot be found.
273
+ #
274
+ # @rbs returns String -- String that is skipped
275
+ def skip_to_comment
276
+ return "" if type?(:kMINUS2)
277
+
278
+ if string = scanner.scan_until(/--/)
279
+ @current_token = [:kMINUS2, "--"]
280
+ string.delete_suffix("--")
281
+ else
282
+ s = scanner.rest
283
+ @current_token = [:kEOF, ""]
284
+ scanner.terminate
285
+ s
286
+ end
287
+ end
288
+ end
289
+
290
+ # @rbs comments: AST::CommentLines
291
+ # @rbs returns AST::Annotations::t?
292
+ def parse_annotation(comments)
293
+ scanner = StringScanner.new(comments.string)
294
+ tokenizer = Tokenizer.new(scanner)
295
+
296
+ tree = AST::Tree.new(:rbs_annotation)
297
+ tokenizer.advance(tree)
298
+
299
+ case
300
+ when tokenizer.type?(:kRBSE)
301
+ tree << tokenizer.current_token
302
+ tree << [:EMBEDDED_RBS, tokenizer.scanner.rest]
303
+ tokenizer.scanner.terminate
304
+ AST::Annotations::Embedded.new(tree, comments)
305
+ when tokenizer.type?(:kRBS)
306
+ tree << tokenizer.current_token
307
+
308
+ tokenizer.advance(tree)
309
+
310
+ case
311
+ when tokenizer.type?(:tLVAR, :tELVAR)
312
+ tree << parse_var_decl(tokenizer)
313
+ AST::Annotations::VarType.new(tree, comments)
314
+ when tokenizer.type?(:kSKIP)
315
+ AST::Annotations::Skip.new(tree, comments)
316
+ when tokenizer.type?(:kRETURNS)
317
+ tree << parse_return_type_decl(tokenizer)
318
+ AST::Annotations::ReturnType.new(tree, comments)
319
+ when tokenizer.type?(:tANNOTATION)
320
+ tree << parse_rbs_annotation(tokenizer)
321
+ AST::Annotations::RBSAnnotation.new(tree, comments)
322
+ when tokenizer.type?(:kINHERITS)
323
+ tree << parse_inherits(tokenizer)
324
+ AST::Annotations::Inherits.new(tree, comments)
325
+ when tokenizer.type?(:kOVERRIDE)
326
+ tree << parse_override(tokenizer)
327
+ AST::Annotations::Override.new(tree, comments)
328
+ when tokenizer.type?(:kUSE)
329
+ tree << parse_use(tokenizer)
330
+ AST::Annotations::Use.new(tree, comments)
331
+ when tokenizer.type?(:kMODULESELF)
332
+ tree << parse_module_self(tokenizer)
333
+ AST::Annotations::ModuleSelf.new(tree, comments)
334
+ when tokenizer.type?(:kGENERIC)
335
+ tree << parse_generic(tokenizer)
336
+ AST::Annotations::Generic.new(tree, comments)
337
+ when tokenizer.type?(:kSELF, :tATIDENT)
338
+ tree << parse_ivar_type(tokenizer)
339
+ AST::Annotations::IvarType.new(tree, comments)
340
+ when tokenizer.type?(:kYIELDS)
341
+ tree << parse_yields(tokenizer)
342
+ AST::Annotations::Yields.new(tree, comments)
343
+ end
344
+ when tokenizer.type?(:kCOLON2)
345
+ tree << tokenizer.current_token
346
+ tokenizer.advance(tree)
347
+ tree << parse_type_method_type(tokenizer, tree)
348
+ AST::Annotations::Assertion.new(tree, comments)
349
+ when tokenizer.type?(:kLBRACKET)
350
+ tree << parse_type_app(tokenizer)
351
+ AST::Annotations::Application.new(tree, comments)
352
+ end
353
+ end
354
+
355
+ # @rbs tokenizer: Tokenizer
356
+ # @rbs returns AST::Tree
357
+ def parse_var_decl(tokenizer)
358
+ tree = AST::Tree.new(:var_decl)
359
+
360
+ tokenizer.consume_token!(:tLVAR, :tELVAR, tree: tree)
361
+
362
+ if tokenizer.type?(:kCOLON)
363
+ tree << tokenizer.current_token
364
+ tokenizer.advance(tree)
365
+ else
366
+ tree << nil
367
+ end
368
+
369
+ tree << parse_type(tokenizer, tree)
370
+
371
+ if tokenizer.type?(:kMINUS2)
372
+ tree << parse_comment(tokenizer)
373
+ else
374
+ tree << nil
375
+ end
376
+
377
+ tree
378
+ end
379
+
380
+ # @rbs tokenizer: Tokenizer
381
+ # @rbs returns AST::Tree
382
+ def parse_return_type_decl(tokenizer)
383
+ tree = AST::Tree.new(:return_type_decl)
384
+
385
+ tokenizer.consume_token!(:kRETURNS, tree: tree)
386
+ tree << parse_type(tokenizer, tree)
387
+ tree << parse_optional(tokenizer, :kMINUS2) { parse_comment(tokenizer) }
388
+
389
+ tree
390
+ end
391
+
392
+ # @rbs tokenizer: Tokenizer
393
+ # @rbs returns AST::Tree
394
+ def parse_comment(tokenizer)
395
+ tree = AST::Tree.new(:comment)
396
+
397
+ tokenizer.type!(:kMINUS2)
398
+
399
+ tree << tokenizer.current_token
400
+ rest = tokenizer.scanner.rest || ""
401
+ tokenizer.scanner.terminate
402
+ tree << [:tCOMMENT, rest]
403
+
404
+ tree
405
+ end
406
+
407
+ # @rbs tokenizer: Tokenizer
408
+ # @rbs returns AST::Tree
409
+ def parse_type_app(tokenizer)
410
+ tree = AST::Tree.new(:tapp)
411
+
412
+ if tokenizer.type?(:kLBRACKET)
413
+ tree << tokenizer.current_token
414
+ tokenizer.advance(tree)
415
+ end
416
+
417
+ types = AST::Tree.new(:types)
418
+ while true
419
+ type = parse_type(tokenizer, types)
420
+ types << type
421
+
422
+ break unless type
423
+ break if type.is_a?(AST::Tree)
424
+
425
+ if tokenizer.type?(:kCOMMA)
426
+ types << tokenizer.current_token
427
+ tokenizer.advance(types)
428
+ end
429
+
430
+ if tokenizer.type?(:kRBRACKET)
431
+ break
432
+ end
433
+ end
434
+ tree << types
435
+
436
+ if tokenizer.type?(:kRBRACKET)
437
+ tree << tokenizer.current_token
438
+ tokenizer.advance(tree)
439
+ end
440
+
441
+ tree
442
+ end
443
+
444
+ # Parse a RBS method type or type and returns it
445
+ #
446
+ # It tries parsing a method type, and then parsing a type if failed.
447
+ #
448
+ # If both parsing failed, it returns a Tree(`:type_syntax_error), consuming all of the remaining input.
449
+ #
450
+ # Note that this doesn't recognize `--` comment unlike `parse_type`.
451
+ #
452
+ # @rbs tokenizer: Tokenizer
453
+ # @rbs parent_tree: AST::Tree
454
+ # @rbs returns MethodType | AST::Tree | Types::t | nil
455
+ def parse_type_method_type(tokenizer, parent_tree)
456
+ buffer = RBS::Buffer.new(name: "", content: tokenizer.scanner.string)
457
+ range = (tokenizer.scanner.charpos - (tokenizer.scanner.matched_size || 0) ..)
458
+ begin
459
+ if type = RBS::Parser.parse_method_type(buffer, range: range, require_eof: false)
460
+ loc = type.location or raise
461
+ size = loc.end_pos - loc.start_pos
462
+ (size - (tokenizer.scanner.matched_size || 0)).times do
463
+ tokenizer.scanner.skip(/./)
464
+ end
465
+ tokenizer.advance(parent_tree)
466
+ type
467
+ else
468
+ tokenizer.advance(parent_tree)
469
+ nil
470
+ end
471
+ rescue RBS::ParsingError
472
+ begin
473
+ if type = RBS::Parser.parse_type(buffer, range: range, require_eof: false)
474
+ loc = type.location or raise
475
+ size = loc.end_pos - loc.start_pos
476
+ (size - (tokenizer.scanner.matched_size || 0)).times do
477
+ tokenizer.scanner.skip(/./)
478
+ end
479
+ tokenizer.advance(parent_tree)
480
+ type
481
+ else
482
+ tokenizer.advance(parent_tree)
483
+ nil
484
+ end
485
+ rescue RBS::ParsingError
486
+ content = (tokenizer.scanner.matched || "") + (tokenizer.scanner.rest || "")
487
+ tree = AST::Tree.new(:type_syntax_error)
488
+ tree << [:tSOURCE, content]
489
+ tokenizer.scanner.terminate
490
+ tree
491
+ end
492
+ end
493
+ end
494
+
495
+ # Parse a RBS type and returns it
496
+ #
497
+ # If parsing failed, it returns a Tree(`:type_syntax_error), consuming
498
+ #
499
+ # 1. All of the input with `--` token if exists (for comments)
500
+ # 2. All of the input (for anything else)
501
+ #
502
+ # ```
503
+ # Integer -- Foo # => Returns `Integer`, tokenizer has `--` as its current token
504
+ # Integer[ -- Foo # => Returns a tree for `Integer[`, tokenizer has `--` as its curren token
505
+ # Integer[ Foo # => Returns a tree for `Integer[ Foo`, tokenizer is at the end of the input
506
+ # ```
507
+ #
508
+ # @rbs tokenizer: Tokenizer
509
+ # @rbs parent_tree: AST::Tree
510
+ # @rbs returns Types::t | AST::Tree | nil
511
+ def parse_type(tokenizer, parent_tree)
512
+ buffer = RBS::Buffer.new(name: "", content: tokenizer.scanner.string)
513
+ range = (tokenizer.scanner.charpos - (tokenizer.scanner.matched_size || 0) ..)
514
+ if type = RBS::Parser.parse_type(buffer, range: range, require_eof: false)
515
+ loc = type.location or raise
516
+ size = loc.end_pos - loc.start_pos
517
+ (size - (tokenizer.scanner.matched_size || 0)).times do
518
+ tokenizer.scanner.skip(/./)
519
+ end
520
+ tokenizer.advance(parent_tree)
521
+ type
522
+ else
523
+ tokenizer.advance(parent_tree)
524
+ nil
525
+ end
526
+ rescue RBS::ParsingError
527
+ content = tokenizer.skip_to_comment
528
+ tree = AST::Tree.new(:type_syntax_error)
529
+ tree << [:tSOURCE, content]
530
+ tree
531
+ end
532
+
533
+ # @rbs tokenizer: Tokenizer
534
+ # @rbs returns AST::Tree
535
+ def parse_rbs_annotation(tokenizer)
536
+ tree = AST::Tree.new(:rbs_annotation)
537
+
538
+ while tokenizer.type?(:tANNOTATION)
539
+ tree << tokenizer.current_token
540
+ tokenizer.advance(tree)
541
+ end
542
+
543
+ tree
544
+ end
545
+
546
+ # @rbs tokznier: Tokenizer
547
+ # @rbs returns AST::Tree
548
+ def parse_inherits(tokenizer)
549
+ tree = AST::Tree.new(:rbs_inherits)
550
+
551
+ if tokenizer.type?(:kINHERITS)
552
+ tree << tokenizer.current_token
553
+ tokenizer.advance(tree)
554
+ end
555
+
556
+ tree << parse_type(tokenizer, tree)
557
+
558
+ tree
559
+ end
560
+
561
+ # Parse `@rbs override` annotation
562
+ #
563
+ # @rbs tokenizer: Tokenizer
564
+ # @rbs returns AST::Tree
565
+ def parse_override(tokenizer)
566
+ tree = AST::Tree.new(:override)
567
+
568
+ if tokenizer.type?(:kOVERRIDE)
569
+ tree << tokenizer.current_token
570
+ tokenizer.advance(tree)
571
+ end
572
+
573
+ tree
574
+ end
575
+
576
+ # Parse `@rbs use [CLAUSES]` annotation
577
+ #
578
+ # @rbs tokenizer: Tokenizer
579
+ # @rbs returns AST::Tree
580
+ def parse_use(tokenizer)
581
+ tree = AST::Tree.new(:use)
582
+
583
+ if tokenizer.type?(:kUSE)
584
+ tree << tokenizer.current_token
585
+ tokenizer.advance(tree)
586
+ end
587
+
588
+ while tokenizer.type?(:kCOLON2, :tUIDENT, :tIFIDENT, :tLVAR)
589
+ tree << parse_use_clause(tokenizer)
590
+
591
+ if tokenizer.type?(:kCOMMA)
592
+ tree << tokenizer.advance(tree)
593
+ else
594
+ tree << nil
595
+ end
596
+ end
597
+
598
+ tree
599
+ end
600
+
601
+ # Parses use clause
602
+ #
603
+ # Returns one of the following form:
604
+ #
605
+ # * [`::`?, [UIDENT, `::`]*, LIDENT, [`as` LIDENT]?]
606
+ # * [`::`?, [UIDENT, `::`]*, UIDENT, [`as` UIDENT]?]
607
+ # * [`::`?, [UIDENT, `::`]*, IFIDENT, [`as`, IFIDENT]?]
608
+ # * [`::`?, [UIDENT) `::`]*, `*`]
609
+ #
610
+ # @rbs tokenizer: Tokenizer
611
+ # @rbs returns AST::Tree
612
+ def parse_use_clause(tokenizer)
613
+ tree = AST::Tree.new(:use_clause)
614
+
615
+ if tokenizer.type?(:kCOLON2)
616
+ tree << tokenizer.current_token
617
+ tokenizer.advance(tree)
618
+ end
619
+
620
+ while true
621
+ case
622
+ when tokenizer.type?(:tUIDENT)
623
+ tree << tokenizer.advance(tree)
624
+
625
+ case
626
+ when tokenizer.type?(:kCOLON2)
627
+ tree << tokenizer.advance(tree)
628
+ else
629
+ break
630
+ end
631
+ else
632
+ break
633
+ end
634
+ end
635
+
636
+ case
637
+ when tokenizer.type?(:tLVAR)
638
+ tree << tokenizer.advance(tree)
639
+ when tokenizer.type?(:tIFIDENT)
640
+ tree << tokenizer.advance(tree)
641
+ when tokenizer.type?(:kSTAR)
642
+ tree << tokenizer.advance(tree)
643
+ return tree
644
+ end
645
+
646
+ if tokenizer.type?(:kAS)
647
+ as_tree = AST::Tree.new(:as)
648
+
649
+ tokenizer.consume_token!(:kAS, tree: as_tree)
650
+ tokenizer.consume_token(:tLVAR, :tIFIDENT, :tUIDENT, tree: as_tree)
651
+
652
+ tree << as_tree
653
+ else
654
+ tree << nil
655
+ end
656
+
657
+ tree
658
+ end
659
+
660
+ # @rbs tokenizer: Tokenizer
661
+ # @rbs returns AST::Tree
662
+ def parse_module_self(tokenizer)
663
+ tree = AST::Tree.new(:module_self)
664
+
665
+ tokenizer.consume_token!(:kMODULESELF, tree: tree)
666
+ tree << parse_type(tokenizer, tree)
667
+
668
+ if tokenizer.type?(:kMINUS2)
669
+ tree << parse_comment(tokenizer)
670
+ else
671
+ tree << nil
672
+ end
673
+
674
+ tree
675
+ end
676
+
677
+ # Yield the block and return the resulting tree if tokenizer has current token of `types`
678
+ #
679
+ # ```rb
680
+ # # Test if tokenize has `--` token, then parse comment or insert `nil` to tree
681
+ #
682
+ # tree << parse_optional(tokenizer, :kMINUS2) do
683
+ # parse_comment(tokenizer)
684
+ # end
685
+ # ```
686
+ #
687
+ # @rbs tokenizer: Tokenizer
688
+ # @rbs types: Array[Symbol]
689
+ # @rbs block: ^() -> AST::Tree
690
+ # @rbs returns AST::Tree?
691
+ def parse_optional(tokenizer, *types, &block)
692
+ if tokenizer.type?(*types)
693
+ yield
694
+ end
695
+ end
696
+
697
+ # @rbs tokenizer: Tokenizer
698
+ # @rbs returns AST::Tree
699
+ def parse_generic(tokenizer)
700
+ tree = AST::Tree.new(:generic)
701
+
702
+ tokenizer.consume_token!(:kGENERIC, tree: tree)
703
+
704
+ tokenizer.consume_token(:kUNCHECKED, tree: tree)
705
+ tokenizer.consume_token(:kIN, :kOUT, tree: tree)
706
+
707
+ tokenizer.consume_token(:tUIDENT, tree: tree)
708
+
709
+ tree << parse_optional(tokenizer, :kLT) do
710
+ bound = AST::Tree.new(:upper_bound)
711
+
712
+ tokenizer.consume_token!(:kLT, tree: bound)
713
+ bound << parse_type(tokenizer, bound)
714
+
715
+ bound
716
+ end
717
+
718
+ tree << parse_optional(tokenizer, :kMINUS2) do
719
+ parse_comment(tokenizer)
720
+ end
721
+
722
+ tree
723
+ end
724
+
725
+ #:: (Tokenizer) -> AST::Tree
726
+ def parse_ivar_type(tokenizer)
727
+ tree = AST::Tree.new(:ivar_type)
728
+
729
+ tokenizer.consume_token(:kSELF, tree: tree)
730
+ tokenizer.consume_token(:kDOT, tree: tree)
731
+
732
+ tokenizer.consume_token(:tATIDENT, tree: tree)
733
+ tokenizer.consume_token(:kCOLON, tree: tree)
734
+
735
+ tree << parse_type(tokenizer, tree)
736
+
737
+ tree << parse_optional(tokenizer, :kMINUS2) do
738
+ parse_comment(tokenizer)
739
+ end
740
+
741
+ tree
742
+ end
743
+
744
+ #:: (Tokenizer) -> AST::Tree
745
+ def parse_yields(tokenizer)
746
+ tree = AST::Tree.new(:yields)
747
+
748
+ tokenizer.consume_token!(:kYIELDS, tree: tree)
749
+ tokenizer.consume_token(:kOPTIONAL, tree: tree)
750
+
751
+ unless (string = tokenizer.skip_to_comment()).empty?
752
+ tree << [:tBLOCKSTR, string]
753
+ else
754
+ tree << nil
755
+ end
756
+
757
+ tree << parse_optional(tokenizer, :kMINUS2) do
758
+ parse_comment(tokenizer)
759
+ end
760
+
761
+ tree
762
+ end
763
+ end
764
+ end
765
+ end