t-ruby 0.0.40 → 0.0.42
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/cli.rb +1 -1
- data/lib/t_ruby/code_emitter.rb +254 -0
- data/lib/t_ruby/compiler.rb +125 -7
- data/lib/t_ruby/config.rb +18 -3
- data/lib/t_ruby/ir.rb +7 -3
- data/lib/t_ruby/parser.rb +181 -6
- data/lib/t_ruby/ruby_version.rb +112 -0
- data/lib/t_ruby/string_utils.rb +80 -0
- data/lib/t_ruby/version.rb +1 -1
- data/lib/t_ruby/watcher.rb +16 -1
- data/lib/t_ruby.rb +3 -0
- metadata +9 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 75019794a577853cf5a7e704febef3868a1ddd06776e43f50dc684d7dde87e3c
|
|
4
|
+
data.tar.gz: 89a255f7e7b68e2becd6314be09470424d5f418324c40bcc77a7311e6006d9f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4000a38df6e83591a15f453af3b4d5f759b1af8e869478737c1d7711e34c7e1d65c89df34e56a6575d41e2f120e951970fde1ac444bf24d73c1167a24de875c4
|
|
7
|
+
data.tar.gz: 9273b4bf7d8a9af3ccbe0fa0d93914b13c9ce09f464902a4974749780871516c8fbaeb33a60d1dca754914dde09e498148ec080ff31fef3b11a0a88d96642eee
|
data/lib/t_ruby/cli.rb
CHANGED
|
@@ -135,7 +135,7 @@ module TRuby
|
|
|
135
135
|
compiler:
|
|
136
136
|
strictness: standard # strict | standard | permissive
|
|
137
137
|
generate_rbs: true
|
|
138
|
-
target_ruby: "
|
|
138
|
+
target_ruby: "#{RubyVersion.current.major}.#{RubyVersion.current.minor}"
|
|
139
139
|
# experimental: []
|
|
140
140
|
# checks:
|
|
141
141
|
# no_implicit_any: false
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
# Version-specific code transformation strategies
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# emitter = CodeEmitter.for_version("4.0")
|
|
8
|
+
# result = emitter.transform(source)
|
|
9
|
+
#
|
|
10
|
+
module CodeEmitter
|
|
11
|
+
# Factory method to get appropriate emitter for target Ruby version
|
|
12
|
+
#
|
|
13
|
+
# @param target_ruby [String] target Ruby version (e.g., "3.0", "4.0")
|
|
14
|
+
# @return [Base] appropriate emitter instance
|
|
15
|
+
def self.for_version(target_ruby)
|
|
16
|
+
version = RubyVersion.parse(target_ruby)
|
|
17
|
+
|
|
18
|
+
if version.numbered_parameters_raise_error?
|
|
19
|
+
Ruby40.new(version)
|
|
20
|
+
elsif version.supports_it_parameter?
|
|
21
|
+
Ruby34.new(version)
|
|
22
|
+
elsif version.supports_anonymous_block_forwarding?
|
|
23
|
+
Ruby31.new(version)
|
|
24
|
+
else
|
|
25
|
+
Ruby30.new(version)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Base class for version-specific code emitters
|
|
30
|
+
class Base
|
|
31
|
+
attr_reader :version
|
|
32
|
+
|
|
33
|
+
def initialize(version)
|
|
34
|
+
@version = version
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Apply all transformations for this version
|
|
38
|
+
#
|
|
39
|
+
# @param source [String] source code to transform
|
|
40
|
+
# @return [String] transformed source code
|
|
41
|
+
def transform(source)
|
|
42
|
+
result = source.dup
|
|
43
|
+
result = transform_numbered_params(result)
|
|
44
|
+
transform_block_forwarding(result)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Transform numbered block parameters (_1, _2, etc.)
|
|
48
|
+
# Default: no transformation
|
|
49
|
+
#
|
|
50
|
+
# @param source [String] source code
|
|
51
|
+
# @return [String] transformed source code
|
|
52
|
+
def transform_numbered_params(source)
|
|
53
|
+
source
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Transform block forwarding syntax
|
|
57
|
+
# Default: no transformation
|
|
58
|
+
#
|
|
59
|
+
# @param source [String] source code
|
|
60
|
+
# @return [String] transformed source code
|
|
61
|
+
def transform_block_forwarding(source)
|
|
62
|
+
source
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Check if this version supports the `it` implicit block parameter
|
|
66
|
+
#
|
|
67
|
+
# @return [Boolean]
|
|
68
|
+
def supports_it?
|
|
69
|
+
false
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check if numbered parameters raise NameError in this version
|
|
73
|
+
#
|
|
74
|
+
# @return [Boolean]
|
|
75
|
+
def numbered_params_error?
|
|
76
|
+
false
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Ruby 3.0 emitter - baseline, no transformations
|
|
81
|
+
class Ruby30 < Base
|
|
82
|
+
# Ruby 3.0 uses standard syntax, no transformations needed
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Ruby 3.1+ emitter - supports anonymous block forwarding
|
|
86
|
+
class Ruby31 < Base
|
|
87
|
+
# Transform `def foo(&block) ... bar(&block)` to `def foo(&) ... bar(&)`
|
|
88
|
+
#
|
|
89
|
+
# Only transforms when the block parameter is ONLY used for forwarding,
|
|
90
|
+
# not when it's called directly (e.g., block.call)
|
|
91
|
+
def transform_block_forwarding(source)
|
|
92
|
+
result = source.dup
|
|
93
|
+
|
|
94
|
+
# Find method definitions with block parameters
|
|
95
|
+
# Pattern: def method_name(&block_name)
|
|
96
|
+
result.gsub!(/def\s+(\w+[?!=]?)\s*\(([^)]*?)&(\w+)\s*\)/) do |_match|
|
|
97
|
+
method_name = ::Regexp.last_match(1)
|
|
98
|
+
other_params = ::Regexp.last_match(2)
|
|
99
|
+
block_name = ::Regexp.last_match(3)
|
|
100
|
+
|
|
101
|
+
# Find the method body to check block usage
|
|
102
|
+
method_start = ::Regexp.last_match.begin(0)
|
|
103
|
+
remaining = result[method_start..]
|
|
104
|
+
|
|
105
|
+
# Check if block is only used for forwarding (not called directly)
|
|
106
|
+
if block_only_forwarded?(remaining, block_name)
|
|
107
|
+
"def #{method_name}(#{other_params}&)"
|
|
108
|
+
else
|
|
109
|
+
"def #{method_name}(#{other_params}&#{block_name})"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Replace block forwarding calls with anonymous forwarding
|
|
114
|
+
# This is a simplified approach - in practice we'd need proper scope tracking
|
|
115
|
+
result.gsub!(/(\w+)\s*\(\s*&(\w+)\s*\)/) do |match|
|
|
116
|
+
call_name = ::Regexp.last_match(1)
|
|
117
|
+
::Regexp.last_match(2)
|
|
118
|
+
|
|
119
|
+
# Check if this block name was converted to anonymous
|
|
120
|
+
if result.include?("def ") && result.include?("(&)")
|
|
121
|
+
"#{call_name}(&)"
|
|
122
|
+
else
|
|
123
|
+
match
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
result
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
# Check if a block parameter is only used for forwarding
|
|
133
|
+
def block_only_forwarded?(method_body, block_name)
|
|
134
|
+
# Simple heuristic: if block_name appears with .call or without &, it's not just forwarding
|
|
135
|
+
# Look for patterns like: block_name.call, block_name.(), yield
|
|
136
|
+
|
|
137
|
+
# Extract method body (until next def or end of class)
|
|
138
|
+
lines = method_body.lines
|
|
139
|
+
depth = 0
|
|
140
|
+
body_lines = []
|
|
141
|
+
|
|
142
|
+
lines.each do |line|
|
|
143
|
+
depth += 1 if line.match?(/\b(def|class|module|do|begin|case|if|unless|while|until)\b/)
|
|
144
|
+
depth -= 1 if line.match?(/\bend\b/)
|
|
145
|
+
body_lines << line
|
|
146
|
+
break if depth <= 0 && body_lines.length > 1
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
body = body_lines.join
|
|
150
|
+
|
|
151
|
+
# Check for direct block usage
|
|
152
|
+
return false if body.match?(/\b#{block_name}\s*\./) # block.call, block.(), etc.
|
|
153
|
+
return false if body.match?(/\b#{block_name}\s*\[/) # block[args]
|
|
154
|
+
return false if body.match?(/\byield\b/) # yield instead of forwarding
|
|
155
|
+
|
|
156
|
+
# Only &block_name patterns - this is forwarding
|
|
157
|
+
true
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Ruby 3.4+ emitter - supports `it` implicit block parameter
|
|
162
|
+
class Ruby34 < Ruby31
|
|
163
|
+
def supports_it?
|
|
164
|
+
true
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Ruby 3.4 still supports _1 syntax, so no transformation needed by default
|
|
168
|
+
# Users can opt-in to using `it` style if they want
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Ruby 4.0+ emitter - _1 raises NameError, must use `it`
|
|
172
|
+
class Ruby40 < Ruby34
|
|
173
|
+
def numbered_params_error?
|
|
174
|
+
true
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Transform numbered parameters to appropriate syntax
|
|
178
|
+
#
|
|
179
|
+
# - Single _1 → it
|
|
180
|
+
# - Multiple (_1, _2) → explicit |k, v| params
|
|
181
|
+
def transform_numbered_params(source)
|
|
182
|
+
result = source.dup
|
|
183
|
+
|
|
184
|
+
# Simple approach: replace all _1 with it when it's the only numbered param in scope
|
|
185
|
+
# For complex cases with _2+, we'd need proper parsing
|
|
186
|
+
# For now, do a global replacement if _2 etc are not present
|
|
187
|
+
if result.match?(/\b_[2-9]\b/)
|
|
188
|
+
# Has multiple numbered params - need to convert to explicit params
|
|
189
|
+
# This is a complex case that requires proper block parsing
|
|
190
|
+
transform_multi_numbered_params(result)
|
|
191
|
+
else
|
|
192
|
+
# Only _1 is used - simple replacement
|
|
193
|
+
result.gsub(/\b_1\b/, "it")
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def transform_multi_numbered_params(source)
|
|
200
|
+
result = source.dup
|
|
201
|
+
|
|
202
|
+
# Find blocks and transform them
|
|
203
|
+
# Use a recursive approach with placeholder replacement
|
|
204
|
+
|
|
205
|
+
# Replace innermost blocks first
|
|
206
|
+
loop do
|
|
207
|
+
changed = false
|
|
208
|
+
result = result.gsub(/\{([^{}]*)\}/) do |block|
|
|
209
|
+
content = ::Regexp.last_match(1)
|
|
210
|
+
max_param = find_max_numbered_param(content)
|
|
211
|
+
|
|
212
|
+
if max_param > 1
|
|
213
|
+
# Multiple params - convert to explicit
|
|
214
|
+
param_names = generate_param_names(max_param)
|
|
215
|
+
new_content = content.dup
|
|
216
|
+
(1..max_param).each do |i|
|
|
217
|
+
new_content.gsub!(/\b_#{i}\b/, param_names[i - 1])
|
|
218
|
+
end
|
|
219
|
+
changed = true
|
|
220
|
+
"{ |#{param_names.join(", ")}| #{new_content.strip} }"
|
|
221
|
+
elsif max_param == 1
|
|
222
|
+
# Single _1 - convert to it
|
|
223
|
+
changed = true
|
|
224
|
+
"{ #{content.gsub(/\b_1\b/, "it").strip} }"
|
|
225
|
+
else
|
|
226
|
+
block
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
break unless changed
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
result
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def find_max_numbered_param(content)
|
|
236
|
+
max = 0
|
|
237
|
+
content.scan(/\b_(\d+)\b/) do |match|
|
|
238
|
+
num = match[0].to_i
|
|
239
|
+
max = num if num > max
|
|
240
|
+
end
|
|
241
|
+
max
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def generate_param_names(count)
|
|
245
|
+
# Generate simple parameter names: a, b, c, ... or k, v for 2
|
|
246
|
+
if count == 2
|
|
247
|
+
%w[k v]
|
|
248
|
+
else
|
|
249
|
+
("a".."z").take(count)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
data/lib/t_ruby/compiler.rb
CHANGED
|
@@ -380,8 +380,8 @@ module TRuby
|
|
|
380
380
|
ir_program = result[:program]
|
|
381
381
|
end
|
|
382
382
|
|
|
383
|
-
# Generate Ruby code using IR-aware generator
|
|
384
|
-
generator = IRCodeGenerator.new
|
|
383
|
+
# Generate Ruby code using IR-aware generator with target Ruby version
|
|
384
|
+
generator = IRCodeGenerator.new(target_ruby: @config.target_ruby)
|
|
385
385
|
generator.generate_with_source(ir_program, source)
|
|
386
386
|
end
|
|
387
387
|
|
|
@@ -434,8 +434,12 @@ module TRuby
|
|
|
434
434
|
|
|
435
435
|
# IR-aware code generator for source-preserving transformation
|
|
436
436
|
class IRCodeGenerator
|
|
437
|
-
|
|
437
|
+
attr_reader :emitter
|
|
438
|
+
|
|
439
|
+
# @param target_ruby [String] target Ruby version (e.g., "3.0", "4.0")
|
|
440
|
+
def initialize(target_ruby: "3.0")
|
|
438
441
|
@output = []
|
|
442
|
+
@emitter = CodeEmitter.for_version(target_ruby)
|
|
439
443
|
end
|
|
440
444
|
|
|
441
445
|
# Generate Ruby code from IR program
|
|
@@ -471,6 +475,9 @@ module TRuby
|
|
|
471
475
|
# Remove return type annotations
|
|
472
476
|
result = erase_return_types(result)
|
|
473
477
|
|
|
478
|
+
# Apply version-specific transformations
|
|
479
|
+
result = @emitter.transform(result)
|
|
480
|
+
|
|
474
481
|
# Clean up extra blank lines
|
|
475
482
|
result.gsub(/\n{3,}/, "\n\n")
|
|
476
483
|
end
|
|
@@ -505,6 +512,7 @@ module TRuby
|
|
|
505
512
|
params = []
|
|
506
513
|
current = ""
|
|
507
514
|
depth = 0
|
|
515
|
+
brace_depth = 0
|
|
508
516
|
|
|
509
517
|
params_str.each_char do |char|
|
|
510
518
|
case char
|
|
@@ -514,9 +522,16 @@ module TRuby
|
|
|
514
522
|
when ">", "]", ")"
|
|
515
523
|
depth -= 1
|
|
516
524
|
current += char
|
|
525
|
+
when "{"
|
|
526
|
+
brace_depth += 1
|
|
527
|
+
current += char
|
|
528
|
+
when "}"
|
|
529
|
+
brace_depth -= 1
|
|
530
|
+
current += char
|
|
517
531
|
when ","
|
|
518
|
-
if depth.zero?
|
|
519
|
-
|
|
532
|
+
if depth.zero? && brace_depth.zero?
|
|
533
|
+
cleaned = clean_param(current.strip)
|
|
534
|
+
params.concat(Array(cleaned)) if cleaned
|
|
520
535
|
current = ""
|
|
521
536
|
else
|
|
522
537
|
current += char
|
|
@@ -526,12 +541,47 @@ module TRuby
|
|
|
526
541
|
end
|
|
527
542
|
end
|
|
528
543
|
|
|
529
|
-
|
|
544
|
+
cleaned = clean_param(current.strip) unless current.empty?
|
|
545
|
+
params.concat(Array(cleaned)) if cleaned
|
|
530
546
|
params.join(", ")
|
|
531
547
|
end
|
|
532
548
|
|
|
533
549
|
# Clean a single parameter (remove type annotation, preserve default value)
|
|
550
|
+
# Returns String or Array of Strings (for keyword args group)
|
|
534
551
|
def clean_param(param)
|
|
552
|
+
param = param.strip
|
|
553
|
+
return nil if param.empty?
|
|
554
|
+
|
|
555
|
+
# 0. 블록 파라미터: &name: Type -> &name
|
|
556
|
+
if param.start_with?("&")
|
|
557
|
+
match = param.match(/^&(\w+)(?::\s*.+)?$/)
|
|
558
|
+
return "&#{match[1]}" if match
|
|
559
|
+
|
|
560
|
+
return param
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
# 1. 더블 스플랫: **name: Type -> **name
|
|
564
|
+
if param.start_with?("**")
|
|
565
|
+
match = param.match(/^\*\*(\w+)(?::\s*.+)?$/)
|
|
566
|
+
return "**#{match[1]}" if match
|
|
567
|
+
|
|
568
|
+
return param
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# 2. 키워드 인자 그룹: { ... } 또는 { ... }: InterfaceName
|
|
572
|
+
if param.start_with?("{")
|
|
573
|
+
return clean_keyword_args_group(param)
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# 3. Hash 리터럴: name: { ... } -> name
|
|
577
|
+
if param.match?(/^\w+:\s*\{/)
|
|
578
|
+
match = param.match(/^(\w+):\s*\{.+\}(?::\s*\w+)?$/)
|
|
579
|
+
return match[1] if match
|
|
580
|
+
|
|
581
|
+
return param
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# 4. 일반 파라미터: name: Type = value -> name = value 또는 name: Type -> name
|
|
535
585
|
# Match: name: Type = value (with default value)
|
|
536
586
|
if (match = param.match(/^(#{TRuby::IDENTIFIER_CHAR}+)\s*:\s*.+?\s*(=\s*.+)$/))
|
|
537
587
|
"#{match[1]} #{match[2]}"
|
|
@@ -543,15 +593,83 @@ module TRuby
|
|
|
543
593
|
end
|
|
544
594
|
end
|
|
545
595
|
|
|
596
|
+
# 키워드 인자 그룹을 Ruby 키워드 인자로 변환
|
|
597
|
+
# { name: String, age: Integer = 0 } -> name:, age: 0
|
|
598
|
+
# { name:, age: 0 }: UserParams -> name:, age: 0
|
|
599
|
+
def clean_keyword_args_group(param)
|
|
600
|
+
# { ... }: InterfaceName 또는 { ... } 형태 파싱
|
|
601
|
+
interface_match = param.match(/^\{(.+)\}\s*:\s*\w+\s*$/)
|
|
602
|
+
inline_match = param.match(/^\{(.+)\}\s*$/) unless interface_match
|
|
603
|
+
|
|
604
|
+
inner_content = if interface_match
|
|
605
|
+
interface_match[1]
|
|
606
|
+
elsif inline_match
|
|
607
|
+
inline_match[1]
|
|
608
|
+
else
|
|
609
|
+
return param
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# 내부 파라미터 분리
|
|
613
|
+
parts = split_nested_content(inner_content)
|
|
614
|
+
keyword_params = []
|
|
615
|
+
|
|
616
|
+
parts.each do |part|
|
|
617
|
+
part = part.strip
|
|
618
|
+
next if part.empty?
|
|
619
|
+
|
|
620
|
+
if interface_match
|
|
621
|
+
# interface 참조: name: default_value 또는 name:
|
|
622
|
+
if (match = part.match(/^(\w+):\s*(.*)$/))
|
|
623
|
+
name = match[1]
|
|
624
|
+
default_value = match[2].strip
|
|
625
|
+
keyword_params << if default_value.empty?
|
|
626
|
+
"#{name}:"
|
|
627
|
+
else
|
|
628
|
+
"#{name}: #{default_value}"
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
elsif (match = part.match(/^(\w+):\s*(.+)$/))
|
|
632
|
+
# 인라인 타입: name: Type = default 또는 name: Type
|
|
633
|
+
name = match[1]
|
|
634
|
+
type_and_default = match[2].strip
|
|
635
|
+
|
|
636
|
+
# Type = default 분리
|
|
637
|
+
default_value = extract_default_value(type_and_default)
|
|
638
|
+
keyword_params << if default_value
|
|
639
|
+
"#{name}: #{default_value}"
|
|
640
|
+
else
|
|
641
|
+
"#{name}:"
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
keyword_params
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
# 중첩된 내용을 콤마로 분리
|
|
650
|
+
def split_nested_content(content)
|
|
651
|
+
StringUtils.split_by_comma(content)
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
# 타입과 기본값에서 기본값만 추출
|
|
655
|
+
def extract_default_value(type_and_default)
|
|
656
|
+
StringUtils.extract_default_value(type_and_default)
|
|
657
|
+
end
|
|
658
|
+
|
|
546
659
|
# Erase return type annotations
|
|
547
660
|
def erase_return_types(source)
|
|
548
661
|
result = source.dup
|
|
549
662
|
|
|
550
|
-
# Remove return type: ): Type or ): Type<Foo> etc.
|
|
663
|
+
# Remove return type after parentheses: ): Type or ): Type<Foo> etc.
|
|
551
664
|
result.gsub!(/\)\s*:\s*[^\n]+?(?=\s*$)/m) do |_match|
|
|
552
665
|
")"
|
|
553
666
|
end
|
|
554
667
|
|
|
668
|
+
# Remove return type for methods without parentheses: def method_name: Type
|
|
669
|
+
result.gsub!(/^(\s*#{TRuby::VISIBILITY_PATTERN}def\s+#{TRuby::METHOD_NAME_PATTERN})\s*:\s*[^\n]+?(?=\s*$)/m) do |_match|
|
|
670
|
+
::Regexp.last_match(1)
|
|
671
|
+
end
|
|
672
|
+
|
|
555
673
|
result
|
|
556
674
|
end
|
|
557
675
|
end
|
data/lib/t_ruby/config.rb
CHANGED
|
@@ -25,7 +25,7 @@ module TRuby
|
|
|
25
25
|
"strictness" => "standard",
|
|
26
26
|
"generate_rbs" => true,
|
|
27
27
|
"type_check" => true,
|
|
28
|
-
"target_ruby" =>
|
|
28
|
+
"target_ruby" => nil, # Auto-detect from current Ruby version
|
|
29
29
|
"experimental" => [],
|
|
30
30
|
"checks" => {
|
|
31
31
|
"no_implicit_any" => false,
|
|
@@ -97,9 +97,24 @@ module TRuby
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
# Get target Ruby version
|
|
100
|
-
#
|
|
100
|
+
# If not specified in config, auto-detects from current Ruby environment
|
|
101
|
+
# @return [String] target Ruby version (e.g., "3.0", "3.2", "4.0")
|
|
102
|
+
# @raise [UnsupportedRubyVersionError] if detected version is not supported
|
|
101
103
|
def target_ruby
|
|
102
|
-
|
|
104
|
+
configured = @compiler["target_ruby"]
|
|
105
|
+
if configured
|
|
106
|
+
RubyVersion.parse(configured).validate!
|
|
107
|
+
configured.to_s
|
|
108
|
+
else
|
|
109
|
+
version = RubyVersion.current.validate!
|
|
110
|
+
"#{version.major}.#{version.minor}"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Get target Ruby version as RubyVersion object
|
|
115
|
+
# @return [RubyVersion] target Ruby version object
|
|
116
|
+
def target_ruby_version
|
|
117
|
+
RubyVersion.parse(target_ruby)
|
|
103
118
|
end
|
|
104
119
|
|
|
105
120
|
# Get list of enabled experimental features
|
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
|
|
data/lib/t_ruby/parser.rb
CHANGED
|
@@ -244,18 +244,36 @@ module TRuby
|
|
|
244
244
|
param_list = split_params(params_str)
|
|
245
245
|
|
|
246
246
|
param_list.each do |param|
|
|
247
|
-
|
|
248
|
-
|
|
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
|
|
249
266
|
end
|
|
250
267
|
|
|
251
268
|
parameters
|
|
252
269
|
end
|
|
253
270
|
|
|
254
271
|
def split_params(params_str)
|
|
255
|
-
# Handle nested generics
|
|
272
|
+
# Handle nested generics, braces, brackets
|
|
256
273
|
result = []
|
|
257
274
|
current = ""
|
|
258
275
|
depth = 0
|
|
276
|
+
brace_depth = 0
|
|
259
277
|
|
|
260
278
|
params_str.each_char do |char|
|
|
261
279
|
case char
|
|
@@ -265,8 +283,14 @@ module TRuby
|
|
|
265
283
|
when ">", "]", ")"
|
|
266
284
|
depth -= 1
|
|
267
285
|
current += char
|
|
286
|
+
when "{"
|
|
287
|
+
brace_depth += 1
|
|
288
|
+
current += char
|
|
289
|
+
when "}"
|
|
290
|
+
brace_depth -= 1
|
|
291
|
+
current += char
|
|
268
292
|
when ","
|
|
269
|
-
if depth.zero?
|
|
293
|
+
if depth.zero? && brace_depth.zero?
|
|
270
294
|
result << current.strip
|
|
271
295
|
current = ""
|
|
272
296
|
else
|
|
@@ -281,8 +305,10 @@ module TRuby
|
|
|
281
305
|
result
|
|
282
306
|
end
|
|
283
307
|
|
|
284
|
-
|
|
285
|
-
|
|
308
|
+
# 더블 스플랫 파라미터 파싱: **opts: Type
|
|
309
|
+
def parse_double_splat_parameter(param)
|
|
310
|
+
# **name: Type
|
|
311
|
+
match = param.match(/^\*\*(\w+)(?::\s*(.+?))?$/)
|
|
286
312
|
return nil unless match
|
|
287
313
|
|
|
288
314
|
param_name = match[1]
|
|
@@ -291,6 +317,155 @@ module TRuby
|
|
|
291
317
|
result = {
|
|
292
318
|
name: param_name,
|
|
293
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,
|
|
294
469
|
}
|
|
295
470
|
|
|
296
471
|
# Parse type with combinator
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
# Error raised when an unsupported Ruby version is detected
|
|
5
|
+
class UnsupportedRubyVersionError < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Value object representing a Ruby version with comparison and feature detection
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# version = RubyVersion.parse("3.4")
|
|
11
|
+
# version.supports_it_parameter? # => true
|
|
12
|
+
# version >= RubyVersion.parse("3.0") # => true
|
|
13
|
+
#
|
|
14
|
+
class RubyVersion
|
|
15
|
+
include Comparable
|
|
16
|
+
|
|
17
|
+
# Supported version range
|
|
18
|
+
MIN_VERSION = [3, 0].freeze
|
|
19
|
+
MAX_MAJOR = 4
|
|
20
|
+
|
|
21
|
+
# Version string pattern: major.minor or major.minor.patch
|
|
22
|
+
VERSION_REGEX = /\A(\d+)\.(\d+)(?:\.(\d+))?\z/
|
|
23
|
+
|
|
24
|
+
attr_reader :major, :minor, :patch
|
|
25
|
+
|
|
26
|
+
# @param major [Integer] major version number
|
|
27
|
+
# @param minor [Integer] minor version number
|
|
28
|
+
# @param patch [Integer] patch version number (default: 0)
|
|
29
|
+
def initialize(major, minor, patch = 0)
|
|
30
|
+
@major = major
|
|
31
|
+
@minor = minor
|
|
32
|
+
@patch = patch
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Parse a version string into a RubyVersion object
|
|
36
|
+
#
|
|
37
|
+
# @param version_string [String, Numeric] version string (e.g., "3.4", "3.4.1")
|
|
38
|
+
# @return [RubyVersion] parsed version object
|
|
39
|
+
# @raise [ArgumentError] if version format is invalid
|
|
40
|
+
def self.parse(version_string)
|
|
41
|
+
str = version_string.to_s
|
|
42
|
+
match = VERSION_REGEX.match(str)
|
|
43
|
+
|
|
44
|
+
raise ArgumentError, "Invalid version: #{version_string}" unless match
|
|
45
|
+
|
|
46
|
+
new(match[1].to_i, match[2].to_i, (match[3] || 0).to_i)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Get the current Ruby version from the environment
|
|
50
|
+
#
|
|
51
|
+
# @return [RubyVersion] current Ruby version
|
|
52
|
+
def self.current
|
|
53
|
+
parse(RUBY_VERSION)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Compare two versions
|
|
57
|
+
#
|
|
58
|
+
# @param other [RubyVersion] version to compare with
|
|
59
|
+
# @return [Integer] -1, 0, or 1
|
|
60
|
+
def <=>(other)
|
|
61
|
+
[major, minor, patch] <=> [other.major, other.minor, other.patch]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Convert to string representation
|
|
65
|
+
#
|
|
66
|
+
# @return [String] version string (e.g., "3.4" or "3.4.1")
|
|
67
|
+
def to_s
|
|
68
|
+
patch.zero? ? "#{major}.#{minor}" : "#{major}.#{minor}.#{patch}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Check if this version is within the supported range (3.0 ~ 4.x)
|
|
72
|
+
#
|
|
73
|
+
# @return [Boolean] true if version is supported
|
|
74
|
+
def supported?
|
|
75
|
+
self >= self.class.parse("#{MIN_VERSION[0]}.#{MIN_VERSION[1]}") && major <= MAX_MAJOR
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Validate that this version is supported, raising an error if not
|
|
79
|
+
#
|
|
80
|
+
# @return [RubyVersion] self if valid
|
|
81
|
+
# @raise [UnsupportedRubyVersionError] if version is not supported
|
|
82
|
+
def validate!
|
|
83
|
+
unless supported?
|
|
84
|
+
raise UnsupportedRubyVersionError,
|
|
85
|
+
"Ruby #{self}는 지원되지 않습니다. 지원 범위: #{MIN_VERSION.join(".")} ~ #{MAX_MAJOR}.x"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Check if this version supports the `it` implicit block parameter (Ruby 3.4+)
|
|
92
|
+
#
|
|
93
|
+
# @return [Boolean] true if `it` parameter is supported
|
|
94
|
+
def supports_it_parameter?
|
|
95
|
+
self >= self.class.parse("3.4")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Check if this version supports anonymous block forwarding `def foo(&) ... end` (Ruby 3.1+)
|
|
99
|
+
#
|
|
100
|
+
# @return [Boolean] true if anonymous block forwarding is supported
|
|
101
|
+
def supports_anonymous_block_forwarding?
|
|
102
|
+
self >= self.class.parse("3.1")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Check if numbered parameters (_1, _2, etc.) raise NameError (Ruby 4.0+)
|
|
106
|
+
#
|
|
107
|
+
# @return [Boolean] true if numbered parameters cause errors
|
|
108
|
+
def numbered_parameters_raise_error?
|
|
109
|
+
self >= self.class.parse("4.0")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
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/watcher.rb
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# listen gem is optional - only required for watch mode
|
|
4
|
+
# This allows T-Ruby core functionality to work on Ruby 4.0+ where listen/ffi may not be available
|
|
5
|
+
begin
|
|
6
|
+
require "listen"
|
|
7
|
+
LISTEN_AVAILABLE = true
|
|
8
|
+
rescue LoadError
|
|
9
|
+
LISTEN_AVAILABLE = false
|
|
10
|
+
end
|
|
4
11
|
|
|
5
12
|
module TRuby
|
|
6
13
|
class Watcher
|
|
@@ -52,6 +59,14 @@ module TRuby
|
|
|
52
59
|
end
|
|
53
60
|
|
|
54
61
|
def watch
|
|
62
|
+
unless LISTEN_AVAILABLE
|
|
63
|
+
puts colorize(:red, "Error: Watch mode requires the 'listen' gem.")
|
|
64
|
+
puts colorize(:yellow, "The 'listen' gem is not available (possibly due to Ruby 4.0+ ffi compatibility).")
|
|
65
|
+
puts colorize(:dim, "Install with: gem install listen")
|
|
66
|
+
puts colorize(:dim, "Or run without watch mode: trc")
|
|
67
|
+
exit 1
|
|
68
|
+
end
|
|
69
|
+
|
|
55
70
|
print_start_message
|
|
56
71
|
|
|
57
72
|
# Initial compilation
|
data/lib/t_ruby.rb
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "t_ruby/version"
|
|
4
4
|
require_relative "t_ruby/version_checker"
|
|
5
|
+
require_relative "t_ruby/ruby_version"
|
|
6
|
+
require_relative "t_ruby/code_emitter"
|
|
5
7
|
require_relative "t_ruby/config"
|
|
6
8
|
|
|
7
9
|
# Core infrastructure (must be loaded first)
|
|
10
|
+
require_relative "t_ruby/string_utils"
|
|
8
11
|
require_relative "t_ruby/ir"
|
|
9
12
|
require_relative "t_ruby/parser_combinator"
|
|
10
13
|
require_relative "t_ruby/smt_solver"
|
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.42
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Y. Fred Kim
|
|
@@ -10,19 +10,19 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: benchmark
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- - "
|
|
23
|
+
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '0'
|
|
26
26
|
description: t-ruby compiles .trb files with type annotations to executable Ruby (.rb)
|
|
27
27
|
and optional type signature files (.rbs)
|
|
28
28
|
email:
|
|
@@ -42,6 +42,7 @@ files:
|
|
|
42
42
|
- lib/t_ruby/bundler_integration.rb
|
|
43
43
|
- lib/t_ruby/cache.rb
|
|
44
44
|
- lib/t_ruby/cli.rb
|
|
45
|
+
- lib/t_ruby/code_emitter.rb
|
|
45
46
|
- lib/t_ruby/compiler.rb
|
|
46
47
|
- lib/t_ruby/config.rb
|
|
47
48
|
- lib/t_ruby/constraint_checker.rb
|
|
@@ -59,8 +60,10 @@ files:
|
|
|
59
60
|
- lib/t_ruby/package_manager.rb
|
|
60
61
|
- lib/t_ruby/parser.rb
|
|
61
62
|
- lib/t_ruby/parser_combinator.rb
|
|
63
|
+
- lib/t_ruby/ruby_version.rb
|
|
62
64
|
- lib/t_ruby/runtime_validator.rb
|
|
63
65
|
- lib/t_ruby/smt_solver.rb
|
|
66
|
+
- lib/t_ruby/string_utils.rb
|
|
64
67
|
- lib/t_ruby/type_alias_registry.rb
|
|
65
68
|
- lib/t_ruby/type_checker.rb
|
|
66
69
|
- lib/t_ruby/type_env.rb
|