Class: TRuby::Compiler

Inherits:
Object
  • Object
show all
Defined in:
lib/t_ruby/compiler.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = nil, optimize: true) ⇒ Compiler

Returns a new instance of Compiler.



16
17
18
19
20
21
22
23
# File 'lib/t_ruby/compiler.rb', line 16

def initialize(config = nil, optimize: true)
  @config = config || Config.new
  @optimize = optimize
  @declaration_loader = DeclarationLoader.new
  @optimizer = IR::Optimizer.new if optimize
  @type_inferrer = ASTTypeInferrer.new if type_check?
  setup_declaration_paths if @config
end

Instance Attribute Details

#declaration_loaderObject (readonly)

Returns the value of attribute declaration_loader.



14
15
16
# File 'lib/t_ruby/compiler.rb', line 14

def declaration_loader
  @declaration_loader
end

#optimizerObject (readonly)

Returns the value of attribute optimizer.



14
15
16
# File 'lib/t_ruby/compiler.rb', line 14

def optimizer
  @optimizer
end

Instance Method Details

#add_declaration_path(path) ⇒ Object

Add a search path for declaration files



157
158
159
# File 'lib/t_ruby/compiler.rb', line 157

def add_declaration_path(path)
  @declaration_loader.add_search_path(path)
end

#compile(input_path) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/t_ruby/compiler.rb', line 29

def compile(input_path)
  unless File.exist?(input_path)
    raise ArgumentError, "File not found: #{input_path}"
  end

  # Handle .rb files separately
  if input_path.end_with?(".rb")
    return copy_ruby_file(input_path)
  end

  unless input_path.end_with?(".trb")
    raise ArgumentError, "Expected .trb or .rb file, got: #{input_path}"
  end

  source = File.read(input_path)

  # Parse with IR support
  parser = Parser.new(source)
  parser.parse

  # Run type checking if enabled
  if type_check? && parser.ir_program
    check_types(parser.ir_program, input_path)
  end

  # Transform source to Ruby code
  output = transform_with_ir(source, parser)

  # Compute output path (respects preserve_structure setting)
  output_path = compute_output_path(input_path, @config.ruby_dir, ".rb")
  FileUtils.mkdir_p(File.dirname(output_path))

  File.write(output_path, output)

  # Generate .rbs file if enabled in config
  if @config.compiler["generate_rbs"]
    rbs_path = compute_output_path(input_path, @config.rbs_dir, ".rbs")
    FileUtils.mkdir_p(File.dirname(rbs_path))
    generate_rbs_from_ir_to_path(rbs_path, parser.ir_program)
  end

  # Generate .d.trb file if enabled in config (legacy support)
  # TODO: Add compiler.generate_dtrb option in future
  if @config.compiler.key?("generate_dtrb") && @config.compiler["generate_dtrb"]
    generate_dtrb_file(input_path, @config.ruby_dir)
  end

  output_path
end

#compile_from_ir(ir_program, output_path) ⇒ Object

Compile from IR program directly



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/t_ruby/compiler.rb', line 132

def compile_from_ir(ir_program, output_path)
  out_dir = File.dirname(output_path)
  FileUtils.mkdir_p(out_dir)

  # Optimize if enabled
  program = ir_program
  if @optimize && @optimizer
    result = @optimizer.optimize(program)
    program = result[:program]
  end

  # Generate Ruby code
  generator = IRCodeGenerator.new
  output = generator.generate(program)
  File.write(output_path, output)

  output_path
end

#compile_string(source, options = {}) ⇒ Hash

Compile T-Ruby source code from a string (useful for WASM/playground)

Parameters:

  • T-Ruby source code

  • (defaults to: {})

    Options for compilation

Options Hash (options):

  • :rbs (Boolean)

    Whether to generate RBS output (default: true)

Returns:

  • Result with :ruby, :rbs, :errors keys



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/t_ruby/compiler.rb', line 84

def compile_string(source, options = {})
  generate_rbs = options.fetch(:rbs, true)

  parser = Parser.new(source)
  parser.parse

  # Transform source to Ruby code
  ruby_output = transform_with_ir(source, parser)

  # Generate RBS if requested
  rbs_output = ""
  if generate_rbs && parser.ir_program
    generator = IR::RBSGenerator.new
    rbs_output = generator.generate(parser.ir_program)
  end

  {
    ruby: ruby_output,
    rbs: rbs_output,
    errors: [],
  }
rescue ParseError => e
  {
    ruby: "",
    rbs: "",
    errors: [e.message],
  }
rescue StandardError => e
  {
    ruby: "",
    rbs: "",
    errors: ["Compilation error: #{e.message}"],
  }
end

#compile_to_ir(input_path) ⇒ Object

Compile to IR without generating output files



120
121
122
123
124
125
126
127
128
129
# File 'lib/t_ruby/compiler.rb', line 120

def compile_to_ir(input_path)
  unless File.exist?(input_path)
    raise ArgumentError, "File not found: #{input_path}"
  end

  source = File.read(input_path)
  parser = Parser.new(source)
  parser.parse
  parser.ir_program
end

#compute_output_path(input_path, output_dir, new_extension) ⇒ String

Compute output path for a source file

Parameters:

  • path to source file

  • base output directory

  • new file extension (e.g., “.rb”, “.rbs”)

Returns:

  • computed output path (always preserves directory structure)



171
172
173
174
175
# File 'lib/t_ruby/compiler.rb', line 171

def compute_output_path(input_path, output_dir, new_extension)
  relative = compute_relative_path(input_path)
  base = relative.sub(/\.[^.]+$/, new_extension)
  File.join(output_dir, base)
end

#compute_relative_path(input_path) ⇒ String

Compute relative path from source directory

Parameters:

  • path to source file

Returns:

  • relative path preserving directory structure



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/t_ruby/compiler.rb', line 180

def compute_relative_path(input_path)
  # Use realpath to resolve symlinks (e.g., /var vs /private/var on macOS)
  absolute_input = resolve_path(input_path)
  source_dirs = @config.source_include

  # Check if file is inside any source_include directory
  if source_dirs.size > 1
    # Multiple source directories: include the source dir name in output
    # src/models/user.trb → src/models/user.trb
    source_dirs.each do |src_dir|
      absolute_src = resolve_path(src_dir)
      next unless absolute_input.start_with?("#{absolute_src}/")

      # Return path relative to parent of source dir (includes src dir name)
      parent_of_src = File.dirname(absolute_src)
      return absolute_input.sub("#{parent_of_src}/", "")
    end
  else
    # Single source directory: exclude the source dir name from output
    # src/models/user.trb → models/user.trb
    src_dir = source_dirs.first
    if src_dir
      absolute_src = resolve_path(src_dir)
      if absolute_input.start_with?("#{absolute_src}/")
        return absolute_input.sub("#{absolute_src}/", "")
      end
    end
  end

  # File outside source directories: use path relative to current working directory
  # external/foo.trb → external/foo.trb
  cwd = resolve_path(".")
  if absolute_input.start_with?("#{cwd}/")
    return absolute_input.sub("#{cwd}/", "")
  end

  # Absolute path from outside cwd: use basename only
  File.basename(input_path)
end

#load_declaration(name) ⇒ Object

Load external declarations from a file



152
153
154
# File 'lib/t_ruby/compiler.rb', line 152

def load_declaration(name)
  @declaration_loader.load(name)
end

#optimization_statsObject

Get optimization statistics (only available after IR compilation)



162
163
164
# File 'lib/t_ruby/compiler.rb', line 162

def optimization_stats
  @optimizer&.stats
end

#type_check?Boolean

Returns:



25
26
27
# File 'lib/t_ruby/compiler.rb', line 25

def type_check?
  @config.type_check?
end