t-ruby 0.0.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +221 -0
- data/bin/trc +6 -0
- data/lib/t_ruby/benchmark.rb +592 -0
- data/lib/t_ruby/bundler_integration.rb +569 -0
- data/lib/t_ruby/cache.rb +774 -0
- data/lib/t_ruby/cli.rb +106 -0
- data/lib/t_ruby/compiler.rb +299 -0
- data/lib/t_ruby/config.rb +53 -0
- data/lib/t_ruby/constraint_checker.rb +441 -0
- data/lib/t_ruby/declaration_generator.rb +298 -0
- data/lib/t_ruby/doc_generator.rb +474 -0
- data/lib/t_ruby/error_handler.rb +132 -0
- data/lib/t_ruby/generic_type_parser.rb +68 -0
- data/lib/t_ruby/intersection_type_parser.rb +30 -0
- data/lib/t_ruby/ir.rb +1301 -0
- data/lib/t_ruby/lsp_server.rb +994 -0
- data/lib/t_ruby/package_manager.rb +735 -0
- data/lib/t_ruby/parser.rb +245 -0
- data/lib/t_ruby/parser_combinator.rb +942 -0
- data/lib/t_ruby/rbs_generator.rb +71 -0
- data/lib/t_ruby/runtime_validator.rb +367 -0
- data/lib/t_ruby/smt_solver.rb +1076 -0
- data/lib/t_ruby/type_alias_registry.rb +102 -0
- data/lib/t_ruby/type_checker.rb +770 -0
- data/lib/t_ruby/type_erasure.rb +26 -0
- data/lib/t_ruby/type_inferencer.rb +580 -0
- data/lib/t_ruby/union_type_parser.rb +38 -0
- data/lib/t_ruby/version.rb +5 -0
- data/lib/t_ruby/watcher.rb +320 -0
- data/lib/t_ruby.rb +42 -0
- metadata +87 -0
|
@@ -0,0 +1,942 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
module ParserCombinator
|
|
5
|
+
# Parse result - either success or failure
|
|
6
|
+
class ParseResult
|
|
7
|
+
attr_reader :value, :remaining, :position, :error
|
|
8
|
+
|
|
9
|
+
def initialize(success:, value: nil, remaining: "", position: 0, error: nil)
|
|
10
|
+
@success = success
|
|
11
|
+
@value = value
|
|
12
|
+
@remaining = remaining
|
|
13
|
+
@position = position
|
|
14
|
+
@error = error
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def success?
|
|
18
|
+
@success
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def failure?
|
|
22
|
+
!@success
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.success(value, remaining, position)
|
|
26
|
+
new(success: true, value: value, remaining: remaining, position: position)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.failure(error, remaining, position)
|
|
30
|
+
new(success: false, error: error, remaining: remaining, position: position)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def map
|
|
34
|
+
return self if failure?
|
|
35
|
+
ParseResult.success(yield(value), remaining, position)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def flat_map
|
|
39
|
+
return self if failure?
|
|
40
|
+
yield(value, remaining, position)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Base parser class
|
|
45
|
+
class Parser
|
|
46
|
+
def parse(input, position = 0)
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Combinators as methods
|
|
51
|
+
|
|
52
|
+
# Sequence: run this parser, then the other
|
|
53
|
+
def >>(other)
|
|
54
|
+
Sequence.new(self, other)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Alternative: try this parser, if it fails try the other
|
|
58
|
+
def |(other)
|
|
59
|
+
Alternative.new(self, other)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Map: transform the result
|
|
63
|
+
def map(&block)
|
|
64
|
+
Map.new(self, block)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# FlatMap: transform with another parser
|
|
68
|
+
def flat_map(&block)
|
|
69
|
+
FlatMap.new(self, block)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Many: zero or more repetitions
|
|
73
|
+
def many
|
|
74
|
+
Many.new(self)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Many1: one or more repetitions
|
|
78
|
+
def many1
|
|
79
|
+
Many1.new(self)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Optional: zero or one
|
|
83
|
+
def optional
|
|
84
|
+
Optional.new(self)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Separated by: parse items separated by delimiter
|
|
88
|
+
def sep_by(delimiter)
|
|
89
|
+
SepBy.new(self, delimiter)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Separated by 1: at least one item
|
|
93
|
+
def sep_by1(delimiter)
|
|
94
|
+
SepBy1.new(self, delimiter)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Between: parse between left and right delimiters
|
|
98
|
+
def between(left, right)
|
|
99
|
+
(left >> self << right).map { |(_, val)| val }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Skip right: parse both, keep left result
|
|
103
|
+
def <<(other)
|
|
104
|
+
SkipRight.new(self, other)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Label: add a descriptive label for error messages
|
|
108
|
+
def label(name)
|
|
109
|
+
Label.new(self, name)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Lookahead: check without consuming
|
|
113
|
+
def lookahead
|
|
114
|
+
Lookahead.new(self)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Not: succeed only if parser fails
|
|
118
|
+
def not_followed_by
|
|
119
|
+
NotFollowedBy.new(self)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
#==========================================================================
|
|
124
|
+
# Primitive Parsers
|
|
125
|
+
#==========================================================================
|
|
126
|
+
|
|
127
|
+
# Parse a literal string
|
|
128
|
+
class Literal < Parser
|
|
129
|
+
def initialize(string)
|
|
130
|
+
@string = string
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse(input, position = 0)
|
|
134
|
+
remaining = input[position..]
|
|
135
|
+
if remaining&.start_with?(@string)
|
|
136
|
+
ParseResult.success(@string, input, position + @string.length)
|
|
137
|
+
else
|
|
138
|
+
ParseResult.failure("Expected '#{@string}'", input, position)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Parse a single character matching predicate
|
|
144
|
+
class Satisfy < Parser
|
|
145
|
+
def initialize(predicate, description = "character")
|
|
146
|
+
@predicate = predicate
|
|
147
|
+
@description = description
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def parse(input, position = 0)
|
|
151
|
+
if position < input.length && @predicate.call(input[position])
|
|
152
|
+
ParseResult.success(input[position], input, position + 1)
|
|
153
|
+
else
|
|
154
|
+
ParseResult.failure("Expected #{@description}", input, position)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Parse using regex
|
|
160
|
+
class Regex < Parser
|
|
161
|
+
def initialize(pattern, description = nil)
|
|
162
|
+
@pattern = pattern.is_a?(Regexp) ? pattern : Regexp.new("^#{pattern}")
|
|
163
|
+
@description = description || @pattern.inspect
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def parse(input, position = 0)
|
|
167
|
+
remaining = input[position..]
|
|
168
|
+
match = @pattern.match(remaining)
|
|
169
|
+
|
|
170
|
+
if match && match.begin(0) == 0
|
|
171
|
+
matched = match[0]
|
|
172
|
+
ParseResult.success(matched, input, position + matched.length)
|
|
173
|
+
else
|
|
174
|
+
ParseResult.failure("Expected #{@description}", input, position)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Parse end of input
|
|
180
|
+
class EndOfInput < Parser
|
|
181
|
+
def parse(input, position = 0)
|
|
182
|
+
if position >= input.length
|
|
183
|
+
ParseResult.success(nil, input, position)
|
|
184
|
+
else
|
|
185
|
+
ParseResult.failure("Expected end of input", input, position)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Always succeed with a value
|
|
191
|
+
class Pure < Parser
|
|
192
|
+
def initialize(value)
|
|
193
|
+
@value = value
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def parse(input, position = 0)
|
|
197
|
+
ParseResult.success(@value, input, position)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Always fail
|
|
202
|
+
class Fail < Parser
|
|
203
|
+
def initialize(message)
|
|
204
|
+
@message = message
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def parse(input, position = 0)
|
|
208
|
+
ParseResult.failure(@message, input, position)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Lazy parser (for recursive grammars)
|
|
213
|
+
class Lazy < Parser
|
|
214
|
+
def initialize(&block)
|
|
215
|
+
@block = block
|
|
216
|
+
@parser = nil
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def parse(input, position = 0)
|
|
220
|
+
@parser ||= @block.call
|
|
221
|
+
@parser.parse(input, position)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
#==========================================================================
|
|
226
|
+
# Combinator Parsers
|
|
227
|
+
#==========================================================================
|
|
228
|
+
|
|
229
|
+
# Sequence two parsers
|
|
230
|
+
class Sequence < Parser
|
|
231
|
+
def initialize(left, right)
|
|
232
|
+
@left = left
|
|
233
|
+
@right = right
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def parse(input, position = 0)
|
|
237
|
+
result1 = @left.parse(input, position)
|
|
238
|
+
return result1 if result1.failure?
|
|
239
|
+
|
|
240
|
+
result2 = @right.parse(input, result1.position)
|
|
241
|
+
return result2 if result2.failure?
|
|
242
|
+
|
|
243
|
+
ParseResult.success([result1.value, result2.value], input, result2.position)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Alternative: try first, if fails try second
|
|
248
|
+
class Alternative < Parser
|
|
249
|
+
def initialize(left, right)
|
|
250
|
+
@left = left
|
|
251
|
+
@right = right
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def parse(input, position = 0)
|
|
255
|
+
result = @left.parse(input, position)
|
|
256
|
+
return result if result.success?
|
|
257
|
+
|
|
258
|
+
@right.parse(input, position)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Map result
|
|
263
|
+
class Map < Parser
|
|
264
|
+
def initialize(parser, func)
|
|
265
|
+
@parser = parser
|
|
266
|
+
@func = func
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def parse(input, position = 0)
|
|
270
|
+
@parser.parse(input, position).map(&@func)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# FlatMap (bind)
|
|
275
|
+
class FlatMap < Parser
|
|
276
|
+
def initialize(parser, func)
|
|
277
|
+
@parser = parser
|
|
278
|
+
@func = func
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def parse(input, position = 0)
|
|
282
|
+
result = @parser.parse(input, position)
|
|
283
|
+
return result if result.failure?
|
|
284
|
+
|
|
285
|
+
next_parser = @func.call(result.value)
|
|
286
|
+
next_parser.parse(input, result.position)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Many: zero or more
|
|
291
|
+
class Many < Parser
|
|
292
|
+
def initialize(parser)
|
|
293
|
+
@parser = parser
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def parse(input, position = 0)
|
|
297
|
+
results = []
|
|
298
|
+
current_pos = position
|
|
299
|
+
|
|
300
|
+
loop do
|
|
301
|
+
result = @parser.parse(input, current_pos)
|
|
302
|
+
break if result.failure?
|
|
303
|
+
|
|
304
|
+
results << result.value
|
|
305
|
+
break if result.position == current_pos # Prevent infinite loop
|
|
306
|
+
current_pos = result.position
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
ParseResult.success(results, input, current_pos)
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Many1: one or more
|
|
314
|
+
class Many1 < Parser
|
|
315
|
+
def initialize(parser)
|
|
316
|
+
@parser = parser
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def parse(input, position = 0)
|
|
320
|
+
first = @parser.parse(input, position)
|
|
321
|
+
return first if first.failure?
|
|
322
|
+
|
|
323
|
+
results = [first.value]
|
|
324
|
+
current_pos = first.position
|
|
325
|
+
|
|
326
|
+
loop do
|
|
327
|
+
result = @parser.parse(input, current_pos)
|
|
328
|
+
break if result.failure?
|
|
329
|
+
|
|
330
|
+
results << result.value
|
|
331
|
+
break if result.position == current_pos
|
|
332
|
+
current_pos = result.position
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
ParseResult.success(results, input, current_pos)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Optional: zero or one
|
|
340
|
+
class Optional < Parser
|
|
341
|
+
def initialize(parser)
|
|
342
|
+
@parser = parser
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def parse(input, position = 0)
|
|
346
|
+
result = @parser.parse(input, position)
|
|
347
|
+
if result.success?
|
|
348
|
+
result
|
|
349
|
+
else
|
|
350
|
+
ParseResult.success(nil, input, position)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Separated by delimiter
|
|
356
|
+
class SepBy < Parser
|
|
357
|
+
def initialize(parser, delimiter)
|
|
358
|
+
@parser = parser
|
|
359
|
+
@delimiter = delimiter
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def parse(input, position = 0)
|
|
363
|
+
first = @parser.parse(input, position)
|
|
364
|
+
return ParseResult.success([], input, position) if first.failure?
|
|
365
|
+
|
|
366
|
+
results = [first.value]
|
|
367
|
+
current_pos = first.position
|
|
368
|
+
|
|
369
|
+
loop do
|
|
370
|
+
delim_result = @delimiter.parse(input, current_pos)
|
|
371
|
+
break if delim_result.failure?
|
|
372
|
+
|
|
373
|
+
item_result = @parser.parse(input, delim_result.position)
|
|
374
|
+
break if item_result.failure?
|
|
375
|
+
|
|
376
|
+
results << item_result.value
|
|
377
|
+
current_pos = item_result.position
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
ParseResult.success(results, input, current_pos)
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Separated by 1 (at least one)
|
|
385
|
+
class SepBy1 < Parser
|
|
386
|
+
def initialize(parser, delimiter)
|
|
387
|
+
@parser = parser
|
|
388
|
+
@delimiter = delimiter
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def parse(input, position = 0)
|
|
392
|
+
first = @parser.parse(input, position)
|
|
393
|
+
return first if first.failure?
|
|
394
|
+
|
|
395
|
+
results = [first.value]
|
|
396
|
+
current_pos = first.position
|
|
397
|
+
|
|
398
|
+
loop do
|
|
399
|
+
delim_result = @delimiter.parse(input, current_pos)
|
|
400
|
+
break if delim_result.failure?
|
|
401
|
+
|
|
402
|
+
item_result = @parser.parse(input, delim_result.position)
|
|
403
|
+
break if item_result.failure?
|
|
404
|
+
|
|
405
|
+
results << item_result.value
|
|
406
|
+
current_pos = item_result.position
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
ParseResult.success(results, input, current_pos)
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Skip right: parse both, return left
|
|
414
|
+
class SkipRight < Parser
|
|
415
|
+
def initialize(left, right)
|
|
416
|
+
@left = left
|
|
417
|
+
@right = right
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def parse(input, position = 0)
|
|
421
|
+
result1 = @left.parse(input, position)
|
|
422
|
+
return result1 if result1.failure?
|
|
423
|
+
|
|
424
|
+
result2 = @right.parse(input, result1.position)
|
|
425
|
+
return result2 if result2.failure?
|
|
426
|
+
|
|
427
|
+
ParseResult.success(result1.value, input, result2.position)
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# Label for error messages
|
|
432
|
+
class Label < Parser
|
|
433
|
+
def initialize(parser, name)
|
|
434
|
+
@parser = parser
|
|
435
|
+
@name = name
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def parse(input, position = 0)
|
|
439
|
+
result = @parser.parse(input, position)
|
|
440
|
+
if result.failure?
|
|
441
|
+
ParseResult.failure("Expected #{@name}", input, position)
|
|
442
|
+
else
|
|
443
|
+
result
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# Lookahead: check without consuming
|
|
449
|
+
class Lookahead < Parser
|
|
450
|
+
def initialize(parser)
|
|
451
|
+
@parser = parser
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def parse(input, position = 0)
|
|
455
|
+
result = @parser.parse(input, position)
|
|
456
|
+
if result.success?
|
|
457
|
+
ParseResult.success(result.value, input, position)
|
|
458
|
+
else
|
|
459
|
+
result
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Not followed by
|
|
465
|
+
class NotFollowedBy < Parser
|
|
466
|
+
def initialize(parser)
|
|
467
|
+
@parser = parser
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def parse(input, position = 0)
|
|
471
|
+
result = @parser.parse(input, position)
|
|
472
|
+
if result.failure?
|
|
473
|
+
ParseResult.success(nil, input, position)
|
|
474
|
+
else
|
|
475
|
+
ParseResult.failure("Unexpected match", input, position)
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Choice: try multiple parsers in order
|
|
481
|
+
class Choice < Parser
|
|
482
|
+
def initialize(*parsers)
|
|
483
|
+
@parsers = parsers
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def parse(input, position = 0)
|
|
487
|
+
best_error = nil
|
|
488
|
+
best_position = position
|
|
489
|
+
|
|
490
|
+
@parsers.each do |parser|
|
|
491
|
+
result = parser.parse(input, position)
|
|
492
|
+
return result if result.success?
|
|
493
|
+
|
|
494
|
+
if result.position >= best_position
|
|
495
|
+
best_error = result.error
|
|
496
|
+
best_position = result.position
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
ParseResult.failure(best_error || "No alternative matched", input, best_position)
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Chainl: left-associative chain
|
|
505
|
+
class ChainLeft < Parser
|
|
506
|
+
def initialize(term, op)
|
|
507
|
+
@term = term
|
|
508
|
+
@op = op
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def parse(input, position = 0)
|
|
512
|
+
first = @term.parse(input, position)
|
|
513
|
+
return first if first.failure?
|
|
514
|
+
|
|
515
|
+
result = first.value
|
|
516
|
+
current_pos = first.position
|
|
517
|
+
|
|
518
|
+
loop do
|
|
519
|
+
op_result = @op.parse(input, current_pos)
|
|
520
|
+
break if op_result.failure?
|
|
521
|
+
|
|
522
|
+
term_result = @term.parse(input, op_result.position)
|
|
523
|
+
break if term_result.failure?
|
|
524
|
+
|
|
525
|
+
result = op_result.value.call(result, term_result.value)
|
|
526
|
+
current_pos = term_result.position
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
ParseResult.success(result, input, current_pos)
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
#==========================================================================
|
|
534
|
+
# DSL Module - Convenience methods
|
|
535
|
+
#==========================================================================
|
|
536
|
+
|
|
537
|
+
module DSL
|
|
538
|
+
def literal(str)
|
|
539
|
+
Literal.new(str)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def regex(pattern, description = nil)
|
|
543
|
+
Regex.new(pattern, description)
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def satisfy(description = "character", &predicate)
|
|
547
|
+
Satisfy.new(predicate, description)
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def char(c)
|
|
551
|
+
Literal.new(c)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def string(str)
|
|
555
|
+
Literal.new(str)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def eof
|
|
559
|
+
EndOfInput.new
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def pure(value)
|
|
563
|
+
Pure.new(value)
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def fail(message)
|
|
567
|
+
Fail.new(message)
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def lazy(&block)
|
|
571
|
+
Lazy.new(&block)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def choice(*parsers)
|
|
575
|
+
Choice.new(*parsers)
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def sequence(*parsers)
|
|
579
|
+
parsers.reduce { |acc, p| acc >> p }
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
# Common character parsers
|
|
583
|
+
def digit
|
|
584
|
+
satisfy("digit") { |c| c =~ /[0-9]/ }
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
def letter
|
|
588
|
+
satisfy("letter") { |c| c =~ /[a-zA-Z]/ }
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def alphanumeric
|
|
592
|
+
satisfy("alphanumeric") { |c| c =~ /[a-zA-Z0-9]/ }
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
def whitespace
|
|
596
|
+
satisfy("whitespace") { |c| c =~ /\s/ }
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def spaces
|
|
600
|
+
whitespace.many.map { |chars| chars.join }
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def spaces1
|
|
604
|
+
whitespace.many1.map { |chars| chars.join }
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def newline
|
|
608
|
+
char("\n") | string("\r\n")
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def identifier
|
|
612
|
+
(letter >> (alphanumeric | char("_")).many).map do |(first, rest)|
|
|
613
|
+
first + rest.join
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def integer
|
|
618
|
+
(char("-").optional >> digit.many1).map do |(sign, digits)|
|
|
619
|
+
num = digits.join.to_i
|
|
620
|
+
sign ? -num : num
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def float
|
|
625
|
+
regex(/\-?\d+\.\d+/, "float").map(&:to_f)
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def quoted_string(quote = '"')
|
|
629
|
+
content = satisfy("string character") { |c| c != quote && c != "\\" }
|
|
630
|
+
escape = (char("\\") >> satisfy("escape char")).map { |(_bs, c)| c }
|
|
631
|
+
|
|
632
|
+
(char(quote) >> (content | escape).many.map(&:join) << char(quote)).map { |(_, str)| str }
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
# Skip whitespace around parser
|
|
636
|
+
def lexeme(parser)
|
|
637
|
+
(spaces >> parser << spaces).map { |(_, val)| val }
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
# Chain for left-associative operators
|
|
641
|
+
def chainl(term, op)
|
|
642
|
+
ChainLeft.new(term, op)
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
#==========================================================================
|
|
647
|
+
# Type Parser - Parse T-Ruby type expressions
|
|
648
|
+
#==========================================================================
|
|
649
|
+
|
|
650
|
+
class TypeParser
|
|
651
|
+
include DSL
|
|
652
|
+
|
|
653
|
+
def initialize
|
|
654
|
+
build_parsers
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
def parse(input)
|
|
658
|
+
result = @type_expr.parse(input.strip)
|
|
659
|
+
if result.success?
|
|
660
|
+
{ success: true, type: result.value, remaining: input[result.position..] }
|
|
661
|
+
else
|
|
662
|
+
{ success: false, error: result.error, position: result.position }
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
private
|
|
667
|
+
|
|
668
|
+
def build_parsers
|
|
669
|
+
# Identifier (type name)
|
|
670
|
+
type_name = identifier.label("type name")
|
|
671
|
+
|
|
672
|
+
# Simple type
|
|
673
|
+
simple_type = type_name.map { |name| IR::SimpleType.new(name: name) }
|
|
674
|
+
|
|
675
|
+
# Lazy reference for recursive types
|
|
676
|
+
type_expr = lazy { @type_expr }
|
|
677
|
+
|
|
678
|
+
# Generic type arguments: <Type, Type, ...>
|
|
679
|
+
generic_args = (
|
|
680
|
+
lexeme(char("<")) >>
|
|
681
|
+
type_expr.sep_by1(lexeme(char(","))) <<
|
|
682
|
+
lexeme(char(">"))
|
|
683
|
+
).map { |(_, types)| types }
|
|
684
|
+
|
|
685
|
+
# Generic type: Base<Args>
|
|
686
|
+
generic_type = (type_name >> generic_args.optional).map do |(name, args)|
|
|
687
|
+
if args && !args.empty?
|
|
688
|
+
IR::GenericType.new(base: name, type_args: args)
|
|
689
|
+
else
|
|
690
|
+
IR::SimpleType.new(name: name)
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
# Nullable type: Type?
|
|
695
|
+
nullable_suffix = char("?")
|
|
696
|
+
|
|
697
|
+
# Parenthesized type
|
|
698
|
+
paren_type = (lexeme(char("(")) >> type_expr << lexeme(char(")"))).map { |(_, t)| t }
|
|
699
|
+
|
|
700
|
+
# Function type: (Params) -> ReturnType
|
|
701
|
+
param_list = (
|
|
702
|
+
lexeme(char("(")) >>
|
|
703
|
+
type_expr.sep_by(lexeme(char(","))) <<
|
|
704
|
+
lexeme(char(")"))
|
|
705
|
+
).map { |(_, params)| params }
|
|
706
|
+
|
|
707
|
+
arrow = lexeme(string("->"))
|
|
708
|
+
|
|
709
|
+
function_type = (param_list >> arrow >> type_expr).map do |((params, _arrow), ret)|
|
|
710
|
+
IR::FunctionType.new(param_types: params, return_type: ret)
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
# Tuple type: [Type, Type, ...]
|
|
714
|
+
tuple_type = (
|
|
715
|
+
lexeme(char("[")) >>
|
|
716
|
+
type_expr.sep_by1(lexeme(char(","))) <<
|
|
717
|
+
lexeme(char("]"))
|
|
718
|
+
).map { |(_, types)| IR::TupleType.new(element_types: types) }
|
|
719
|
+
|
|
720
|
+
# Primary type (before operators)
|
|
721
|
+
primary_type = choice(
|
|
722
|
+
function_type,
|
|
723
|
+
tuple_type,
|
|
724
|
+
paren_type,
|
|
725
|
+
generic_type
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
# With optional nullable suffix
|
|
729
|
+
base_type = (primary_type >> nullable_suffix.optional).map do |(type, nullable)|
|
|
730
|
+
nullable ? IR::NullableType.new(inner_type: type) : type
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
# Union type: Type | Type | ...
|
|
734
|
+
union_op = lexeme(char("|"))
|
|
735
|
+
union_type = base_type.sep_by1(union_op).map do |types|
|
|
736
|
+
types.length == 1 ? types.first : IR::UnionType.new(types: types)
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
# Intersection type: Type & Type & ...
|
|
740
|
+
intersection_op = lexeme(char("&"))
|
|
741
|
+
@type_expr = union_type.sep_by1(intersection_op).map do |types|
|
|
742
|
+
types.length == 1 ? types.first : IR::IntersectionType.new(types: types)
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
#==========================================================================
|
|
748
|
+
# Declaration Parser - Parse T-Ruby declarations
|
|
749
|
+
#==========================================================================
|
|
750
|
+
|
|
751
|
+
class DeclarationParser
|
|
752
|
+
include DSL
|
|
753
|
+
|
|
754
|
+
def initialize
|
|
755
|
+
@type_parser = TypeParser.new
|
|
756
|
+
build_parsers
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
def parse(input)
|
|
760
|
+
result = @declaration.parse(input.strip)
|
|
761
|
+
if result.success?
|
|
762
|
+
{ success: true, declarations: result.value }
|
|
763
|
+
else
|
|
764
|
+
{ success: false, error: result.error, position: result.position }
|
|
765
|
+
end
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
def parse_file(input)
|
|
769
|
+
result = @program.parse(input)
|
|
770
|
+
if result.success?
|
|
771
|
+
{ success: true, declarations: result.value.compact }
|
|
772
|
+
else
|
|
773
|
+
{ success: false, error: result.error, position: result.position }
|
|
774
|
+
end
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
private
|
|
778
|
+
|
|
779
|
+
def build_parsers
|
|
780
|
+
# Type expression (delegate to TypeParser)
|
|
781
|
+
type_expr = lazy { parse_type_inline }
|
|
782
|
+
|
|
783
|
+
# Keywords
|
|
784
|
+
kw_type = lexeme(string("type"))
|
|
785
|
+
kw_interface = lexeme(string("interface"))
|
|
786
|
+
kw_def = lexeme(string("def"))
|
|
787
|
+
kw_end = lexeme(string("end"))
|
|
788
|
+
kw_class = lexeme(string("class"))
|
|
789
|
+
kw_module = lexeme(string("module"))
|
|
790
|
+
|
|
791
|
+
# Type alias: type Name = Definition
|
|
792
|
+
type_alias = (
|
|
793
|
+
kw_type >>
|
|
794
|
+
lexeme(identifier) <<
|
|
795
|
+
lexeme(char("=")) >>
|
|
796
|
+
regex(/[^\n]+/).map(&:strip)
|
|
797
|
+
).map do |((_, name), definition)|
|
|
798
|
+
type_result = @type_parser.parse(definition)
|
|
799
|
+
if type_result[:success]
|
|
800
|
+
IR::TypeAlias.new(name: name, definition: type_result[:type])
|
|
801
|
+
else
|
|
802
|
+
nil
|
|
803
|
+
end
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
# Interface member: name: Type
|
|
807
|
+
interface_member = (
|
|
808
|
+
lexeme(identifier) <<
|
|
809
|
+
lexeme(char(":")) >>
|
|
810
|
+
regex(/[^\n]+/).map(&:strip)
|
|
811
|
+
).map do |(name, type_str)|
|
|
812
|
+
type_result = @type_parser.parse(type_str)
|
|
813
|
+
if type_result[:success]
|
|
814
|
+
IR::InterfaceMember.new(name: name, type_signature: type_result[:type])
|
|
815
|
+
else
|
|
816
|
+
nil
|
|
817
|
+
end
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
# Interface: interface Name ... end
|
|
821
|
+
interface_body = (interface_member << (newline | spaces)).many
|
|
822
|
+
|
|
823
|
+
interface_decl = (
|
|
824
|
+
kw_interface >>
|
|
825
|
+
lexeme(identifier) <<
|
|
826
|
+
(newline | spaces) >>
|
|
827
|
+
interface_body <<
|
|
828
|
+
kw_end
|
|
829
|
+
).map do |((_, name), members)|
|
|
830
|
+
IR::Interface.new(name: name, members: members.compact)
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
# Parameter: name: Type or name
|
|
834
|
+
param = (
|
|
835
|
+
identifier >>
|
|
836
|
+
(lexeme(char(":")) >> regex(/[^,)]+/).map(&:strip)).optional
|
|
837
|
+
).map do |(name, type_str)|
|
|
838
|
+
type_node = if type_str
|
|
839
|
+
type_str_val = type_str.is_a?(Array) ? type_str.last : type_str
|
|
840
|
+
result = @type_parser.parse(type_str_val)
|
|
841
|
+
result[:success] ? result[:type] : nil
|
|
842
|
+
end
|
|
843
|
+
IR::Parameter.new(name: name, type_annotation: type_node)
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
# Parameters list
|
|
847
|
+
params_list = (
|
|
848
|
+
lexeme(char("(")) >>
|
|
849
|
+
param.sep_by(lexeme(char(","))) <<
|
|
850
|
+
lexeme(char(")"))
|
|
851
|
+
).map { |(_, params)| params }
|
|
852
|
+
|
|
853
|
+
# Return type annotation
|
|
854
|
+
return_type = (
|
|
855
|
+
lexeme(char(":")) >>
|
|
856
|
+
regex(/[^\n]+/).map(&:strip)
|
|
857
|
+
).map { |(_, type_str)| type_str }.optional
|
|
858
|
+
|
|
859
|
+
# Method definition: def name(params): ReturnType
|
|
860
|
+
method_def = (
|
|
861
|
+
kw_def >>
|
|
862
|
+
identifier >>
|
|
863
|
+
params_list.optional >>
|
|
864
|
+
return_type
|
|
865
|
+
).map do |(((_, name), params), ret_str)|
|
|
866
|
+
ret_type = if ret_str
|
|
867
|
+
result = @type_parser.parse(ret_str)
|
|
868
|
+
result[:success] ? result[:type] : nil
|
|
869
|
+
end
|
|
870
|
+
IR::MethodDef.new(
|
|
871
|
+
name: name,
|
|
872
|
+
params: params || [],
|
|
873
|
+
return_type: ret_type
|
|
874
|
+
)
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
# Any declaration
|
|
878
|
+
@declaration = choice(
|
|
879
|
+
type_alias,
|
|
880
|
+
interface_decl,
|
|
881
|
+
method_def
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
# Line (declaration or empty)
|
|
885
|
+
line = (@declaration << (newline | eof)) | (spaces >> newline).map { nil }
|
|
886
|
+
|
|
887
|
+
# Program (multiple declarations)
|
|
888
|
+
@program = line.many
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
def parse_type_inline
|
|
892
|
+
Lazy.new { @type_parser.instance_variable_get(:@type_expr) }
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
#==========================================================================
|
|
897
|
+
# Error Reporting
|
|
898
|
+
#==========================================================================
|
|
899
|
+
|
|
900
|
+
class ParseError
|
|
901
|
+
attr_reader :message, :position, :line, :column, :input
|
|
902
|
+
|
|
903
|
+
def initialize(message:, position:, input:)
|
|
904
|
+
@message = message
|
|
905
|
+
@position = position
|
|
906
|
+
@input = input
|
|
907
|
+
@line, @column = calculate_line_column
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
def to_s
|
|
911
|
+
"Parse error at line #{@line}, column #{@column}: #{@message}"
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
def context(lines_before: 2, lines_after: 1)
|
|
915
|
+
input_lines = @input.split("\n")
|
|
916
|
+
start_line = [@line - lines_before - 1, 0].max
|
|
917
|
+
end_line = [@line + lines_after - 1, input_lines.length - 1].min
|
|
918
|
+
|
|
919
|
+
result = []
|
|
920
|
+
(start_line..end_line).each do |i|
|
|
921
|
+
prefix = i == @line - 1 ? ">>> " : " "
|
|
922
|
+
result << "#{prefix}#{i + 1}: #{input_lines[i]}"
|
|
923
|
+
|
|
924
|
+
if i == @line - 1
|
|
925
|
+
result << " " + " " * (@column + @line.to_s.length + 1) + "^"
|
|
926
|
+
end
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
result.join("\n")
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
private
|
|
933
|
+
|
|
934
|
+
def calculate_line_column
|
|
935
|
+
lines = @input[0...@position].split("\n", -1)
|
|
936
|
+
line = lines.length
|
|
937
|
+
column = lines.last&.length || 0
|
|
938
|
+
[line, column + 1]
|
|
939
|
+
end
|
|
940
|
+
end
|
|
941
|
+
end
|
|
942
|
+
end
|