t-ruby 0.0.39 → 0.0.41
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/compiler.rb +136 -65
- data/lib/t_ruby/heredoc_detector.rb +74 -0
- data/lib/t_ruby/ir.rb +22 -5
- data/lib/t_ruby/lsp_server.rb +1 -1
- data/lib/t_ruby/parser.rb +222 -49
- data/lib/t_ruby/string_utils.rb +80 -0
- data/lib/t_ruby/version.rb +1 -1
- data/lib/t_ruby.rb +2 -1
- metadata +3 -2
- data/lib/t_ruby/rbs_generator.rb +0 -69
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a7c7a93e1498a150b541ea7f7838c08435a33ead72774b7e4c0b8ead951e9d3
|
|
4
|
+
data.tar.gz: 1fbfb9ea6b276e130ef04891f39d691e362d5352dc4f0209b00009105f13c3a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82cbc96204194f51b65f484aeda25383e285ce902c325e269766e77871e9dccacc548111e864ba2bfb27ca48fc69c7e22c5a0b1eee8e7c25e4763417ffe2c8fe
|
|
7
|
+
data.tar.gz: ed5f8ad72344e624e8a95d1e9a48078e0f6679d914f978974e0117b75bbc11ce2fbe08ece39360b20ce777ef5c5c333f37376f216272c1e6aa983eb18585fc92
|
data/lib/t_ruby/compiler.rb
CHANGED
|
@@ -7,16 +7,17 @@ module TRuby
|
|
|
7
7
|
# \p{L} matches any Unicode letter, \p{N} matches any Unicode number
|
|
8
8
|
IDENTIFIER_CHAR = '[\p{L}\p{N}_]'
|
|
9
9
|
METHOD_NAME_PATTERN = "#{IDENTIFIER_CHAR}+[?!]?".freeze
|
|
10
|
+
# Visibility modifiers for method definitions
|
|
11
|
+
VISIBILITY_PATTERN = '(?:(?:private|protected|public)\s+)?'
|
|
10
12
|
|
|
11
13
|
class Compiler
|
|
12
|
-
attr_reader :declaration_loader, :
|
|
14
|
+
attr_reader :declaration_loader, :optimizer
|
|
13
15
|
|
|
14
|
-
def initialize(config = nil,
|
|
16
|
+
def initialize(config = nil, optimize: true)
|
|
15
17
|
@config = config || Config.new
|
|
16
|
-
@use_ir = use_ir
|
|
17
18
|
@optimize = optimize
|
|
18
19
|
@declaration_loader = DeclarationLoader.new
|
|
19
|
-
@optimizer = IR::Optimizer.new if
|
|
20
|
+
@optimizer = IR::Optimizer.new if optimize
|
|
20
21
|
@type_inferrer = ASTTypeInferrer.new if type_check?
|
|
21
22
|
setup_declaration_paths if @config
|
|
22
23
|
end
|
|
@@ -42,16 +43,16 @@ module TRuby
|
|
|
42
43
|
source = File.read(input_path)
|
|
43
44
|
|
|
44
45
|
# Parse with IR support
|
|
45
|
-
parser = Parser.new(source
|
|
46
|
-
|
|
46
|
+
parser = Parser.new(source)
|
|
47
|
+
parser.parse
|
|
47
48
|
|
|
48
49
|
# Run type checking if enabled
|
|
49
|
-
if type_check? &&
|
|
50
|
+
if type_check? && parser.ir_program
|
|
50
51
|
check_types(parser.ir_program, input_path)
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
# Transform source to Ruby code
|
|
54
|
-
output =
|
|
55
|
+
output = transform_with_ir(source, parser)
|
|
55
56
|
|
|
56
57
|
# Compute output path (respects preserve_structure setting)
|
|
57
58
|
output_path = compute_output_path(input_path, @config.ruby_dir, ".rb")
|
|
@@ -63,11 +64,7 @@ module TRuby
|
|
|
63
64
|
if @config.compiler["generate_rbs"]
|
|
64
65
|
rbs_path = compute_output_path(input_path, @config.rbs_dir, ".rbs")
|
|
65
66
|
FileUtils.mkdir_p(File.dirname(rbs_path))
|
|
66
|
-
|
|
67
|
-
generate_rbs_from_ir_to_path(rbs_path, parser.ir_program)
|
|
68
|
-
else
|
|
69
|
-
generate_rbs_file_to_path(rbs_path, parse_result)
|
|
70
|
-
end
|
|
67
|
+
generate_rbs_from_ir_to_path(rbs_path, parser.ir_program)
|
|
71
68
|
end
|
|
72
69
|
|
|
73
70
|
# Generate .d.trb file if enabled in config (legacy support)
|
|
@@ -87,25 +84,17 @@ module TRuby
|
|
|
87
84
|
def compile_string(source, options = {})
|
|
88
85
|
generate_rbs = options.fetch(:rbs, true)
|
|
89
86
|
|
|
90
|
-
parser = Parser.new(source
|
|
91
|
-
|
|
87
|
+
parser = Parser.new(source)
|
|
88
|
+
parser.parse
|
|
92
89
|
|
|
93
90
|
# Transform source to Ruby code
|
|
94
|
-
ruby_output =
|
|
91
|
+
ruby_output = transform_with_ir(source, parser)
|
|
95
92
|
|
|
96
93
|
# Generate RBS if requested
|
|
97
94
|
rbs_output = ""
|
|
98
|
-
if generate_rbs
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
rbs_output = generator.generate(parser.ir_program)
|
|
102
|
-
else
|
|
103
|
-
generator = RBSGenerator.new
|
|
104
|
-
rbs_output = generator.generate(
|
|
105
|
-
parse_result[:functions] || [],
|
|
106
|
-
parse_result[:type_aliases] || []
|
|
107
|
-
)
|
|
108
|
-
end
|
|
95
|
+
if generate_rbs && parser.ir_program
|
|
96
|
+
generator = IR::RBSGenerator.new
|
|
97
|
+
rbs_output = generator.generate(parser.ir_program)
|
|
109
98
|
end
|
|
110
99
|
|
|
111
100
|
{
|
|
@@ -134,7 +123,7 @@ module TRuby
|
|
|
134
123
|
end
|
|
135
124
|
|
|
136
125
|
source = File.read(input_path)
|
|
137
|
-
parser = Parser.new(source
|
|
126
|
+
parser = Parser.new(source)
|
|
138
127
|
parser.parse
|
|
139
128
|
parser.ir_program
|
|
140
129
|
end
|
|
@@ -380,10 +369,10 @@ module TRuby
|
|
|
380
369
|
@declaration_loader.add_search_path("./lib/types")
|
|
381
370
|
end
|
|
382
371
|
|
|
383
|
-
# Transform using IR system
|
|
372
|
+
# Transform using IR system
|
|
384
373
|
def transform_with_ir(source, parser)
|
|
385
374
|
ir_program = parser.ir_program
|
|
386
|
-
return
|
|
375
|
+
return source unless ir_program
|
|
387
376
|
|
|
388
377
|
# Run optimization passes if enabled
|
|
389
378
|
if @optimize && @optimizer
|
|
@@ -396,33 +385,15 @@ module TRuby
|
|
|
396
385
|
generator.generate_with_source(ir_program, source)
|
|
397
386
|
end
|
|
398
387
|
|
|
399
|
-
# Legacy transformation using TypeErasure (backward compatible)
|
|
400
|
-
def transform_legacy(source, parse_result)
|
|
401
|
-
if parse_result[:type] == :success
|
|
402
|
-
eraser = TypeErasure.new(source)
|
|
403
|
-
eraser.erase
|
|
404
|
-
else
|
|
405
|
-
source
|
|
406
|
-
end
|
|
407
|
-
end
|
|
408
|
-
|
|
409
388
|
# Generate RBS from IR to a specific path
|
|
410
389
|
def generate_rbs_from_ir_to_path(rbs_path, ir_program)
|
|
390
|
+
return unless ir_program
|
|
391
|
+
|
|
411
392
|
generator = IR::RBSGenerator.new
|
|
412
393
|
rbs_content = generator.generate(ir_program)
|
|
413
394
|
File.write(rbs_path, rbs_content) unless rbs_content.strip.empty?
|
|
414
395
|
end
|
|
415
396
|
|
|
416
|
-
# Legacy RBS generation to a specific path
|
|
417
|
-
def generate_rbs_file_to_path(rbs_path, parse_result)
|
|
418
|
-
generator = RBSGenerator.new
|
|
419
|
-
rbs_content = generator.generate(
|
|
420
|
-
parse_result[:functions] || [],
|
|
421
|
-
parse_result[:type_aliases] || []
|
|
422
|
-
)
|
|
423
|
-
File.write(rbs_path, rbs_content) unless rbs_content.empty?
|
|
424
|
-
end
|
|
425
|
-
|
|
426
397
|
def generate_dtrb_file(input_path, out_dir)
|
|
427
398
|
dtrb_path = compute_output_path(input_path, out_dir, DeclarationGenerator::DECLARATION_EXTENSION)
|
|
428
399
|
FileUtils.mkdir_p(File.dirname(dtrb_path))
|
|
@@ -511,7 +482,8 @@ module TRuby
|
|
|
511
482
|
result = source.dup
|
|
512
483
|
|
|
513
484
|
# Match function definitions and remove type annotations from parameters
|
|
514
|
-
|
|
485
|
+
# Also supports visibility modifiers: private def, protected def, public def
|
|
486
|
+
result.gsub!(/^(\s*#{TRuby::VISIBILITY_PATTERN}def\s+#{TRuby::METHOD_NAME_PATTERN}\s*\()([^)]+)(\)\s*)(?::\s*[^\n]+)?(\s*$)/) do |_match|
|
|
515
487
|
indent = ::Regexp.last_match(1)
|
|
516
488
|
params = ::Regexp.last_match(2)
|
|
517
489
|
close_paren = ::Regexp.last_match(3)
|
|
@@ -533,6 +505,7 @@ module TRuby
|
|
|
533
505
|
params = []
|
|
534
506
|
current = ""
|
|
535
507
|
depth = 0
|
|
508
|
+
brace_depth = 0
|
|
536
509
|
|
|
537
510
|
params_str.each_char do |char|
|
|
538
511
|
case char
|
|
@@ -542,9 +515,16 @@ module TRuby
|
|
|
542
515
|
when ">", "]", ")"
|
|
543
516
|
depth -= 1
|
|
544
517
|
current += char
|
|
518
|
+
when "{"
|
|
519
|
+
brace_depth += 1
|
|
520
|
+
current += char
|
|
521
|
+
when "}"
|
|
522
|
+
brace_depth -= 1
|
|
523
|
+
current += char
|
|
545
524
|
when ","
|
|
546
|
-
if depth.zero?
|
|
547
|
-
|
|
525
|
+
if depth.zero? && brace_depth.zero?
|
|
526
|
+
cleaned = clean_param(current.strip)
|
|
527
|
+
params.concat(Array(cleaned)) if cleaned
|
|
548
528
|
current = ""
|
|
549
529
|
else
|
|
550
530
|
current += char
|
|
@@ -554,37 +534,128 @@ module TRuby
|
|
|
554
534
|
end
|
|
555
535
|
end
|
|
556
536
|
|
|
557
|
-
|
|
537
|
+
cleaned = clean_param(current.strip) unless current.empty?
|
|
538
|
+
params.concat(Array(cleaned)) if cleaned
|
|
558
539
|
params.join(", ")
|
|
559
540
|
end
|
|
560
541
|
|
|
561
|
-
# Clean a single parameter (remove type annotation)
|
|
542
|
+
# Clean a single parameter (remove type annotation, preserve default value)
|
|
543
|
+
# Returns String or Array of Strings (for keyword args group)
|
|
562
544
|
def clean_param(param)
|
|
563
|
-
|
|
564
|
-
|
|
545
|
+
param = param.strip
|
|
546
|
+
return nil if param.empty?
|
|
547
|
+
|
|
548
|
+
# 1. 더블 스플랫: **name: Type -> **name
|
|
549
|
+
if param.start_with?("**")
|
|
550
|
+
match = param.match(/^\*\*(\w+)(?::\s*.+)?$/)
|
|
551
|
+
return "**#{match[1]}" if match
|
|
552
|
+
|
|
553
|
+
return param
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# 2. 키워드 인자 그룹: { ... } 또는 { ... }: InterfaceName
|
|
557
|
+
if param.start_with?("{")
|
|
558
|
+
return clean_keyword_args_group(param)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# 3. Hash 리터럴: name: { ... } -> name
|
|
562
|
+
if param.match?(/^\w+:\s*\{/)
|
|
563
|
+
match = param.match(/^(\w+):\s*\{.+\}(?::\s*\w+)?$/)
|
|
564
|
+
return match[1] if match
|
|
565
|
+
|
|
566
|
+
return param
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# 4. 일반 파라미터: name: Type = value -> name = value 또는 name: Type -> name
|
|
570
|
+
# Match: name: Type = value (with default value)
|
|
571
|
+
if (match = param.match(/^(#{TRuby::IDENTIFIER_CHAR}+)\s*:\s*.+?\s*(=\s*.+)$/))
|
|
572
|
+
"#{match[1]} #{match[2]}"
|
|
573
|
+
# Match: name: Type (without default value)
|
|
574
|
+
elsif (match = param.match(/^(#{TRuby::IDENTIFIER_CHAR}+)\s*:/))
|
|
565
575
|
match[1]
|
|
566
576
|
else
|
|
567
577
|
param
|
|
568
578
|
end
|
|
569
579
|
end
|
|
570
580
|
|
|
581
|
+
# 키워드 인자 그룹을 Ruby 키워드 인자로 변환
|
|
582
|
+
# { name: String, age: Integer = 0 } -> name:, age: 0
|
|
583
|
+
# { name:, age: 0 }: UserParams -> name:, age: 0
|
|
584
|
+
def clean_keyword_args_group(param)
|
|
585
|
+
# { ... }: InterfaceName 또는 { ... } 형태 파싱
|
|
586
|
+
interface_match = param.match(/^\{(.+)\}\s*:\s*\w+\s*$/)
|
|
587
|
+
inline_match = param.match(/^\{(.+)\}\s*$/) unless interface_match
|
|
588
|
+
|
|
589
|
+
inner_content = if interface_match
|
|
590
|
+
interface_match[1]
|
|
591
|
+
elsif inline_match
|
|
592
|
+
inline_match[1]
|
|
593
|
+
else
|
|
594
|
+
return param
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# 내부 파라미터 분리
|
|
598
|
+
parts = split_nested_content(inner_content)
|
|
599
|
+
keyword_params = []
|
|
600
|
+
|
|
601
|
+
parts.each do |part|
|
|
602
|
+
part = part.strip
|
|
603
|
+
next if part.empty?
|
|
604
|
+
|
|
605
|
+
if interface_match
|
|
606
|
+
# interface 참조: name: default_value 또는 name:
|
|
607
|
+
if (match = part.match(/^(\w+):\s*(.*)$/))
|
|
608
|
+
name = match[1]
|
|
609
|
+
default_value = match[2].strip
|
|
610
|
+
keyword_params << if default_value.empty?
|
|
611
|
+
"#{name}:"
|
|
612
|
+
else
|
|
613
|
+
"#{name}: #{default_value}"
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
elsif (match = part.match(/^(\w+):\s*(.+)$/))
|
|
617
|
+
# 인라인 타입: name: Type = default 또는 name: Type
|
|
618
|
+
name = match[1]
|
|
619
|
+
type_and_default = match[2].strip
|
|
620
|
+
|
|
621
|
+
# Type = default 분리
|
|
622
|
+
default_value = extract_default_value(type_and_default)
|
|
623
|
+
keyword_params << if default_value
|
|
624
|
+
"#{name}: #{default_value}"
|
|
625
|
+
else
|
|
626
|
+
"#{name}:"
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
keyword_params
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
# 중첩된 내용을 콤마로 분리
|
|
635
|
+
def split_nested_content(content)
|
|
636
|
+
StringUtils.split_by_comma(content)
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# 타입과 기본값에서 기본값만 추출
|
|
640
|
+
def extract_default_value(type_and_default)
|
|
641
|
+
StringUtils.extract_default_value(type_and_default)
|
|
642
|
+
end
|
|
643
|
+
|
|
571
644
|
# Erase return type annotations
|
|
572
645
|
def erase_return_types(source)
|
|
573
646
|
result = source.dup
|
|
574
647
|
|
|
575
|
-
# Remove return type: ): Type or ): Type<Foo> etc.
|
|
648
|
+
# Remove return type after parentheses: ): Type or ): Type<Foo> etc.
|
|
576
649
|
result.gsub!(/\)\s*:\s*[^\n]+?(?=\s*$)/m) do |_match|
|
|
577
650
|
")"
|
|
578
651
|
end
|
|
579
652
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
653
|
+
# Remove return type for methods without parentheses: def method_name: Type
|
|
654
|
+
result.gsub!(/^(\s*#{TRuby::VISIBILITY_PATTERN}def\s+#{TRuby::METHOD_NAME_PATTERN})\s*:\s*[^\n]+?(?=\s*$)/m) do |_match|
|
|
655
|
+
::Regexp.last_match(1)
|
|
656
|
+
end
|
|
583
657
|
|
|
584
|
-
|
|
585
|
-
class LegacyCompiler < Compiler
|
|
586
|
-
def initialize(config)
|
|
587
|
-
super(config, use_ir: false, optimize: false)
|
|
658
|
+
result
|
|
588
659
|
end
|
|
589
660
|
end
|
|
590
661
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
# Detects regions that should be skipped during parsing:
|
|
5
|
+
# - Heredoc content
|
|
6
|
+
# - Block comments (=begin/=end)
|
|
7
|
+
class HeredocDetector
|
|
8
|
+
# Heredoc start patterns:
|
|
9
|
+
# <<IDENTIFIER, <<-IDENTIFIER, <<~IDENTIFIER
|
|
10
|
+
# <<'IDENTIFIER', <<"IDENTIFIER"
|
|
11
|
+
HEREDOC_START_PATTERN = /<<([~-])?(['"]?)(\w+)\2/
|
|
12
|
+
|
|
13
|
+
# Detect all skippable ranges in lines (heredocs and block comments)
|
|
14
|
+
# @param lines [Array<String>] source lines
|
|
15
|
+
# @return [Array<Range>] content ranges to skip (0-indexed)
|
|
16
|
+
def self.detect(lines)
|
|
17
|
+
ranges = []
|
|
18
|
+
i = 0
|
|
19
|
+
|
|
20
|
+
while i < lines.length
|
|
21
|
+
line = lines[i]
|
|
22
|
+
|
|
23
|
+
# Check for =begin block comment
|
|
24
|
+
if line.strip == "=begin"
|
|
25
|
+
start_line = i
|
|
26
|
+
i += 1
|
|
27
|
+
|
|
28
|
+
# Find =end
|
|
29
|
+
while i < lines.length
|
|
30
|
+
break if lines[i].strip == "=end"
|
|
31
|
+
|
|
32
|
+
i += 1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Range covers from =begin to =end (inclusive)
|
|
36
|
+
ranges << (start_line..i) if i < lines.length
|
|
37
|
+
# Check for heredoc
|
|
38
|
+
elsif (match = line.match(HEREDOC_START_PATTERN))
|
|
39
|
+
delimiter = match[3]
|
|
40
|
+
squiggly = match[1] == "~"
|
|
41
|
+
start_line = i
|
|
42
|
+
i += 1
|
|
43
|
+
|
|
44
|
+
# Find closing delimiter
|
|
45
|
+
while i < lines.length
|
|
46
|
+
# For squiggly heredoc or dash heredoc, delimiter can be indented
|
|
47
|
+
# For regular heredoc, delimiter must be at line start
|
|
48
|
+
if squiggly || match[1] == "-"
|
|
49
|
+
break if lines[i].strip == delimiter
|
|
50
|
+
elsif lines[i].chomp == delimiter
|
|
51
|
+
break
|
|
52
|
+
end
|
|
53
|
+
i += 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Range covers content lines (after start, up to and including end delimiter)
|
|
57
|
+
ranges << ((start_line + 1)..i) if i < lines.length
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
i += 1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
ranges
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Check if a line index is inside any skippable region
|
|
67
|
+
# @param line_index [Integer] line index to check
|
|
68
|
+
# @param heredoc_ranges [Array<Range>] ranges from detect()
|
|
69
|
+
# @return [Boolean]
|
|
70
|
+
def self.inside_heredoc?(line_index, heredoc_ranges)
|
|
71
|
+
heredoc_ranges.any? { |range| range.include?(line_index) }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/t_ruby/ir.rb
CHANGED
|
@@ -147,15 +147,19 @@ module TRuby
|
|
|
147
147
|
|
|
148
148
|
# Method parameter
|
|
149
149
|
class Parameter < Node
|
|
150
|
-
attr_accessor :name, :type_annotation, :default_value, :kind
|
|
150
|
+
attr_accessor :name, :type_annotation, :default_value, :kind, :interface_ref
|
|
151
151
|
|
|
152
|
-
# kind: :required, :optional, :rest, :keyrest, :block
|
|
153
|
-
|
|
152
|
+
# kind: :required, :optional, :rest, :keyrest, :block, :keyword
|
|
153
|
+
# :keyword - 키워드 인자 (구조분해): { name: String } → def foo(name:)
|
|
154
|
+
# :keyrest - 더블 스플랫: **opts: Type → def foo(**opts)
|
|
155
|
+
# interface_ref - interface 참조 타입 (예: }: UserParams 부분)
|
|
156
|
+
def initialize(name:, type_annotation: nil, default_value: nil, kind: :required, interface_ref: nil, **opts)
|
|
154
157
|
super(**opts)
|
|
155
158
|
@name = name
|
|
156
159
|
@type_annotation = type_annotation
|
|
157
160
|
@default_value = default_value
|
|
158
161
|
@kind = kind
|
|
162
|
+
@interface_ref = interface_ref
|
|
159
163
|
end
|
|
160
164
|
end
|
|
161
165
|
|
|
@@ -778,7 +782,8 @@ module TRuby
|
|
|
778
782
|
name: info[:name],
|
|
779
783
|
params: params,
|
|
780
784
|
return_type: info[:return_type] ? parse_type(info[:return_type]) : nil,
|
|
781
|
-
body: body
|
|
785
|
+
body: body,
|
|
786
|
+
visibility: info[:visibility] || :public
|
|
782
787
|
)
|
|
783
788
|
end
|
|
784
789
|
|
|
@@ -1051,7 +1056,8 @@ module TRuby
|
|
|
1051
1056
|
end
|
|
1052
1057
|
|
|
1053
1058
|
return_type ||= "untyped"
|
|
1054
|
-
|
|
1059
|
+
visibility_prefix = format_visibility(node.visibility)
|
|
1060
|
+
emit("#{visibility_prefix}def #{node.name}: (#{params}) -> #{return_type}")
|
|
1055
1061
|
end
|
|
1056
1062
|
|
|
1057
1063
|
def visit_class_decl(node)
|
|
@@ -1092,6 +1098,17 @@ module TRuby
|
|
|
1092
1098
|
def emit(text)
|
|
1093
1099
|
@output << ((" " * @indent) + text)
|
|
1094
1100
|
end
|
|
1101
|
+
|
|
1102
|
+
def format_visibility(visibility)
|
|
1103
|
+
# RBS only supports private visibility, not protected
|
|
1104
|
+
# See: https://github.com/ruby/rbs/issues/579
|
|
1105
|
+
case visibility
|
|
1106
|
+
when :private
|
|
1107
|
+
"private "
|
|
1108
|
+
else
|
|
1109
|
+
""
|
|
1110
|
+
end
|
|
1111
|
+
end
|
|
1095
1112
|
end
|
|
1096
1113
|
|
|
1097
1114
|
#==========================================================================
|
data/lib/t_ruby/lsp_server.rb
CHANGED
data/lib/t_ruby/parser.rb
CHANGED
|
@@ -12,15 +12,16 @@ module TRuby
|
|
|
12
12
|
IDENTIFIER_CHAR = '[\p{L}\p{N}_]'
|
|
13
13
|
# Method names can end with ? or !
|
|
14
14
|
METHOD_NAME_PATTERN = "#{IDENTIFIER_CHAR}+[?!]?".freeze
|
|
15
|
+
# Visibility modifiers for method definitions
|
|
16
|
+
VISIBILITY_PATTERN = '(?:(?:private|protected|public)\s+)?'
|
|
15
17
|
|
|
16
|
-
attr_reader :source, :ir_program
|
|
18
|
+
attr_reader :source, :ir_program
|
|
17
19
|
|
|
18
|
-
def initialize(source,
|
|
20
|
+
def initialize(source, parse_body: true)
|
|
19
21
|
@source = source
|
|
20
22
|
@lines = source.split("\n")
|
|
21
|
-
@use_combinator = use_combinator
|
|
22
23
|
@parse_body = parse_body
|
|
23
|
-
@type_parser = ParserCombinator::TypeParser.new
|
|
24
|
+
@type_parser = ParserCombinator::TypeParser.new
|
|
24
25
|
@body_parser = BodyParser.new if parse_body
|
|
25
26
|
@ir_program = nil
|
|
26
27
|
end
|
|
@@ -32,7 +33,16 @@ module TRuby
|
|
|
32
33
|
classes = []
|
|
33
34
|
i = 0
|
|
34
35
|
|
|
36
|
+
# Pre-detect heredoc regions to skip
|
|
37
|
+
heredoc_ranges = HeredocDetector.detect(@lines)
|
|
38
|
+
|
|
35
39
|
while i < @lines.length
|
|
40
|
+
# Skip lines inside heredoc content
|
|
41
|
+
if HeredocDetector.inside_heredoc?(i, heredoc_ranges)
|
|
42
|
+
i += 1
|
|
43
|
+
next
|
|
44
|
+
end
|
|
45
|
+
|
|
36
46
|
line = @lines[i]
|
|
37
47
|
|
|
38
48
|
# Match type alias definitions
|
|
@@ -62,7 +72,7 @@ module TRuby
|
|
|
62
72
|
end
|
|
63
73
|
|
|
64
74
|
# Match function definitions (top-level only, not inside class)
|
|
65
|
-
if line.match?(/^\s
|
|
75
|
+
if line.match?(/^\s*#{VISIBILITY_PATTERN}def\s+#{IDENTIFIER_CHAR}+/)
|
|
66
76
|
func_info, next_i = parse_function_with_body(i)
|
|
67
77
|
if func_info
|
|
68
78
|
functions << func_info
|
|
@@ -82,11 +92,9 @@ module TRuby
|
|
|
82
92
|
classes: classes,
|
|
83
93
|
}
|
|
84
94
|
|
|
85
|
-
# Build IR
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@ir_program = builder.build(result, source: @source)
|
|
89
|
-
end
|
|
95
|
+
# Build IR
|
|
96
|
+
builder = IR::Builder.new
|
|
97
|
+
@ir_program = builder.build(result, source: @source)
|
|
90
98
|
|
|
91
99
|
result
|
|
92
100
|
end
|
|
@@ -97,10 +105,8 @@ module TRuby
|
|
|
97
105
|
@ir_program
|
|
98
106
|
end
|
|
99
107
|
|
|
100
|
-
# Parse a type expression using combinator
|
|
108
|
+
# Parse a type expression using combinator
|
|
101
109
|
def parse_type(type_string)
|
|
102
|
-
return nil unless @use_combinator
|
|
103
|
-
|
|
104
110
|
result = @type_parser.parse(type_string)
|
|
105
111
|
result[:success] ? result[:type] : nil
|
|
106
112
|
end
|
|
@@ -149,16 +155,14 @@ module TRuby
|
|
|
149
155
|
alias_name = match[1]
|
|
150
156
|
definition = match[2].strip
|
|
151
157
|
|
|
152
|
-
# Use combinator for complex type parsing
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
end
|
|
158
|
+
# Use combinator for complex type parsing
|
|
159
|
+
type_result = @type_parser.parse(definition)
|
|
160
|
+
if type_result[:success]
|
|
161
|
+
return {
|
|
162
|
+
name: alias_name,
|
|
163
|
+
definition: definition,
|
|
164
|
+
ir_type: type_result[:type],
|
|
165
|
+
}
|
|
162
166
|
end
|
|
163
167
|
|
|
164
168
|
{
|
|
@@ -173,12 +177,14 @@ module TRuby
|
|
|
173
177
|
# def foo(): Type - no params but with return type
|
|
174
178
|
# def foo(params) - with params, no return type
|
|
175
179
|
# def foo - no params, no return type
|
|
176
|
-
|
|
180
|
+
# Also supports visibility modifiers: private def, protected def, public def
|
|
181
|
+
match = line.match(/^\s*(?:(private|protected|public)\s+)?def\s+(#{METHOD_NAME_PATTERN})\s*(?:\((.*?)\))?\s*(?::\s*(.+?))?\s*$/)
|
|
177
182
|
return nil unless match
|
|
178
183
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
184
|
+
visibility = match[1] ? match[1].to_sym : :public
|
|
185
|
+
function_name = match[2]
|
|
186
|
+
params_str = match[3] || ""
|
|
187
|
+
return_type_str = match[4]&.strip
|
|
182
188
|
|
|
183
189
|
# Validate return type if present
|
|
184
190
|
if return_type_str
|
|
@@ -191,10 +197,11 @@ module TRuby
|
|
|
191
197
|
name: function_name,
|
|
192
198
|
params: params,
|
|
193
199
|
return_type: return_type_str,
|
|
200
|
+
visibility: visibility,
|
|
194
201
|
}
|
|
195
202
|
|
|
196
|
-
# Parse return type with combinator
|
|
197
|
-
if
|
|
203
|
+
# Parse return type with combinator
|
|
204
|
+
if return_type_str
|
|
198
205
|
type_result = @type_parser.parse(return_type_str)
|
|
199
206
|
result[:ir_return_type] = type_result[:type] if type_result[:success]
|
|
200
207
|
end
|
|
@@ -237,18 +244,36 @@ module TRuby
|
|
|
237
244
|
param_list = split_params(params_str)
|
|
238
245
|
|
|
239
246
|
param_list.each do |param|
|
|
240
|
-
|
|
241
|
-
|
|
247
|
+
param = param.strip
|
|
248
|
+
|
|
249
|
+
# 1. 더블 스플랫: **name: Type
|
|
250
|
+
if param.start_with?("**")
|
|
251
|
+
param_info = parse_double_splat_parameter(param)
|
|
252
|
+
parameters << param_info if param_info
|
|
253
|
+
# 2. 키워드 인자 그룹: { ... } 또는 { ... }: InterfaceName
|
|
254
|
+
elsif param.start_with?("{")
|
|
255
|
+
keyword_params = parse_keyword_args_group(param)
|
|
256
|
+
parameters.concat(keyword_params) if keyword_params
|
|
257
|
+
# 3. Hash 리터럴: name: { ... }
|
|
258
|
+
elsif param.match?(/^\w+:\s*\{/)
|
|
259
|
+
param_info = parse_hash_literal_parameter(param)
|
|
260
|
+
parameters << param_info if param_info
|
|
261
|
+
# 4. 일반 위치 인자: name: Type 또는 name: Type = default
|
|
262
|
+
else
|
|
263
|
+
param_info = parse_single_parameter(param)
|
|
264
|
+
parameters << param_info if param_info
|
|
265
|
+
end
|
|
242
266
|
end
|
|
243
267
|
|
|
244
268
|
parameters
|
|
245
269
|
end
|
|
246
270
|
|
|
247
271
|
def split_params(params_str)
|
|
248
|
-
# Handle nested generics
|
|
272
|
+
# Handle nested generics, braces, brackets
|
|
249
273
|
result = []
|
|
250
274
|
current = ""
|
|
251
275
|
depth = 0
|
|
276
|
+
brace_depth = 0
|
|
252
277
|
|
|
253
278
|
params_str.each_char do |char|
|
|
254
279
|
case char
|
|
@@ -258,8 +283,14 @@ module TRuby
|
|
|
258
283
|
when ">", "]", ")"
|
|
259
284
|
depth -= 1
|
|
260
285
|
current += char
|
|
286
|
+
when "{"
|
|
287
|
+
brace_depth += 1
|
|
288
|
+
current += char
|
|
289
|
+
when "}"
|
|
290
|
+
brace_depth -= 1
|
|
291
|
+
current += char
|
|
261
292
|
when ","
|
|
262
|
-
if depth.zero?
|
|
293
|
+
if depth.zero? && brace_depth.zero?
|
|
263
294
|
result << current.strip
|
|
264
295
|
current = ""
|
|
265
296
|
else
|
|
@@ -274,8 +305,10 @@ module TRuby
|
|
|
274
305
|
result
|
|
275
306
|
end
|
|
276
307
|
|
|
277
|
-
|
|
278
|
-
|
|
308
|
+
# 더블 스플랫 파라미터 파싱: **opts: Type
|
|
309
|
+
def parse_double_splat_parameter(param)
|
|
310
|
+
# **name: Type
|
|
311
|
+
match = param.match(/^\*\*(\w+)(?::\s*(.+?))?$/)
|
|
279
312
|
return nil unless match
|
|
280
313
|
|
|
281
314
|
param_name = match[1]
|
|
@@ -284,10 +317,159 @@ module TRuby
|
|
|
284
317
|
result = {
|
|
285
318
|
name: param_name,
|
|
286
319
|
type: type_str,
|
|
320
|
+
kind: :keyrest,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if type_str
|
|
324
|
+
type_result = @type_parser.parse(type_str)
|
|
325
|
+
result[:ir_type] = type_result[:type] if type_result[:success]
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
result
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# 키워드 인자 그룹 파싱: { name: String, age: Integer = 0 } 또는 { name:, age: 0 }: InterfaceName
|
|
332
|
+
def parse_keyword_args_group(param)
|
|
333
|
+
# { ... }: InterfaceName 형태 확인
|
|
334
|
+
# 또는 { ... } 만 있는 형태 (인라인 타입)
|
|
335
|
+
interface_match = param.match(/^\{(.+)\}\s*:\s*(\w+)\s*$/)
|
|
336
|
+
inline_match = param.match(/^\{(.+)\}\s*$/) unless interface_match
|
|
337
|
+
|
|
338
|
+
if interface_match
|
|
339
|
+
inner_content = interface_match[1]
|
|
340
|
+
interface_name = interface_match[2]
|
|
341
|
+
parse_keyword_args_with_interface(inner_content, interface_name)
|
|
342
|
+
elsif inline_match
|
|
343
|
+
inner_content = inline_match[1]
|
|
344
|
+
parse_keyword_args_inline(inner_content)
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# interface 참조 키워드 인자 파싱: { name:, age: 0 }: UserParams
|
|
349
|
+
def parse_keyword_args_with_interface(inner_content, interface_name)
|
|
350
|
+
parameters = []
|
|
351
|
+
parts = split_keyword_args(inner_content)
|
|
352
|
+
|
|
353
|
+
parts.each do |part|
|
|
354
|
+
part = part.strip
|
|
355
|
+
next if part.empty?
|
|
356
|
+
|
|
357
|
+
# name: default_value 또는 name: 형태
|
|
358
|
+
next unless part.match?(/^(\w+):\s*(.*)$/)
|
|
359
|
+
|
|
360
|
+
match = part.match(/^(\w+):\s*(.*)$/)
|
|
361
|
+
param_name = match[1]
|
|
362
|
+
default_value = match[2].strip
|
|
363
|
+
default_value = nil if default_value.empty?
|
|
364
|
+
|
|
365
|
+
parameters << {
|
|
366
|
+
name: param_name,
|
|
367
|
+
type: nil, # interface에서 타입을 가져옴
|
|
368
|
+
default_value: default_value,
|
|
369
|
+
kind: :keyword,
|
|
370
|
+
interface_ref: interface_name,
|
|
371
|
+
}
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
parameters
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# 인라인 타입 키워드 인자 파싱: { name: String, age: Integer = 0 }
|
|
378
|
+
def parse_keyword_args_inline(inner_content)
|
|
379
|
+
parameters = []
|
|
380
|
+
parts = split_keyword_args(inner_content)
|
|
381
|
+
|
|
382
|
+
parts.each do |part|
|
|
383
|
+
part = part.strip
|
|
384
|
+
next if part.empty?
|
|
385
|
+
|
|
386
|
+
# name: Type = default 또는 name: Type 형태
|
|
387
|
+
next unless part.match?(/^(\w+):\s*(.+)$/)
|
|
388
|
+
|
|
389
|
+
match = part.match(/^(\w+):\s*(.+)$/)
|
|
390
|
+
param_name = match[1]
|
|
391
|
+
type_and_default = match[2].strip
|
|
392
|
+
|
|
393
|
+
# Type = default 분리
|
|
394
|
+
type_str, default_value = split_type_and_default(type_and_default)
|
|
395
|
+
|
|
396
|
+
result = {
|
|
397
|
+
name: param_name,
|
|
398
|
+
type: type_str,
|
|
399
|
+
default_value: default_value,
|
|
400
|
+
kind: :keyword,
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if type_str
|
|
404
|
+
type_result = @type_parser.parse(type_str)
|
|
405
|
+
result[:ir_type] = type_result[:type] if type_result[:success]
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
parameters << result
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
parameters
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# 키워드 인자 내부를 콤마로 분리 (중첩된 제네릭/배열/해시 고려)
|
|
415
|
+
def split_keyword_args(content)
|
|
416
|
+
StringUtils.split_by_comma(content)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# 타입과 기본값 분리: "String = 0" -> ["String", "0"]
|
|
420
|
+
def split_type_and_default(type_and_default)
|
|
421
|
+
StringUtils.split_type_and_default(type_and_default)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Hash 리터럴 파라미터 파싱: config: { host: String, port: Integer }
|
|
425
|
+
def parse_hash_literal_parameter(param)
|
|
426
|
+
# name: { ... } 또는 name: { ... }: InterfaceName
|
|
427
|
+
match = param.match(/^(\w+):\s*(\{.+\})(?::\s*(\w+))?$/)
|
|
428
|
+
return nil unless match
|
|
429
|
+
|
|
430
|
+
param_name = match[1]
|
|
431
|
+
hash_type = match[2]
|
|
432
|
+
interface_name = match[3]
|
|
433
|
+
|
|
434
|
+
result = {
|
|
435
|
+
name: param_name,
|
|
436
|
+
type: interface_name || hash_type,
|
|
437
|
+
kind: :required,
|
|
438
|
+
hash_type_def: hash_type, # 원본 해시 타입 정의 저장
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
result[:interface_ref] = interface_name if interface_name
|
|
442
|
+
|
|
443
|
+
result
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def parse_single_parameter(param)
|
|
447
|
+
# name: Type = default 또는 name: Type 또는 name
|
|
448
|
+
# 기본값이 있는 경우 먼저 처리
|
|
449
|
+
type_str = nil
|
|
450
|
+
default_value = nil
|
|
451
|
+
|
|
452
|
+
if param.include?(":")
|
|
453
|
+
match = param.match(/^(\w+):\s*(.+)$/)
|
|
454
|
+
return nil unless match
|
|
455
|
+
|
|
456
|
+
param_name = match[1]
|
|
457
|
+
type_and_default = match[2].strip
|
|
458
|
+
type_str, default_value = split_type_and_default(type_and_default)
|
|
459
|
+
else
|
|
460
|
+
# 타입 없이 이름만 있는 경우
|
|
461
|
+
param_name = param.strip
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
result = {
|
|
465
|
+
name: param_name,
|
|
466
|
+
type: type_str,
|
|
467
|
+
default_value: default_value,
|
|
468
|
+
kind: default_value ? :optional : :required,
|
|
287
469
|
}
|
|
288
470
|
|
|
289
|
-
# Parse type with combinator
|
|
290
|
-
if
|
|
471
|
+
# Parse type with combinator
|
|
472
|
+
if type_str
|
|
291
473
|
type_result = @type_parser.parse(type_str)
|
|
292
474
|
result[:ir_type] = type_result[:type] if type_result[:success]
|
|
293
475
|
end
|
|
@@ -326,7 +508,7 @@ module TRuby
|
|
|
326
508
|
current_line = @lines[i]
|
|
327
509
|
|
|
328
510
|
# Match method definitions inside class
|
|
329
|
-
if current_line.match?(/^\s
|
|
511
|
+
if current_line.match?(/^\s*#{VISIBILITY_PATTERN}def\s+#{IDENTIFIER_CHAR}+/)
|
|
330
512
|
method_info, next_i = parse_method_in_class(i, class_end)
|
|
331
513
|
if method_info
|
|
332
514
|
methods << method_info
|
|
@@ -438,10 +620,8 @@ module TRuby
|
|
|
438
620
|
}
|
|
439
621
|
|
|
440
622
|
# Parse member type with combinator
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
member[:ir_type] = type_result[:type] if type_result[:success]
|
|
444
|
-
end
|
|
623
|
+
type_result = @type_parser.parse(member[:type])
|
|
624
|
+
member[:ir_type] = type_result[:type] if type_result[:success]
|
|
445
625
|
|
|
446
626
|
members << member
|
|
447
627
|
end
|
|
@@ -453,11 +633,4 @@ module TRuby
|
|
|
453
633
|
[{ name: interface_name, members: members }, i]
|
|
454
634
|
end
|
|
455
635
|
end
|
|
456
|
-
|
|
457
|
-
# Legacy Parser for backward compatibility (regex-only)
|
|
458
|
-
class LegacyParser < Parser
|
|
459
|
-
def initialize(source)
|
|
460
|
-
super(source, use_combinator: false)
|
|
461
|
-
end
|
|
462
|
-
end
|
|
463
636
|
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
# 문자열 파싱을 위한 공통 유틸리티 모듈
|
|
5
|
+
# 파서와 컴파일러에서 공유하는 중첩 괄호 처리 로직
|
|
6
|
+
module StringUtils
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# 중첩된 괄호를 고려하여 콤마로 문자열 분리
|
|
10
|
+
# @param content [String] 분리할 문자열
|
|
11
|
+
# @return [Array<String>] 분리된 문자열 배열
|
|
12
|
+
def split_by_comma(content)
|
|
13
|
+
result = []
|
|
14
|
+
current = ""
|
|
15
|
+
depth = 0
|
|
16
|
+
|
|
17
|
+
content.each_char do |char|
|
|
18
|
+
case char
|
|
19
|
+
when "<", "[", "(", "{"
|
|
20
|
+
depth += 1
|
|
21
|
+
current += char
|
|
22
|
+
when ">", "]", ")", "}"
|
|
23
|
+
depth -= 1
|
|
24
|
+
current += char
|
|
25
|
+
when ","
|
|
26
|
+
if depth.zero?
|
|
27
|
+
result << current.strip
|
|
28
|
+
current = ""
|
|
29
|
+
else
|
|
30
|
+
current += char
|
|
31
|
+
end
|
|
32
|
+
else
|
|
33
|
+
current += char
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
result << current.strip unless current.empty?
|
|
38
|
+
result
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# 타입과 기본값 분리: "String = 0" -> ["String", "0"]
|
|
42
|
+
# 중첩된 괄호 내부의 = 는 무시
|
|
43
|
+
# @param type_and_default [String] "Type = default" 형태의 문자열
|
|
44
|
+
# @return [Array] [type_str, default_value] 또는 [type_str, nil]
|
|
45
|
+
def split_type_and_default(type_and_default)
|
|
46
|
+
depth = 0
|
|
47
|
+
equals_pos = nil
|
|
48
|
+
|
|
49
|
+
type_and_default.each_char.with_index do |char, i|
|
|
50
|
+
case char
|
|
51
|
+
when "<", "[", "(", "{"
|
|
52
|
+
depth += 1
|
|
53
|
+
when ">", "]", ")", "}"
|
|
54
|
+
depth -= 1
|
|
55
|
+
when "="
|
|
56
|
+
if depth.zero?
|
|
57
|
+
equals_pos = i
|
|
58
|
+
break
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if equals_pos
|
|
64
|
+
type_str = type_and_default[0...equals_pos].strip
|
|
65
|
+
default_value = type_and_default[(equals_pos + 1)..].strip
|
|
66
|
+
[type_str, default_value]
|
|
67
|
+
else
|
|
68
|
+
[type_and_default, nil]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# 기본값만 추출 (타입은 버림)
|
|
73
|
+
# @param type_and_default [String] "Type = default" 형태의 문자열
|
|
74
|
+
# @return [String, nil] 기본값 또는 nil
|
|
75
|
+
def extract_default_value(type_and_default)
|
|
76
|
+
_, default_value = split_type_and_default(type_and_default)
|
|
77
|
+
default_value
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/t_ruby/version.rb
CHANGED
data/lib/t_ruby.rb
CHANGED
|
@@ -5,12 +5,14 @@ require_relative "t_ruby/version_checker"
|
|
|
5
5
|
require_relative "t_ruby/config"
|
|
6
6
|
|
|
7
7
|
# Core infrastructure (must be loaded first)
|
|
8
|
+
require_relative "t_ruby/string_utils"
|
|
8
9
|
require_relative "t_ruby/ir"
|
|
9
10
|
require_relative "t_ruby/parser_combinator"
|
|
10
11
|
require_relative "t_ruby/smt_solver"
|
|
11
12
|
|
|
12
13
|
# Basic components
|
|
13
14
|
require_relative "t_ruby/type_alias_registry"
|
|
15
|
+
require_relative "t_ruby/heredoc_detector"
|
|
14
16
|
require_relative "t_ruby/body_parser"
|
|
15
17
|
require_relative "t_ruby/parser"
|
|
16
18
|
require_relative "t_ruby/union_type_parser"
|
|
@@ -18,7 +20,6 @@ require_relative "t_ruby/generic_type_parser"
|
|
|
18
20
|
require_relative "t_ruby/intersection_type_parser"
|
|
19
21
|
require_relative "t_ruby/type_erasure"
|
|
20
22
|
require_relative "t_ruby/error_handler"
|
|
21
|
-
require_relative "t_ruby/rbs_generator"
|
|
22
23
|
require_relative "t_ruby/declaration_generator"
|
|
23
24
|
require_relative "t_ruby/compiler"
|
|
24
25
|
require_relative "t_ruby/lsp_server"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: t-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.41
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Y. Fred Kim
|
|
@@ -52,15 +52,16 @@ files:
|
|
|
52
52
|
- lib/t_ruby/docs_example_verifier.rb
|
|
53
53
|
- lib/t_ruby/error_handler.rb
|
|
54
54
|
- lib/t_ruby/generic_type_parser.rb
|
|
55
|
+
- lib/t_ruby/heredoc_detector.rb
|
|
55
56
|
- lib/t_ruby/intersection_type_parser.rb
|
|
56
57
|
- lib/t_ruby/ir.rb
|
|
57
58
|
- lib/t_ruby/lsp_server.rb
|
|
58
59
|
- lib/t_ruby/package_manager.rb
|
|
59
60
|
- lib/t_ruby/parser.rb
|
|
60
61
|
- lib/t_ruby/parser_combinator.rb
|
|
61
|
-
- lib/t_ruby/rbs_generator.rb
|
|
62
62
|
- lib/t_ruby/runtime_validator.rb
|
|
63
63
|
- lib/t_ruby/smt_solver.rb
|
|
64
|
+
- lib/t_ruby/string_utils.rb
|
|
64
65
|
- lib/t_ruby/type_alias_registry.rb
|
|
65
66
|
- lib/t_ruby/type_checker.rb
|
|
66
67
|
- lib/t_ruby/type_env.rb
|
data/lib/t_ruby/rbs_generator.rb
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module TRuby
|
|
4
|
-
class RBSGenerator
|
|
5
|
-
def initialize
|
|
6
|
-
# RBS generation configuration
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def generate(functions, type_aliases)
|
|
10
|
-
# Add type aliases
|
|
11
|
-
lines = type_aliases.map do |type_alias|
|
|
12
|
-
generate_type_alias(type_alias)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
lines << "" if type_aliases.any? && functions.any?
|
|
16
|
-
|
|
17
|
-
# Add function signatures
|
|
18
|
-
functions.each do |func|
|
|
19
|
-
lines << generate_function_signature(func)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
lines.compact.join("\n")
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def generate_type_aliases(aliases)
|
|
26
|
-
aliases.map { |alias_def| generate_type_alias(alias_def) }.join("\n")
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def generate_type_alias(type_alias)
|
|
30
|
-
name = type_alias[:name]
|
|
31
|
-
definition = type_alias[:definition]
|
|
32
|
-
|
|
33
|
-
"type #{name} = #{definition}"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def generate_function_signature(func)
|
|
37
|
-
name = func[:name]
|
|
38
|
-
params = func[:params] || []
|
|
39
|
-
return_type = func[:return_type]
|
|
40
|
-
|
|
41
|
-
param_str = format_parameters(params)
|
|
42
|
-
return_str = format_return_type(return_type)
|
|
43
|
-
|
|
44
|
-
"def #{name}: (#{param_str}) -> #{return_str}"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def format_parameters(params)
|
|
50
|
-
return if params.empty?
|
|
51
|
-
|
|
52
|
-
param_strs = params.map do |param|
|
|
53
|
-
param_name = param[:name]
|
|
54
|
-
param_type = param[:type] || "Object"
|
|
55
|
-
|
|
56
|
-
"#{param_name}: #{param_type}"
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
param_strs.join(", ")
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def format_return_type(return_type)
|
|
63
|
-
return "void" if return_type == "void"
|
|
64
|
-
return "nil" if return_type.nil?
|
|
65
|
-
|
|
66
|
-
return_type
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|