t-ruby 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +221 -0
- data/bin/trc +6 -0
- data/lib/t_ruby/benchmark.rb +592 -0
- data/lib/t_ruby/bundler_integration.rb +569 -0
- data/lib/t_ruby/cache.rb +774 -0
- data/lib/t_ruby/cli.rb +106 -0
- data/lib/t_ruby/compiler.rb +299 -0
- data/lib/t_ruby/config.rb +53 -0
- data/lib/t_ruby/constraint_checker.rb +441 -0
- data/lib/t_ruby/declaration_generator.rb +298 -0
- data/lib/t_ruby/doc_generator.rb +474 -0
- data/lib/t_ruby/error_handler.rb +132 -0
- data/lib/t_ruby/generic_type_parser.rb +68 -0
- data/lib/t_ruby/intersection_type_parser.rb +30 -0
- data/lib/t_ruby/ir.rb +1301 -0
- data/lib/t_ruby/lsp_server.rb +994 -0
- data/lib/t_ruby/package_manager.rb +735 -0
- data/lib/t_ruby/parser.rb +245 -0
- data/lib/t_ruby/parser_combinator.rb +942 -0
- data/lib/t_ruby/rbs_generator.rb +71 -0
- data/lib/t_ruby/runtime_validator.rb +367 -0
- data/lib/t_ruby/smt_solver.rb +1076 -0
- data/lib/t_ruby/type_alias_registry.rb +102 -0
- data/lib/t_ruby/type_checker.rb +770 -0
- data/lib/t_ruby/type_erasure.rb +26 -0
- data/lib/t_ruby/type_inferencer.rb +580 -0
- data/lib/t_ruby/union_type_parser.rb +38 -0
- data/lib/t_ruby/version.rb +5 -0
- data/lib/t_ruby/watcher.rb +320 -0
- data/lib/t_ruby.rb +42 -0
- metadata +87 -0
data/lib/t_ruby/cli.rb
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
class CLI
|
|
5
|
+
HELP_TEXT = <<~HELP
|
|
6
|
+
t-ruby compiler (trc) v#{VERSION}
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
trc <file.trb> Compile a .trb file to .rb
|
|
10
|
+
trc --watch, -w Watch input files and recompile on change
|
|
11
|
+
trc --decl <file.trb> Generate .d.trb declaration file
|
|
12
|
+
trc --lsp Start LSP server (for IDE integration)
|
|
13
|
+
trc --version, -v Show version
|
|
14
|
+
trc --help, -h Show this help
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
trc hello.trb Compile hello.trb to build/hello.rb
|
|
18
|
+
trc -w Watch all .trb files in current directory
|
|
19
|
+
trc -w src/ Watch all .trb files in src/ directory
|
|
20
|
+
trc --watch hello.trb Watch specific file for changes
|
|
21
|
+
trc --decl hello.trb Generate hello.d.trb declaration file
|
|
22
|
+
trc --lsp Start language server for VS Code
|
|
23
|
+
HELP
|
|
24
|
+
|
|
25
|
+
def self.run(args)
|
|
26
|
+
new(args).run
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def initialize(args)
|
|
30
|
+
@args = args
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def run
|
|
34
|
+
if @args.empty? || @args.include?("--help") || @args.include?("-h")
|
|
35
|
+
puts HELP_TEXT
|
|
36
|
+
return
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if @args.include?("--version") || @args.include?("-v")
|
|
40
|
+
puts "trc #{VERSION}"
|
|
41
|
+
return
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if @args.include?("--lsp")
|
|
45
|
+
start_lsp_server
|
|
46
|
+
return
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if @args.include?("--watch") || @args.include?("-w")
|
|
50
|
+
start_watch_mode
|
|
51
|
+
return
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if @args.include?("--decl")
|
|
55
|
+
input_file = @args[@args.index("--decl") + 1]
|
|
56
|
+
generate_declaration(input_file)
|
|
57
|
+
return
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
input_file = @args.first
|
|
61
|
+
compile(input_file)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def start_lsp_server
|
|
67
|
+
server = LSPServer.new
|
|
68
|
+
server.run
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def start_watch_mode
|
|
72
|
+
# Get paths to watch (everything after --watch or -w flag)
|
|
73
|
+
watch_index = @args.index("--watch") || @args.index("-w")
|
|
74
|
+
paths = @args[(watch_index + 1)..]
|
|
75
|
+
|
|
76
|
+
# Default to current directory if no paths specified
|
|
77
|
+
paths = ["."] if paths.empty?
|
|
78
|
+
|
|
79
|
+
config = Config.new
|
|
80
|
+
watcher = Watcher.new(paths: paths, config: config)
|
|
81
|
+
watcher.watch
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def generate_declaration(input_file)
|
|
85
|
+
config = Config.new
|
|
86
|
+
generator = DeclarationGenerator.new
|
|
87
|
+
|
|
88
|
+
output_path = generator.generate_file(input_file, config.out_dir)
|
|
89
|
+
puts "Generated: #{input_file} -> #{output_path}"
|
|
90
|
+
rescue ArgumentError => e
|
|
91
|
+
puts "Error: #{e.message}"
|
|
92
|
+
exit 1
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def compile(input_file)
|
|
96
|
+
config = Config.new
|
|
97
|
+
compiler = Compiler.new(config)
|
|
98
|
+
|
|
99
|
+
output_path = compiler.compile(input_file)
|
|
100
|
+
puts "Compiled: #{input_file} -> #{output_path}"
|
|
101
|
+
rescue ArgumentError => e
|
|
102
|
+
puts "Error: #{e.message}"
|
|
103
|
+
exit 1
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module TRuby
|
|
6
|
+
class Compiler
|
|
7
|
+
attr_reader :declaration_loader, :use_ir, :optimizer
|
|
8
|
+
|
|
9
|
+
def initialize(config, use_ir: true, optimize: true)
|
|
10
|
+
@config = config
|
|
11
|
+
@use_ir = use_ir
|
|
12
|
+
@optimize = optimize
|
|
13
|
+
@declaration_loader = DeclarationLoader.new
|
|
14
|
+
@optimizer = IR::Optimizer.new if use_ir && optimize
|
|
15
|
+
setup_declaration_paths
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def compile(input_path)
|
|
19
|
+
unless File.exist?(input_path)
|
|
20
|
+
raise ArgumentError, "File not found: #{input_path}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
unless input_path.end_with?(".trb")
|
|
24
|
+
raise ArgumentError, "Expected .trb file, got: #{input_path}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
source = File.read(input_path)
|
|
28
|
+
|
|
29
|
+
# Parse with IR support
|
|
30
|
+
parser = Parser.new(source, use_combinator: @use_ir)
|
|
31
|
+
parse_result = parser.parse
|
|
32
|
+
|
|
33
|
+
# Transform source to Ruby code
|
|
34
|
+
output = @use_ir ? transform_with_ir(source, parser) : transform_legacy(source, parse_result)
|
|
35
|
+
|
|
36
|
+
out_dir = @config.out_dir
|
|
37
|
+
FileUtils.mkdir_p(out_dir)
|
|
38
|
+
|
|
39
|
+
base_filename = File.basename(input_path, ".trb")
|
|
40
|
+
output_path = File.join(out_dir, base_filename + ".rb")
|
|
41
|
+
|
|
42
|
+
File.write(output_path, output)
|
|
43
|
+
|
|
44
|
+
# Generate .rbs file if enabled in config
|
|
45
|
+
if @config.emit["rbs"]
|
|
46
|
+
if @use_ir && parser.ir_program
|
|
47
|
+
generate_rbs_from_ir(base_filename, out_dir, parser.ir_program)
|
|
48
|
+
else
|
|
49
|
+
generate_rbs_file(base_filename, out_dir, parse_result)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Generate .d.trb file if enabled in config
|
|
54
|
+
if @config.emit["dtrb"]
|
|
55
|
+
generate_dtrb_file(input_path, out_dir)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
output_path
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Compile to IR without generating output files
|
|
62
|
+
def compile_to_ir(input_path)
|
|
63
|
+
unless File.exist?(input_path)
|
|
64
|
+
raise ArgumentError, "File not found: #{input_path}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
source = File.read(input_path)
|
|
68
|
+
parser = Parser.new(source, use_combinator: true)
|
|
69
|
+
parser.parse
|
|
70
|
+
parser.ir_program
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Compile from IR program directly
|
|
74
|
+
def compile_from_ir(ir_program, output_path)
|
|
75
|
+
out_dir = File.dirname(output_path)
|
|
76
|
+
FileUtils.mkdir_p(out_dir)
|
|
77
|
+
|
|
78
|
+
# Optimize if enabled
|
|
79
|
+
program = ir_program
|
|
80
|
+
if @optimize && @optimizer
|
|
81
|
+
result = @optimizer.optimize(program)
|
|
82
|
+
program = result[:program]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Generate Ruby code
|
|
86
|
+
generator = IRCodeGenerator.new
|
|
87
|
+
output = generator.generate(program)
|
|
88
|
+
File.write(output_path, output)
|
|
89
|
+
|
|
90
|
+
output_path
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Load external declarations from a file
|
|
94
|
+
def load_declaration(name)
|
|
95
|
+
@declaration_loader.load(name)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Add a search path for declaration files
|
|
99
|
+
def add_declaration_path(path)
|
|
100
|
+
@declaration_loader.add_search_path(path)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Get optimization statistics (only available after IR compilation)
|
|
104
|
+
def optimization_stats
|
|
105
|
+
@optimizer&.stats
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def setup_declaration_paths
|
|
111
|
+
# Add default declaration paths
|
|
112
|
+
@declaration_loader.add_search_path(@config.out_dir)
|
|
113
|
+
@declaration_loader.add_search_path(@config.src_dir)
|
|
114
|
+
@declaration_loader.add_search_path("./types")
|
|
115
|
+
@declaration_loader.add_search_path("./lib/types")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Transform using IR system (new approach)
|
|
119
|
+
def transform_with_ir(source, parser)
|
|
120
|
+
ir_program = parser.ir_program
|
|
121
|
+
return transform_legacy(source, parser.parse) unless ir_program
|
|
122
|
+
|
|
123
|
+
# Run optimization passes if enabled
|
|
124
|
+
if @optimize && @optimizer
|
|
125
|
+
result = @optimizer.optimize(ir_program)
|
|
126
|
+
ir_program = result[:program]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Generate Ruby code using IR-aware generator
|
|
130
|
+
generator = IRCodeGenerator.new
|
|
131
|
+
generator.generate_with_source(ir_program, source)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Legacy transformation using TypeErasure (backward compatible)
|
|
135
|
+
def transform_legacy(source, parse_result)
|
|
136
|
+
if parse_result[:type] == :success
|
|
137
|
+
eraser = TypeErasure.new(source)
|
|
138
|
+
eraser.erase
|
|
139
|
+
else
|
|
140
|
+
source
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Generate RBS from IR
|
|
145
|
+
def generate_rbs_from_ir(base_filename, out_dir, ir_program)
|
|
146
|
+
generator = IR::RBSGenerator.new
|
|
147
|
+
rbs_content = generator.generate(ir_program)
|
|
148
|
+
|
|
149
|
+
rbs_path = File.join(out_dir, base_filename + ".rbs")
|
|
150
|
+
File.write(rbs_path, rbs_content) unless rbs_content.strip.empty?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Legacy RBS generation
|
|
154
|
+
def generate_rbs_file(base_filename, out_dir, parse_result)
|
|
155
|
+
generator = RBSGenerator.new
|
|
156
|
+
rbs_content = generator.generate(
|
|
157
|
+
parse_result[:functions] || [],
|
|
158
|
+
parse_result[:type_aliases] || []
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
rbs_path = File.join(out_dir, base_filename + ".rbs")
|
|
162
|
+
File.write(rbs_path, rbs_content) unless rbs_content.empty?
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def generate_dtrb_file(input_path, out_dir)
|
|
166
|
+
generator = DeclarationGenerator.new
|
|
167
|
+
generator.generate_file(input_path, out_dir)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# IR-aware code generator for source-preserving transformation
|
|
172
|
+
class IRCodeGenerator
|
|
173
|
+
def initialize
|
|
174
|
+
@output = []
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Generate Ruby code from IR program
|
|
178
|
+
def generate(program)
|
|
179
|
+
generator = IR::CodeGenerator.new
|
|
180
|
+
generator.generate(program)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Generate Ruby code while preserving source structure
|
|
184
|
+
def generate_with_source(program, source)
|
|
185
|
+
result = source.dup
|
|
186
|
+
|
|
187
|
+
# Collect type alias names to remove
|
|
188
|
+
type_alias_names = program.declarations
|
|
189
|
+
.select { |d| d.is_a?(IR::TypeAlias) }
|
|
190
|
+
.map(&:name)
|
|
191
|
+
|
|
192
|
+
# Collect interface names to remove
|
|
193
|
+
interface_names = program.declarations
|
|
194
|
+
.select { |d| d.is_a?(IR::Interface) }
|
|
195
|
+
.map(&:name)
|
|
196
|
+
|
|
197
|
+
# Remove type alias definitions
|
|
198
|
+
result = result.gsub(/^\s*type\s+\w+\s*=\s*.+?$\n?/, '')
|
|
199
|
+
|
|
200
|
+
# Remove interface definitions (multi-line)
|
|
201
|
+
result = result.gsub(/^\s*interface\s+\w+.*?^\s*end\s*$/m, '')
|
|
202
|
+
|
|
203
|
+
# Remove parameter type annotations using IR info
|
|
204
|
+
# Enhanced: Handle complex types (generics, unions, etc.)
|
|
205
|
+
result = erase_parameter_types(result)
|
|
206
|
+
|
|
207
|
+
# Remove return type annotations
|
|
208
|
+
result = erase_return_types(result)
|
|
209
|
+
|
|
210
|
+
# Clean up extra blank lines
|
|
211
|
+
result = result.gsub(/\n{3,}/, "\n\n")
|
|
212
|
+
|
|
213
|
+
result
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
private
|
|
217
|
+
|
|
218
|
+
# Erase parameter type annotations
|
|
219
|
+
def erase_parameter_types(source)
|
|
220
|
+
result = source.dup
|
|
221
|
+
|
|
222
|
+
# Match function definitions and remove type annotations from parameters
|
|
223
|
+
result.gsub!(/^(\s*def\s+\w+\s*\()([^)]+)(\)\s*)(?::\s*[^\n]+)?(\s*$)/) do |match|
|
|
224
|
+
indent = $1
|
|
225
|
+
params = $2
|
|
226
|
+
close_paren = $3
|
|
227
|
+
ending = $4
|
|
228
|
+
|
|
229
|
+
# Remove type annotations from each parameter
|
|
230
|
+
cleaned_params = remove_param_types(params)
|
|
231
|
+
|
|
232
|
+
"#{indent}#{cleaned_params}#{close_paren.rstrip}#{ending}"
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
result
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Remove type annotations from parameter list
|
|
239
|
+
def remove_param_types(params_str)
|
|
240
|
+
return params_str if params_str.strip.empty?
|
|
241
|
+
|
|
242
|
+
params = []
|
|
243
|
+
current = ""
|
|
244
|
+
depth = 0
|
|
245
|
+
|
|
246
|
+
params_str.each_char do |char|
|
|
247
|
+
case char
|
|
248
|
+
when "<", "[", "("
|
|
249
|
+
depth += 1
|
|
250
|
+
current += char
|
|
251
|
+
when ">", "]", ")"
|
|
252
|
+
depth -= 1
|
|
253
|
+
current += char
|
|
254
|
+
when ","
|
|
255
|
+
if depth == 0
|
|
256
|
+
params << clean_param(current.strip)
|
|
257
|
+
current = ""
|
|
258
|
+
else
|
|
259
|
+
current += char
|
|
260
|
+
end
|
|
261
|
+
else
|
|
262
|
+
current += char
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
params << clean_param(current.strip) unless current.empty?
|
|
267
|
+
params.join(", ")
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Clean a single parameter (remove type annotation)
|
|
271
|
+
def clean_param(param)
|
|
272
|
+
# Match: name: Type or name
|
|
273
|
+
if match = param.match(/^(\w+)\s*:/)
|
|
274
|
+
match[1]
|
|
275
|
+
else
|
|
276
|
+
param
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Erase return type annotations
|
|
281
|
+
def erase_return_types(source)
|
|
282
|
+
result = source.dup
|
|
283
|
+
|
|
284
|
+
# Remove return type: ): Type or ): Type<Foo> etc.
|
|
285
|
+
result.gsub!(/\)\s*:\s*[^\n]+?(?=\s*$)/m) do |match|
|
|
286
|
+
")"
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
result
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Legacy Compiler for backward compatibility (no IR)
|
|
294
|
+
class LegacyCompiler < Compiler
|
|
295
|
+
def initialize(config)
|
|
296
|
+
super(config, use_ir: false, optimize: false)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module TRuby
|
|
6
|
+
class Config
|
|
7
|
+
DEFAULT_CONFIG = {
|
|
8
|
+
"emit" => {
|
|
9
|
+
"rb" => true,
|
|
10
|
+
"rbs" => false,
|
|
11
|
+
"dtrb" => false
|
|
12
|
+
},
|
|
13
|
+
"paths" => {
|
|
14
|
+
"src" => "./src",
|
|
15
|
+
"out" => "./build"
|
|
16
|
+
},
|
|
17
|
+
"strict" => {
|
|
18
|
+
"rbs_compat" => true,
|
|
19
|
+
"null_safety" => false,
|
|
20
|
+
"inference" => "basic"
|
|
21
|
+
}
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
attr_reader :emit, :paths, :strict
|
|
25
|
+
|
|
26
|
+
def initialize(config_path = nil)
|
|
27
|
+
config = load_config(config_path)
|
|
28
|
+
@emit = config["emit"]
|
|
29
|
+
@paths = config["paths"]
|
|
30
|
+
@strict = config["strict"]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def out_dir
|
|
34
|
+
@paths["out"]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def src_dir
|
|
38
|
+
@paths["src"]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def load_config(config_path)
|
|
44
|
+
if config_path && File.exist?(config_path)
|
|
45
|
+
YAML.safe_load_file(config_path, permitted_classes: [Symbol])
|
|
46
|
+
elsif File.exist?(".trb.yml")
|
|
47
|
+
YAML.safe_load_file(".trb.yml", permitted_classes: [Symbol])
|
|
48
|
+
else
|
|
49
|
+
DEFAULT_CONFIG.dup
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|