Skip to content

Commit f1e6ba6

Browse files
authored
feat: add direct .trb file execution without intermediate files (#37)
* feat: add direct .trb file execution without intermediate files (#26) Implements t-ruby command and trc run subcommand for running .trb files directly in memory without generating .rb or .rbs files, similar to Node.js's --experimental-strip-types. - Add bin/t-ruby executable as main entry point - Add lib/t_ruby/runner.rb with Thor-based CLI and Runner class - Add trc run command that delegates to t-ruby via exec - Add thor gem dependency for CLI implementation * fix: restore listen gem in Gemfile.lock for CI compatibility Remove conditional Ruby version check for listen gem to ensure Gemfile.lock contains the dependency for all Ruby versions. * style: use %w[] for word array in gemspec * test: add tests for Runner and RunnerCLI classes - Add runner_spec.rb with tests for TRuby::Runner and TRuby::RunnerCLI - Add CLI tests for 'trc run' command delegation - Test file execution, ARGV passing, compile errors, and help/version
1 parent c6a520f commit f1e6ba6

File tree

9 files changed

+517
-6
lines changed

9 files changed

+517
-6
lines changed

Gemfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ group :development, :test do
1313
gem "simplecov", "~> 0.22.0", require: false
1414
gem "simplecov-lcov", "~> 0.8.0", require: false
1515

16-
# listen gem for watch mode - skip on Ruby 4.0+ due to ffi compatibility
17-
gem "listen", "~> 3.8" if RUBY_VERSION < "4.0"
16+
# listen gem for watch mode
17+
# Note: May have compatibility issues on Ruby 4.0+ due to ffi
18+
gem "listen", "~> 3.8"
1819
end

Gemfile.lock

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ PATH
33
specs:
44
t-ruby (0.0.43)
55
benchmark
6+
thor (~> 1.0)
67

78
GEM
89
remote: https://rubygems.org/
@@ -11,8 +12,8 @@ GEM
1112
benchmark (0.5.0)
1213
diff-lcs (1.6.2)
1314
docile (1.4.1)
14-
ffi (1.17.2)
15-
ffi (1.17.2-x86_64-linux-gnu)
15+
ffi (1.17.3)
16+
ffi (1.17.3-x86_64-linux-gnu)
1617
json (2.17.1)
1718
language_server-protocol (3.17.0.5)
1819
lint_roller (1.1.0)
@@ -71,9 +72,10 @@ GEM
7172
simplecov-html (0.13.2)
7273
simplecov-lcov (0.8.0)
7374
simplecov_json_formatter (0.1.4)
75+
thor (1.4.0)
7476
unicode-display_width (3.2.0)
7577
unicode-emoji (~> 4.1)
76-
unicode-emoji (4.1.0)
78+
unicode-emoji (4.2.0)
7779

7880
PLATFORMS
7981
ruby

bin/t-ruby

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require_relative "../lib/t_ruby"
5+
6+
TRuby::RunnerCLI.start(ARGV)

lib/t_ruby.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
require_relative "t_ruby/compiler"
3030
require_relative "t_ruby/lsp_server"
3131
require_relative "t_ruby/watcher"
32+
require_relative "t_ruby/runner"
3233
require_relative "t_ruby/cli"
3334

3435
# Milestone 4: Advanced Features

lib/t_ruby/cli.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class CLI
1313
trc --watch, -w Watch input files and recompile on change
1414
trc --decl <file.trb> Generate .d.trb declaration file
1515
trc --lsp Start LSP server (for IDE integration)
16+
trc run <file.trb> Run a .trb file directly (delegates to t-ruby)
1617
trc update Update t-ruby to the latest version
1718
trc --version, -v Show version (and check for updates)
1819
trc --help, -h Show this help
@@ -27,6 +28,7 @@ class CLI
2728
trc --watch hello.trb Watch specific file for changes
2829
trc --decl hello.trb Generate hello.d.trb declaration file
2930
trc --lsp Start language server for VS Code
31+
trc run hello.trb Run hello.trb directly without compilation
3032
HELP
3133

3234
def self.run(args)
@@ -64,6 +66,11 @@ def run
6466
return
6567
end
6668

69+
if @args.first == "run"
70+
run_direct
71+
return
72+
end
73+
6774
if @args.include?("--watch") || @args.include?("-w")
6875
start_watch_mode
6976
return
@@ -186,6 +193,16 @@ def start_lsp_server
186193
server.run
187194
end
188195

196+
def run_direct
197+
remaining_args = @args[1..] || []
198+
199+
# Find t-ruby executable path
200+
t_ruby_bin = File.expand_path("../../bin/t-ruby", __dir__)
201+
202+
# Execute t-ruby (replaces current process)
203+
exec(t_ruby_bin, *remaining_args)
204+
end
205+
189206
def start_watch_mode
190207
# Get paths to watch (everything after --watch or -w flag)
191208
watch_index = @args.index("--watch") || @args.index("-w")

lib/t_ruby/runner.rb

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# frozen_string_literal: true
2+
3+
require "thor"
4+
5+
module TRuby
6+
# Thor-based CLI for t-ruby command
7+
# Runs .trb files directly without generating intermediate files
8+
class RunnerCLI < Thor
9+
def self.exit_on_failure?
10+
true
11+
end
12+
13+
# Override Thor's default behavior to treat unknown arguments as the file to run
14+
def self.start(given_args = ARGV, _config = {})
15+
# Handle version flag
16+
if given_args.include?("--version") || given_args.include?("-v")
17+
new.version
18+
return
19+
end
20+
21+
# Handle help flag or no arguments
22+
if given_args.empty? || given_args.include?("--help") || given_args.include?("-h")
23+
new.help
24+
return
25+
end
26+
27+
# Treat first argument as file, rest as script arguments
28+
file = given_args.first
29+
args = given_args[1..] || []
30+
31+
runner = Runner.new
32+
runner.run_file(file, args)
33+
end
34+
35+
desc "FILE [ARGS...]", "Run a .trb file directly without generating files"
36+
def run_file(file, *args)
37+
runner = Runner.new
38+
runner.run_file(file, args)
39+
end
40+
41+
map %w[--version -v] => :version
42+
desc "--version, -v", "Show version"
43+
def version
44+
puts "t-ruby #{VERSION}"
45+
end
46+
47+
desc "--help, -h", "Show help"
48+
def help
49+
puts <<~HELP
50+
t-ruby v#{VERSION} - Run T-Ruby files directly
51+
52+
Usage:
53+
t-ruby <file.trb> Run a .trb file directly
54+
t-ruby <file.trb> [args...] Run with arguments passed to the script
55+
t-ruby --version, -v Show version
56+
t-ruby --help, -h Show this help
57+
58+
Examples:
59+
t-ruby hello.trb Run hello.trb
60+
t-ruby server.trb 8080 Run with argument 8080
61+
t-ruby script.trb foo bar Run with multiple arguments
62+
63+
Notes:
64+
- No .rb or .rbs files are generated
65+
- Type annotations are stripped at runtime
66+
- Arguments after the file are passed to ARGV
67+
HELP
68+
end
69+
end
70+
71+
# Runner class - executes T-Ruby code directly
72+
# Can be used as a library or through RunnerCLI
73+
class Runner
74+
def initialize(config = nil)
75+
@config = config || Config.new
76+
@compiler = Compiler.new(@config)
77+
end
78+
79+
# Run a .trb file directly
80+
# @param input_path [String] Path to the .trb file
81+
# @param argv [Array<String>] Arguments to pass to the script via ARGV
82+
def run_file(input_path, argv = [])
83+
unless File.exist?(input_path)
84+
warn "Error: File not found: #{input_path}"
85+
exit 1
86+
end
87+
88+
source = File.read(input_path)
89+
result = @compiler.compile_string(source)
90+
91+
if result[:errors].any?
92+
result[:errors].each { |e| warn e }
93+
exit 1
94+
end
95+
96+
execute_ruby(result[:ruby], input_path, argv)
97+
end
98+
99+
# Run T-Ruby source code from a string
100+
# @param source [String] T-Ruby source code
101+
# @param filename [String] Filename for error reporting
102+
# @param argv [Array<String>] Arguments to pass via ARGV
103+
# @return [Boolean] true if execution succeeded
104+
def run_string(source, filename: "(t-ruby)", argv: [])
105+
result = @compiler.compile_string(source)
106+
107+
if result[:errors].any?
108+
result[:errors].each { |e| warn e }
109+
return false
110+
end
111+
112+
execute_ruby(result[:ruby], filename, argv)
113+
true
114+
end
115+
116+
private
117+
118+
# Execute Ruby code with proper script environment
119+
# @param ruby_code [String] Ruby code to execute
120+
# @param filename [String] Script filename (for $0 and stack traces)
121+
# @param argv [Array<String>] Script arguments
122+
def execute_ruby(ruby_code, filename, argv)
123+
# Set up script environment
124+
ARGV.replace(argv)
125+
$0 = filename
126+
127+
# Execute using eval with filename and line number preserved
128+
# This ensures stack traces point to the original .trb file
129+
TOPLEVEL_BINDING.eval(ruby_code, filename, 1)
130+
end
131+
end
132+
end

spec/t_ruby/cli_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,37 @@
478478
end
479479
end
480480

481+
describe "#run with run command" do
482+
it "delegates to t-ruby executable via exec" do
483+
cli = TRuby::CLI.new(["run", "test.trb"])
484+
485+
# exec replaces the process, so we need to mock it
486+
allow(cli).to receive(:exec)
487+
488+
cli.run
489+
490+
expect(cli).to have_received(:exec).with(
491+
a_string_ending_with("bin/t-ruby"),
492+
"test.trb"
493+
)
494+
end
495+
496+
it "passes additional arguments to t-ruby" do
497+
cli = TRuby::CLI.new(["run", "script.trb", "arg1", "arg2"])
498+
499+
allow(cli).to receive(:exec)
500+
501+
cli.run
502+
503+
expect(cli).to have_received(:exec).with(
504+
a_string_ending_with("bin/t-ruby"),
505+
"script.trb",
506+
"arg1",
507+
"arg2"
508+
)
509+
end
510+
end
511+
481512
describe "#run with --watch flag" do
482513
it "starts watch mode with default path" do
483514
watcher = instance_double(TRuby::Watcher)

0 commit comments

Comments
 (0)