t-ruby 0.0.42 → 0.0.43

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/lib/t_ruby/ast_type_inferrer.rb +2 -0
  3. data/lib/t_ruby/cache.rb +40 -10
  4. data/lib/t_ruby/cli.rb +13 -8
  5. data/lib/t_ruby/compiler.rb +168 -0
  6. data/lib/t_ruby/diagnostic.rb +115 -0
  7. data/lib/t_ruby/diagnostic_formatter.rb +162 -0
  8. data/lib/t_ruby/error_handler.rb +201 -35
  9. data/lib/t_ruby/error_reporter.rb +57 -0
  10. data/lib/t_ruby/ir.rb +39 -1
  11. data/lib/t_ruby/lsp_server.rb +40 -97
  12. data/lib/t_ruby/parser.rb +18 -4
  13. data/lib/t_ruby/parser_combinator/combinators/alternative.rb +20 -0
  14. data/lib/t_ruby/parser_combinator/combinators/chain_left.rb +34 -0
  15. data/lib/t_ruby/parser_combinator/combinators/choice.rb +29 -0
  16. data/lib/t_ruby/parser_combinator/combinators/flat_map.rb +21 -0
  17. data/lib/t_ruby/parser_combinator/combinators/label.rb +22 -0
  18. data/lib/t_ruby/parser_combinator/combinators/lookahead.rb +21 -0
  19. data/lib/t_ruby/parser_combinator/combinators/many.rb +29 -0
  20. data/lib/t_ruby/parser_combinator/combinators/many1.rb +32 -0
  21. data/lib/t_ruby/parser_combinator/combinators/map.rb +17 -0
  22. data/lib/t_ruby/parser_combinator/combinators/not_followed_by.rb +21 -0
  23. data/lib/t_ruby/parser_combinator/combinators/optional.rb +21 -0
  24. data/lib/t_ruby/parser_combinator/combinators/sep_by.rb +34 -0
  25. data/lib/t_ruby/parser_combinator/combinators/sep_by1.rb +34 -0
  26. data/lib/t_ruby/parser_combinator/combinators/sequence.rb +23 -0
  27. data/lib/t_ruby/parser_combinator/combinators/skip_right.rb +23 -0
  28. data/lib/t_ruby/parser_combinator/declaration_parser.rb +147 -0
  29. data/lib/t_ruby/parser_combinator/dsl.rb +115 -0
  30. data/lib/t_ruby/parser_combinator/parse_error.rb +48 -0
  31. data/lib/t_ruby/parser_combinator/parse_result.rb +46 -0
  32. data/lib/t_ruby/parser_combinator/parser.rb +84 -0
  33. data/lib/t_ruby/parser_combinator/primitives/end_of_input.rb +16 -0
  34. data/lib/t_ruby/parser_combinator/primitives/fail.rb +16 -0
  35. data/lib/t_ruby/parser_combinator/primitives/lazy.rb +18 -0
  36. data/lib/t_ruby/parser_combinator/primitives/literal.rb +21 -0
  37. data/lib/t_ruby/parser_combinator/primitives/pure.rb +16 -0
  38. data/lib/t_ruby/parser_combinator/primitives/regex.rb +25 -0
  39. data/lib/t_ruby/parser_combinator/primitives/satisfy.rb +21 -0
  40. data/lib/t_ruby/parser_combinator/token/expression_parser.rb +541 -0
  41. data/lib/t_ruby/parser_combinator/token/statement_parser.rb +644 -0
  42. data/lib/t_ruby/parser_combinator/token/token_alternative.rb +20 -0
  43. data/lib/t_ruby/parser_combinator/token/token_body_parser.rb +54 -0
  44. data/lib/t_ruby/parser_combinator/token/token_declaration_parser.rb +920 -0
  45. data/lib/t_ruby/parser_combinator/token/token_dsl.rb +16 -0
  46. data/lib/t_ruby/parser_combinator/token/token_label.rb +22 -0
  47. data/lib/t_ruby/parser_combinator/token/token_many.rb +29 -0
  48. data/lib/t_ruby/parser_combinator/token/token_many1.rb +32 -0
  49. data/lib/t_ruby/parser_combinator/token/token_map.rb +17 -0
  50. data/lib/t_ruby/parser_combinator/token/token_matcher.rb +29 -0
  51. data/lib/t_ruby/parser_combinator/token/token_optional.rb +21 -0
  52. data/lib/t_ruby/parser_combinator/token/token_parse_result.rb +40 -0
  53. data/lib/t_ruby/parser_combinator/token/token_parser.rb +62 -0
  54. data/lib/t_ruby/parser_combinator/token/token_sep_by.rb +34 -0
  55. data/lib/t_ruby/parser_combinator/token/token_sep_by1.rb +34 -0
  56. data/lib/t_ruby/parser_combinator/token/token_sequence.rb +23 -0
  57. data/lib/t_ruby/parser_combinator/token/token_skip_right.rb +23 -0
  58. data/lib/t_ruby/parser_combinator/type_parser.rb +103 -0
  59. data/lib/t_ruby/parser_combinator.rb +64 -936
  60. data/lib/t_ruby/scanner.rb +883 -0
  61. data/lib/t_ruby/version.rb +1 -1
  62. data/lib/t_ruby/watcher.rb +67 -75
  63. data/lib/t_ruby.rb +15 -1
  64. metadata +51 -2
  65. data/lib/t_ruby/body_parser.rb +0 -561
@@ -0,0 +1,920 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Token Declaration Parser - Parse top-level declarations
6
+ class TokenDeclarationParser
7
+ include TokenDSL
8
+
9
+ # Parse error with location info
10
+ class ParseError
11
+ attr_reader :message, :line, :column, :token
12
+
13
+ def initialize(message, token: nil)
14
+ @message = message
15
+ @token = token
16
+ @line = token&.line || 1
17
+ @column = token&.column || 1
18
+ end
19
+
20
+ def to_s
21
+ "Line #{@line}, Column #{@column}: #{@message}"
22
+ end
23
+ end
24
+
25
+ attr_reader :errors
26
+
27
+ def initialize
28
+ @statement_parser = StatementParser.new
29
+ @expression_parser = ExpressionParser.new
30
+ @errors = []
31
+ end
32
+
33
+ def parse_declaration(tokens, position = 0)
34
+ return TokenParseResult.failure("End of input", tokens, position) if position >= tokens.length
35
+
36
+ position = skip_newlines(tokens, position)
37
+ return TokenParseResult.failure("End of input", tokens, position) if position >= tokens.length
38
+
39
+ token = tokens[position]
40
+
41
+ case token.type
42
+ when :def
43
+ parse_method_def(tokens, position)
44
+ when :public, :private, :protected
45
+ parse_visibility_method(tokens, position)
46
+ when :class
47
+ parse_class(tokens, position)
48
+ when :module
49
+ parse_module(tokens, position)
50
+ when :type
51
+ parse_type_alias(tokens, position)
52
+ when :interface
53
+ parse_interface(tokens, position)
54
+ else
55
+ TokenParseResult.failure("Expected declaration, got #{token.type}", tokens, position)
56
+ end
57
+ end
58
+
59
+ def parse_program(tokens, position = 0)
60
+ declarations = []
61
+ @errors = []
62
+
63
+ loop do
64
+ position = skip_newlines(tokens, position)
65
+ break if position >= tokens.length
66
+ break if tokens[position].type == :eof
67
+
68
+ token = tokens[position]
69
+
70
+ # Check if this looks like a declaration keyword
71
+ unless declaration_keyword?(token.type)
72
+ # Not a declaration - skip to next line (top-level expression is allowed)
73
+ position = skip_to_next_line(tokens, position)
74
+ next
75
+ end
76
+
77
+ result = parse_declaration(tokens, position)
78
+
79
+ if result.failure?
80
+ # Collect error and try to recover
81
+ # Use result.position for accurate error location (where the error actually occurred)
82
+ error_pos = result.position
83
+ error_token = tokens[error_pos] if error_pos < tokens.length
84
+ @errors << ParseError.new(result.error, token: error_token)
85
+
86
+ # Try to skip to next declaration (find next 'def', 'class', etc.)
87
+ position = skip_to_next_declaration(tokens, position)
88
+ next
89
+ end
90
+
91
+ declarations << result.value
92
+ position = result.position
93
+ end
94
+
95
+ program = IR::Program.new(declarations: declarations)
96
+ TokenParseResult.success(program, tokens, position)
97
+ end
98
+
99
+ # Check if parsing encountered any errors
100
+ def has_errors?
101
+ !@errors.empty?
102
+ end
103
+
104
+ private
105
+
106
+ def skip_newlines(tokens, position)
107
+ position += 1 while position < tokens.length && %i[newline comment].include?(tokens[position].type)
108
+ position
109
+ end
110
+
111
+ # Check if token type is a declaration keyword
112
+ def declaration_keyword?(type)
113
+ %i[def class module type interface public private protected].include?(type)
114
+ end
115
+
116
+ # Skip to the next line (for top-level expressions)
117
+ def skip_to_next_line(tokens, position)
118
+ while position < tokens.length
119
+ break if tokens[position].type == :newline
120
+
121
+ position += 1
122
+ end
123
+ position += 1 if position < tokens.length # skip the newline itself
124
+ position
125
+ end
126
+
127
+ # Skip to the next top-level declaration keyword for error recovery
128
+ def skip_to_next_declaration(tokens, position)
129
+ declaration_keywords = %i[def class module type interface public private protected]
130
+
131
+ # First, skip the current token
132
+ position += 1
133
+
134
+ while position < tokens.length
135
+ token = tokens[position]
136
+
137
+ # Found a declaration keyword at start of line (or after newline)
138
+ if declaration_keywords.include?(token.type)
139
+ # Check if this is at start of a logical line
140
+ prev_token = tokens[position - 1] if position.positive?
141
+ if prev_token.nil? || prev_token.type == :newline
142
+ return position
143
+ end
144
+ end
145
+
146
+ # Skip to next line if we hit newline
147
+ if token.type == :newline
148
+ position += 1
149
+ # Skip comments and blank lines
150
+ position = skip_newlines(tokens, position)
151
+ next
152
+ end
153
+
154
+ position += 1
155
+ end
156
+
157
+ position
158
+ end
159
+
160
+ def parse_method_def(tokens, position, visibility: :public)
161
+ # Capture def token's location before consuming
162
+ def_token = tokens[position]
163
+ def_line = def_token.line
164
+ def_column = def_token.column
165
+
166
+ position += 1 # consume 'def'
167
+
168
+ # Parse method name (identifier or operator)
169
+ return TokenParseResult.failure("Expected method name", tokens, position) if position >= tokens.length
170
+
171
+ method_name = tokens[position].value
172
+ position += 1
173
+
174
+ # Check for unexpected tokens after method name (indicates space in method name)
175
+ if position < tokens.length
176
+ next_token = tokens[position]
177
+ # After method name, only these are valid: ( : newline end
178
+ # If we see an identifier, it means there was a space in the method name
179
+ if next_token.type == :identifier
180
+ return TokenParseResult.failure(
181
+ "Unexpected token '#{next_token.value}' after method name '#{method_name}' - method names cannot contain spaces",
182
+ tokens,
183
+ position
184
+ )
185
+ end
186
+ end
187
+
188
+ # Parse parameters
189
+ params = []
190
+ if position < tokens.length && tokens[position].type == :lparen
191
+ position += 1 # consume (
192
+
193
+ # Parse parameter list
194
+ unless tokens[position].type == :rparen
195
+ loop do
196
+ param_result = parse_parameter(tokens, position)
197
+ return param_result if param_result.failure?
198
+
199
+ # Handle keyword args group which returns an array
200
+ if param_result.value.is_a?(Array)
201
+ params.concat(param_result.value)
202
+ else
203
+ params << param_result.value
204
+ end
205
+ position = param_result.position
206
+
207
+ break unless tokens[position]&.type == :comma
208
+
209
+ position += 1
210
+ end
211
+ end
212
+
213
+ return TokenParseResult.failure("Expected ')'", tokens, position) unless tokens[position]&.type == :rparen
214
+
215
+ position += 1
216
+ end
217
+
218
+ # Parse return type
219
+ return_type = nil
220
+ if position < tokens.length && tokens[position].type == :colon
221
+ colon_token = tokens[position]
222
+
223
+ # Check: no space allowed before colon (method name or ) must be adjacent to :)
224
+ prev_token = tokens[position - 1]
225
+ if prev_token && prev_token.end_pos < colon_token.start_pos
226
+ return TokenParseResult.failure(
227
+ "No space allowed before ':' for return type annotation",
228
+ tokens,
229
+ position
230
+ )
231
+ end
232
+
233
+ position += 1
234
+
235
+ # Check: space required after colon before type name
236
+ if position < tokens.length
237
+ type_token = tokens[position]
238
+ if colon_token.end_pos == type_token.start_pos
239
+ return TokenParseResult.failure(
240
+ "Space required after ':' before return type",
241
+ tokens,
242
+ position
243
+ )
244
+ end
245
+ end
246
+
247
+ type_result = parse_type(tokens, position)
248
+ return type_result if type_result.failure?
249
+
250
+ return_type = type_result.value
251
+ position = type_result.position
252
+ elsif position < tokens.length && tokens[position].type == :symbol
253
+ # Handle case where :TypeName was scanned as a symbol (no space after colon)
254
+ # In method definition context, this is a syntax error
255
+ symbol_token = tokens[position]
256
+ type_name = symbol_token.value[1..] # Remove leading ':'
257
+
258
+ # Only if it looks like a type name (starts with uppercase)
259
+ if type_name =~ /^[A-Z]/
260
+ # Check: no space allowed before colon
261
+ prev_token = tokens[position - 1]
262
+ if prev_token && prev_token.end_pos < symbol_token.start_pos
263
+ return TokenParseResult.failure(
264
+ "No space allowed before ':' for return type annotation",
265
+ tokens,
266
+ position
267
+ )
268
+ end
269
+
270
+ # Error: space required after colon
271
+ return TokenParseResult.failure(
272
+ "Space required after ':' before return type",
273
+ tokens,
274
+ position
275
+ )
276
+ end
277
+ end
278
+
279
+ position = skip_newlines(tokens, position)
280
+
281
+ # Parse body
282
+ body_result = @statement_parser.parse_block(tokens, position)
283
+ position = body_result.position
284
+ position = skip_newlines(tokens, position)
285
+
286
+ # Expect 'end'
287
+ if position < tokens.length && tokens[position].type == :end
288
+ position += 1
289
+ end
290
+
291
+ node = IR::MethodDef.new(
292
+ name: method_name,
293
+ params: params,
294
+ return_type: return_type,
295
+ body: body_result.value,
296
+ visibility: visibility,
297
+ location: "#{def_line}:#{def_column}"
298
+ )
299
+ TokenParseResult.success(node, tokens, position)
300
+ end
301
+
302
+ def parse_visibility_method(tokens, position)
303
+ visibility = tokens[position].type
304
+ position += 1
305
+
306
+ if position < tokens.length && tokens[position].type == :def
307
+ parse_method_def(tokens, position, visibility: visibility)
308
+ else
309
+ TokenParseResult.failure("Expected 'def' after visibility modifier", tokens, position)
310
+ end
311
+ end
312
+
313
+ def parse_parameter(tokens, position)
314
+ return TokenParseResult.failure("Expected parameter", tokens, position) if position >= tokens.length
315
+
316
+ # Check for different parameter types
317
+ case tokens[position].type
318
+ when :lbrace
319
+ # Keyword args group: { name: Type, age: Type = default }
320
+ return parse_keyword_args_group(tokens, position)
321
+
322
+ when :star
323
+ # Splat parameter *args
324
+ position += 1
325
+ return TokenParseResult.failure("Expected parameter name after *", tokens, position) if position >= tokens.length
326
+
327
+ name = tokens[position].value
328
+ position += 1
329
+
330
+ # Check for type annotation: *args: Type
331
+ type_annotation = nil
332
+ if position < tokens.length && tokens[position].type == :colon
333
+ position += 1
334
+ type_result = parse_type(tokens, position)
335
+ return type_result if type_result.failure?
336
+
337
+ type_annotation = type_result.value
338
+ position = type_result.position
339
+ end
340
+
341
+ param = IR::Parameter.new(name: name, kind: :rest, type_annotation: type_annotation)
342
+ return TokenParseResult.success(param, tokens, position)
343
+
344
+ when :star_star
345
+ # Double splat **opts or **opts: Type
346
+ position += 1
347
+ return TokenParseResult.failure("Expected parameter name after **", tokens, position) if position >= tokens.length
348
+
349
+ name = tokens[position].value
350
+ position += 1
351
+
352
+ # Check for type annotation: **opts: Type
353
+ type_annotation = nil
354
+ if position < tokens.length && tokens[position].type == :colon
355
+ position += 1
356
+ type_result = parse_type(tokens, position)
357
+ return type_result if type_result.failure?
358
+
359
+ type_annotation = type_result.value
360
+ position = type_result.position
361
+ end
362
+
363
+ param = IR::Parameter.new(name: name, kind: :keyrest, type_annotation: type_annotation)
364
+ return TokenParseResult.success(param, tokens, position)
365
+
366
+ when :amp
367
+ # Block parameter &block or &block: Type
368
+ position += 1
369
+ return TokenParseResult.failure("Expected parameter name after &", tokens, position) if position >= tokens.length
370
+
371
+ name = tokens[position].value
372
+ position += 1
373
+
374
+ # Check for type annotation: &block: Type
375
+ type_annotation = nil
376
+ if position < tokens.length && tokens[position].type == :colon
377
+ position += 1
378
+ type_result = parse_type(tokens, position)
379
+ return type_result if type_result.failure?
380
+
381
+ type_annotation = type_result.value
382
+ position = type_result.position
383
+ end
384
+
385
+ param = IR::Parameter.new(name: name, kind: :block, type_annotation: type_annotation)
386
+ return TokenParseResult.success(param, tokens, position)
387
+ end
388
+
389
+ # Regular parameter: name or name: Type or name: Type = default
390
+ name = tokens[position].value
391
+ position += 1
392
+
393
+ type_annotation = nil
394
+ default_value = nil
395
+
396
+ if position < tokens.length && tokens[position].type == :colon
397
+ position += 1
398
+
399
+ # Check if next token is a type (constant/identifier) or a default value
400
+ if position < tokens.length
401
+ type_result = parse_type(tokens, position)
402
+ return type_result if type_result.failure?
403
+
404
+ type_annotation = type_result.value
405
+ position = type_result.position
406
+ end
407
+ end
408
+
409
+ # Check for default value: = expression
410
+ if position < tokens.length && tokens[position].type == :eq
411
+ position += 1
412
+ # Skip the default value expression (parse until comma, rparen, or newline)
413
+ position = skip_default_value(tokens, position)
414
+ default_value = true # Just mark that there's a default value
415
+ end
416
+
417
+ kind = default_value ? :optional : :required
418
+ param = IR::Parameter.new(name: name, type_annotation: type_annotation, default_value: default_value, kind: kind)
419
+ TokenParseResult.success(param, tokens, position)
420
+ end
421
+
422
+ # Parse keyword args group: { name: Type, age: Type = default } or { name:, age: default }: InterfaceName
423
+ def parse_keyword_args_group(tokens, position)
424
+ position += 1 # consume '{'
425
+
426
+ params = []
427
+ while position < tokens.length && tokens[position].type != :rbrace
428
+ # Skip newlines inside braces
429
+ position = skip_newlines(tokens, position)
430
+ break if position >= tokens.length || tokens[position].type == :rbrace
431
+
432
+ # Parse each keyword arg: name: Type or name: Type = default or name: or name: default
433
+ return TokenParseResult.failure("Expected parameter name", tokens, position) unless tokens[position].type == :identifier
434
+
435
+ name = tokens[position].value
436
+ position += 1
437
+
438
+ type_annotation = nil
439
+ default_value = nil
440
+
441
+ if position < tokens.length && tokens[position].type == :colon
442
+ position += 1
443
+
444
+ # Check what follows the colon
445
+ if position < tokens.length
446
+ next_token = tokens[position]
447
+
448
+ # If it's a type (constant), parse the type
449
+ if next_token.type == :constant
450
+ type_result = parse_type(tokens, position)
451
+ unless type_result.failure?
452
+ type_annotation = type_result.value
453
+ position = type_result.position
454
+ end
455
+ elsif next_token.type != :comma && next_token.type != :rbrace && next_token.type != :newline
456
+ # Ruby-style default value (without =): name: default_value
457
+ # e.g., { name:, limit: 10 }: InterfaceName
458
+ position = skip_default_value_in_braces(tokens, position)
459
+ default_value = true
460
+ end
461
+ # If next_token is comma/rbrace/newline, it's shorthand `name:` with no type or default
462
+ end
463
+ end
464
+
465
+ # Check for default value: = expression (T-Ruby style with equals sign)
466
+ if position < tokens.length && tokens[position].type == :eq
467
+ position += 1
468
+ position = skip_default_value_in_braces(tokens, position)
469
+ default_value = true
470
+ end
471
+
472
+ params << IR::Parameter.new(name: name, type_annotation: type_annotation, default_value: default_value, kind: :keyword)
473
+
474
+ # Skip comma
475
+ if position < tokens.length && tokens[position].type == :comma
476
+ position += 1
477
+ end
478
+
479
+ position = skip_newlines(tokens, position)
480
+ end
481
+
482
+ return TokenParseResult.failure("Expected '}'", tokens, position) unless position < tokens.length && tokens[position].type == :rbrace
483
+
484
+ position += 1 # consume '}'
485
+
486
+ # Check for interface type annotation: { ... }: InterfaceName
487
+ interface_type = nil
488
+ if position < tokens.length && tokens[position].type == :colon
489
+ position += 1
490
+ type_result = parse_type(tokens, position)
491
+ unless type_result.failure?
492
+ interface_type = type_result.value
493
+ position = type_result.position
494
+ end
495
+ end
496
+
497
+ # If there's an interface type, set it as interface_ref for each param
498
+ if interface_type
499
+ params.each { |p| p.interface_ref = interface_type }
500
+ end
501
+
502
+ # Return the array of keyword params wrapped in a result
503
+ # We'll handle this specially in parse_method_def
504
+ TokenParseResult.success(params, tokens, position)
505
+ end
506
+
507
+ # Skip a default value expression (until comma, rparen, or newline)
508
+ def skip_default_value(tokens, position)
509
+ depth = 0
510
+ while position < tokens.length
511
+ token = tokens[position]
512
+ case token.type
513
+ when :lparen, :lbracket, :lbrace
514
+ depth += 1
515
+ when :rparen
516
+ return position if depth.zero?
517
+
518
+ depth -= 1
519
+ when :rbracket, :rbrace
520
+ depth -= 1
521
+ when :comma
522
+ return position if depth.zero?
523
+ when :newline
524
+ return position if depth.zero?
525
+ end
526
+ position += 1
527
+ end
528
+ position
529
+ end
530
+
531
+ # Skip a default value expression inside braces (until comma, rbrace, or newline)
532
+ def skip_default_value_in_braces(tokens, position)
533
+ depth = 0
534
+ while position < tokens.length
535
+ token = tokens[position]
536
+ case token.type
537
+ when :lparen, :lbracket
538
+ depth += 1
539
+ when :rparen, :rbracket
540
+ depth -= 1
541
+ when :lbrace
542
+ depth += 1
543
+ when :rbrace
544
+ return position if depth.zero?
545
+
546
+ depth -= 1
547
+ when :comma
548
+ return position if depth.zero?
549
+ when :newline
550
+ return position if depth.zero?
551
+ end
552
+ position += 1
553
+ end
554
+ position
555
+ end
556
+
557
+ def parse_class(tokens, position)
558
+ position += 1 # consume 'class'
559
+
560
+ # Parse class name
561
+ return TokenParseResult.failure("Expected class name", tokens, position) if position >= tokens.length
562
+
563
+ class_name = tokens[position].value
564
+ position += 1
565
+
566
+ # Check for superclass
567
+ superclass = nil
568
+ if position < tokens.length && tokens[position].type == :lt
569
+ position += 1
570
+ superclass = tokens[position].value
571
+ position += 1
572
+ end
573
+
574
+ position = skip_newlines(tokens, position)
575
+
576
+ # Parse class body (methods and instance variables)
577
+ body = []
578
+ instance_vars = []
579
+
580
+ loop do
581
+ position = skip_newlines(tokens, position)
582
+ break if position >= tokens.length
583
+ break if tokens[position].type == :end
584
+
585
+ if tokens[position].type == :ivar && tokens[position + 1]&.type == :colon
586
+ # Instance variable declaration: @name: Type
587
+ ivar_result = parse_instance_var_decl(tokens, position)
588
+ return ivar_result if ivar_result.failure?
589
+
590
+ instance_vars << ivar_result.value
591
+ position = ivar_result.position
592
+ elsif %i[def public private protected].include?(tokens[position].type)
593
+ method_result = parse_declaration(tokens, position)
594
+ return method_result if method_result.failure?
595
+
596
+ body << method_result.value
597
+ position = method_result.position
598
+ else
599
+ break
600
+ end
601
+ end
602
+
603
+ # Expect 'end'
604
+ if position < tokens.length && tokens[position].type == :end
605
+ position += 1
606
+ end
607
+
608
+ node = IR::ClassDecl.new(
609
+ name: class_name,
610
+ superclass: superclass,
611
+ body: body,
612
+ instance_vars: instance_vars
613
+ )
614
+ TokenParseResult.success(node, tokens, position)
615
+ end
616
+
617
+ def parse_instance_var_decl(tokens, position)
618
+ # @name: Type
619
+ name = tokens[position].value[1..] # remove @ prefix
620
+ position += 2 # skip @name and :
621
+
622
+ type_result = parse_type(tokens, position)
623
+ return type_result if type_result.failure?
624
+
625
+ node = IR::InstanceVariable.new(name: name, type_annotation: type_result.value)
626
+ TokenParseResult.success(node, tokens, type_result.position)
627
+ end
628
+
629
+ def parse_module(tokens, position)
630
+ position += 1 # consume 'module'
631
+
632
+ # Parse module name
633
+ return TokenParseResult.failure("Expected module name", tokens, position) if position >= tokens.length
634
+
635
+ module_name = tokens[position].value
636
+ position += 1
637
+
638
+ position = skip_newlines(tokens, position)
639
+
640
+ # Parse module body
641
+ body = []
642
+
643
+ loop do
644
+ position = skip_newlines(tokens, position)
645
+ break if position >= tokens.length
646
+ break if tokens[position].type == :end
647
+
648
+ break unless %i[def public private protected].include?(tokens[position].type)
649
+
650
+ method_result = parse_declaration(tokens, position)
651
+ return method_result if method_result.failure?
652
+
653
+ body << method_result.value
654
+ position = method_result.position
655
+ end
656
+
657
+ # Expect 'end'
658
+ if position < tokens.length && tokens[position].type == :end
659
+ position += 1
660
+ end
661
+
662
+ node = IR::ModuleDecl.new(name: module_name, body: body)
663
+ TokenParseResult.success(node, tokens, position)
664
+ end
665
+
666
+ def parse_type_alias(tokens, position)
667
+ position += 1 # consume 'type'
668
+
669
+ # Parse type name
670
+ return TokenParseResult.failure("Expected type name", tokens, position) if position >= tokens.length
671
+
672
+ type_name = tokens[position].value
673
+ position += 1
674
+
675
+ # Expect '='
676
+ return TokenParseResult.failure("Expected '='", tokens, position) unless tokens[position]&.type == :eq
677
+
678
+ position += 1
679
+
680
+ # Parse type definition
681
+ type_result = parse_type(tokens, position)
682
+ return type_result if type_result.failure?
683
+
684
+ node = IR::TypeAlias.new(name: type_name, definition: type_result.value)
685
+ TokenParseResult.success(node, tokens, type_result.position)
686
+ end
687
+
688
+ def parse_interface(tokens, position)
689
+ position += 1 # consume 'interface'
690
+
691
+ # Parse interface name
692
+ return TokenParseResult.failure("Expected interface name", tokens, position) if position >= tokens.length
693
+
694
+ interface_name = tokens[position].value
695
+ position += 1
696
+
697
+ position = skip_newlines(tokens, position)
698
+
699
+ # Parse interface members
700
+ members = []
701
+
702
+ loop do
703
+ position = skip_newlines(tokens, position)
704
+ break if position >= tokens.length
705
+ break if tokens[position].type == :end
706
+
707
+ member_result = parse_interface_member(tokens, position)
708
+ break if member_result.failure?
709
+
710
+ members << member_result.value
711
+ position = member_result.position
712
+ end
713
+
714
+ # Expect 'end'
715
+ if position < tokens.length && tokens[position].type == :end
716
+ position += 1
717
+ end
718
+
719
+ node = IR::Interface.new(name: interface_name, members: members)
720
+ TokenParseResult.success(node, tokens, position)
721
+ end
722
+
723
+ def parse_interface_member(tokens, position)
724
+ # name: Type
725
+ return TokenParseResult.failure("Expected member name", tokens, position) if position >= tokens.length
726
+
727
+ name = tokens[position].value
728
+ position += 1
729
+
730
+ return TokenParseResult.failure("Expected ':'", tokens, position) unless tokens[position]&.type == :colon
731
+
732
+ position += 1
733
+
734
+ type_result = parse_type(tokens, position)
735
+ return type_result if type_result.failure?
736
+
737
+ node = IR::InterfaceMember.new(name: name, type_signature: type_result.value)
738
+ TokenParseResult.success(node, tokens, type_result.position)
739
+ end
740
+
741
+ def parse_type(tokens, position)
742
+ return TokenParseResult.failure("Expected type", tokens, position) if position >= tokens.length
743
+
744
+ # Parse primary type
745
+ result = parse_primary_type(tokens, position)
746
+ return result if result.failure?
747
+
748
+ type = result.value
749
+ position = result.position
750
+
751
+ # Check for union type
752
+ types = [type]
753
+ while position < tokens.length && tokens[position].type == :pipe
754
+ position += 1
755
+ next_result = parse_primary_type(tokens, position)
756
+ return next_result if next_result.failure?
757
+
758
+ types << next_result.value
759
+ position = next_result.position
760
+ end
761
+
762
+ if types.length > 1
763
+ node = IR::UnionType.new(types: types)
764
+ TokenParseResult.success(node, tokens, position)
765
+ else
766
+ TokenParseResult.success(type, tokens, position)
767
+ end
768
+ end
769
+
770
+ def parse_primary_type(tokens, position)
771
+ return TokenParseResult.failure("Expected type", tokens, position) if position >= tokens.length
772
+
773
+ # Check for function type: -> ReturnType
774
+ if tokens[position].type == :arrow
775
+ position += 1
776
+ return_result = parse_primary_type(tokens, position)
777
+ return return_result if return_result.failure?
778
+
779
+ node = IR::FunctionType.new(param_types: [], return_type: return_result.value)
780
+ return TokenParseResult.success(node, tokens, return_result.position)
781
+ end
782
+
783
+ # Check for tuple type: (Type, Type) -> ReturnType
784
+ if tokens[position].type == :lparen
785
+ position += 1
786
+ param_types = []
787
+
788
+ unless tokens[position].type == :rparen
789
+ loop do
790
+ type_result = parse_type(tokens, position)
791
+ return type_result if type_result.failure?
792
+
793
+ param_types << type_result.value
794
+ position = type_result.position
795
+
796
+ break unless tokens[position]&.type == :comma
797
+
798
+ position += 1
799
+ end
800
+ end
801
+
802
+ return TokenParseResult.failure("Expected ')'", tokens, position) unless tokens[position]&.type == :rparen
803
+
804
+ position += 1
805
+
806
+ # Check for function arrow
807
+ if position < tokens.length && tokens[position].type == :arrow
808
+ position += 1
809
+ return_result = parse_primary_type(tokens, position)
810
+ return return_result if return_result.failure?
811
+
812
+ node = IR::FunctionType.new(param_types: param_types, return_type: return_result.value)
813
+ return TokenParseResult.success(node, tokens, return_result.position)
814
+ else
815
+ node = IR::TupleType.new(element_types: param_types)
816
+ return TokenParseResult.success(node, tokens, position)
817
+ end
818
+ end
819
+
820
+ # Check for hash literal type: { key: Type, key2: Type }
821
+ if tokens[position].type == :lbrace
822
+ return parse_hash_literal_type(tokens, position)
823
+ end
824
+
825
+ # Simple type or generic type
826
+ type_name = tokens[position].value
827
+ position += 1
828
+
829
+ # Check for generic arguments: Type<Args>
830
+ if position < tokens.length && tokens[position].type == :lt
831
+ position += 1
832
+ type_args = []
833
+
834
+ loop do
835
+ arg_result = parse_type(tokens, position)
836
+ return arg_result if arg_result.failure?
837
+
838
+ type_args << arg_result.value
839
+ position = arg_result.position
840
+
841
+ break unless tokens[position]&.type == :comma
842
+
843
+ position += 1
844
+ end
845
+
846
+ return TokenParseResult.failure("Expected '>'", tokens, position) unless tokens[position]&.type == :gt
847
+
848
+ position += 1
849
+
850
+ node = IR::GenericType.new(base: type_name, type_args: type_args)
851
+ TokenParseResult.success(node, tokens, position)
852
+ elsif position < tokens.length && tokens[position].type == :question
853
+ # Check for nullable: Type?
854
+ position += 1
855
+ inner = IR::SimpleType.new(name: type_name)
856
+ node = IR::NullableType.new(inner_type: inner)
857
+ TokenParseResult.success(node, tokens, position)
858
+ else
859
+ node = IR::SimpleType.new(name: type_name)
860
+ TokenParseResult.success(node, tokens, position)
861
+ end
862
+ end
863
+
864
+ # Parse hash literal type: { key: Type, key2: Type }
865
+ # Used for typed hash parameters like: def foo(config: { host: String, port: Integer })
866
+ def parse_hash_literal_type(tokens, position)
867
+ return TokenParseResult.failure("Expected '{'", tokens, position) unless tokens[position]&.type == :lbrace
868
+
869
+ position += 1 # consume '{'
870
+
871
+ fields = []
872
+ while position < tokens.length && tokens[position].type != :rbrace
873
+ # Skip newlines inside braces
874
+ position = skip_newlines(tokens, position)
875
+ break if position >= tokens.length || tokens[position].type == :rbrace
876
+
877
+ # Parse field: name: Type
878
+ unless tokens[position].type == :identifier
879
+ return TokenParseResult.failure("Expected field name", tokens, position)
880
+ end
881
+
882
+ field_name = tokens[position].value
883
+ position += 1
884
+
885
+ unless tokens[position]&.type == :colon
886
+ return TokenParseResult.failure("Expected ':' after field name", tokens, position)
887
+ end
888
+
889
+ position += 1
890
+
891
+ type_result = parse_type(tokens, position)
892
+ return type_result if type_result.failure?
893
+
894
+ fields << { name: field_name, type: type_result.value }
895
+ position = type_result.position
896
+
897
+ # Handle optional default value (skip it for type purposes)
898
+ if position < tokens.length && tokens[position].type == :eq
899
+ position += 1
900
+ position = skip_default_value_in_braces(tokens, position)
901
+ end
902
+
903
+ # Skip comma if present
904
+ if position < tokens.length && tokens[position].type == :comma
905
+ position += 1
906
+ end
907
+ end
908
+
909
+ unless tokens[position]&.type == :rbrace
910
+ return TokenParseResult.failure("Expected '}'", tokens, position)
911
+ end
912
+
913
+ position += 1 # consume '}'
914
+
915
+ node = IR::HashLiteralType.new(fields: fields)
916
+ TokenParseResult.success(node, tokens, position)
917
+ end
918
+ end
919
+ end
920
+ end