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,644 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Statement Parser - Parse statements into IR nodes
6
+ class StatementParser
7
+ include TokenDSL
8
+
9
+ def initialize
10
+ @expression_parser = ExpressionParser.new
11
+ end
12
+
13
+ def parse_statement(tokens, position = 0)
14
+ return TokenParseResult.failure("End of input", tokens, position) if position >= tokens.length
15
+
16
+ # Skip newlines
17
+ position = skip_newlines(tokens, position)
18
+ return TokenParseResult.failure("End of input", tokens, position) if position >= tokens.length
19
+
20
+ token = tokens[position]
21
+
22
+ case token.type
23
+ when :return
24
+ parse_return(tokens, position)
25
+ when :if
26
+ parse_if(tokens, position)
27
+ when :unless
28
+ parse_unless(tokens, position)
29
+ when :while
30
+ parse_while(tokens, position)
31
+ when :until
32
+ parse_until(tokens, position)
33
+ when :case
34
+ parse_case(tokens, position)
35
+ when :begin
36
+ parse_begin(tokens, position)
37
+ else
38
+ # Could be assignment or expression
39
+ parse_assignment_or_expression(tokens, position)
40
+ end
41
+ end
42
+
43
+ def parse_block(tokens, position = 0)
44
+ statements = []
45
+
46
+ loop do
47
+ position = skip_newlines(tokens, position)
48
+ break if position >= tokens.length
49
+
50
+ token = tokens[position]
51
+ break if token.type == :eof
52
+ break if %i[end else elsif when rescue ensure].include?(token.type)
53
+
54
+ result = parse_statement(tokens, position)
55
+ break if result.failure?
56
+
57
+ statements << result.value
58
+ position = result.position
59
+ end
60
+
61
+ node = IR::Block.new(statements: statements)
62
+ TokenParseResult.success(node, tokens, position)
63
+ end
64
+
65
+ private
66
+
67
+ def skip_newlines(tokens, position)
68
+ position += 1 while position < tokens.length && %i[newline comment].include?(tokens[position].type)
69
+ position
70
+ end
71
+
72
+ def parse_return(tokens, position)
73
+ position += 1 # consume 'return'
74
+
75
+ # Check if there's a return value
76
+ position = skip_newlines_if_not_modifier(tokens, position)
77
+
78
+ if position >= tokens.length ||
79
+ tokens[position].type == :eof ||
80
+ tokens[position].type == :newline ||
81
+ end_of_statement?(tokens, position)
82
+ node = IR::Return.new(value: nil)
83
+ return TokenParseResult.success(node, tokens, position)
84
+ end
85
+
86
+ # Parse return value expression
87
+ expr_result = @expression_parser.parse_expression(tokens, position)
88
+ return expr_result if expr_result.failure?
89
+
90
+ # Check for modifier
91
+ modifier_result = parse_modifier(tokens, expr_result.position, IR::Return.new(value: expr_result.value))
92
+ return modifier_result if modifier_result.success? && modifier_result.value.is_a?(IR::Conditional)
93
+
94
+ node = IR::Return.new(value: expr_result.value)
95
+ TokenParseResult.success(node, tokens, expr_result.position)
96
+ end
97
+
98
+ def parse_if(tokens, position)
99
+ position += 1 # consume 'if'
100
+
101
+ # Parse condition
102
+ cond_result = @expression_parser.parse_expression(tokens, position)
103
+ return cond_result if cond_result.failure?
104
+
105
+ position = cond_result.position
106
+
107
+ # Skip newline after condition
108
+ position = skip_newlines(tokens, position)
109
+
110
+ # Parse then branch
111
+ then_result = parse_block(tokens, position)
112
+ position = then_result.position
113
+ position = skip_newlines(tokens, position)
114
+
115
+ # Check for elsif or else
116
+ else_branch = nil
117
+ if position < tokens.length && tokens[position].type == :elsif
118
+ elsif_result = parse_if(tokens, position) # Reuse if parsing for elsif
119
+ return elsif_result if elsif_result.failure?
120
+
121
+ else_branch = elsif_result.value
122
+ position = elsif_result.position
123
+ elsif position < tokens.length && tokens[position].type == :else
124
+ position += 1 # consume 'else'
125
+ position = skip_newlines(tokens, position)
126
+ else_result = parse_block(tokens, position)
127
+ else_branch = else_result.value
128
+ position = else_result.position
129
+ position = skip_newlines(tokens, position)
130
+ end
131
+
132
+ # Expect 'end' (unless it was an elsif chain)
133
+ if position < tokens.length && tokens[position].type == :end
134
+ position += 1
135
+ end
136
+
137
+ node = IR::Conditional.new(
138
+ kind: :if,
139
+ condition: cond_result.value,
140
+ then_branch: then_result.value,
141
+ else_branch: else_branch
142
+ )
143
+ TokenParseResult.success(node, tokens, position)
144
+ end
145
+
146
+ def parse_unless(tokens, position)
147
+ position += 1 # consume 'unless'
148
+
149
+ # Parse condition
150
+ cond_result = @expression_parser.parse_expression(tokens, position)
151
+ return cond_result if cond_result.failure?
152
+
153
+ position = cond_result.position
154
+
155
+ # Skip newline
156
+ position = skip_newlines(tokens, position)
157
+
158
+ # Parse then branch
159
+ then_result = parse_block(tokens, position)
160
+ position = then_result.position
161
+ position = skip_newlines(tokens, position)
162
+
163
+ # Check for else
164
+ else_branch = nil
165
+ if position < tokens.length && tokens[position].type == :else
166
+ position += 1
167
+ position = skip_newlines(tokens, position)
168
+ else_result = parse_block(tokens, position)
169
+ else_branch = else_result.value
170
+ position = else_result.position
171
+ position = skip_newlines(tokens, position)
172
+ end
173
+
174
+ # Expect 'end'
175
+ if position < tokens.length && tokens[position].type == :end
176
+ position += 1
177
+ end
178
+
179
+ node = IR::Conditional.new(
180
+ kind: :unless,
181
+ condition: cond_result.value,
182
+ then_branch: then_result.value,
183
+ else_branch: else_branch
184
+ )
185
+ TokenParseResult.success(node, tokens, position)
186
+ end
187
+
188
+ def parse_while(tokens, position)
189
+ position += 1 # consume 'while'
190
+
191
+ # Parse condition
192
+ cond_result = @expression_parser.parse_expression(tokens, position)
193
+ return cond_result if cond_result.failure?
194
+
195
+ position = cond_result.position
196
+
197
+ # Skip newline
198
+ position = skip_newlines(tokens, position)
199
+
200
+ # Parse body
201
+ body_result = parse_block(tokens, position)
202
+ position = body_result.position
203
+ position = skip_newlines(tokens, position)
204
+
205
+ # Expect 'end'
206
+ if position < tokens.length && tokens[position].type == :end
207
+ position += 1
208
+ end
209
+
210
+ node = IR::Loop.new(
211
+ kind: :while,
212
+ condition: cond_result.value,
213
+ body: body_result.value
214
+ )
215
+ TokenParseResult.success(node, tokens, position)
216
+ end
217
+
218
+ def parse_until(tokens, position)
219
+ position += 1 # consume 'until'
220
+
221
+ # Parse condition
222
+ cond_result = @expression_parser.parse_expression(tokens, position)
223
+ return cond_result if cond_result.failure?
224
+
225
+ position = cond_result.position
226
+
227
+ # Skip newline
228
+ position = skip_newlines(tokens, position)
229
+
230
+ # Parse body
231
+ body_result = parse_block(tokens, position)
232
+ position = body_result.position
233
+ position = skip_newlines(tokens, position)
234
+
235
+ # Expect 'end'
236
+ if position < tokens.length && tokens[position].type == :end
237
+ position += 1
238
+ end
239
+
240
+ node = IR::Loop.new(
241
+ kind: :until,
242
+ condition: cond_result.value,
243
+ body: body_result.value
244
+ )
245
+ TokenParseResult.success(node, tokens, position)
246
+ end
247
+
248
+ def parse_case(tokens, position)
249
+ position += 1 # consume 'case'
250
+
251
+ # Parse subject (optional)
252
+ subject = nil
253
+ position = skip_newlines(tokens, position)
254
+
255
+ if position < tokens.length && tokens[position].type != :when
256
+ subj_result = @expression_parser.parse_expression(tokens, position)
257
+ if subj_result.success?
258
+ subject = subj_result.value
259
+ position = subj_result.position
260
+ end
261
+ end
262
+
263
+ position = skip_newlines(tokens, position)
264
+
265
+ # Parse when clauses
266
+ when_clauses = []
267
+ while position < tokens.length && tokens[position].type == :when
268
+ when_result = parse_when_clause(tokens, position)
269
+ return when_result if when_result.failure?
270
+
271
+ when_clauses << when_result.value
272
+ position = when_result.position
273
+ position = skip_newlines(tokens, position)
274
+ end
275
+
276
+ # Parse else clause
277
+ else_clause = nil
278
+ if position < tokens.length && tokens[position].type == :else
279
+ position += 1
280
+ position = skip_newlines(tokens, position)
281
+ else_result = parse_block(tokens, position)
282
+ else_clause = else_result.value
283
+ position = else_result.position
284
+ position = skip_newlines(tokens, position)
285
+ end
286
+
287
+ # Expect 'end'
288
+ if position < tokens.length && tokens[position].type == :end
289
+ position += 1
290
+ end
291
+
292
+ node = IR::CaseExpr.new(
293
+ subject: subject,
294
+ when_clauses: when_clauses,
295
+ else_clause: else_clause
296
+ )
297
+ TokenParseResult.success(node, tokens, position)
298
+ end
299
+
300
+ def parse_when_clause(tokens, position)
301
+ position += 1 # consume 'when'
302
+
303
+ # Parse patterns (comma-separated)
304
+ patterns = []
305
+ loop do
306
+ pattern_result = @expression_parser.parse_expression(tokens, position)
307
+ return pattern_result if pattern_result.failure?
308
+
309
+ patterns << pattern_result.value
310
+ position = pattern_result.position
311
+
312
+ break unless tokens[position]&.type == :comma
313
+
314
+ position += 1
315
+ end
316
+
317
+ position = skip_newlines(tokens, position)
318
+
319
+ # Parse body
320
+ body_result = parse_block(tokens, position)
321
+ position = body_result.position
322
+
323
+ node = IR::WhenClause.new(patterns: patterns, body: body_result.value)
324
+ TokenParseResult.success(node, tokens, position)
325
+ end
326
+
327
+ def parse_begin(tokens, position)
328
+ position += 1 # consume 'begin'
329
+ position = skip_newlines(tokens, position)
330
+
331
+ # Parse body
332
+ body_result = parse_block(tokens, position)
333
+ position = body_result.position
334
+ position = skip_newlines(tokens, position)
335
+
336
+ # Parse rescue clauses
337
+ rescue_clauses = []
338
+ while position < tokens.length && tokens[position].type == :rescue
339
+ rescue_result = parse_rescue_clause(tokens, position)
340
+ return rescue_result if rescue_result.failure?
341
+
342
+ rescue_clauses << rescue_result.value
343
+ position = rescue_result.position
344
+ position = skip_newlines(tokens, position)
345
+ end
346
+
347
+ # Parse else clause (runs if no exception)
348
+ else_clause = nil
349
+ if position < tokens.length && tokens[position].type == :else
350
+ position += 1
351
+ position = skip_newlines(tokens, position)
352
+ else_result = parse_block(tokens, position)
353
+ else_clause = else_result.value
354
+ position = else_result.position
355
+ position = skip_newlines(tokens, position)
356
+ end
357
+
358
+ # Parse ensure clause
359
+ ensure_clause = nil
360
+ if position < tokens.length && tokens[position].type == :ensure
361
+ position += 1
362
+ position = skip_newlines(tokens, position)
363
+ ensure_result = parse_block(tokens, position)
364
+ ensure_clause = ensure_result.value
365
+ position = ensure_result.position
366
+ position = skip_newlines(tokens, position)
367
+ end
368
+
369
+ # Expect 'end'
370
+ if position < tokens.length && tokens[position].type == :end
371
+ position += 1
372
+ end
373
+
374
+ node = IR::BeginBlock.new(
375
+ body: body_result.value,
376
+ rescue_clauses: rescue_clauses,
377
+ else_clause: else_clause,
378
+ ensure_clause: ensure_clause
379
+ )
380
+ TokenParseResult.success(node, tokens, position)
381
+ end
382
+
383
+ def parse_rescue_clause(tokens, position)
384
+ position += 1 # consume 'rescue'
385
+
386
+ exception_types = []
387
+ variable = nil
388
+
389
+ # Check for exception types and variable binding
390
+ # Format: rescue ExType, ExType2 => var or rescue => var
391
+ # Parse exception types
392
+ if position < tokens.length && !%i[newline hash_rocket].include?(tokens[position].type) && (tokens[position].type == :constant)
393
+ loop do
394
+ if tokens[position].type == :constant
395
+ exception_types << tokens[position].value
396
+ position += 1
397
+ end
398
+ break unless tokens[position]&.type == :comma
399
+
400
+ position += 1
401
+ end
402
+ end
403
+
404
+ # Check for => var binding
405
+ if position < tokens.length && tokens[position].type == :hash_rocket
406
+ position += 1
407
+ if tokens[position]&.type == :identifier
408
+ variable = tokens[position].value
409
+ position += 1
410
+ end
411
+ end
412
+
413
+ position = skip_newlines(tokens, position)
414
+
415
+ # Parse body
416
+ body_result = parse_block(tokens, position)
417
+ position = body_result.position
418
+
419
+ node = IR::RescueClause.new(
420
+ exception_types: exception_types,
421
+ variable: variable,
422
+ body: body_result.value
423
+ )
424
+ TokenParseResult.success(node, tokens, position)
425
+ end
426
+
427
+ def parse_assignment_or_expression(tokens, position)
428
+ # Check for typed assignment: name: Type = value
429
+ if tokens[position].type == :identifier &&
430
+ tokens[position + 1]&.type == :colon &&
431
+ tokens[position + 2]&.type == :constant
432
+ return parse_typed_assignment(tokens, position)
433
+ end
434
+
435
+ # Check for simple assignment patterns
436
+ if assignable_token?(tokens[position])
437
+ next_pos = position + 1
438
+
439
+ # Simple assignment: x = value
440
+ if tokens[next_pos]&.type == :eq
441
+ return parse_simple_assignment(tokens, position)
442
+ end
443
+
444
+ # Compound assignment: x += value, x -= value, etc.
445
+ if compound_assignment_token?(tokens[next_pos])
446
+ return parse_compound_assignment(tokens, position)
447
+ end
448
+ end
449
+
450
+ # Parse as expression
451
+ expr_result = @expression_parser.parse_expression(tokens, position)
452
+ return expr_result if expr_result.failure?
453
+
454
+ # Check for statement modifiers
455
+ parse_modifier(tokens, expr_result.position, expr_result.value)
456
+ end
457
+
458
+ def parse_typed_assignment(tokens, position)
459
+ target = tokens[position].value
460
+ position += 2 # skip identifier and colon
461
+
462
+ # Parse type annotation (simple constant for now)
463
+ type_annotation = IR::SimpleType.new(name: tokens[position].value)
464
+ position += 1
465
+
466
+ # Expect '='
467
+ return TokenParseResult.failure("Expected '='", tokens, position) unless tokens[position]&.type == :eq
468
+
469
+ position += 1
470
+
471
+ # Parse value
472
+ value_result = @expression_parser.parse_expression(tokens, position)
473
+ return value_result if value_result.failure?
474
+
475
+ node = IR::Assignment.new(
476
+ target: target,
477
+ value: value_result.value,
478
+ type_annotation: type_annotation
479
+ )
480
+ TokenParseResult.success(node, tokens, value_result.position)
481
+ end
482
+
483
+ def parse_simple_assignment(tokens, position)
484
+ target = tokens[position].value
485
+ position += 2 # skip variable and '='
486
+
487
+ # Check for statement expressions (case, if, begin, etc.) as value
488
+ value_result = case tokens[position]&.type
489
+ when :case
490
+ parse_case(tokens, position)
491
+ when :if
492
+ parse_if(tokens, position)
493
+ when :unless
494
+ parse_unless(tokens, position)
495
+ when :begin
496
+ parse_begin(tokens, position)
497
+ else
498
+ @expression_parser.parse_expression(tokens, position)
499
+ end
500
+ return value_result if value_result.failure?
501
+
502
+ node = IR::Assignment.new(target: target, value: value_result.value)
503
+ TokenParseResult.success(node, tokens, value_result.position)
504
+ end
505
+
506
+ def parse_compound_assignment(tokens, position)
507
+ target = tokens[position].value
508
+ op_token = tokens[position + 1]
509
+ position += 2 # skip variable and operator
510
+
511
+ # Map compound operator to binary operator
512
+ op_map = {
513
+ plus_eq: :+,
514
+ minus_eq: :-,
515
+ star_eq: :*,
516
+ slash_eq: :/,
517
+ percent_eq: :%,
518
+ }
519
+ binary_op = op_map[op_token.type]
520
+
521
+ # Parse right-hand side
522
+ rhs_result = @expression_parser.parse_expression(tokens, position)
523
+ return rhs_result if rhs_result.failure?
524
+
525
+ # Create expanded form: x = x + value
526
+ target_ref = IR::VariableRef.new(name: target, scope: infer_scope(target))
527
+ binary_expr = IR::BinaryOp.new(
528
+ operator: binary_op,
529
+ left: target_ref,
530
+ right: rhs_result.value
531
+ )
532
+
533
+ node = IR::Assignment.new(target: target, value: binary_expr)
534
+
535
+ # Check for statement modifiers
536
+ parse_modifier(tokens, rhs_result.position, node)
537
+ end
538
+
539
+ def parse_modifier(tokens, position, statement)
540
+ return TokenParseResult.success(statement, tokens, position) if position >= tokens.length
541
+
542
+ token = tokens[position]
543
+ case token.type
544
+ when :if
545
+ position += 1
546
+ cond_result = @expression_parser.parse_expression(tokens, position)
547
+ return cond_result if cond_result.failure?
548
+
549
+ then_branch = statement.is_a?(IR::Block) ? statement : IR::Block.new(statements: [statement])
550
+ node = IR::Conditional.new(
551
+ kind: :if,
552
+ condition: cond_result.value,
553
+ then_branch: then_branch
554
+ )
555
+ TokenParseResult.success(node, tokens, cond_result.position)
556
+
557
+ when :unless
558
+ position += 1
559
+ cond_result = @expression_parser.parse_expression(tokens, position)
560
+ return cond_result if cond_result.failure?
561
+
562
+ then_branch = statement.is_a?(IR::Block) ? statement : IR::Block.new(statements: [statement])
563
+ node = IR::Conditional.new(
564
+ kind: :unless,
565
+ condition: cond_result.value,
566
+ then_branch: then_branch
567
+ )
568
+ TokenParseResult.success(node, tokens, cond_result.position)
569
+
570
+ when :while
571
+ position += 1
572
+ cond_result = @expression_parser.parse_expression(tokens, position)
573
+ return cond_result if cond_result.failure?
574
+
575
+ body = statement.is_a?(IR::Block) ? statement : IR::Block.new(statements: [statement])
576
+ node = IR::Loop.new(
577
+ kind: :while,
578
+ condition: cond_result.value,
579
+ body: body
580
+ )
581
+ TokenParseResult.success(node, tokens, cond_result.position)
582
+
583
+ when :until
584
+ position += 1
585
+ cond_result = @expression_parser.parse_expression(tokens, position)
586
+ return cond_result if cond_result.failure?
587
+
588
+ body = statement.is_a?(IR::Block) ? statement : IR::Block.new(statements: [statement])
589
+ node = IR::Loop.new(
590
+ kind: :until,
591
+ condition: cond_result.value,
592
+ body: body
593
+ )
594
+ TokenParseResult.success(node, tokens, cond_result.position)
595
+
596
+ else
597
+ TokenParseResult.success(statement, tokens, position)
598
+ end
599
+ end
600
+
601
+ def assignable_token?(token)
602
+ return false unless token
603
+
604
+ %i[identifier ivar cvar gvar].include?(token.type)
605
+ end
606
+
607
+ def compound_assignment_token?(token)
608
+ return false unless token
609
+
610
+ %i[plus_eq minus_eq star_eq slash_eq percent_eq].include?(token.type)
611
+ end
612
+
613
+ def end_of_statement?(tokens, position)
614
+ return true if position >= tokens.length
615
+
616
+ %i[newline eof end else elsif when rescue ensure].include?(tokens[position].type)
617
+ end
618
+
619
+ def skip_newlines_if_not_modifier(tokens, position)
620
+ # Don't skip newlines if next token after newline is a modifier
621
+ if tokens[position]&.type == :newline
622
+ next_pos = position + 1
623
+ next_pos += 1 while next_pos < tokens.length && tokens[next_pos].type == :newline
624
+ # If next meaningful token is a modifier, return original position
625
+ if next_pos < tokens.length && %i[if unless while until].include?(tokens[next_pos].type)
626
+ return position
627
+ end
628
+ end
629
+ skip_newlines(tokens, position)
630
+ end
631
+
632
+ def infer_scope(name)
633
+ case name[0]
634
+ when "@"
635
+ name[1] == "@" ? :class : :instance
636
+ when "$"
637
+ :global
638
+ else
639
+ :local
640
+ end
641
+ end
642
+ end
643
+ end
644
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRuby
4
+ module ParserCombinator
5
+ # Alternative: try first, if fails try second
6
+ class TokenAlternative < TokenParser
7
+ def initialize(left, right)
8
+ @left = left
9
+ @right = right
10
+ end
11
+
12
+ def parse(tokens, position = 0)
13
+ result = @left.parse(tokens, position)
14
+ return result if result.success?
15
+
16
+ @right.parse(tokens, position)
17
+ end
18
+ end
19
+ end
20
+ end