konpeito 0.2.4 → 0.3.0
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 +4 -4
- data/.rubocop.yml +645 -0
- data/CHANGELOG.md +37 -0
- data/Justfile +107 -0
- data/README.md +143 -43
- data/konpeito.gemspec +3 -2
- data/lib/konpeito/cli/build_command.rb +21 -3
- data/lib/konpeito/cli/completion_command.rb +298 -0
- data/lib/konpeito/cli/deps_command.rb +129 -21
- data/lib/konpeito/cli/fmt_command.rb +24 -132
- data/lib/konpeito/cli/run_command.rb +29 -3
- data/lib/konpeito/cli.rb +45 -14
- data/lib/konpeito/codegen/builtin_methods.rb +16 -0
- data/lib/konpeito/codegen/cruby_backend.rb +76 -6
- data/lib/konpeito/codegen/jvm_generator.rb +100 -9
- data/lib/konpeito/codegen/llvm_generator.rb +907 -195
- data/lib/konpeito/dependency_resolver.rb +32 -9
- data/lib/konpeito/hir/builder.rb +369 -57
- data/lib/konpeito/hir/nodes.rb +25 -5
- data/lib/konpeito/type_checker/rbs_loader.rb +3 -2
- data/lib/konpeito/ui/app.rb +1 -1
- data/lib/konpeito/version.rb +1 -1
- data/lib/konpeito.rb +0 -7
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +32 -0
- metadata +6 -23
- data/lib/konpeito/cli/lsp_command.rb +0 -40
- data/lib/konpeito/formatter/formatter.rb +0 -1214
- data/lib/konpeito/lsp/document_manager.rb +0 -820
- data/lib/konpeito/lsp/server.rb +0 -183
- data/lib/konpeito/lsp/transport.rb +0 -38
- data/test_native_array.rb +0 -172
- data/test_native_array_class.rb +0 -197
- data/test_native_class.rb +0 -151
|
@@ -1,60 +1,46 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../formatter/formatter"
|
|
4
|
-
|
|
5
3
|
module Konpeito
|
|
6
4
|
module Commands
|
|
7
|
-
# Fmt command - format Ruby source files
|
|
5
|
+
# Fmt command - format Ruby source files via RuboCop
|
|
8
6
|
class FmtCommand < BaseCommand
|
|
9
7
|
def self.command_name
|
|
10
8
|
"fmt"
|
|
11
9
|
end
|
|
12
10
|
|
|
13
11
|
def self.description
|
|
14
|
-
"Format Ruby source files"
|
|
12
|
+
"Format Ruby source files (via RuboCop)"
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
def run
|
|
18
16
|
parse_options!
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
rubocop_args = build_rubocop_args
|
|
19
|
+
success = system("bundle", "exec", "rubocop", *rubocop_args)
|
|
20
|
+
exit 1 unless success
|
|
21
|
+
end
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
end
|
|
23
|
+
# Visible for testing
|
|
24
|
+
def build_rubocop_args
|
|
25
|
+
rubocop_args = []
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@files.each do |file|
|
|
34
|
-
result = format_file(file)
|
|
35
|
-
case result
|
|
36
|
-
when :changed
|
|
37
|
-
changed += 1
|
|
38
|
-
when :unchanged
|
|
39
|
-
unchanged += 1
|
|
40
|
-
when :error
|
|
41
|
-
errored += 1
|
|
42
|
-
end
|
|
27
|
+
if options[:check]
|
|
28
|
+
# Check mode: report violations without modifying files
|
|
29
|
+
else
|
|
30
|
+
# Default: auto-correct
|
|
31
|
+
rubocop_args << "-A"
|
|
43
32
|
end
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
parts << "#{changed} changed" if changed > 0
|
|
48
|
-
parts << "#{unchanged} unchanged" if unchanged > 0
|
|
49
|
-
parts << "#{errored} error(s)" if errored > 0
|
|
50
|
-
emit("Finished", parts.join(", "))
|
|
51
|
-
end
|
|
34
|
+
rubocop_args << "--format" << "quiet" if options[:quiet]
|
|
35
|
+
rubocop_args << "--no-color" unless options[:color]
|
|
52
36
|
|
|
53
|
-
|
|
54
|
-
|
|
37
|
+
options[:exclude].each do |pattern|
|
|
38
|
+
rubocop_args << "--exclude" << pattern
|
|
55
39
|
end
|
|
56
40
|
|
|
57
|
-
|
|
41
|
+
rubocop_args.concat(args) unless args.empty?
|
|
42
|
+
|
|
43
|
+
rubocop_args
|
|
58
44
|
end
|
|
59
45
|
|
|
60
46
|
protected
|
|
@@ -64,7 +50,6 @@ module Konpeito
|
|
|
64
50
|
verbose: false,
|
|
65
51
|
color: $stderr.tty?,
|
|
66
52
|
check: false,
|
|
67
|
-
diff: false,
|
|
68
53
|
quiet: false,
|
|
69
54
|
exclude: []
|
|
70
55
|
}
|
|
@@ -75,9 +60,8 @@ module Konpeito
|
|
|
75
60
|
options[:check] = true
|
|
76
61
|
end
|
|
77
62
|
|
|
78
|
-
opts.on("--diff", "
|
|
79
|
-
options[:
|
|
80
|
-
options[:check] = true # diff implies check
|
|
63
|
+
opts.on("--diff", "Check formatting without modifying files (alias for --check)") do
|
|
64
|
+
options[:check] = true
|
|
81
65
|
end
|
|
82
66
|
|
|
83
67
|
opts.on("-q", "--quiet", "Suppress non-error output") do
|
|
@@ -99,101 +83,9 @@ module Konpeito
|
|
|
99
83
|
konpeito fmt Format all Ruby files
|
|
100
84
|
konpeito fmt src/main.rb Format specific file
|
|
101
85
|
konpeito fmt --check Check without modifying
|
|
102
|
-
konpeito fmt --diff
|
|
86
|
+
konpeito fmt --diff Check without modifying (alias)
|
|
103
87
|
BANNER
|
|
104
88
|
end
|
|
105
|
-
|
|
106
|
-
private
|
|
107
|
-
|
|
108
|
-
def find_ruby_files
|
|
109
|
-
default_exclude = ["vendor/", ".bundle/", ".konpeito_cache/", "tools/"]
|
|
110
|
-
all_exclude = default_exclude + options[:exclude]
|
|
111
|
-
|
|
112
|
-
Dir.glob("**/*.rb").reject do |f|
|
|
113
|
-
all_exclude.any? { |pat| f.start_with?(pat) || File.fnmatch?(pat, f) }
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def format_file(file)
|
|
118
|
-
unless File.exist?(file)
|
|
119
|
-
$stderr.puts "Warning: #{file} not found, skipping"
|
|
120
|
-
return :error
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
source = File.read(file)
|
|
124
|
-
formatter = Formatter::Formatter.new(source, filepath: file)
|
|
125
|
-
formatted = formatter.format
|
|
126
|
-
|
|
127
|
-
if source == formatted
|
|
128
|
-
return :unchanged
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
if options[:diff]
|
|
132
|
-
show_diff(file, source, formatted)
|
|
133
|
-
emit("Formatted", file) unless options[:quiet]
|
|
134
|
-
return :changed
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
if options[:check]
|
|
138
|
-
emit_warn("Unformatted", file) unless options[:quiet]
|
|
139
|
-
return :changed
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# Write formatted content
|
|
143
|
-
File.write(file, formatted)
|
|
144
|
-
emit("Formatted", file) unless options[:quiet]
|
|
145
|
-
:changed
|
|
146
|
-
rescue => e
|
|
147
|
-
$stderr.puts "Error formatting #{file}: #{e.message}"
|
|
148
|
-
:error
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def show_diff(file, original, formatted)
|
|
152
|
-
orig_lines = original.lines
|
|
153
|
-
fmt_lines = formatted.lines
|
|
154
|
-
|
|
155
|
-
# Simple unified diff
|
|
156
|
-
$stdout.puts "--- #{file}"
|
|
157
|
-
$stdout.puts "+++ #{file} (formatted)"
|
|
158
|
-
|
|
159
|
-
# Find differing regions
|
|
160
|
-
max_len = [orig_lines.size, fmt_lines.size].max
|
|
161
|
-
i = 0
|
|
162
|
-
while i < max_len
|
|
163
|
-
if orig_lines[i] != fmt_lines[i]
|
|
164
|
-
# Find the end of this diff hunk
|
|
165
|
-
hunk_start = i
|
|
166
|
-
while i < max_len && orig_lines[i] != fmt_lines[i]
|
|
167
|
-
i += 1
|
|
168
|
-
end
|
|
169
|
-
hunk_end = i
|
|
170
|
-
|
|
171
|
-
# Context
|
|
172
|
-
ctx_start = [hunk_start - 3, 0].max
|
|
173
|
-
ctx_end = [hunk_end + 3, max_len].min
|
|
174
|
-
|
|
175
|
-
$stdout.puts "@@ -#{ctx_start + 1},#{hunk_end - ctx_start} +#{ctx_start + 1},#{hunk_end - ctx_start} @@"
|
|
176
|
-
|
|
177
|
-
(ctx_start...ctx_end).each do |j|
|
|
178
|
-
if j >= hunk_start && j < hunk_end
|
|
179
|
-
if j < orig_lines.size && orig_lines[j]
|
|
180
|
-
line = orig_lines[j].chomp
|
|
181
|
-
$stdout.puts options[:color] ? "\e[31m-#{line}\e[0m" : "-#{line}"
|
|
182
|
-
end
|
|
183
|
-
if j < fmt_lines.size && fmt_lines[j]
|
|
184
|
-
line = fmt_lines[j].chomp
|
|
185
|
-
$stdout.puts options[:color] ? "\e[32m+#{line}\e[0m" : "+#{line}"
|
|
186
|
-
end
|
|
187
|
-
else
|
|
188
|
-
line = (orig_lines[j] || fmt_lines[j] || "").chomp
|
|
189
|
-
$stdout.puts " #{line}"
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
else
|
|
193
|
-
i += 1
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
89
|
end
|
|
198
90
|
end
|
|
199
91
|
end
|
|
@@ -48,6 +48,7 @@ module Konpeito
|
|
|
48
48
|
classpath: nil,
|
|
49
49
|
rbs_paths: config.rbs_paths.dup,
|
|
50
50
|
require_paths: config.require_paths.dup,
|
|
51
|
+
inline_rbs: false,
|
|
51
52
|
lib: false
|
|
52
53
|
}
|
|
53
54
|
end
|
|
@@ -69,6 +70,10 @@ module Konpeito
|
|
|
69
70
|
options[:require_paths] << path
|
|
70
71
|
end
|
|
71
72
|
|
|
73
|
+
opts.on("--inline", "Use inline RBS annotations (# @rbs, #:) from Ruby source") do
|
|
74
|
+
options[:inline_rbs] = true
|
|
75
|
+
end
|
|
76
|
+
|
|
72
77
|
super
|
|
73
78
|
end
|
|
74
79
|
|
|
@@ -78,6 +83,7 @@ module Konpeito
|
|
|
78
83
|
|
|
79
84
|
Examples:
|
|
80
85
|
konpeito run src/main.rb Build and run (native)
|
|
86
|
+
konpeito run --inline src/main.rb Build and run with inline RBS
|
|
81
87
|
konpeito run --target jvm src/main.rb Build and run (JVM)
|
|
82
88
|
BANNER
|
|
83
89
|
end
|
|
@@ -104,11 +110,16 @@ module Konpeito
|
|
|
104
110
|
def run_native(source_file)
|
|
105
111
|
require "tmpdir"
|
|
106
112
|
|
|
107
|
-
|
|
113
|
+
# Use a subdirectory to avoid conflicts, but keep the basename matching
|
|
114
|
+
# the Init_ function name (Ruby requires Init_<basename> to match the filename)
|
|
115
|
+
@run_tmpdir = File.join(Dir.tmpdir, "konpeito_run_#{Process.pid}")
|
|
116
|
+
FileUtils.mkdir_p(@run_tmpdir)
|
|
117
|
+
output_file = File.join(@run_tmpdir, "#{File.basename(source_file, '.rb')}#{Platform.shared_lib_extension}")
|
|
108
118
|
|
|
109
119
|
build_args = ["-o", output_file]
|
|
110
120
|
build_args << "-v" if options[:verbose]
|
|
111
121
|
build_args << "--no-color" unless options[:color]
|
|
122
|
+
build_args << "--inline" if options[:inline_rbs]
|
|
112
123
|
options[:rbs_paths].each { |p| build_args << "--rbs" << p }
|
|
113
124
|
options[:require_paths].each { |p| build_args << "-I" << p }
|
|
114
125
|
build_args << source_file
|
|
@@ -116,9 +127,11 @@ module Konpeito
|
|
|
116
127
|
Commands::BuildCommand.new(build_args, config: config).run
|
|
117
128
|
|
|
118
129
|
emit("Running", output_file)
|
|
119
|
-
|
|
130
|
+
# Run without Bundler environment so the compiled extension can load
|
|
131
|
+
# any installed gem (not just those in the current Gemfile)
|
|
132
|
+
run_without_bundler("ruby", "-r", output_file, "-e", "")
|
|
120
133
|
ensure
|
|
121
|
-
FileUtils.
|
|
134
|
+
FileUtils.rm_rf(@run_tmpdir) if @run_tmpdir && Dir.exist?(@run_tmpdir)
|
|
122
135
|
end
|
|
123
136
|
|
|
124
137
|
def build_classpath
|
|
@@ -135,10 +148,23 @@ module Konpeito
|
|
|
135
148
|
parts.reject(&:empty?).join(Platform.classpath_separator)
|
|
136
149
|
end
|
|
137
150
|
|
|
151
|
+
# Run a command without Bundler's environment restrictions.
|
|
152
|
+
# When konpeito is invoked via `bundle exec`, child processes inherit
|
|
153
|
+
# BUNDLE_GEMFILE/RUBYOPT which restrict gem access. The compiled extension
|
|
154
|
+
# may require gems not in the current Gemfile.
|
|
155
|
+
def run_without_bundler(*cmd)
|
|
156
|
+
if defined?(Bundler) && Bundler.respond_to?(:with_unbundled_env)
|
|
157
|
+
Bundler.with_unbundled_env { system(*cmd) }
|
|
158
|
+
else
|
|
159
|
+
system(*cmd)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
138
163
|
def build_jvm_args(source_file, classpath)
|
|
139
164
|
build_args = ["--target", "jvm"]
|
|
140
165
|
build_args << "-v" if options[:verbose]
|
|
141
166
|
build_args << "--no-color" unless options[:color]
|
|
167
|
+
build_args << "--inline" if options[:inline_rbs]
|
|
142
168
|
build_args += ["--classpath", classpath] unless classpath.empty?
|
|
143
169
|
options[:rbs_paths].each { |p| build_args << "--rbs" << p }
|
|
144
170
|
options[:require_paths].each { |p| build_args << "-I" << p }
|
data/lib/konpeito/cli.rb
CHANGED
|
@@ -5,7 +5,6 @@ require_relative "cli/config"
|
|
|
5
5
|
require_relative "cli/base_command"
|
|
6
6
|
require_relative "cli/build_command"
|
|
7
7
|
require_relative "cli/check_command"
|
|
8
|
-
require_relative "cli/lsp_command"
|
|
9
8
|
require_relative "cli/init_command"
|
|
10
9
|
require_relative "cli/fmt_command"
|
|
11
10
|
require_relative "cli/test_command"
|
|
@@ -13,6 +12,7 @@ require_relative "cli/watch_command"
|
|
|
13
12
|
require_relative "cli/run_command"
|
|
14
13
|
require_relative "cli/deps_command"
|
|
15
14
|
require_relative "cli/doctor_command"
|
|
15
|
+
require_relative "cli/completion_command"
|
|
16
16
|
|
|
17
17
|
module Konpeito
|
|
18
18
|
# Main CLI router - dispatches to subcommands
|
|
@@ -21,13 +21,13 @@ module Konpeito
|
|
|
21
21
|
"build" => Commands::BuildCommand,
|
|
22
22
|
"run" => Commands::RunCommand,
|
|
23
23
|
"check" => Commands::CheckCommand,
|
|
24
|
-
"lsp" => Commands::LspCommand,
|
|
25
24
|
"init" => Commands::InitCommand,
|
|
26
25
|
"fmt" => Commands::FmtCommand,
|
|
27
26
|
"test" => Commands::TestCommand,
|
|
28
27
|
"watch" => Commands::WatchCommand,
|
|
29
28
|
"deps" => Commands::DepsCommand,
|
|
30
|
-
"doctor" => Commands::DoctorCommand
|
|
29
|
+
"doctor" => Commands::DoctorCommand,
|
|
30
|
+
"completion" => Commands::CompletionCommand
|
|
31
31
|
}.freeze
|
|
32
32
|
|
|
33
33
|
attr_reader :args
|
|
@@ -62,6 +62,10 @@ module Konpeito
|
|
|
62
62
|
else
|
|
63
63
|
# Unknown command
|
|
64
64
|
$stderr.puts "Unknown command: #{command_name}"
|
|
65
|
+
suggestion = find_similar_command(command_name)
|
|
66
|
+
$stderr.puts " Did you mean: #{suggestion}?" if suggestion
|
|
67
|
+
$stderr.puts ""
|
|
68
|
+
$stderr.puts "Available commands: #{COMMANDS.keys.join(', ')}"
|
|
65
69
|
$stderr.puts "Run 'konpeito --help' for usage information."
|
|
66
70
|
exit 1
|
|
67
71
|
end
|
|
@@ -69,6 +73,43 @@ module Konpeito
|
|
|
69
73
|
|
|
70
74
|
private
|
|
71
75
|
|
|
76
|
+
def find_similar_command(input)
|
|
77
|
+
best = nil
|
|
78
|
+
best_distance = Float::INFINITY
|
|
79
|
+
|
|
80
|
+
COMMANDS.each_key do |name|
|
|
81
|
+
# Prefix match (e.g. "b" -> "build")
|
|
82
|
+
return name if name.start_with?(input) && input.length >= 1
|
|
83
|
+
|
|
84
|
+
d = levenshtein(input, name)
|
|
85
|
+
if d < best_distance && d <= 2
|
|
86
|
+
best_distance = d
|
|
87
|
+
best = name
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
best
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def levenshtein(a, b)
|
|
94
|
+
m = a.length
|
|
95
|
+
n = b.length
|
|
96
|
+
return n if m == 0
|
|
97
|
+
return m if n == 0
|
|
98
|
+
|
|
99
|
+
d = Array.new(m + 1) { |i| i }
|
|
100
|
+
(1..n).each do |j|
|
|
101
|
+
prev = d[0]
|
|
102
|
+
d[0] = j
|
|
103
|
+
(1..m).each do |i|
|
|
104
|
+
cost = a[i - 1] == b[j - 1] ? 0 : 1
|
|
105
|
+
temp = d[i]
|
|
106
|
+
d[i] = [d[i] + 1, d[i - 1] + 1, prev + cost].min
|
|
107
|
+
prev = temp
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
d[m]
|
|
111
|
+
end
|
|
112
|
+
|
|
72
113
|
def run_command(command_name, command_args)
|
|
73
114
|
command_class = COMMANDS[command_name]
|
|
74
115
|
config = Commands::Config.new
|
|
@@ -105,6 +146,7 @@ module Konpeito
|
|
|
105
146
|
puts " konpeito test Run tests"
|
|
106
147
|
puts " konpeito fmt Format source files"
|
|
107
148
|
puts " konpeito doctor Check environment"
|
|
149
|
+
puts " konpeito completion zsh Generate shell completions"
|
|
108
150
|
puts ""
|
|
109
151
|
puts "Legacy mode (backwards compatible):"
|
|
110
152
|
puts " konpeito source.rb Same as: konpeito build source.rb"
|
|
@@ -171,7 +213,6 @@ module Konpeito
|
|
|
171
213
|
require_paths: [],
|
|
172
214
|
color: $stderr.tty?,
|
|
173
215
|
debug: false,
|
|
174
|
-
lsp: false,
|
|
175
216
|
profile: false,
|
|
176
217
|
incremental: false,
|
|
177
218
|
clean_cache: false
|
|
@@ -181,12 +222,6 @@ module Konpeito
|
|
|
181
222
|
def run
|
|
182
223
|
parse_options!
|
|
183
224
|
|
|
184
|
-
# Start LSP server if requested
|
|
185
|
-
if options[:lsp]
|
|
186
|
-
Commands::LspCommand.new([], config: Commands::Config.new).run
|
|
187
|
-
return
|
|
188
|
-
end
|
|
189
|
-
|
|
190
225
|
if args.empty?
|
|
191
226
|
puts "Usage: konpeito [options] <source.rb>"
|
|
192
227
|
puts "Run 'konpeito --help' for more information."
|
|
@@ -272,10 +307,6 @@ module Konpeito
|
|
|
272
307
|
options[:color] = false
|
|
273
308
|
end
|
|
274
309
|
|
|
275
|
-
opts.on("--lsp", "Start Language Server Protocol server") do
|
|
276
|
-
options[:lsp] = true
|
|
277
|
-
end
|
|
278
|
-
|
|
279
310
|
opts.on("--incremental", "Enable incremental compilation (cache unchanged files)") do
|
|
280
311
|
options[:incremental] = true
|
|
281
312
|
end
|
|
@@ -44,6 +44,22 @@ module Konpeito
|
|
|
44
44
|
|
|
45
45
|
# String hash - exported (useful for Hash key optimization)
|
|
46
46
|
hash: { c_func: "rb_str_hash", arity: 0, return_type: :Integer, conv: :simple },
|
|
47
|
+
|
|
48
|
+
# String repeat - exported
|
|
49
|
+
:* => { c_func: "rb_str_times", arity: 1, return_type: :String, conv: :simple },
|
|
50
|
+
|
|
51
|
+
# String freeze - exported
|
|
52
|
+
freeze: { c_func: "rb_str_freeze", arity: 0, return_type: :String, conv: :simple },
|
|
53
|
+
|
|
54
|
+
# String replace - exported
|
|
55
|
+
replace: { c_func: "rb_str_replace", arity: 1, return_type: :String, conv: :simple },
|
|
56
|
+
|
|
57
|
+
# String succ/next - exported
|
|
58
|
+
succ: { c_func: "rb_str_succ", arity: 0, return_type: :String, conv: :simple },
|
|
59
|
+
next: { c_func: "rb_str_succ", arity: 0, return_type: :String, conv: :simple },
|
|
60
|
+
|
|
61
|
+
# String inspect - exported
|
|
62
|
+
inspect: { c_func: "rb_str_inspect", arity: 0, return_type: :String, conv: :simple },
|
|
47
63
|
},
|
|
48
64
|
|
|
49
65
|
Array: {
|
|
@@ -65,7 +65,7 @@ module Konpeito
|
|
|
65
65
|
unless @debug
|
|
66
66
|
FileUtils.rm_f(obj_file)
|
|
67
67
|
end
|
|
68
|
-
FileUtils.rm_f(init_c_file)
|
|
68
|
+
# FileUtils.rm_f(init_c_file) # DEBUG: keep for inspection
|
|
69
69
|
FileUtils.rm_f(init_obj_file)
|
|
70
70
|
FileUtils.rm_f(profile_c_file) if profile_c_file
|
|
71
71
|
FileUtils.rm_f(profile_obj_file) if profile_obj_file
|
|
@@ -241,11 +241,32 @@ module Konpeito
|
|
|
241
241
|
lines << ""
|
|
242
242
|
end
|
|
243
243
|
|
|
244
|
-
# Load stdlib dependencies first
|
|
244
|
+
# Load stdlib/gem dependencies first
|
|
245
|
+
# Use rb_funcallv(rb_cObject, ...) instead of rb_require() or rb_funcallv(rb_mKernel, ...)
|
|
246
|
+
# rb_require() bypasses RubyGems gem activation entirely.
|
|
247
|
+
# rb_funcallv(rb_mKernel, ...) calls the Kernel singleton method which also
|
|
248
|
+
# fails to activate gems. rb_funcallv(rb_cObject, ...) calls require as an
|
|
249
|
+
# instance method on Object (which includes Kernel), going through RubyGems'
|
|
250
|
+
# full gem activation path.
|
|
245
251
|
unless @stdlib_requires.empty?
|
|
246
|
-
lines << " /* Load
|
|
252
|
+
lines << " /* Load dependencies */"
|
|
253
|
+
lines << " {"
|
|
254
|
+
lines << " ID require_id = rb_intern(\"require\");"
|
|
247
255
|
@stdlib_requires.each do |lib_name|
|
|
248
|
-
lines << "
|
|
256
|
+
lines << " {"
|
|
257
|
+
lines << " VALUE args[] = { rb_str_new_cstr(\"#{lib_name}\") };"
|
|
258
|
+
lines << " rb_funcallv(rb_cObject, require_id, 1, args);"
|
|
259
|
+
lines << " }"
|
|
260
|
+
end
|
|
261
|
+
lines << " }"
|
|
262
|
+
lines << ""
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Load runtime native extensions (require_relative pointing to .bundle/.so)
|
|
266
|
+
unless @runtime_native_extensions.empty?
|
|
267
|
+
lines << " /* Load runtime native extensions */"
|
|
268
|
+
@runtime_native_extensions.each do |ext|
|
|
269
|
+
lines << " rb_require(\"#{ext[:path]}\");"
|
|
249
270
|
end
|
|
250
271
|
lines << ""
|
|
251
272
|
end
|
|
@@ -266,7 +287,15 @@ module Konpeito
|
|
|
266
287
|
|
|
267
288
|
# Use -1 arity for variadic functions
|
|
268
289
|
arity = llvm_generator.variadic_functions[mangled_name] ? -1 : func.params.size - 1
|
|
269
|
-
|
|
290
|
+
|
|
291
|
+
if module_def.module_function_methods&.include?(method_name)
|
|
292
|
+
# module_function: available as both public module method AND private instance method
|
|
293
|
+
lines << " rb_define_module_function(#{module_var}, \"#{method_name}\", #{mangled_name}, #{arity});"
|
|
294
|
+
elsif module_def.private_methods&.include?(method_name)
|
|
295
|
+
lines << " rb_define_private_method(#{module_var}, \"#{method_name}\", #{mangled_name}, #{arity});"
|
|
296
|
+
else
|
|
297
|
+
lines << " rb_define_method(#{module_var}, \"#{method_name}\", #{mangled_name}, #{arity});"
|
|
298
|
+
end
|
|
270
299
|
end
|
|
271
300
|
|
|
272
301
|
# Register singleton methods (def self.method)
|
|
@@ -292,6 +321,25 @@ module Konpeito
|
|
|
292
321
|
end
|
|
293
322
|
end
|
|
294
323
|
|
|
324
|
+
# Execute top-level include/extend/prepend before class definitions
|
|
325
|
+
# so that constants from included modules are available for superclass resolution
|
|
326
|
+
# (e.g., `include Kumiki; class CounterComponent < Component`)
|
|
327
|
+
unless hir.toplevel_includes.empty?
|
|
328
|
+
lines << " /* Top-level include/extend/prepend */"
|
|
329
|
+
hir.toplevel_includes.each do |kind, module_name|
|
|
330
|
+
module_expr = "rb_const_get(rb_cObject, rb_intern(\"#{module_name}\"))"
|
|
331
|
+
case kind
|
|
332
|
+
when :include
|
|
333
|
+
lines << " rb_include_module(rb_cObject, #{module_expr});"
|
|
334
|
+
when :extend
|
|
335
|
+
lines << " rb_extend_object(rb_cObject, #{module_expr});"
|
|
336
|
+
when :prepend
|
|
337
|
+
lines << " rb_prepend_module(rb_cObject, #{module_expr});"
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
lines << ""
|
|
341
|
+
end
|
|
342
|
+
|
|
295
343
|
# Define NativeClasses with TypedData allocator
|
|
296
344
|
native_classes.each do |class_name, class_type|
|
|
297
345
|
lines.concat(generate_native_class_init(class_name, class_type))
|
|
@@ -429,6 +477,16 @@ module Konpeito
|
|
|
429
477
|
lines << " rb_define_private_method(rb_cObject, \"#{func_def.name}\", #{mangled_name}, #{arity});"
|
|
430
478
|
end
|
|
431
479
|
|
|
480
|
+
# Call top-level entry point if present.
|
|
481
|
+
# rb_vm_top_self() is not exported from libruby in Ruby 4.0; use rb_cObject instead.
|
|
482
|
+
# Top-level calls like module_function/private/public/include on Object are semantically
|
|
483
|
+
# equivalent to the same calls on the main object for our purposes.
|
|
484
|
+
has_main = hir.functions.any? { |f| f.name == "__main__" && !f.owner_class && !f.owner_module }
|
|
485
|
+
if has_main
|
|
486
|
+
lines << " /* Run top-level code */"
|
|
487
|
+
lines << " rn___main__(rb_cObject);"
|
|
488
|
+
end
|
|
489
|
+
|
|
432
490
|
lines << "}"
|
|
433
491
|
lines << ""
|
|
434
492
|
|
|
@@ -939,10 +997,22 @@ module Konpeito
|
|
|
939
997
|
end
|
|
940
998
|
|
|
941
999
|
if @rbs_loader
|
|
1000
|
+
stdlib_ui_dir = File.expand_path("../stdlib/ui", __dir__)
|
|
1001
|
+
|
|
942
1002
|
@rbs_loader.all_ffi_libraries.each do |lib_name|
|
|
1003
|
+
link_name = lib_name.sub(/^lib/, "")
|
|
1004
|
+
|
|
1005
|
+
# On macOS the UI runtime ships as a Ruby .bundle (loadable module),
|
|
1006
|
+
# not as a .dylib. Bundles cannot be used as -l link targets; their
|
|
1007
|
+
# symbols are resolved at runtime via -undefined dynamic_lookup, so
|
|
1008
|
+
# we simply skip the -l flag for them.
|
|
1009
|
+
bundle_path = File.join(stdlib_ui_dir, "#{link_name}.bundle")
|
|
1010
|
+
if File.exist?(bundle_path) && RbConfig::CONFIG["host_os"] =~ /darwin/
|
|
1011
|
+
next
|
|
1012
|
+
end
|
|
1013
|
+
|
|
943
1014
|
# Convert library name to linker flag
|
|
944
1015
|
# "libm" -> "-lm", "libfoo" -> "-lfoo", "foo" -> "-lfoo"
|
|
945
|
-
link_name = lib_name.sub(/^lib/, "")
|
|
946
1016
|
flags << "-l#{link_name}"
|
|
947
1017
|
end
|
|
948
1018
|
end
|