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.
- checksums.yaml +4 -4
- data/lib/t_ruby/ast_type_inferrer.rb +2 -0
- data/lib/t_ruby/cache.rb +40 -10
- data/lib/t_ruby/cli.rb +13 -8
- data/lib/t_ruby/compiler.rb +168 -0
- data/lib/t_ruby/diagnostic.rb +115 -0
- data/lib/t_ruby/diagnostic_formatter.rb +162 -0
- data/lib/t_ruby/error_handler.rb +201 -35
- data/lib/t_ruby/error_reporter.rb +57 -0
- data/lib/t_ruby/ir.rb +39 -1
- data/lib/t_ruby/lsp_server.rb +40 -97
- data/lib/t_ruby/parser.rb +18 -4
- data/lib/t_ruby/parser_combinator/combinators/alternative.rb +20 -0
- data/lib/t_ruby/parser_combinator/combinators/chain_left.rb +34 -0
- data/lib/t_ruby/parser_combinator/combinators/choice.rb +29 -0
- data/lib/t_ruby/parser_combinator/combinators/flat_map.rb +21 -0
- data/lib/t_ruby/parser_combinator/combinators/label.rb +22 -0
- data/lib/t_ruby/parser_combinator/combinators/lookahead.rb +21 -0
- data/lib/t_ruby/parser_combinator/combinators/many.rb +29 -0
- data/lib/t_ruby/parser_combinator/combinators/many1.rb +32 -0
- data/lib/t_ruby/parser_combinator/combinators/map.rb +17 -0
- data/lib/t_ruby/parser_combinator/combinators/not_followed_by.rb +21 -0
- data/lib/t_ruby/parser_combinator/combinators/optional.rb +21 -0
- data/lib/t_ruby/parser_combinator/combinators/sep_by.rb +34 -0
- data/lib/t_ruby/parser_combinator/combinators/sep_by1.rb +34 -0
- data/lib/t_ruby/parser_combinator/combinators/sequence.rb +23 -0
- data/lib/t_ruby/parser_combinator/combinators/skip_right.rb +23 -0
- data/lib/t_ruby/parser_combinator/declaration_parser.rb +147 -0
- data/lib/t_ruby/parser_combinator/dsl.rb +115 -0
- data/lib/t_ruby/parser_combinator/parse_error.rb +48 -0
- data/lib/t_ruby/parser_combinator/parse_result.rb +46 -0
- data/lib/t_ruby/parser_combinator/parser.rb +84 -0
- data/lib/t_ruby/parser_combinator/primitives/end_of_input.rb +16 -0
- data/lib/t_ruby/parser_combinator/primitives/fail.rb +16 -0
- data/lib/t_ruby/parser_combinator/primitives/lazy.rb +18 -0
- data/lib/t_ruby/parser_combinator/primitives/literal.rb +21 -0
- data/lib/t_ruby/parser_combinator/primitives/pure.rb +16 -0
- data/lib/t_ruby/parser_combinator/primitives/regex.rb +25 -0
- data/lib/t_ruby/parser_combinator/primitives/satisfy.rb +21 -0
- data/lib/t_ruby/parser_combinator/token/expression_parser.rb +541 -0
- data/lib/t_ruby/parser_combinator/token/statement_parser.rb +644 -0
- data/lib/t_ruby/parser_combinator/token/token_alternative.rb +20 -0
- data/lib/t_ruby/parser_combinator/token/token_body_parser.rb +54 -0
- data/lib/t_ruby/parser_combinator/token/token_declaration_parser.rb +920 -0
- data/lib/t_ruby/parser_combinator/token/token_dsl.rb +16 -0
- data/lib/t_ruby/parser_combinator/token/token_label.rb +22 -0
- data/lib/t_ruby/parser_combinator/token/token_many.rb +29 -0
- data/lib/t_ruby/parser_combinator/token/token_many1.rb +32 -0
- data/lib/t_ruby/parser_combinator/token/token_map.rb +17 -0
- data/lib/t_ruby/parser_combinator/token/token_matcher.rb +29 -0
- data/lib/t_ruby/parser_combinator/token/token_optional.rb +21 -0
- data/lib/t_ruby/parser_combinator/token/token_parse_result.rb +40 -0
- data/lib/t_ruby/parser_combinator/token/token_parser.rb +62 -0
- data/lib/t_ruby/parser_combinator/token/token_sep_by.rb +34 -0
- data/lib/t_ruby/parser_combinator/token/token_sep_by1.rb +34 -0
- data/lib/t_ruby/parser_combinator/token/token_sequence.rb +23 -0
- data/lib/t_ruby/parser_combinator/token/token_skip_right.rb +23 -0
- data/lib/t_ruby/parser_combinator/type_parser.rb +103 -0
- data/lib/t_ruby/parser_combinator.rb +64 -936
- data/lib/t_ruby/scanner.rb +883 -0
- data/lib/t_ruby/version.rb +1 -1
- data/lib/t_ruby/watcher.rb +67 -75
- data/lib/t_ruby.rb +15 -1
- metadata +51 -2
- 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
|