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,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
|