konpeito 0.1.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 +7 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +75 -0
- data/CONTRIBUTING.md +123 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/Rakefile +11 -0
- data/bin/konpeito +6 -0
- data/konpeito.gemspec +43 -0
- data/lib/konpeito/ast/typed_ast.rb +620 -0
- data/lib/konpeito/ast/visitor.rb +78 -0
- data/lib/konpeito/cache/cache_manager.rb +230 -0
- data/lib/konpeito/cache/dependency_graph.rb +192 -0
- data/lib/konpeito/cache.rb +8 -0
- data/lib/konpeito/cli/base_command.rb +187 -0
- data/lib/konpeito/cli/build_command.rb +220 -0
- data/lib/konpeito/cli/check_command.rb +104 -0
- data/lib/konpeito/cli/config.rb +231 -0
- data/lib/konpeito/cli/deps_command.rb +128 -0
- data/lib/konpeito/cli/doctor_command.rb +340 -0
- data/lib/konpeito/cli/fmt_command.rb +199 -0
- data/lib/konpeito/cli/init_command.rb +312 -0
- data/lib/konpeito/cli/lsp_command.rb +40 -0
- data/lib/konpeito/cli/run_command.rb +150 -0
- data/lib/konpeito/cli/test_command.rb +248 -0
- data/lib/konpeito/cli/watch_command.rb +212 -0
- data/lib/konpeito/cli.rb +301 -0
- data/lib/konpeito/codegen/builtin_methods.rb +229 -0
- data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
- data/lib/konpeito/codegen/debug_info.rb +352 -0
- data/lib/konpeito/codegen/inliner.rb +486 -0
- data/lib/konpeito/codegen/jvm_backend.rb +197 -0
- data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
- data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
- data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
- data/lib/konpeito/codegen/monomorphizer.rb +359 -0
- data/lib/konpeito/codegen/profile_runtime.c +341 -0
- data/lib/konpeito/codegen/profiler.rb +99 -0
- data/lib/konpeito/compiler.rb +592 -0
- data/lib/konpeito/dependency_resolver.rb +296 -0
- data/lib/konpeito/diagnostics/collector.rb +127 -0
- data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
- data/lib/konpeito/diagnostics/renderer.rb +144 -0
- data/lib/konpeito/formatter/formatter.rb +1214 -0
- data/lib/konpeito/hir/builder.rb +7167 -0
- data/lib/konpeito/hir/nodes.rb +2465 -0
- data/lib/konpeito/lsp/document_manager.rb +820 -0
- data/lib/konpeito/lsp/server.rb +183 -0
- data/lib/konpeito/lsp/transport.rb +38 -0
- data/lib/konpeito/parser/prism_adapter.rb +65 -0
- data/lib/konpeito/platform.rb +103 -0
- data/lib/konpeito/profile/report.rb +136 -0
- data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
- data/lib/konpeito/stdlib/compression/compression.rb +72 -0
- data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
- data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
- data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
- data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
- data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
- data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
- data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
- data/lib/konpeito/stdlib/http/extconf.rb +19 -0
- data/lib/konpeito/stdlib/http/http.rb +125 -0
- data/lib/konpeito/stdlib/http/http.rbs +57 -0
- data/lib/konpeito/stdlib/http/http_native.c +440 -0
- data/lib/konpeito/stdlib/json/extconf.rb +17 -0
- data/lib/konpeito/stdlib/json/json.rb +44 -0
- data/lib/konpeito/stdlib/json/json.rbs +33 -0
- data/lib/konpeito/stdlib/json/json_native.c +286 -0
- data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
- data/lib/konpeito/stdlib/ui/ui.rb +318 -0
- data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
- data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
- data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
- data/lib/konpeito/type_checker/inferrer.rb +565 -0
- data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
- data/lib/konpeito/type_checker/type_resolver.rb +276 -0
- data/lib/konpeito/type_checker/types.rb +1434 -0
- data/lib/konpeito/type_checker/unification.rb +323 -0
- data/lib/konpeito/ui/animation/animated_state.rb +80 -0
- data/lib/konpeito/ui/animation/easing.rb +59 -0
- data/lib/konpeito/ui/animation/value_tween.rb +66 -0
- data/lib/konpeito/ui/app.rb +379 -0
- data/lib/konpeito/ui/box.rb +38 -0
- data/lib/konpeito/ui/castella.rb +70 -0
- data/lib/konpeito/ui/castella_native.rb +76 -0
- data/lib/konpeito/ui/chart/area_chart.rb +305 -0
- data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
- data/lib/konpeito/ui/chart/base_chart.rb +210 -0
- data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
- data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
- data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
- data/lib/konpeito/ui/chart/line_chart.rb +289 -0
- data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
- data/lib/konpeito/ui/chart/scales.rb +77 -0
- data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
- data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
- data/lib/konpeito/ui/column.rb +271 -0
- data/lib/konpeito/ui/core.rb +2199 -0
- data/lib/konpeito/ui/dsl.rb +443 -0
- data/lib/konpeito/ui/frame.rb +171 -0
- data/lib/konpeito/ui/frame_native.rb +494 -0
- data/lib/konpeito/ui/markdown/ast.rb +124 -0
- data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
- data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
- data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
- data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
- data/lib/konpeito/ui/markdown/parser.rb +805 -0
- data/lib/konpeito/ui/markdown/renderer.rb +639 -0
- data/lib/konpeito/ui/markdown/theme.rb +165 -0
- data/lib/konpeito/ui/render_node.rb +260 -0
- data/lib/konpeito/ui/row.rb +207 -0
- data/lib/konpeito/ui/spacer.rb +18 -0
- data/lib/konpeito/ui/style.rb +799 -0
- data/lib/konpeito/ui/theme.rb +563 -0
- data/lib/konpeito/ui/themes/material.rb +35 -0
- data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
- data/lib/konpeito/ui/widgets/button.rb +103 -0
- data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
- data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
- data/lib/konpeito/ui/widgets/container.rb +91 -0
- data/lib/konpeito/ui/widgets/data_table.rb +667 -0
- data/lib/konpeito/ui/widgets/divider.rb +29 -0
- data/lib/konpeito/ui/widgets/image.rb +105 -0
- data/lib/konpeito/ui/widgets/input.rb +485 -0
- data/lib/konpeito/ui/widgets/markdown.rb +57 -0
- data/lib/konpeito/ui/widgets/modal.rb +163 -0
- data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
- data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
- data/lib/konpeito/ui/widgets/net_image.rb +100 -0
- data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
- data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
- data/lib/konpeito/ui/widgets/slider.rb +133 -0
- data/lib/konpeito/ui/widgets/switch.rb +84 -0
- data/lib/konpeito/ui/widgets/tabs.rb +157 -0
- data/lib/konpeito/ui/widgets/text.rb +110 -0
- data/lib/konpeito/ui/widgets/tree.rb +426 -0
- data/lib/konpeito/version.rb +5 -0
- data/lib/konpeito.rb +109 -0
- data/test_native_array.rb +172 -0
- data/test_native_array_class.rb +197 -0
- data/test_native_class.rb +151 -0
- data/tools/konpeito-asm/build.sh +65 -0
- data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
- data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
- data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
- metadata +267 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "transport"
|
|
4
|
+
require_relative "document_manager"
|
|
5
|
+
|
|
6
|
+
module Konpeito
|
|
7
|
+
module LSP
|
|
8
|
+
# Main LSP server that handles client requests
|
|
9
|
+
class Server
|
|
10
|
+
attr_reader :transport, :document_manager
|
|
11
|
+
|
|
12
|
+
def initialize(input: $stdin, output: $stdout)
|
|
13
|
+
@transport = Transport.new(input: input, output: output)
|
|
14
|
+
@document_manager = DocumentManager.new(@transport)
|
|
15
|
+
@running = false
|
|
16
|
+
@initialized = false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Start the LSP server main loop
|
|
20
|
+
def start
|
|
21
|
+
@running = true
|
|
22
|
+
while @running
|
|
23
|
+
request = @transport.read
|
|
24
|
+
break unless request
|
|
25
|
+
|
|
26
|
+
response = handle_request(request)
|
|
27
|
+
@transport.write(response) if response
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Stop the server
|
|
32
|
+
def stop
|
|
33
|
+
@running = false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Handle a single request (exposed for testing)
|
|
37
|
+
# @param request [Hash] The LSP request
|
|
38
|
+
# @return [Hash, nil] The response or nil for notifications
|
|
39
|
+
def handle_request(request)
|
|
40
|
+
method = request[:method]
|
|
41
|
+
id = request[:id]
|
|
42
|
+
params = request[:params] || {}
|
|
43
|
+
|
|
44
|
+
result = case method
|
|
45
|
+
when "initialize"
|
|
46
|
+
handle_initialize(params)
|
|
47
|
+
when "initialized"
|
|
48
|
+
# Notification, no response needed
|
|
49
|
+
nil
|
|
50
|
+
when "shutdown"
|
|
51
|
+
handle_shutdown
|
|
52
|
+
when "exit"
|
|
53
|
+
@running = false
|
|
54
|
+
nil
|
|
55
|
+
when "textDocument/didOpen"
|
|
56
|
+
handle_did_open(params)
|
|
57
|
+
when "textDocument/didChange"
|
|
58
|
+
handle_did_change(params)
|
|
59
|
+
when "textDocument/didClose"
|
|
60
|
+
handle_did_close(params)
|
|
61
|
+
when "textDocument/hover"
|
|
62
|
+
handle_hover(params)
|
|
63
|
+
when "textDocument/completion"
|
|
64
|
+
handle_completion(params)
|
|
65
|
+
when "textDocument/definition"
|
|
66
|
+
handle_definition(params)
|
|
67
|
+
when "textDocument/references"
|
|
68
|
+
handle_references(params)
|
|
69
|
+
when "textDocument/rename"
|
|
70
|
+
handle_rename(params)
|
|
71
|
+
when "textDocument/prepareRename"
|
|
72
|
+
handle_prepare_rename(params)
|
|
73
|
+
else
|
|
74
|
+
# Unknown method
|
|
75
|
+
if id
|
|
76
|
+
{ error: { code: -32601, message: "Method not found: #{method}" } }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Build response for requests (not notifications)
|
|
81
|
+
if id && result != :notification
|
|
82
|
+
{ jsonrpc: "2.0", id: id, result: result }
|
|
83
|
+
else
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def handle_initialize(params)
|
|
91
|
+
@initialized = true
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
capabilities: {
|
|
95
|
+
textDocumentSync: {
|
|
96
|
+
openClose: true,
|
|
97
|
+
change: 1 # Full content sync
|
|
98
|
+
},
|
|
99
|
+
hoverProvider: true,
|
|
100
|
+
completionProvider: {
|
|
101
|
+
triggerCharacters: ["."],
|
|
102
|
+
resolveProvider: false
|
|
103
|
+
},
|
|
104
|
+
definitionProvider: true,
|
|
105
|
+
referencesProvider: true,
|
|
106
|
+
renameProvider: {
|
|
107
|
+
prepareProvider: true
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
serverInfo: {
|
|
111
|
+
name: "konpeito-lsp",
|
|
112
|
+
version: Konpeito::VERSION
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def handle_shutdown
|
|
118
|
+
@initialized = false
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def handle_did_open(params)
|
|
123
|
+
uri = params.dig(:textDocument, :uri)
|
|
124
|
+
text = params.dig(:textDocument, :text)
|
|
125
|
+
@document_manager.open(uri, text)
|
|
126
|
+
:notification
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def handle_did_change(params)
|
|
130
|
+
uri = params.dig(:textDocument, :uri)
|
|
131
|
+
# We use full sync, so first contentChanges has full text
|
|
132
|
+
changes = params[:contentChanges]
|
|
133
|
+
text = changes&.first&.dig(:text)
|
|
134
|
+
@document_manager.change(uri, text) if text
|
|
135
|
+
:notification
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def handle_did_close(params)
|
|
139
|
+
uri = params.dig(:textDocument, :uri)
|
|
140
|
+
@document_manager.close(uri)
|
|
141
|
+
:notification
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def handle_hover(params)
|
|
145
|
+
uri = params.dig(:textDocument, :uri)
|
|
146
|
+
position = params[:position]
|
|
147
|
+
@document_manager.hover(uri, position)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def handle_completion(params)
|
|
151
|
+
uri = params.dig(:textDocument, :uri)
|
|
152
|
+
position = params[:position]
|
|
153
|
+
@document_manager.completion(uri, position)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def handle_definition(params)
|
|
157
|
+
uri = params.dig(:textDocument, :uri)
|
|
158
|
+
position = params[:position]
|
|
159
|
+
@document_manager.definition(uri, position)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def handle_references(params)
|
|
163
|
+
uri = params.dig(:textDocument, :uri)
|
|
164
|
+
position = params[:position]
|
|
165
|
+
include_declaration = params.dig(:context, :includeDeclaration) != false
|
|
166
|
+
@document_manager.references(uri, position, include_declaration: include_declaration)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def handle_rename(params)
|
|
170
|
+
uri = params.dig(:textDocument, :uri)
|
|
171
|
+
position = params[:position]
|
|
172
|
+
new_name = params[:newName]
|
|
173
|
+
@document_manager.rename(uri, position, new_name)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def handle_prepare_rename(params)
|
|
177
|
+
uri = params.dig(:textDocument, :uri)
|
|
178
|
+
position = params[:position]
|
|
179
|
+
@document_manager.prepare_rename(uri, position)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "language_server-protocol"
|
|
4
|
+
|
|
5
|
+
module Konpeito
|
|
6
|
+
module LSP
|
|
7
|
+
# Handles LSP JSON-RPC communication over stdio
|
|
8
|
+
class Transport
|
|
9
|
+
def initialize(input: $stdin, output: $stdout)
|
|
10
|
+
@reader = LanguageServer::Protocol::Transport::Io::Reader.new(input)
|
|
11
|
+
@writer = LanguageServer::Protocol::Transport::Io::Writer.new(output)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Read next request/notification from client
|
|
15
|
+
# @return [Hash, nil] The request object or nil if EOF
|
|
16
|
+
def read
|
|
17
|
+
@reader.read
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Write response to client
|
|
21
|
+
# @param response [Hash] The response object
|
|
22
|
+
def write(response)
|
|
23
|
+
@writer.write(response)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Send notification to client (no response expected)
|
|
27
|
+
# @param method [String] The notification method
|
|
28
|
+
# @param params [Hash] The notification parameters
|
|
29
|
+
def notify(method, params)
|
|
30
|
+
@writer.write({
|
|
31
|
+
jsonrpc: "2.0",
|
|
32
|
+
method: method,
|
|
33
|
+
params: params
|
|
34
|
+
})
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Konpeito
|
|
6
|
+
module Parser
|
|
7
|
+
class PrismAdapter
|
|
8
|
+
class << self
|
|
9
|
+
def parse(source, filepath: "(eval)")
|
|
10
|
+
result = Prism.parse(source, filepath: filepath)
|
|
11
|
+
|
|
12
|
+
unless result.success?
|
|
13
|
+
errors = result.errors.map { |e| format_error(e, filepath) }
|
|
14
|
+
raise ParseError, "Parse errors:\n#{errors.join("\n")}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
result.value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parse_file(filepath)
|
|
21
|
+
source = File.read(filepath)
|
|
22
|
+
parse(source, filepath: filepath)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Detect require and require_relative calls in AST
|
|
26
|
+
# Returns array of { type: :require | :require_relative, name: String }
|
|
27
|
+
def detect_requires(ast)
|
|
28
|
+
requires = []
|
|
29
|
+
visitor = RequireVisitor.new(requires)
|
|
30
|
+
visitor.visit(ast)
|
|
31
|
+
requires
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def format_error(error, filepath)
|
|
37
|
+
loc = error.location
|
|
38
|
+
" #{filepath}:#{loc.start_line}:#{loc.start_column}: #{error.message}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Visitor to detect require/require_relative calls
|
|
43
|
+
class RequireVisitor < Prism::Visitor
|
|
44
|
+
def initialize(requires)
|
|
45
|
+
@requires = requires
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def visit_call_node(node)
|
|
49
|
+
if node.receiver.nil? && %w[require require_relative].include?(node.name.to_s)
|
|
50
|
+
arg = node.arguments&.arguments&.first
|
|
51
|
+
if arg.is_a?(Prism::StringNode)
|
|
52
|
+
@requires << {
|
|
53
|
+
type: node.name.to_sym,
|
|
54
|
+
name: arg.unescaped,
|
|
55
|
+
line: node.location.start_line,
|
|
56
|
+
column: node.location.start_column
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
super
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
module Konpeito
|
|
6
|
+
module Platform
|
|
7
|
+
def self.windows?
|
|
8
|
+
!!(RbConfig::CONFIG["host_os"] =~ /mingw|mswin|cygwin/)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.macos?
|
|
12
|
+
!!(RbConfig::CONFIG["host_os"] =~ /darwin/)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.linux?
|
|
16
|
+
!!(RbConfig::CONFIG["host_os"] =~ /linux/)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# CRuby extension file extension
|
|
20
|
+
def self.shared_lib_extension
|
|
21
|
+
if macos? then ".bundle"
|
|
22
|
+
elsif windows? then ".dll"
|
|
23
|
+
else ".so"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# JVM classpath separator
|
|
28
|
+
def self.classpath_separator
|
|
29
|
+
windows? ? ";" : ":"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Platform-independent executable finder (replaces `which`)
|
|
33
|
+
def self.find_executable(name)
|
|
34
|
+
exts = windows? ? (ENV["PATHEXT"] || ".COM;.EXE;.BAT").split(";") : [""]
|
|
35
|
+
ENV["PATH"].split(File::PATH_SEPARATOR).each do |dir|
|
|
36
|
+
exts.each do |ext|
|
|
37
|
+
path = File.join(dir, "#{name}#{ext}")
|
|
38
|
+
return path if File.executable?(path) && !File.directory?(path)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Find LLVM tool (clang, llc, opt) with version suffix fallback
|
|
45
|
+
def self.find_llvm_tool(name)
|
|
46
|
+
["#{name}-20", "#{name}-19", "#{name}-18", name].each do |tool|
|
|
47
|
+
path = find_executable(tool)
|
|
48
|
+
return path if path
|
|
49
|
+
end
|
|
50
|
+
platform_llvm_paths(name).each { |p| return p if File.exist?(p) }
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Find LLVM shared library for FFI (debug_info.rb)
|
|
55
|
+
def self.find_llvm_lib
|
|
56
|
+
candidates = if macos?
|
|
57
|
+
["/opt/homebrew/opt/llvm@20/lib/libLLVM-20.dylib",
|
|
58
|
+
"/usr/local/opt/llvm@20/lib/libLLVM-20.dylib"]
|
|
59
|
+
elsif windows?
|
|
60
|
+
["C:/Program Files/LLVM/bin/LLVM-C.dll"]
|
|
61
|
+
else
|
|
62
|
+
["/usr/lib/llvm-20/lib/libLLVM-20.so", "/usr/lib/llvm-20/lib/libLLVM.so",
|
|
63
|
+
"/usr/lib64/llvm20/lib/libLLVM-20.so", "/usr/lib/libLLVM-20.so"]
|
|
64
|
+
end
|
|
65
|
+
candidates.find { |p| File.exist?(p) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.default_java_home
|
|
69
|
+
if macos? then "/opt/homebrew/opt/openjdk@21"
|
|
70
|
+
elsif windows? then "C:/Program Files/Java/jdk-21"
|
|
71
|
+
else "/usr/lib/jvm/java-21-openjdk"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.java_install_hint
|
|
76
|
+
if macos? then "brew install openjdk@21"
|
|
77
|
+
elsif windows? then "winget install EclipseAdoptium.Temurin.21.JDK"
|
|
78
|
+
else "sudo apt install openjdk-21-jdk (Ubuntu) / sudo dnf install java-21-openjdk-devel (Fedora)"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.llvm_install_hint
|
|
83
|
+
if macos? then "brew install llvm@20"
|
|
84
|
+
elsif windows? then "winget install LLVM.LLVM"
|
|
85
|
+
else "sudo apt install llvm-20 clang-20 (Ubuntu) / sudo dnf install llvm20 clang20 (Fedora)"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.debugger_tune
|
|
90
|
+
macos? ? "lldb" : "gdb"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private_class_method def self.platform_llvm_paths(name)
|
|
94
|
+
if macos?
|
|
95
|
+
["/opt/homebrew/opt/llvm@20/bin/#{name}", "/usr/local/opt/llvm@20/bin/#{name}"]
|
|
96
|
+
elsif windows?
|
|
97
|
+
["C:/Program Files/LLVM/bin/#{name}.exe"]
|
|
98
|
+
else
|
|
99
|
+
["/usr/lib/llvm-20/bin/#{name}", "/usr/lib/llvm-19/bin/#{name}", "/usr/bin/#{name}"]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Konpeito
|
|
6
|
+
module Profile
|
|
7
|
+
# Represents profiling data for a single function
|
|
8
|
+
class FunctionProfile
|
|
9
|
+
attr_reader :name, :calls, :time_ms, :percent
|
|
10
|
+
|
|
11
|
+
def initialize(name:, calls:, time_ms:, percent:)
|
|
12
|
+
@name = name
|
|
13
|
+
@calls = calls
|
|
14
|
+
@time_ms = time_ms
|
|
15
|
+
@percent = percent
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_h
|
|
19
|
+
{
|
|
20
|
+
name: @name,
|
|
21
|
+
calls: @calls,
|
|
22
|
+
time_ms: @time_ms,
|
|
23
|
+
percent: @percent
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Reads and formats profile reports from JSON files
|
|
29
|
+
class Report
|
|
30
|
+
attr_reader :functions, :total_time_ms
|
|
31
|
+
|
|
32
|
+
def initialize(json_path)
|
|
33
|
+
raise ArgumentError, "Profile file not found: #{json_path}" unless File.exist?(json_path)
|
|
34
|
+
|
|
35
|
+
@json_path = json_path
|
|
36
|
+
data = JSON.parse(File.read(json_path))
|
|
37
|
+
@functions = data["functions"].map do |f|
|
|
38
|
+
FunctionProfile.new(
|
|
39
|
+
name: f["name"],
|
|
40
|
+
calls: f["calls"],
|
|
41
|
+
time_ms: f["time_ms"],
|
|
42
|
+
percent: f["percent"]
|
|
43
|
+
)
|
|
44
|
+
end.sort_by { |f| -f.time_ms }
|
|
45
|
+
@total_time_ms = data["total_time_ms"]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def to_text(max_name_length: 40)
|
|
49
|
+
lines = []
|
|
50
|
+
lines << "Konpeito Profile Report"
|
|
51
|
+
lines << "=" * 76
|
|
52
|
+
lines << ""
|
|
53
|
+
lines << format("%-#{max_name_length}s %12s %12s %8s", "Function", "Calls", "Time (ms)", "%")
|
|
54
|
+
lines << format("%-#{max_name_length}s %12s %12s %8s",
|
|
55
|
+
"-" * max_name_length, "-" * 12, "-" * 12, "-" * 8)
|
|
56
|
+
|
|
57
|
+
@functions.each do |f|
|
|
58
|
+
name = truncate_name(f.name, max_name_length)
|
|
59
|
+
lines << format("%-#{max_name_length}s %12d %12.3f %7.2f%%",
|
|
60
|
+
name, f.calls, f.time_ms, f.percent)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
lines << ""
|
|
64
|
+
lines << "Total time: #{@total_time_ms.round(3)} ms"
|
|
65
|
+
lines.join("\n")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_json_pretty
|
|
69
|
+
JSON.pretty_generate({
|
|
70
|
+
functions: @functions.map(&:to_h),
|
|
71
|
+
total_time_ms: @total_time_ms
|
|
72
|
+
})
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def hottest_functions(n = 5)
|
|
76
|
+
@functions.first(n)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def most_called_functions(n = 5)
|
|
80
|
+
@functions.sort_by { |f| -f.calls }.first(n)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get the path to the flame graph folded file
|
|
84
|
+
def flame_graph_path
|
|
85
|
+
@json_path.sub(/\.json$/, ".folded")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Check if flame graph data exists
|
|
89
|
+
def flame_graph_available?
|
|
90
|
+
File.exist?(flame_graph_path)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Read flame graph data as array of [stack, samples]
|
|
94
|
+
def flame_graph_stacks
|
|
95
|
+
return [] unless flame_graph_available?
|
|
96
|
+
|
|
97
|
+
File.readlines(flame_graph_path).map do |line|
|
|
98
|
+
parts = line.strip.split(" ")
|
|
99
|
+
samples = parts.pop.to_i
|
|
100
|
+
stack = parts.join(" ")
|
|
101
|
+
[stack, samples]
|
|
102
|
+
end.sort_by { |_, samples| -samples }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Generate flame graph text summary
|
|
106
|
+
def flame_graph_summary(max_stacks: 10)
|
|
107
|
+
stacks = flame_graph_stacks.first(max_stacks)
|
|
108
|
+
return "No flame graph data available" if stacks.empty?
|
|
109
|
+
|
|
110
|
+
lines = []
|
|
111
|
+
lines << "Flame Graph Stack Summary (Top #{max_stacks})"
|
|
112
|
+
lines << "=" * 76
|
|
113
|
+
lines << ""
|
|
114
|
+
|
|
115
|
+
total_samples = flame_graph_stacks.sum { |_, s| s }
|
|
116
|
+
stacks.each do |stack, samples|
|
|
117
|
+
percent = total_samples > 0 ? (samples * 100.0 / total_samples) : 0
|
|
118
|
+
lines << format("%7d (%5.1f%%) %s", samples, percent, stack)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
lines << ""
|
|
122
|
+
lines << "To generate flame graph SVG:"
|
|
123
|
+
lines << " flamegraph.pl #{flame_graph_path} > profile.svg"
|
|
124
|
+
lines.join("\n")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def truncate_name(name, max_length)
|
|
130
|
+
return name if name.length <= max_length
|
|
131
|
+
|
|
132
|
+
"#{name[0, max_length - 3]}..."
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|