rbs-inline 0.1.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.
@@ -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