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