t-ruby 0.0.41 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a7c7a93e1498a150b541ea7f7838c08435a33ead72774b7e4c0b8ead951e9d3
4
- data.tar.gz: 1fbfb9ea6b276e130ef04891f39d691e362d5352dc4f0209b00009105f13c3a0
3
+ metadata.gz: 75019794a577853cf5a7e704febef3868a1ddd06776e43f50dc684d7dde87e3c
4
+ data.tar.gz: 89a255f7e7b68e2becd6314be09470424d5f418324c40bcc77a7311e6006d9f0
5
5
  SHA512:
6
- metadata.gz: 82cbc96204194f51b65f484aeda25383e285ce902c325e269766e77871e9dccacc548111e864ba2bfb27ca48fc69c7e22c5a0b1eee8e7c25e4763417ffe2c8fe
7
- data.tar.gz: ed5f8ad72344e624e8a95d1e9a48078e0f6679d914f978974e0117b75bbc11ce2fbe08ece39360b20ce777ef5c5c333f37376f216272c1e6aa983eb18585fc92
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: "3.0"
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
@@ -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
- def initialize
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
@@ -545,6 +552,14 @@ module TRuby
545
552
  param = param.strip
546
553
  return nil if param.empty?
547
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
+
548
563
  # 1. 더블 스플랫: **name: Type -> **name
549
564
  if param.start_with?("**")
550
565
  match = param.match(/^\*\*(\w+)(?::\s*.+)?$/)
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" => "3.0",
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
- # @return [String] target Ruby version (e.g., "3.0", "3.2")
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
- (@compiler["target_ruby"] || "3.0").to_s
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TRuby
4
- VERSION = "0.0.41"
4
+ VERSION = "0.0.42"
5
5
  end
@@ -1,6 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "listen"
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,6 +2,8 @@
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)
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.41
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: listen
13
+ name: benchmark
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - "~>"
16
+ - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '3.8'
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: '3.8'
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,6 +60,7 @@ 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
64
66
  - lib/t_ruby/string_utils.rb