mt-lang 0.2.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/AUTHORS +3 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +82 -0
- data/LICENSE +21 -0
- data/README.md +1208 -0
- data/Rakefile +332 -0
- data/bin/mtc +8 -0
- data/bin/profile-mtc-checks +133 -0
- data/bin/tracy-profiler +0 -0
- data/docs/build-guide.md +536 -0
- data/docs/index.html +2778 -0
- data/docs/language-design.md +1519 -0
- data/docs/language-manual.md +1534 -0
- data/lib/milk_tea/base.rb +49 -0
- data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
- data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
- data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
- data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
- data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
- data/lib/milk_tea/bindings/bindgen.rb +207 -0
- data/lib/milk_tea/bindings/cli.rb +121 -0
- data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
- data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
- data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
- data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
- data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
- data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
- data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
- data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
- data/lib/milk_tea/bindings/steamworks.rb +629 -0
- data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
- data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
- data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
- data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
- data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
- data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
- data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
- data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
- data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
- data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
- data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
- data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
- data/lib/milk_tea/bindings.rb +23 -0
- data/lib/milk_tea/core/ast.rb +311 -0
- data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
- data/lib/milk_tea/core/binding_types.rb +18 -0
- data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
- data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
- data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
- data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
- data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
- data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
- data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
- data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
- data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
- data/lib/milk_tea/core/c_backend/statements.rb +563 -0
- data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
- data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
- data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
- data/lib/milk_tea/core/c_backend.rb +287 -0
- data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
- data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
- data/lib/milk_tea/core/compile_time.rb +329 -0
- data/lib/milk_tea/core/control_flow/builder.rb +582 -0
- data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
- data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
- data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
- data/lib/milk_tea/core/control_flow/graph.rb +90 -0
- data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
- data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
- data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
- data/lib/milk_tea/core/control_flow/termination.rb +31 -0
- data/lib/milk_tea/core/control_flow.rb +34 -0
- data/lib/milk_tea/core/cst.rb +48 -0
- data/lib/milk_tea/core/cst_builder.rb +19 -0
- data/lib/milk_tea/core/ir.rb +85 -0
- data/lib/milk_tea/core/keywords.rb +97 -0
- data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
- data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
- data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
- data/lib/milk_tea/core/lexer/indentation.rb +62 -0
- data/lib/milk_tea/core/lexer/numbers.rb +96 -0
- data/lib/milk_tea/core/lexer/recovery.rb +32 -0
- data/lib/milk_tea/core/lexer/strings.rb +167 -0
- data/lib/milk_tea/core/lexer/symbols.rb +71 -0
- data/lib/milk_tea/core/lexer/trivia.rb +53 -0
- data/lib/milk_tea/core/lexer.rb +430 -0
- data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
- data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
- data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
- data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
- data/lib/milk_tea/core/lowering/async.rb +714 -0
- data/lib/milk_tea/core/lowering/block.rb +1052 -0
- data/lib/milk_tea/core/lowering/calls.rb +1565 -0
- data/lib/milk_tea/core/lowering/declarations.rb +214 -0
- data/lib/milk_tea/core/lowering/dyn.rb +206 -0
- data/lib/milk_tea/core/lowering/events.rb +1054 -0
- data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
- data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
- data/lib/milk_tea/core/lowering/functions.rb +242 -0
- data/lib/milk_tea/core/lowering/loops.rb +1087 -0
- data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
- data/lib/milk_tea/core/lowering/proc.rb +419 -0
- data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
- data/lib/milk_tea/core/lowering/scans.rb +220 -0
- data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
- data/lib/milk_tea/core/lowering/utils.rb +1453 -0
- data/lib/milk_tea/core/lowering.rb +378 -0
- data/lib/milk_tea/core/module_binder.rb +181 -0
- data/lib/milk_tea/core/module_loader/errors.rb +21 -0
- data/lib/milk_tea/core/module_loader.rb +479 -0
- data/lib/milk_tea/core/module_path_resolver.rb +153 -0
- data/lib/milk_tea/core/module_roots.rb +88 -0
- data/lib/milk_tea/core/parser/attributes.rb +71 -0
- data/lib/milk_tea/core/parser/blocks.rb +119 -0
- data/lib/milk_tea/core/parser/declarations.rb +749 -0
- data/lib/milk_tea/core/parser/expressions.rb +624 -0
- data/lib/milk_tea/core/parser/recovery.rb +131 -0
- data/lib/milk_tea/core/parser/statements.rb +756 -0
- data/lib/milk_tea/core/parser/types.rb +271 -0
- data/lib/milk_tea/core/parser.rb +400 -0
- data/lib/milk_tea/core/prelude_installer.rb +29 -0
- data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
- data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
- data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
- data/lib/milk_tea/core/pretty_printer.rb +17 -0
- data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
- data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
- data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
- data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
- data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
- data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
- data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
- data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
- data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
- data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
- data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
- data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
- data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
- data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
- data/lib/milk_tea/core/token.rb +26 -0
- data/lib/milk_tea/core/token_stream.rb +30 -0
- data/lib/milk_tea/core/types/layout.rb +243 -0
- data/lib/milk_tea/core/types/predicates.rb +609 -0
- data/lib/milk_tea/core/types/registry.rb +83 -0
- data/lib/milk_tea/core/types/types.rb +1696 -0
- data/lib/milk_tea/core/types/visitor.rb +442 -0
- data/lib/milk_tea/core.rb +25 -0
- data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
- data/lib/milk_tea/dap/protocol.rb +58 -0
- data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
- data/lib/milk_tea/dap/server/debug_map.rb +291 -0
- data/lib/milk_tea/dap/server/handlers.rb +374 -0
- data/lib/milk_tea/dap/server/launch.rb +261 -0
- data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
- data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
- data/lib/milk_tea/dap/server/utilities.rb +160 -0
- data/lib/milk_tea/dap/server/wire.rb +55 -0
- data/lib/milk_tea/dap/server.rb +131 -0
- data/lib/milk_tea/dap/session.rb +152 -0
- data/lib/milk_tea/dap.rb +6 -0
- data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
- data/lib/milk_tea/lsp/diagnostics.rb +611 -0
- data/lib/milk_tea/lsp/protocol.rb +104 -0
- data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
- data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
- data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
- data/lib/milk_tea/lsp/server/completion.rb +1099 -0
- data/lib/milk_tea/lsp/server/configuration.rb +167 -0
- data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
- data/lib/milk_tea/lsp/server/definition.rb +779 -0
- data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
- data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
- data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
- data/lib/milk_tea/lsp/server/formatting.rb +575 -0
- data/lib/milk_tea/lsp/server/hover.rb +1465 -0
- data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
- data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
- data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
- data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
- data/lib/milk_tea/lsp/server/progress.rb +47 -0
- data/lib/milk_tea/lsp/server/references.rb +433 -0
- data/lib/milk_tea/lsp/server/rename.rb +598 -0
- data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
- data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
- data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
- data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
- data/lib/milk_tea/lsp/server/utilities.rb +520 -0
- data/lib/milk_tea/lsp/server.rb +415 -0
- data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
- data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
- data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
- data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
- data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
- data/lib/milk_tea/lsp/workspace/store.rb +154 -0
- data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
- data/lib/milk_tea/lsp/workspace.rb +127 -0
- data/lib/milk_tea/lsp.rb +13 -0
- data/lib/milk_tea/packages/atomic_write.rb +63 -0
- data/lib/milk_tea/packages/dependency_solver.rb +194 -0
- data/lib/milk_tea/packages/graph.rb +139 -0
- data/lib/milk_tea/packages/lock.rb +421 -0
- data/lib/milk_tea/packages/manager_cli.rb +485 -0
- data/lib/milk_tea/packages/manifest.rb +356 -0
- data/lib/milk_tea/packages/manifest_editor.rb +134 -0
- data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
- data/lib/milk_tea/packages/registry_store.rb +420 -0
- data/lib/milk_tea/packages/services.rb +41 -0
- data/lib/milk_tea/packages/source_cache.rb +71 -0
- data/lib/milk_tea/packages/source_fetcher.rb +124 -0
- data/lib/milk_tea/packages/source_resolver.rb +389 -0
- data/lib/milk_tea/packages/version.rb +164 -0
- data/lib/milk_tea/packages.rb +16 -0
- data/lib/milk_tea/tooling/asset_pack.rb +141 -0
- data/lib/milk_tea/tooling/build.rb +1124 -0
- data/lib/milk_tea/tooling/build_cache.rb +240 -0
- data/lib/milk_tea/tooling/cli.rb +2688 -0
- data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
- data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
- data/lib/milk_tea/tooling/debug_map.rb +248 -0
- data/lib/milk_tea/tooling/docs_app.rb +369 -0
- data/lib/milk_tea/tooling/error_formatter.rb +86 -0
- data/lib/milk_tea/tooling/formatter.rb +787 -0
- data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
- data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
- data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
- data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
- data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
- data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
- data/lib/milk_tea/tooling/linter/rules.rb +593 -0
- data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
- data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
- data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
- data/lib/milk_tea/tooling/linter.rb +798 -0
- data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
- data/lib/milk_tea/tooling/public/css/docs.css +166 -0
- data/lib/milk_tea/tooling/public/js/docs.js +94 -0
- data/lib/milk_tea/tooling/run.rb +408 -0
- data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
- data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
- data/lib/milk_tea/tooling/views/404.erb +7 -0
- data/lib/milk_tea/tooling/views/index.erb +87 -0
- data/lib/milk_tea/tooling/views/layout.erb +69 -0
- data/lib/milk_tea/tooling/views/module.erb +97 -0
- data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
- data/lib/milk_tea/tooling.rb +20 -0
- data/lib/milk_tea.rb +8 -0
- metadata +426 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
module DAP
|
|
5
|
+
class Server
|
|
6
|
+
module ServerLLDBBackend
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def using_lldb_backend?
|
|
10
|
+
@session.backend_kind == "lldb-dap"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def start_lldb_backend(args)
|
|
14
|
+
return if @lldb_backend
|
|
15
|
+
|
|
16
|
+
adapter_path = args["adapterPath"].to_s
|
|
17
|
+
adapter_command = if adapter_path.empty?
|
|
18
|
+
@default_adapter_command || ["lldb-dap"]
|
|
19
|
+
else
|
|
20
|
+
expanded = File.expand_path(adapter_path)
|
|
21
|
+
expanded.end_with?(".rb") ? [RbConfig.ruby, expanded] : [expanded]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@lldb_backend = if @backend_factory
|
|
25
|
+
build_backend_via_factory(adapter_command)
|
|
26
|
+
else
|
|
27
|
+
Backends::LLDBDAP.new(
|
|
28
|
+
adapter_command: adapter_command,
|
|
29
|
+
on_event: method(:handle_backend_event),
|
|
30
|
+
on_request: method(:handle_backend_request)
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
@lldb_backend.start!
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def stop_lldb_backend
|
|
37
|
+
@backend_start_thread&.join(0.2)
|
|
38
|
+
@backend_start_thread = nil
|
|
39
|
+
@backend_auto_continue_after_configuration = false
|
|
40
|
+
@backend_stopped_thread_id = nil
|
|
41
|
+
@backend_pause_requested = false
|
|
42
|
+
@lldb_backend&.stop!
|
|
43
|
+
ensure
|
|
44
|
+
@lldb_backend = nil
|
|
45
|
+
@backend_initialized = false
|
|
46
|
+
@backend_capabilities = {}
|
|
47
|
+
reset_backend_configuration_ready
|
|
48
|
+
clear_debug_context
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def ensure_backend_initialized(client_arguments)
|
|
52
|
+
return { "success" => true, "body" => @backend_capabilities } if @backend_initialized
|
|
53
|
+
|
|
54
|
+
response = backend_request("initialize", backend_initialize_arguments(client_arguments))
|
|
55
|
+
if response["success"]
|
|
56
|
+
@backend_initialized = true
|
|
57
|
+
@backend_capabilities = response["body"] || {}
|
|
58
|
+
end
|
|
59
|
+
response
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def backend_request(command, arguments)
|
|
63
|
+
unless @lldb_backend
|
|
64
|
+
return {
|
|
65
|
+
"success" => false,
|
|
66
|
+
"message" => "lldb-dap backend is not running"
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
@lldb_backend.request(command, arguments)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def request_backend_terminate(arguments)
|
|
74
|
+
join_background_threads
|
|
75
|
+
terminate_response = backend_request("terminate", arguments)
|
|
76
|
+
return terminate_response if terminate_response["success"]
|
|
77
|
+
return terminate_response unless backend_terminate_unsupported?(terminate_response)
|
|
78
|
+
|
|
79
|
+
disconnect_response = backend_request("disconnect", arguments)
|
|
80
|
+
return disconnect_response unless disconnect_response["success"]
|
|
81
|
+
|
|
82
|
+
stop_lldb_backend
|
|
83
|
+
cleanup_tmp_dirs
|
|
84
|
+
unless @session.terminated?
|
|
85
|
+
@session.terminate!
|
|
86
|
+
write_event("terminated")
|
|
87
|
+
end
|
|
88
|
+
@session.request_exit!
|
|
89
|
+
disconnect_response
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def start_backend_start_request(request, arguments)
|
|
93
|
+
@backend_start_thread = Thread.new do
|
|
94
|
+
Thread.current.report_on_exception = false if Thread.current.respond_to?(:report_on_exception=)
|
|
95
|
+
|
|
96
|
+
backend_response = backend_request(request["command"], arguments)
|
|
97
|
+
write_backend_response(request, backend_response)
|
|
98
|
+
rescue StandardError => e
|
|
99
|
+
write_error_response(request, "Internal error: #{e.message}")
|
|
100
|
+
ensure
|
|
101
|
+
@backend_start_thread = nil
|
|
102
|
+
@backend_configuration_mutex.synchronize do
|
|
103
|
+
@backend_configuration_condition.broadcast
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def write_backend_response(request, backend_response)
|
|
109
|
+
if backend_response["success"]
|
|
110
|
+
write_response(request, backend_response["body"] || {})
|
|
111
|
+
else
|
|
112
|
+
write_error_response(request, backend_error_message(backend_response))
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def backend_error_message(backend_response)
|
|
117
|
+
message = backend_response["message"].to_s
|
|
118
|
+
return message unless message.empty?
|
|
119
|
+
|
|
120
|
+
error_body = backend_response.dig("body", "error")
|
|
121
|
+
if error_body.is_a?(Hash)
|
|
122
|
+
format = error_body["format"].to_s
|
|
123
|
+
return format unless format.empty?
|
|
124
|
+
|
|
125
|
+
error_message = error_body["message"].to_s
|
|
126
|
+
return error_message unless error_message.empty?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
"backend request failed"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def backend_terminate_unsupported?(backend_response)
|
|
133
|
+
message = backend_error_message(backend_response).downcase
|
|
134
|
+
message.include?("unknown request") || message.include?("unsupported command")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def build_backend_via_factory(adapter_command)
|
|
138
|
+
arity = @backend_factory.arity
|
|
139
|
+
if arity < 0 || arity >= 3
|
|
140
|
+
@backend_factory.call(adapter_command, method(:handle_backend_event), method(:handle_backend_request))
|
|
141
|
+
else
|
|
142
|
+
@backend_factory.call(adapter_command, method(:handle_backend_event))
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def handle_backend_event(message)
|
|
147
|
+
event = message["event"].to_s
|
|
148
|
+
body = message["body"]
|
|
149
|
+
|
|
150
|
+
# The outer Milk Tea adapter owns the initialize/initialized handshake.
|
|
151
|
+
# Forwarding lldb-dap's own initialized event causes VS Code to repeat
|
|
152
|
+
# the configuration phase and emit a second configurationDone.
|
|
153
|
+
if event == "initialized"
|
|
154
|
+
mark_backend_configuration_ready
|
|
155
|
+
return
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
if event == "stopped"
|
|
159
|
+
pause_requested = @backend_pause_requested
|
|
160
|
+
body = rewrite_backend_stopped_event_body(body)
|
|
161
|
+
emit_pause_diagnostic_async(body) if pause_requested
|
|
162
|
+
@backend_stopped_thread_id = body && body["threadId"]
|
|
163
|
+
@backend_pause_requested = false
|
|
164
|
+
@last_forwarded_continued_signature = nil
|
|
165
|
+
elsif event == "continued"
|
|
166
|
+
signature = continued_event_signature(body)
|
|
167
|
+
return if signature && signature == @last_forwarded_continued_signature
|
|
168
|
+
|
|
169
|
+
@backend_stopped_thread_id = nil
|
|
170
|
+
@backend_pause_requested = false
|
|
171
|
+
@last_forwarded_continued_signature = signature
|
|
172
|
+
elsif event == "terminated" || event == "exited"
|
|
173
|
+
@backend_stopped_thread_id = nil
|
|
174
|
+
@backend_pause_requested = false
|
|
175
|
+
@last_forwarded_continued_signature = nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
write_event(event, body)
|
|
179
|
+
|
|
180
|
+
return unless event == "terminated" || event == "exited"
|
|
181
|
+
|
|
182
|
+
@session.terminate!
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def handle_backend_request(message)
|
|
186
|
+
client_seq = @session.next_seq
|
|
187
|
+
queue = Queue.new
|
|
188
|
+
@client_request_mutex.synchronize do
|
|
189
|
+
@pending_client_responses[client_seq] = queue
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
write_message({
|
|
193
|
+
seq: client_seq,
|
|
194
|
+
type: "request",
|
|
195
|
+
command: message["command"],
|
|
196
|
+
arguments: message["arguments"] || {}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
timeout_seconds = reverse_request_timeout
|
|
200
|
+
client_response = Timeout.timeout(timeout_seconds) { queue.pop }
|
|
201
|
+
build_backend_request_response(message, client_response)
|
|
202
|
+
rescue Timeout::Error
|
|
203
|
+
{
|
|
204
|
+
"seq" => @session.next_seq,
|
|
205
|
+
"type" => "response",
|
|
206
|
+
"request_seq" => message["seq"],
|
|
207
|
+
"success" => false,
|
|
208
|
+
"command" => message["command"],
|
|
209
|
+
"message" => format("client request timed out after %.1fs: %s", timeout_seconds, message["command"])
|
|
210
|
+
}
|
|
211
|
+
ensure
|
|
212
|
+
@client_request_mutex.synchronize do
|
|
213
|
+
@pending_client_responses.delete(client_seq) if defined?(client_seq)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def reverse_request_timeout
|
|
218
|
+
timeout = @reverse_request_timeout.to_f
|
|
219
|
+
timeout.positive? ? timeout : 15.0
|
|
220
|
+
rescue StandardError
|
|
221
|
+
15.0
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def handle_client_response(message)
|
|
225
|
+
queue = @client_request_mutex.synchronize do
|
|
226
|
+
@pending_client_responses[message["request_seq"]]
|
|
227
|
+
end
|
|
228
|
+
queue&.push(message)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def build_backend_request_response(request, client_response)
|
|
232
|
+
response = {
|
|
233
|
+
"seq" => @session.next_seq,
|
|
234
|
+
"type" => "response",
|
|
235
|
+
"request_seq" => request["seq"],
|
|
236
|
+
"success" => !!client_response["success"],
|
|
237
|
+
"command" => request["command"]
|
|
238
|
+
}
|
|
239
|
+
response["body"] = client_response["body"] if client_response.key?("body")
|
|
240
|
+
response["message"] = client_response["message"] if client_response["message"]
|
|
241
|
+
response
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def backend_configuration_ready?
|
|
245
|
+
@backend_configuration_mutex.synchronize { @backend_configuration_ready }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def backend_start_pending?
|
|
249
|
+
thread = @backend_start_thread
|
|
250
|
+
thread && thread.alive?
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def backend_configuration_pending?
|
|
254
|
+
backend_start_pending? && !backend_configuration_ready?
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def wait_for_backend_configuration_ready(timeout: 30)
|
|
258
|
+
return true unless backend_configuration_pending?
|
|
259
|
+
|
|
260
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
|
261
|
+
|
|
262
|
+
@backend_configuration_mutex.synchronize do
|
|
263
|
+
while @backend_start_thread&.alive? && !@backend_configuration_ready
|
|
264
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
265
|
+
return false if remaining <= 0
|
|
266
|
+
|
|
267
|
+
@backend_configuration_condition.wait(@backend_configuration_mutex, remaining)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
true
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def mark_backend_configuration_ready
|
|
275
|
+
@backend_configuration_mutex.synchronize do
|
|
276
|
+
@backend_configuration_ready = true
|
|
277
|
+
@backend_configuration_condition.broadcast
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def reset_backend_configuration_ready
|
|
282
|
+
@backend_configuration_mutex.synchronize do
|
|
283
|
+
@backend_configuration_ready = false
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def backend_thread_id_for_auto_continue
|
|
288
|
+
response = backend_request("threads", {})
|
|
289
|
+
return nil unless response["success"]
|
|
290
|
+
|
|
291
|
+
threads = response.dig("body", "threads")
|
|
292
|
+
return nil unless threads.is_a?(Array)
|
|
293
|
+
|
|
294
|
+
first_thread = threads.first
|
|
295
|
+
return nil unless first_thread.is_a?(Hash)
|
|
296
|
+
|
|
297
|
+
first_thread["id"]
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def backend_initialize_arguments(client_arguments)
|
|
301
|
+
{
|
|
302
|
+
"adapterID" => "milk-tea",
|
|
303
|
+
"clientID" => "milk-tea",
|
|
304
|
+
"linesStartAt1" => true,
|
|
305
|
+
"columnsStartAt1" => true,
|
|
306
|
+
"pathFormat" => "path"
|
|
307
|
+
}.merge(client_arguments)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def filter_breakpoint_for_backend(breakpoint)
|
|
311
|
+
breakpoint.each_with_object({}) do |(key, value), result|
|
|
312
|
+
next if key.to_s == "id" || key.to_s == "verified"
|
|
313
|
+
|
|
314
|
+
result[key.to_s] = value
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def backend_continue_arguments
|
|
319
|
+
thread_id = @backend_stopped_thread_id || backend_thread_id_for_auto_continue
|
|
320
|
+
return {} unless thread_id
|
|
321
|
+
|
|
322
|
+
{ "threadId" => thread_id }
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def continued_event_signature(body)
|
|
326
|
+
return nil unless body.is_a?(Hash)
|
|
327
|
+
|
|
328
|
+
[
|
|
329
|
+
normalize_reference_key(dap_value(body, "threadId")),
|
|
330
|
+
dap_value(body, "allThreadsContinued") == true,
|
|
331
|
+
]
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def rewrite_backend_stopped_event_body(body)
|
|
335
|
+
return body unless @backend_pause_requested
|
|
336
|
+
return body unless body.is_a?(Hash)
|
|
337
|
+
|
|
338
|
+
reason = dap_value(body, "reason").to_s
|
|
339
|
+
description = dap_value(body, "description").to_s
|
|
340
|
+
text = dap_value(body, "text").to_s
|
|
341
|
+
return body unless reason == "exception"
|
|
342
|
+
return body unless description.match?(/\bSIGSTOP\b/) || text.match?(/\bSIGSTOP\b/)
|
|
343
|
+
|
|
344
|
+
rewritten = body.dup
|
|
345
|
+
dap_set(rewritten, "reason", "pause")
|
|
346
|
+
dap_set(rewritten, "description", "Paused") if !description.empty? || rewritten.key?("description") || rewritten.key?(:description)
|
|
347
|
+
dap_set(rewritten, "text", "Paused") if !text.empty? || rewritten.key?("text") || rewritten.key?(:text)
|
|
348
|
+
rewritten
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def effective_adapter_capabilities
|
|
352
|
+
capabilities = ADAPTER_CAPABILITIES.merge(
|
|
353
|
+
@backend_capabilities.transform_keys { |key| key.to_sym }
|
|
354
|
+
)
|
|
355
|
+
capabilities[:supportsSetVariable] = true if backend_supports_set_variable? || backend_supports_set_expression?
|
|
356
|
+
|
|
357
|
+
backend_filters = @backend_capabilities["exceptionBreakpointFilters"]
|
|
358
|
+
if backend_filters.is_a?(Array) && !backend_filters.empty?
|
|
359
|
+
capabilities[:exceptionBreakpointFilters] = backend_filters
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
capabilities
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def backend_supports_set_variable?
|
|
366
|
+
capability_enabled?("supportsSetVariable")
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def backend_supports_set_expression?
|
|
370
|
+
capability_enabled?("supportsSetExpression")
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def capability_enabled?(name)
|
|
374
|
+
value = @backend_capabilities[name] || @backend_capabilities[name.to_sym]
|
|
375
|
+
!!value
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
module DAP
|
|
5
|
+
class Server
|
|
6
|
+
module ServerPauseDiagnostics
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def emit_pause_diagnostic_async(body)
|
|
10
|
+
return unless body.is_a?(Hash)
|
|
11
|
+
|
|
12
|
+
thread_id = normalize_reference_key(dap_value(body, "threadId"))
|
|
13
|
+
return if thread_id.nil?
|
|
14
|
+
|
|
15
|
+
track_background_thread(Thread.new(thread_id) do |diagnostic_thread_id|
|
|
16
|
+
stack_response = backend_request("stackTrace", {
|
|
17
|
+
"threadId" => diagnostic_thread_id,
|
|
18
|
+
"startFrame" => 0,
|
|
19
|
+
"levels" => 8
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
write_event("output", {
|
|
23
|
+
category: "console",
|
|
24
|
+
output: "#{pause_diagnostic_output(diagnostic_thread_id, stack_response)}\n"
|
|
25
|
+
})
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
write_event("output", {
|
|
28
|
+
category: "console",
|
|
29
|
+
output: "[milk-tea dap] pause top frame unavailable thread=#{diagnostic_thread_id}: #{e.message}\n"
|
|
30
|
+
})
|
|
31
|
+
end)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def pause_diagnostic_output(thread_id, stack_response)
|
|
35
|
+
unless stack_response["success"]
|
|
36
|
+
return "[milk-tea dap] pause focus unavailable thread=#{thread_id}: #{backend_error_message(stack_response)}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
frames = stack_response.dig("body", "stackFrames")
|
|
40
|
+
frames = frames.select { |candidate| candidate.is_a?(Hash) } if frames.is_a?(Array)
|
|
41
|
+
return "[milk-tea dap] pause focus unavailable thread=#{thread_id}: no stack frames" if !frames.is_a?(Array) || frames.empty?
|
|
42
|
+
|
|
43
|
+
raw_frame = frames.first
|
|
44
|
+
informative_index = frames.index { |frame| informative_pause_frame?(frame) }
|
|
45
|
+
|
|
46
|
+
unless informative_index
|
|
47
|
+
return "[milk-tea dap] pause top frame thread=#{thread_id}: #{pause_frame_summary(raw_frame, include_ip: true)}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
focus_frames = frames.drop(informative_index).first(3)
|
|
51
|
+
focus = focus_frames.map { |frame| pause_frame_summary(frame) }.join(" <- ")
|
|
52
|
+
raw_suffix = informative_index.zero? ? "" : " raw=#{pause_frame_summary(raw_frame, include_ip: true)}"
|
|
53
|
+
|
|
54
|
+
"[milk-tea dap] pause focus thread=#{thread_id}: #{focus}#{raw_suffix}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def informative_pause_frame?(frame)
|
|
58
|
+
name = dap_value(frame, "name").to_s
|
|
59
|
+
return false if name.empty?
|
|
60
|
+
return false if name.match?(/\A___lldb_unnamed_symbol_/)
|
|
61
|
+
return false if %w[clock_nanosleep __nanosleep nanosleep].include?(name)
|
|
62
|
+
|
|
63
|
+
source = dap_value(frame, "source")
|
|
64
|
+
source_path = source.is_a?(Hash) ? dap_value(source, "path").to_s : ""
|
|
65
|
+
|
|
66
|
+
return true if source_path.start_with?("/usr/src/debug/")
|
|
67
|
+
return true if !source_path.empty? && !source_path.start_with?("/usr/lib/") && !source_path.start_with?("/lib/")
|
|
68
|
+
|
|
69
|
+
true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def pause_frame_summary(frame, include_ip: false)
|
|
73
|
+
frame_name = dap_value(frame, "name").to_s
|
|
74
|
+
frame_name = "(anonymous)" if frame_name.empty?
|
|
75
|
+
|
|
76
|
+
instruction_pointer = dap_value(frame, "instructionPointerReference").to_s
|
|
77
|
+
source = dap_value(frame, "source")
|
|
78
|
+
source_path = source.is_a?(Hash) ? dap_value(source, "path").to_s : ""
|
|
79
|
+
source_name = source.is_a?(Hash) ? dap_value(source, "name").to_s : ""
|
|
80
|
+
line = dap_value(frame, "line")
|
|
81
|
+
|
|
82
|
+
location_source = if !source_path.empty? && !source_path.include?("`")
|
|
83
|
+
source_path
|
|
84
|
+
elsif !source_name.empty?
|
|
85
|
+
source_name
|
|
86
|
+
elsif !source_path.empty?
|
|
87
|
+
source_path
|
|
88
|
+
else
|
|
89
|
+
instruction_pointer
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
location = if location_source.to_s.empty?
|
|
93
|
+
"(unknown)"
|
|
94
|
+
elsif line.to_i.positive? && !location_source.include?(":#{line}")
|
|
95
|
+
"#{location_source}:#{line}"
|
|
96
|
+
else
|
|
97
|
+
location_source
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if include_ip && !instruction_pointer.empty? && location != instruction_pointer
|
|
101
|
+
"#{frame_name} @ #{location} ip=#{instruction_pointer}"
|
|
102
|
+
else
|
|
103
|
+
"#{frame_name} @ #{location}"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
module DAP
|
|
5
|
+
class Server
|
|
6
|
+
module ServerUtilities
|
|
7
|
+
PERF_LOG_THRESHOLD_MS = 20
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def register_handlers
|
|
12
|
+
@handlers["initialize"] = method(:handle_initialize)
|
|
13
|
+
@handlers["setBreakpoints"] = method(:handle_set_breakpoints)
|
|
14
|
+
@handlers["setFunctionBreakpoints"] = method(:handle_set_function_breakpoints)
|
|
15
|
+
@handlers["configurationDone"] = method(:handle_configuration_done)
|
|
16
|
+
@handlers["launch"] = method(:handle_launch)
|
|
17
|
+
@handlers["attach"] = method(:handle_attach)
|
|
18
|
+
@handlers["threads"] = method(:handle_threads)
|
|
19
|
+
@handlers["stackTrace"] = method(:handle_stack_trace)
|
|
20
|
+
@handlers["scopes"] = method(:handle_scopes)
|
|
21
|
+
@handlers["variables"] = method(:handle_variables)
|
|
22
|
+
@handlers["continue"] = method(:handle_continue)
|
|
23
|
+
@handlers["next"] = method(:handle_next)
|
|
24
|
+
@handlers["stepIn"] = method(:handle_step_in)
|
|
25
|
+
@handlers["stepOut"] = method(:handle_step_out)
|
|
26
|
+
@handlers["pause"] = method(:handle_pause)
|
|
27
|
+
@handlers["terminate"] = method(:handle_terminate)
|
|
28
|
+
@handlers["disconnect"] = method(:handle_disconnect)
|
|
29
|
+
@handlers["setExceptionBreakpoints"] = method(:handle_set_exception_breakpoints)
|
|
30
|
+
@handlers["evaluate"] = method(:handle_evaluate)
|
|
31
|
+
@handlers["setExpression"] = method(:handle_set_expression)
|
|
32
|
+
@handlers["setVariable"] = method(:handle_set_variable)
|
|
33
|
+
@handlers["dataBreakpointInfo"] = method(:handle_data_breakpoint_info)
|
|
34
|
+
@handlers["source"] = method(:handle_source)
|
|
35
|
+
@handlers["loadedSources"] = method(:handle_loaded_sources)
|
|
36
|
+
@handlers["restart"] = method(:handle_restart)
|
|
37
|
+
@handlers["cancel"] = method(:handle_cancel)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def process_message(message)
|
|
41
|
+
if message["type"] == "response"
|
|
42
|
+
handle_client_response(message)
|
|
43
|
+
return
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
return unless message["type"] == "request"
|
|
47
|
+
|
|
48
|
+
command = message["command"]
|
|
49
|
+
handler = @handlers[command]
|
|
50
|
+
|
|
51
|
+
unless command == "initialize" || @session.initialized?
|
|
52
|
+
write_error_response(message, "initialize request must be sent first")
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
57
|
+
|
|
58
|
+
if handler
|
|
59
|
+
handler.call(message)
|
|
60
|
+
elsif using_lldb_backend?
|
|
61
|
+
backend_response = backend_request(command, message["arguments"] || {})
|
|
62
|
+
write_backend_response(message, backend_response)
|
|
63
|
+
else
|
|
64
|
+
write_error_response(message, "Command '#{command}' is not supported by the process backend. Use the lldb-dap backend for full debugging features.")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(1)
|
|
68
|
+
if dap_perf_logging? && (dap_perf_verbose? || elapsed_ms > PERF_LOG_THRESHOLD_MS)
|
|
69
|
+
detail = dap_perf_verbose? ? " #{summarize_dap_arguments(message['arguments'])}" : ""
|
|
70
|
+
warn "[DAP perf] #{command} #{elapsed_ms}ms#{detail}"
|
|
71
|
+
end
|
|
72
|
+
rescue StandardError => e
|
|
73
|
+
write_error_response(message, "Internal error: #{e.message}")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def dap_perf_logging?
|
|
77
|
+
@dap_perf_logging ||= !ENV.fetch('MILK_TEA_DAP_PERF', nil).to_s.empty?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def dap_perf_verbose?
|
|
81
|
+
@dap_perf_verbose ||= ENV.fetch('MILK_TEA_DAP_PERF', nil).to_s == 'verbose'
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def summarize_dap_arguments(arguments)
|
|
85
|
+
return "" unless arguments.is_a?(Hash)
|
|
86
|
+
|
|
87
|
+
keys = arguments.keys.map(&:to_s).sort
|
|
88
|
+
source_path = arguments.dig('source', 'path')
|
|
89
|
+
frame_id = arguments['frameId']
|
|
90
|
+
expression = arguments['expression']
|
|
91
|
+
|
|
92
|
+
bits = []
|
|
93
|
+
bits << "keys=#{keys.join(',')}" unless keys.empty?
|
|
94
|
+
bits << "source=#{source_path}" if source_path
|
|
95
|
+
bits << "frameId=#{frame_id}" if frame_id
|
|
96
|
+
bits << "expr=#{expression.inspect}" if expression
|
|
97
|
+
bits.join(' ')
|
|
98
|
+
rescue StandardError
|
|
99
|
+
""
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def dap_value(payload, key)
|
|
103
|
+
return nil unless payload.is_a?(Hash)
|
|
104
|
+
|
|
105
|
+
payload[key] || payload[key.to_sym]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def dap_set(payload, key, value)
|
|
109
|
+
return unless payload.is_a?(Hash)
|
|
110
|
+
|
|
111
|
+
if payload.key?(key)
|
|
112
|
+
payload[key] = value
|
|
113
|
+
elsif payload.key?(key.to_sym)
|
|
114
|
+
payload[key.to_sym] = value
|
|
115
|
+
else
|
|
116
|
+
payload[key] = value
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def normalize_reference_key(value)
|
|
121
|
+
return nil if value.nil?
|
|
122
|
+
return value if value.is_a?(Integer)
|
|
123
|
+
|
|
124
|
+
text = value.to_s.strip
|
|
125
|
+
return text.to_i if text.match?(/\A\d+\z/)
|
|
126
|
+
|
|
127
|
+
text
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def track_background_thread(thread)
|
|
131
|
+
@background_threads_mutex.synchronize do
|
|
132
|
+
@background_threads << thread
|
|
133
|
+
end
|
|
134
|
+
thread
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def join_background_threads(timeout: 0.5)
|
|
138
|
+
threads = @background_threads_mutex.synchronize do
|
|
139
|
+
threads = @background_threads
|
|
140
|
+
@background_threads = []
|
|
141
|
+
threads
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
threads.each do |thread|
|
|
145
|
+
thread.join(timeout)
|
|
146
|
+
rescue StandardError
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def cleanup_tmp_dirs
|
|
152
|
+
@tmp_dirs.each do |dir|
|
|
153
|
+
FileUtils.remove_entry(dir) if File.exist?(dir)
|
|
154
|
+
end
|
|
155
|
+
@tmp_dirs.clear
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|