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.
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