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,541 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module TRuby
6
+ module ParserCombinator
7
+ # Expression Parser - Parse expressions into IR nodes
8
+ # Uses Pratt parser (operator precedence parsing) for correct precedence
9
+ class ExpressionParser
10
+ include TokenDSL
11
+
12
+ # Operator precedence levels (higher = binds tighter)
13
+ PRECEDENCE = {
14
+ or_or: 1, # ||
15
+ and_and: 2, # &&
16
+ eq_eq: 3, # ==
17
+ bang_eq: 3, # !=
18
+ lt: 4, # <
19
+ gt: 4, # >
20
+ lt_eq: 4, # <=
21
+ gt_eq: 4, # >=
22
+ spaceship: 4, # <=>
23
+ pipe: 5, # | (bitwise or)
24
+ amp: 6, # & (bitwise and)
25
+ plus: 7, # +
26
+ minus: 7, # -
27
+ star: 8, # *
28
+ slash: 8, # /
29
+ percent: 8, # %
30
+ star_star: 9, # ** (right-associative)
31
+ }.freeze
32
+
33
+ # Right-associative operators
34
+ RIGHT_ASSOC = Set.new([:star_star]).freeze
35
+
36
+ # Token type to operator symbol mapping
37
+ OPERATOR_SYMBOLS = {
38
+ or_or: :"||",
39
+ and_and: :"&&",
40
+ eq_eq: :==,
41
+ bang_eq: :!=,
42
+ lt: :<,
43
+ gt: :>,
44
+ lt_eq: :<=,
45
+ gt_eq: :>=,
46
+ spaceship: :<=>,
47
+ plus: :+,
48
+ minus: :-,
49
+ star: :*,
50
+ slash: :/,
51
+ percent: :%,
52
+ star_star: :**,
53
+ pipe: :|,
54
+ amp: :&,
55
+ }.freeze
56
+
57
+ def parse_expression(tokens, position = 0)
58
+ parse_precedence(tokens, position, 0)
59
+ end
60
+
61
+ private
62
+
63
+ def parse_precedence(tokens, position, min_precedence)
64
+ result = parse_unary(tokens, position)
65
+ return result if result.failure?
66
+
67
+ left = result.value
68
+ pos = result.position
69
+
70
+ loop do
71
+ break if pos >= tokens.length || tokens[pos].type == :eof
72
+
73
+ operator_type = tokens[pos].type
74
+ precedence = PRECEDENCE[operator_type]
75
+ break unless precedence && precedence >= min_precedence
76
+
77
+ pos += 1 # consume operator
78
+
79
+ # Handle right associativity
80
+ next_min = RIGHT_ASSOC.include?(operator_type) ? precedence : precedence + 1
81
+ right_result = parse_precedence(tokens, pos, next_min)
82
+ return right_result if right_result.failure?
83
+
84
+ right = right_result.value
85
+ pos = right_result.position
86
+
87
+ left = IR::BinaryOp.new(
88
+ operator: OPERATOR_SYMBOLS[operator_type],
89
+ left: left,
90
+ right: right
91
+ )
92
+ end
93
+
94
+ # 삼항 연산자: condition ? then_branch : else_branch
95
+ if pos < tokens.length && tokens[pos].type == :question
96
+ pos += 1 # consume '?'
97
+
98
+ then_result = parse_expression(tokens, pos)
99
+ return then_result if then_result.failure?
100
+
101
+ pos = then_result.position
102
+
103
+ unless tokens[pos]&.type == :colon
104
+ return TokenParseResult.failure("Expected ':' in ternary operator", tokens, pos)
105
+ end
106
+
107
+ pos += 1 # consume ':'
108
+
109
+ else_result = parse_expression(tokens, pos)
110
+ return else_result if else_result.failure?
111
+
112
+ left = IR::Conditional.new(
113
+ kind: :ternary,
114
+ condition: left,
115
+ then_branch: then_result.value,
116
+ else_branch: else_result.value
117
+ )
118
+ pos = else_result.position
119
+ end
120
+
121
+ TokenParseResult.success(left, tokens, pos)
122
+ end
123
+
124
+ def parse_unary(tokens, position)
125
+ return TokenParseResult.failure("End of input", tokens, position) if position >= tokens.length
126
+
127
+ token = tokens[position]
128
+
129
+ case token.type
130
+ when :bang
131
+ result = parse_unary(tokens, position + 1)
132
+ return result if result.failure?
133
+
134
+ node = IR::UnaryOp.new(operator: :!, operand: result.value)
135
+ TokenParseResult.success(node, tokens, result.position)
136
+ when :minus
137
+ result = parse_unary(tokens, position + 1)
138
+ return result if result.failure?
139
+
140
+ # For negative number literals, we could fold them
141
+ node = if result.value.is_a?(IR::Literal) && result.value.literal_type == :integer
142
+ IR::Literal.new(value: -result.value.value, literal_type: :integer)
143
+ elsif result.value.is_a?(IR::Literal) && result.value.literal_type == :float
144
+ IR::Literal.new(value: -result.value.value, literal_type: :float)
145
+ else
146
+ IR::UnaryOp.new(operator: :-, operand: result.value)
147
+ end
148
+ TokenParseResult.success(node, tokens, result.position)
149
+ else
150
+ parse_postfix(tokens, position)
151
+ end
152
+ end
153
+
154
+ def parse_postfix(tokens, position)
155
+ result = parse_primary(tokens, position)
156
+ return result if result.failure?
157
+
158
+ left = result.value
159
+ pos = result.position
160
+
161
+ loop do
162
+ break if pos >= tokens.length || tokens[pos].type == :eof
163
+
164
+ case tokens[pos].type
165
+ when :dot
166
+ # Method call with receiver: obj.method or obj.method(args)
167
+ pos += 1
168
+ return TokenParseResult.failure("Expected method name after '.'", tokens, pos) if pos >= tokens.length
169
+
170
+ method_token = tokens[pos]
171
+ unless method_token.type == :identifier || keywords.key?(method_token.value)
172
+ return TokenParseResult.failure("Expected method name", tokens, pos)
173
+ end
174
+
175
+ method_name = method_token.value
176
+ pos += 1
177
+
178
+ # Check for arguments
179
+ args = []
180
+ if pos < tokens.length && tokens[pos].type == :lparen
181
+ args_result = parse_arguments(tokens, pos)
182
+ return args_result if args_result.failure?
183
+
184
+ args = args_result.value
185
+ pos = args_result.position
186
+ end
187
+
188
+ left = IR::MethodCall.new(
189
+ receiver: left,
190
+ method_name: method_name,
191
+ arguments: args
192
+ )
193
+ when :lbracket
194
+ # Array access: arr[index]
195
+ pos += 1
196
+ index_result = parse_expression(tokens, pos)
197
+ return index_result if index_result.failure?
198
+
199
+ pos = index_result.position
200
+ return TokenParseResult.failure("Expected ']'", tokens, pos) unless tokens[pos]&.type == :rbracket
201
+
202
+ pos += 1
203
+
204
+ left = IR::MethodCall.new(
205
+ receiver: left,
206
+ method_name: "[]",
207
+ arguments: [index_result.value]
208
+ )
209
+ when :lparen
210
+ # Function call without explicit receiver (left is identifier -> method call)
211
+ break unless left.is_a?(IR::VariableRef) && left.scope == :local
212
+
213
+ args_result = parse_arguments(tokens, pos)
214
+ return args_result if args_result.failure?
215
+
216
+ left = IR::MethodCall.new(
217
+ method_name: left.name,
218
+ arguments: args_result.value
219
+ )
220
+ pos = args_result.position
221
+
222
+ else
223
+ break
224
+ end
225
+ end
226
+
227
+ TokenParseResult.success(left, tokens, pos)
228
+ end
229
+
230
+ def parse_primary(tokens, position)
231
+ return TokenParseResult.failure("End of input", tokens, position) if position >= tokens.length
232
+
233
+ token = tokens[position]
234
+
235
+ case token.type
236
+ when :integer
237
+ node = IR::Literal.new(value: token.value.to_i, literal_type: :integer)
238
+ TokenParseResult.success(node, tokens, position + 1)
239
+
240
+ when :float
241
+ node = IR::Literal.new(value: token.value.to_f, literal_type: :float)
242
+ TokenParseResult.success(node, tokens, position + 1)
243
+
244
+ when :string
245
+ # Remove quotes from string value
246
+ value = token.value[1..-2]
247
+ node = IR::Literal.new(value: value, literal_type: :string)
248
+ TokenParseResult.success(node, tokens, position + 1)
249
+
250
+ when :string_start
251
+ # Interpolated string: string_start, string_content*, string_end
252
+ parse_interpolated_string(tokens, position)
253
+
254
+ when :symbol
255
+ # Remove : from symbol value
256
+ value = token.value[1..].to_sym
257
+ node = IR::Literal.new(value: value, literal_type: :symbol)
258
+ TokenParseResult.success(node, tokens, position + 1)
259
+
260
+ when true
261
+ node = IR::Literal.new(value: true, literal_type: :boolean)
262
+ TokenParseResult.success(node, tokens, position + 1)
263
+
264
+ when false
265
+ node = IR::Literal.new(value: false, literal_type: :boolean)
266
+ TokenParseResult.success(node, tokens, position + 1)
267
+
268
+ when :nil
269
+ node = IR::Literal.new(value: nil, literal_type: :nil)
270
+ TokenParseResult.success(node, tokens, position + 1)
271
+
272
+ when :identifier
273
+ node = IR::VariableRef.new(name: token.value, scope: :local)
274
+ TokenParseResult.success(node, tokens, position + 1)
275
+
276
+ when :constant
277
+ node = IR::VariableRef.new(name: token.value, scope: :constant)
278
+ TokenParseResult.success(node, tokens, position + 1)
279
+
280
+ when :ivar
281
+ node = IR::VariableRef.new(name: token.value, scope: :instance)
282
+ TokenParseResult.success(node, tokens, position + 1)
283
+
284
+ when :cvar
285
+ node = IR::VariableRef.new(name: token.value, scope: :class)
286
+ TokenParseResult.success(node, tokens, position + 1)
287
+
288
+ when :gvar
289
+ node = IR::VariableRef.new(name: token.value, scope: :global)
290
+ TokenParseResult.success(node, tokens, position + 1)
291
+
292
+ when :lparen
293
+ # Parenthesized expression
294
+ result = parse_expression(tokens, position + 1)
295
+ return result if result.failure?
296
+
297
+ pos = result.position
298
+ return TokenParseResult.failure("Expected ')'", tokens, pos) unless tokens[pos]&.type == :rparen
299
+
300
+ TokenParseResult.success(result.value, tokens, pos + 1)
301
+
302
+ when :lbracket
303
+ # Array literal
304
+ parse_array_literal(tokens, position)
305
+
306
+ when :lbrace
307
+ # Hash literal
308
+ parse_hash_literal(tokens, position)
309
+
310
+ else
311
+ TokenParseResult.failure("Unexpected token: #{token.type}", tokens, position)
312
+ end
313
+ end
314
+
315
+ def parse_arguments(tokens, position)
316
+ return TokenParseResult.failure("Expected '('", tokens, position) unless tokens[position]&.type == :lparen
317
+
318
+ position += 1
319
+
320
+ args = []
321
+
322
+ # Empty arguments
323
+ if tokens[position]&.type == :rparen
324
+ return TokenParseResult.success(args, tokens, position + 1)
325
+ end
326
+
327
+ # Parse first argument
328
+ result = parse_argument(tokens, position)
329
+ return result if result.failure?
330
+
331
+ args << result.value
332
+ position = result.position
333
+
334
+ # Parse remaining arguments
335
+ while tokens[position]&.type == :comma
336
+ position += 1
337
+ result = parse_argument(tokens, position)
338
+ return result if result.failure?
339
+
340
+ args << result.value
341
+ position = result.position
342
+ end
343
+
344
+ return TokenParseResult.failure("Expected ')'", tokens, position) unless tokens[position]&.type == :rparen
345
+
346
+ TokenParseResult.success(args, tokens, position + 1)
347
+ end
348
+
349
+ # Parse a single argument (handles splat, double splat, and keyword arguments)
350
+ def parse_argument(tokens, position)
351
+ # Double splat argument: **expr
352
+ if tokens[position]&.type == :star_star
353
+ position += 1
354
+ expr_result = parse_expression(tokens, position)
355
+ return expr_result if expr_result.failure?
356
+
357
+ # Wrap in a splat node (we'll use MethodCall with special name for now)
358
+ node = IR::MethodCall.new(
359
+ method_name: "**",
360
+ arguments: [expr_result.value]
361
+ )
362
+ return TokenParseResult.success(node, tokens, expr_result.position)
363
+ end
364
+
365
+ # Single splat argument: *expr
366
+ if tokens[position]&.type == :star
367
+ position += 1
368
+ expr_result = parse_expression(tokens, position)
369
+ return expr_result if expr_result.failure?
370
+
371
+ node = IR::MethodCall.new(
372
+ method_name: "*",
373
+ arguments: [expr_result.value]
374
+ )
375
+ return TokenParseResult.success(node, tokens, expr_result.position)
376
+ end
377
+
378
+ # Keyword argument: name: value
379
+ if tokens[position]&.type == :identifier && tokens[position + 1]&.type == :colon
380
+ key_name = tokens[position].value
381
+ position += 2 # skip identifier and colon
382
+
383
+ value_result = parse_expression(tokens, position)
384
+ return value_result if value_result.failure?
385
+
386
+ # Create a hash pair for keyword argument
387
+ key = IR::Literal.new(value: key_name.to_sym, literal_type: :symbol)
388
+ node = IR::HashPair.new(key: key, value: value_result.value)
389
+ return TokenParseResult.success(node, tokens, value_result.position)
390
+ end
391
+
392
+ # Regular expression argument
393
+ parse_expression(tokens, position)
394
+ end
395
+
396
+ def parse_array_literal(tokens, position)
397
+ return TokenParseResult.failure("Expected '['", tokens, position) unless tokens[position]&.type == :lbracket
398
+
399
+ position += 1
400
+
401
+ elements = []
402
+
403
+ # Empty array
404
+ if tokens[position]&.type == :rbracket
405
+ node = IR::ArrayLiteral.new(elements: elements)
406
+ return TokenParseResult.success(node, tokens, position + 1)
407
+ end
408
+
409
+ # Parse first element
410
+ result = parse_expression(tokens, position)
411
+ return result if result.failure?
412
+
413
+ elements << result.value
414
+ position = result.position
415
+
416
+ # Parse remaining elements
417
+ while tokens[position]&.type == :comma
418
+ position += 1
419
+ result = parse_expression(tokens, position)
420
+ return result if result.failure?
421
+
422
+ elements << result.value
423
+ position = result.position
424
+ end
425
+
426
+ return TokenParseResult.failure("Expected ']'", tokens, position) unless tokens[position]&.type == :rbracket
427
+
428
+ node = IR::ArrayLiteral.new(elements: elements)
429
+ TokenParseResult.success(node, tokens, position + 1)
430
+ end
431
+
432
+ def parse_hash_literal(tokens, position)
433
+ return TokenParseResult.failure("Expected '{'", tokens, position) unless tokens[position]&.type == :lbrace
434
+
435
+ position += 1
436
+
437
+ pairs = []
438
+
439
+ # Empty hash
440
+ if tokens[position]&.type == :rbrace
441
+ node = IR::HashLiteral.new(pairs: pairs)
442
+ return TokenParseResult.success(node, tokens, position + 1)
443
+ end
444
+
445
+ # Parse first pair
446
+ pair_result = parse_hash_pair(tokens, position)
447
+ return pair_result if pair_result.failure?
448
+
449
+ pairs << pair_result.value
450
+ position = pair_result.position
451
+
452
+ # Parse remaining pairs
453
+ while tokens[position]&.type == :comma
454
+ position += 1
455
+ pair_result = parse_hash_pair(tokens, position)
456
+ return pair_result if pair_result.failure?
457
+
458
+ pairs << pair_result.value
459
+ position = pair_result.position
460
+ end
461
+
462
+ return TokenParseResult.failure("Expected '}'", tokens, position) unless tokens[position]&.type == :rbrace
463
+
464
+ node = IR::HashLiteral.new(pairs: pairs)
465
+ TokenParseResult.success(node, tokens, position + 1)
466
+ end
467
+
468
+ def parse_hash_pair(tokens, position)
469
+ # Handle symbol key shorthand: key: value
470
+ if tokens[position]&.type == :identifier && tokens[position + 1]&.type == :colon
471
+ key = IR::Literal.new(value: tokens[position].value.to_sym, literal_type: :symbol)
472
+ position += 2 # skip identifier and colon
473
+ else
474
+ # Parse key expression
475
+ key_result = parse_expression(tokens, position)
476
+ return key_result if key_result.failure?
477
+
478
+ key = key_result.value
479
+ position = key_result.position
480
+
481
+ # Expect => or :
482
+ return TokenParseResult.failure("Expected ':' or '=>' in hash pair", tokens, position) unless tokens[position]&.type == :colon
483
+
484
+ position += 1
485
+
486
+ end
487
+
488
+ # Parse value expression
489
+ value_result = parse_expression(tokens, position)
490
+ return value_result if value_result.failure?
491
+
492
+ pair = IR::HashPair.new(key: key, value: value_result.value)
493
+ TokenParseResult.success(pair, tokens, value_result.position)
494
+ end
495
+
496
+ def parse_interpolated_string(tokens, position)
497
+ # string_start token contains the opening quote
498
+ position += 1
499
+
500
+ parts = []
501
+
502
+ while position < tokens.length
503
+ token = tokens[position]
504
+
505
+ case token.type
506
+ when :string_content
507
+ parts << IR::Literal.new(value: token.value, literal_type: :string)
508
+ position += 1
509
+ when :interpolation_start
510
+ # Skip #{ and parse expression
511
+ position += 1
512
+ expr_result = parse_expression(tokens, position)
513
+ return expr_result if expr_result.failure?
514
+
515
+ parts << expr_result.value
516
+ position = expr_result.position
517
+
518
+ # Expect interpolation_end (})
519
+ return TokenParseResult.failure("Expected '}'", tokens, position) unless tokens[position]&.type == :interpolation_end
520
+
521
+ position += 1
522
+
523
+ when :string_end
524
+ position += 1
525
+ break
526
+ else
527
+ return TokenParseResult.failure("Unexpected token in string: #{token.type}", tokens, position)
528
+ end
529
+ end
530
+
531
+ # Create interpolated string node
532
+ node = IR::InterpolatedString.new(parts: parts)
533
+ TokenParseResult.success(node, tokens, position)
534
+ end
535
+
536
+ def keywords
537
+ @keywords ||= TRuby::Scanner::KEYWORDS
538
+ end
539
+ end
540
+ end
541
+ end