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.
Files changed (258) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/AUTHORS +3 -0
  4. data/Gemfile +19 -0
  5. data/Gemfile.lock +82 -0
  6. data/LICENSE +21 -0
  7. data/README.md +1208 -0
  8. data/Rakefile +332 -0
  9. data/bin/mtc +8 -0
  10. data/bin/profile-mtc-checks +133 -0
  11. data/bin/tracy-profiler +0 -0
  12. data/docs/build-guide.md +536 -0
  13. data/docs/index.html +2778 -0
  14. data/docs/language-design.md +1519 -0
  15. data/docs/language-manual.md +1534 -0
  16. data/lib/milk_tea/base.rb +49 -0
  17. data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
  18. data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
  19. data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
  20. data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
  21. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
  22. data/lib/milk_tea/bindings/bindgen.rb +207 -0
  23. data/lib/milk_tea/bindings/cli.rb +121 -0
  24. data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
  25. data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
  26. data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
  27. data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
  28. data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
  29. data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
  30. data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
  31. data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
  32. data/lib/milk_tea/bindings/steamworks.rb +629 -0
  33. data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
  34. data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
  35. data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
  36. data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
  37. data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
  38. data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
  39. data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
  40. data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
  41. data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
  42. data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
  43. data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
  44. data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
  45. data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
  46. data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
  47. data/lib/milk_tea/bindings.rb +23 -0
  48. data/lib/milk_tea/core/ast.rb +311 -0
  49. data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
  50. data/lib/milk_tea/core/binding_types.rb +18 -0
  51. data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
  52. data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
  53. data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
  54. data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
  55. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
  56. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
  57. data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
  58. data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
  59. data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
  60. data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
  61. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
  62. data/lib/milk_tea/core/c_backend/statements.rb +563 -0
  63. data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
  64. data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
  65. data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
  66. data/lib/milk_tea/core/c_backend.rb +287 -0
  67. data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
  68. data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
  69. data/lib/milk_tea/core/compile_time.rb +329 -0
  70. data/lib/milk_tea/core/control_flow/builder.rb +582 -0
  71. data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
  72. data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
  73. data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
  74. data/lib/milk_tea/core/control_flow/graph.rb +90 -0
  75. data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
  76. data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
  77. data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
  78. data/lib/milk_tea/core/control_flow/termination.rb +31 -0
  79. data/lib/milk_tea/core/control_flow.rb +34 -0
  80. data/lib/milk_tea/core/cst.rb +48 -0
  81. data/lib/milk_tea/core/cst_builder.rb +19 -0
  82. data/lib/milk_tea/core/ir.rb +85 -0
  83. data/lib/milk_tea/core/keywords.rb +97 -0
  84. data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
  85. data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
  86. data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
  87. data/lib/milk_tea/core/lexer/indentation.rb +62 -0
  88. data/lib/milk_tea/core/lexer/numbers.rb +96 -0
  89. data/lib/milk_tea/core/lexer/recovery.rb +32 -0
  90. data/lib/milk_tea/core/lexer/strings.rb +167 -0
  91. data/lib/milk_tea/core/lexer/symbols.rb +71 -0
  92. data/lib/milk_tea/core/lexer/trivia.rb +53 -0
  93. data/lib/milk_tea/core/lexer.rb +430 -0
  94. data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
  95. data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
  96. data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
  97. data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
  98. data/lib/milk_tea/core/lowering/async.rb +714 -0
  99. data/lib/milk_tea/core/lowering/block.rb +1052 -0
  100. data/lib/milk_tea/core/lowering/calls.rb +1565 -0
  101. data/lib/milk_tea/core/lowering/declarations.rb +214 -0
  102. data/lib/milk_tea/core/lowering/dyn.rb +206 -0
  103. data/lib/milk_tea/core/lowering/events.rb +1054 -0
  104. data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
  105. data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
  106. data/lib/milk_tea/core/lowering/functions.rb +242 -0
  107. data/lib/milk_tea/core/lowering/loops.rb +1087 -0
  108. data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
  109. data/lib/milk_tea/core/lowering/proc.rb +419 -0
  110. data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
  111. data/lib/milk_tea/core/lowering/scans.rb +220 -0
  112. data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
  113. data/lib/milk_tea/core/lowering/utils.rb +1453 -0
  114. data/lib/milk_tea/core/lowering.rb +378 -0
  115. data/lib/milk_tea/core/module_binder.rb +181 -0
  116. data/lib/milk_tea/core/module_loader/errors.rb +21 -0
  117. data/lib/milk_tea/core/module_loader.rb +479 -0
  118. data/lib/milk_tea/core/module_path_resolver.rb +153 -0
  119. data/lib/milk_tea/core/module_roots.rb +88 -0
  120. data/lib/milk_tea/core/parser/attributes.rb +71 -0
  121. data/lib/milk_tea/core/parser/blocks.rb +119 -0
  122. data/lib/milk_tea/core/parser/declarations.rb +749 -0
  123. data/lib/milk_tea/core/parser/expressions.rb +624 -0
  124. data/lib/milk_tea/core/parser/recovery.rb +131 -0
  125. data/lib/milk_tea/core/parser/statements.rb +756 -0
  126. data/lib/milk_tea/core/parser/types.rb +271 -0
  127. data/lib/milk_tea/core/parser.rb +400 -0
  128. data/lib/milk_tea/core/prelude_installer.rb +29 -0
  129. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
  130. data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
  131. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
  132. data/lib/milk_tea/core/pretty_printer.rb +17 -0
  133. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
  134. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
  135. data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
  136. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
  137. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
  138. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
  139. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
  140. data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
  141. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
  142. data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
  143. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
  144. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
  145. data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
  146. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
  147. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
  148. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
  149. data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
  150. data/lib/milk_tea/core/token.rb +26 -0
  151. data/lib/milk_tea/core/token_stream.rb +30 -0
  152. data/lib/milk_tea/core/types/layout.rb +243 -0
  153. data/lib/milk_tea/core/types/predicates.rb +609 -0
  154. data/lib/milk_tea/core/types/registry.rb +83 -0
  155. data/lib/milk_tea/core/types/types.rb +1696 -0
  156. data/lib/milk_tea/core/types/visitor.rb +442 -0
  157. data/lib/milk_tea/core.rb +25 -0
  158. data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
  159. data/lib/milk_tea/dap/protocol.rb +58 -0
  160. data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
  161. data/lib/milk_tea/dap/server/debug_map.rb +291 -0
  162. data/lib/milk_tea/dap/server/handlers.rb +374 -0
  163. data/lib/milk_tea/dap/server/launch.rb +261 -0
  164. data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
  165. data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
  166. data/lib/milk_tea/dap/server/utilities.rb +160 -0
  167. data/lib/milk_tea/dap/server/wire.rb +55 -0
  168. data/lib/milk_tea/dap/server.rb +131 -0
  169. data/lib/milk_tea/dap/session.rb +152 -0
  170. data/lib/milk_tea/dap.rb +6 -0
  171. data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
  172. data/lib/milk_tea/lsp/diagnostics.rb +611 -0
  173. data/lib/milk_tea/lsp/protocol.rb +104 -0
  174. data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
  175. data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
  176. data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
  177. data/lib/milk_tea/lsp/server/completion.rb +1099 -0
  178. data/lib/milk_tea/lsp/server/configuration.rb +167 -0
  179. data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
  180. data/lib/milk_tea/lsp/server/definition.rb +779 -0
  181. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
  182. data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
  183. data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
  184. data/lib/milk_tea/lsp/server/formatting.rb +575 -0
  185. data/lib/milk_tea/lsp/server/hover.rb +1465 -0
  186. data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
  187. data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
  188. data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
  189. data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
  190. data/lib/milk_tea/lsp/server/progress.rb +47 -0
  191. data/lib/milk_tea/lsp/server/references.rb +433 -0
  192. data/lib/milk_tea/lsp/server/rename.rb +598 -0
  193. data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
  194. data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
  195. data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
  196. data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
  197. data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
  198. data/lib/milk_tea/lsp/server/utilities.rb +520 -0
  199. data/lib/milk_tea/lsp/server.rb +415 -0
  200. data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
  201. data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
  202. data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
  203. data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
  204. data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
  205. data/lib/milk_tea/lsp/workspace/store.rb +154 -0
  206. data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
  207. data/lib/milk_tea/lsp/workspace.rb +127 -0
  208. data/lib/milk_tea/lsp.rb +13 -0
  209. data/lib/milk_tea/packages/atomic_write.rb +63 -0
  210. data/lib/milk_tea/packages/dependency_solver.rb +194 -0
  211. data/lib/milk_tea/packages/graph.rb +139 -0
  212. data/lib/milk_tea/packages/lock.rb +421 -0
  213. data/lib/milk_tea/packages/manager_cli.rb +485 -0
  214. data/lib/milk_tea/packages/manifest.rb +356 -0
  215. data/lib/milk_tea/packages/manifest_editor.rb +134 -0
  216. data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
  217. data/lib/milk_tea/packages/registry_store.rb +420 -0
  218. data/lib/milk_tea/packages/services.rb +41 -0
  219. data/lib/milk_tea/packages/source_cache.rb +71 -0
  220. data/lib/milk_tea/packages/source_fetcher.rb +124 -0
  221. data/lib/milk_tea/packages/source_resolver.rb +389 -0
  222. data/lib/milk_tea/packages/version.rb +164 -0
  223. data/lib/milk_tea/packages.rb +16 -0
  224. data/lib/milk_tea/tooling/asset_pack.rb +141 -0
  225. data/lib/milk_tea/tooling/build.rb +1124 -0
  226. data/lib/milk_tea/tooling/build_cache.rb +240 -0
  227. data/lib/milk_tea/tooling/cli.rb +2688 -0
  228. data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
  229. data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
  230. data/lib/milk_tea/tooling/debug_map.rb +248 -0
  231. data/lib/milk_tea/tooling/docs_app.rb +369 -0
  232. data/lib/milk_tea/tooling/error_formatter.rb +86 -0
  233. data/lib/milk_tea/tooling/formatter.rb +787 -0
  234. data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
  235. data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
  236. data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
  237. data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
  238. data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
  239. data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
  240. data/lib/milk_tea/tooling/linter/rules.rb +593 -0
  241. data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
  242. data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
  243. data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
  244. data/lib/milk_tea/tooling/linter.rb +798 -0
  245. data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
  246. data/lib/milk_tea/tooling/public/css/docs.css +166 -0
  247. data/lib/milk_tea/tooling/public/js/docs.js +94 -0
  248. data/lib/milk_tea/tooling/run.rb +408 -0
  249. data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
  250. data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
  251. data/lib/milk_tea/tooling/views/404.erb +7 -0
  252. data/lib/milk_tea/tooling/views/index.erb +87 -0
  253. data/lib/milk_tea/tooling/views/layout.erb +69 -0
  254. data/lib/milk_tea/tooling/views/module.erb +97 -0
  255. data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
  256. data/lib/milk_tea/tooling.rb +20 -0
  257. data/lib/milk_tea.rb +8 -0
  258. metadata +426 -0
@@ -0,0 +1,374 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module DAP
5
+ class Server
6
+ module ServerHandlers
7
+ private
8
+
9
+ def handle_initialize(message)
10
+ if @session.initialized?
11
+ write_error_response(message, "initialize can only be called once")
12
+ return
13
+ end
14
+
15
+ @session.initialize!
16
+
17
+ if default_backend_kind == "lldb-dap"
18
+ start_lldb_backend({})
19
+ backend_response = ensure_backend_initialized(message["arguments"] || {})
20
+ return write_backend_response(message, backend_response) unless backend_response["success"]
21
+ end
22
+
23
+ write_response(message, effective_adapter_capabilities)
24
+
25
+ write_event("initialized")
26
+ end
27
+
28
+ def handle_set_breakpoints(message)
29
+ args = message["arguments"] || {}
30
+ source = args["source"] || {}
31
+ source_path = source["path"].to_s
32
+ requested = args["breakpoints"] || Array(args["lines"]).map { |line| { "line" => line } }
33
+ breakpoints = @session.set_breakpoints(source_path, requested)
34
+
35
+ if using_lldb_backend?
36
+ if backend_configuration_pending?
37
+ @breakpoints_synced_to_backend = false
38
+ write_response(message, { breakpoints: breakpoints })
39
+ return
40
+ end
41
+
42
+ backend_response = backend_request("setBreakpoints", {
43
+ "source" => source,
44
+ "breakpoints" => requested
45
+ })
46
+ @breakpoints_synced_to_backend = false if backend_response["success"]
47
+ return write_backend_response(message, backend_response)
48
+ end
49
+
50
+ # Process backend: mark breakpoints unverified
51
+ if default_backend_kind == "process"
52
+ breakpoints.each do |bp|
53
+ bp["verified"] = false
54
+ bp["message"] = "Process backend runs without a debugger; set breakpoints via lldb-dap backend instead."
55
+ end
56
+ end
57
+
58
+ write_response(message, { breakpoints: breakpoints })
59
+ end
60
+
61
+ def handle_set_function_breakpoints(message)
62
+ args = message["arguments"] || {}
63
+ requested = args["breakpoints"] || []
64
+ breakpoints = @session.set_function_breakpoints(requested)
65
+
66
+ if using_lldb_backend?
67
+ if backend_configuration_pending?
68
+ @function_breakpoints_synced_to_backend = false
69
+ write_response(message, { breakpoints: breakpoints })
70
+ return
71
+ end
72
+
73
+ backend_response = backend_request("setFunctionBreakpoints", {
74
+ "breakpoints" => requested
75
+ })
76
+ @function_breakpoints_synced_to_backend = false if backend_response["success"]
77
+ return write_backend_response(message, backend_response)
78
+ end
79
+
80
+ write_response(message, { breakpoints: breakpoints })
81
+ end
82
+
83
+ def handle_configuration_done(message)
84
+ @session.configuration_done!
85
+
86
+ if using_lldb_backend?
87
+ backend_response = ensure_backend_initialized({})
88
+ return write_backend_response(message, backend_response) unless backend_response["success"]
89
+
90
+ unless wait_for_backend_configuration_ready
91
+ write_error_response(message, "lldb-dap did not become ready for configuration")
92
+ return
93
+ end
94
+
95
+ sync_breakpoints_to_backend
96
+ sync_function_breakpoints_to_backend
97
+ sync_exception_breakpoints_to_backend
98
+ backend_response = backend_request("configurationDone", {})
99
+ return write_backend_response(message, backend_response) unless backend_response["success"]
100
+
101
+ if @backend_auto_continue_after_configuration
102
+ continue_response = backend_request("continue", backend_continue_arguments)
103
+ @backend_auto_continue_after_configuration = false
104
+ return write_error_response(message, backend_error_message(continue_response)) unless continue_response["success"]
105
+ end
106
+
107
+ write_response(message, backend_response["body"] || {})
108
+ return
109
+ end
110
+
111
+ write_response(message, {})
112
+ maybe_start_or_stop_on_entry
113
+ end
114
+
115
+ def handle_launch(message)
116
+ request_start(message)
117
+ end
118
+
119
+ def handle_attach(message)
120
+ args = message["arguments"] || {}
121
+ unless args["backend"].to_s == "lldb-dap"
122
+ write_error_response(message, "attach requires the lldb-dap backend")
123
+ return
124
+ end
125
+
126
+ request_start(message)
127
+ end
128
+
129
+ def handle_threads(message)
130
+ return write_backend_response(message, backend_request("threads", {})) if using_lldb_backend?
131
+
132
+ write_response(message, {
133
+ threads: [
134
+ { id: @session.thread_id, name: "main" }
135
+ ]
136
+ })
137
+ end
138
+
139
+ def handle_stack_trace(message)
140
+ if using_lldb_backend?
141
+ maybe_load_debug_map_from_backend_modules
142
+ backend_response = backend_request("stackTrace", message["arguments"] || {})
143
+ rewrite_stack_trace_response(backend_response)
144
+ return write_backend_response(message, backend_response)
145
+ end
146
+
147
+ source_path = @session.program_path || "(unknown)"
148
+ write_response(message, {
149
+ stackFrames: [
150
+ {
151
+ id: 1,
152
+ name: "main",
153
+ line: 1,
154
+ column: 1,
155
+ source: {
156
+ name: File.basename(source_path),
157
+ path: source_path
158
+ }
159
+ }
160
+ ],
161
+ totalFrames: 1
162
+ })
163
+ end
164
+
165
+ def handle_scopes(message)
166
+ if using_lldb_backend?
167
+ arguments = message["arguments"] || {}
168
+ backend_response = backend_request("scopes", arguments)
169
+ rewrite_scopes_response(backend_response, arguments["frameId"])
170
+ return write_backend_response(message, backend_response)
171
+ end
172
+
173
+ write_response(message, {
174
+ scopes: [
175
+ {
176
+ name: "Locals",
177
+ variablesReference: 1,
178
+ expensive: false
179
+ }
180
+ ]
181
+ })
182
+ end
183
+
184
+ def handle_variables(message)
185
+ if using_lldb_backend?
186
+ arguments = message["arguments"] || {}
187
+ backend_response = backend_request("variables", arguments)
188
+ rewrite_variables_response(backend_response, arguments["variablesReference"])
189
+ return write_backend_response(message, backend_response)
190
+ end
191
+
192
+ _args = message["arguments"] || {}
193
+ write_response(message, { variables: [] })
194
+ end
195
+
196
+ def handle_continue(message)
197
+ return write_backend_response(message, backend_request("continue", message["arguments"] || {})) if using_lldb_backend?
198
+
199
+ start_runtime_if_needed
200
+ continue_runtime_if_paused
201
+ write_response(message, { allThreadsContinued: true })
202
+ write_event("continued", {
203
+ threadId: @session.thread_id,
204
+ allThreadsContinued: true
205
+ })
206
+ end
207
+
208
+ def handle_next(message)
209
+ return write_backend_response(message, backend_request("next", message["arguments"] || {})) if using_lldb_backend?
210
+
211
+ write_error_response(message, "next is not supported by the process backend")
212
+ end
213
+
214
+ def handle_step_in(message)
215
+ return write_backend_response(message, backend_request("stepIn", message["arguments"] || {})) if using_lldb_backend?
216
+
217
+ write_error_response(message, "stepIn is not supported by the process backend")
218
+ end
219
+
220
+ def handle_step_out(message)
221
+ return write_backend_response(message, backend_request("stepOut", message["arguments"] || {})) if using_lldb_backend?
222
+
223
+ write_error_response(message, "stepOut is not supported by the process backend")
224
+ end
225
+
226
+ def handle_pause(message)
227
+ if using_lldb_backend?
228
+ @backend_pause_requested = true
229
+ backend_response = backend_request("pause", message["arguments"] || {})
230
+ @backend_pause_requested = false unless backend_response["success"]
231
+ return write_backend_response(message, backend_response)
232
+ end
233
+
234
+ unless pause_runtime_if_running
235
+ return write_error_response(message, "pause failed: process is not running")
236
+ end
237
+
238
+ write_response(message, {})
239
+ write_event("stopped", {
240
+ reason: "pause",
241
+ threadId: @session.thread_id,
242
+ allThreadsStopped: true
243
+ })
244
+ end
245
+
246
+ def handle_terminate(message)
247
+ if using_lldb_backend?
248
+ backend_response = request_backend_terminate(message["arguments"] || {})
249
+ write_backend_response(message, backend_response)
250
+ return
251
+ end
252
+
253
+ terminate_runtime_if_running
254
+ write_response(message, {})
255
+ return if @session.runtime_started?
256
+
257
+ @session.terminate!
258
+ write_event("terminated")
259
+ write_event("exited", { exitCode: 0 })
260
+ cleanup_tmp_dirs
261
+ @session.request_exit!
262
+ end
263
+
264
+ def handle_disconnect(message)
265
+ if using_lldb_backend?
266
+ join_background_threads
267
+ backend_response = backend_request("disconnect", message["arguments"] || {})
268
+ write_backend_response(message, backend_response)
269
+ stop_lldb_backend
270
+ cleanup_tmp_dirs
271
+ @session.request_exit!
272
+ return
273
+ end
274
+
275
+ write_response(message, {})
276
+ terminate_runtime_if_running
277
+ unless @session.runtime_started?
278
+ @session.terminate!
279
+ write_event("terminated")
280
+ write_event("exited", { exitCode: 0 })
281
+ end
282
+ cleanup_tmp_dirs
283
+ @session.request_exit!
284
+ end
285
+
286
+ def handle_set_exception_breakpoints(message)
287
+ arguments = message["arguments"] || {}
288
+ @session.set_exception_breakpoints(arguments)
289
+
290
+ if using_lldb_backend?
291
+ if backend_configuration_pending?
292
+ @exception_breakpoints_synced_to_backend = false
293
+ write_response(message, {})
294
+ return
295
+ end
296
+
297
+ backend_response = backend_request("setExceptionBreakpoints", arguments)
298
+ @exception_breakpoints_synced_to_backend = false if backend_response["success"]
299
+ return write_backend_response(message, backend_response)
300
+ end
301
+
302
+ write_response(message, {})
303
+ end
304
+
305
+ def handle_evaluate(message)
306
+ if using_lldb_backend?
307
+ arguments = rewrite_evaluate_arguments(message["arguments"] || {})
308
+ return write_backend_response(message, backend_request("evaluate", arguments))
309
+ end
310
+
311
+ write_error_response(message, "evaluate is not supported by the process backend")
312
+ end
313
+
314
+ def handle_set_expression(message)
315
+ if using_lldb_backend?
316
+ arguments = rewrite_set_expression_arguments(message["arguments"] || {})
317
+ return write_backend_response(message, backend_request("setExpression", arguments))
318
+ end
319
+
320
+ write_error_response(message, "setExpression is not supported by the process backend")
321
+ end
322
+
323
+ def handle_set_variable(message)
324
+ if using_lldb_backend?
325
+ arguments = rewrite_set_variable_arguments(message["arguments"] || {})
326
+ return write_backend_response(message, request_set_variable(arguments))
327
+ end
328
+
329
+ write_error_response(message, "setVariable is not supported by the process backend")
330
+ end
331
+
332
+ def handle_data_breakpoint_info(message)
333
+ if using_lldb_backend?
334
+ arguments = rewrite_data_breakpoint_info_arguments(message["arguments"] || {})
335
+ return write_backend_response(message, backend_request("dataBreakpointInfo", arguments))
336
+ end
337
+
338
+ write_error_response(message, "dataBreakpointInfo is not supported by the process backend")
339
+ end
340
+
341
+ def handle_source(message)
342
+ return write_backend_response(message, backend_request("source", message["arguments"] || {})) if using_lldb_backend?
343
+
344
+ write_error_response(message, "source retrieval is not supported")
345
+ end
346
+
347
+ def handle_loaded_sources(message)
348
+ return write_backend_response(message, backend_request("loadedSources", message["arguments"] || {})) if using_lldb_backend?
349
+
350
+ write_response(message, { sources: [] })
351
+ end
352
+
353
+ def handle_restart(message)
354
+ return write_backend_response(message, backend_request("restart", message["arguments"] || {})) if using_lldb_backend?
355
+
356
+ write_error_response(message, "restart is not supported by the process backend")
357
+ end
358
+
359
+ def handle_cancel(message)
360
+ args = message["arguments"] || {}
361
+ request_id = args["requestId"] || args["progressId"]
362
+
363
+ if using_lldb_backend?
364
+ backend_response = backend_request("cancel", { "requestId" => request_id }.compact)
365
+ write_backend_response(message, backend_response)
366
+ else
367
+ join_background_threads(timeout: 0.1)
368
+ write_response(message, {})
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end
374
+ end
@@ -0,0 +1,261 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module DAP
5
+ class Server
6
+ module ServerLaunch
7
+ private
8
+
9
+ def request_start(message)
10
+ if @session.launched?
11
+ write_error_response(message, "Program already launched")
12
+ return
13
+ end
14
+
15
+ args = message["arguments"] || {}
16
+ if message["command"] == "launch" && args["program"].to_s.empty?
17
+ write_error_response(message, "launch requires a non-empty 'program' argument")
18
+ return
19
+ end
20
+
21
+ resolved = resolve_debug_program(message["command"], args["program"])
22
+ unless resolved[:ok]
23
+ write_error_response(message, resolved[:error])
24
+ return
25
+ end
26
+
27
+ program_args = args["args"] || []
28
+ unless program_args.is_a?(Array)
29
+ write_error_response(message, "launch 'args' must be an array when provided")
30
+ return
31
+ end
32
+
33
+ no_debug = message["command"] == "launch" && args["noDebug"] == true
34
+ stop_on_entry = no_debug ? false : (args.key?("stopOnEntry") ? !!args["stopOnEntry"] : true)
35
+ backend_kind = if no_debug
36
+ "process"
37
+ else
38
+ resolved_backend_kind(args["backend"])
39
+ end
40
+ unless backend_kind
41
+ write_error_response(message, "unsupported backend: #{args['backend']}")
42
+ return
43
+ end
44
+
45
+ @session.request_start!(
46
+ program_path: args["program"].to_s.empty? ? nil : args["program"],
47
+ runnable_path: resolved[:runnable_path],
48
+ program_args:,
49
+ stop_on_entry:,
50
+ backend_kind:
51
+ )
52
+ @session.launch!
53
+ @backend_auto_continue_after_configuration = using_lldb_backend? && message["command"] == "launch" && !stop_on_entry
54
+ @backend_stopped_thread_id = nil if using_lldb_backend?
55
+ @last_forwarded_continued_signature = nil
56
+ load_debug_map(resolved[:runnable_path])
57
+
58
+ if using_lldb_backend?
59
+ start_lldb_backend(args)
60
+ reset_backend_configuration_ready
61
+ init_response = ensure_backend_initialized({})
62
+ return write_backend_response(message, init_response) unless init_response["success"]
63
+
64
+ # Merge backend capabilities with adapter capabilities and notify the client.
65
+ merged_caps = effective_adapter_capabilities
66
+ write_event("capabilities", merged_caps)
67
+
68
+ launch_arguments = backend_start_arguments(message["command"], args, resolved)
69
+ @breakpoints_synced_to_backend = false
70
+ @function_breakpoints_synced_to_backend = false
71
+ @exception_breakpoints_synced_to_backend = false
72
+ start_backend_start_request(message, launch_arguments)
73
+ return
74
+ end
75
+
76
+ write_response(message, {})
77
+ write_event("process", {
78
+ name: File.basename(@session.program_path.to_s),
79
+ startMethod: "launch",
80
+ isLocalProcess: true
81
+ })
82
+ maybe_start_or_stop_on_entry
83
+ end
84
+
85
+ def maybe_start_or_stop_on_entry
86
+ return if using_lldb_backend?
87
+
88
+ return unless @session.launched?
89
+ return unless @session.configuration_done?
90
+ return if @session.runtime_started?
91
+
92
+ if @session.stop_on_entry?
93
+ return if @session.entry_stop_emitted?
94
+
95
+ write_stopped("entry")
96
+ @session.mark_entry_stop_emitted!
97
+ else
98
+ start_runtime_if_needed
99
+ end
100
+ end
101
+
102
+ def write_stopped(reason)
103
+ write_event("stopped", {
104
+ reason: reason,
105
+ threadId: @session.thread_id,
106
+ allThreadsStopped: true
107
+ })
108
+ end
109
+
110
+ def default_backend_kind
111
+ @preferred_backend_kind
112
+ end
113
+
114
+ def resolved_backend_kind(requested_backend)
115
+ backend = requested_backend.to_s.strip
116
+ backend = default_backend_kind if backend.empty?
117
+ return backend if backend == "process" || backend == "lldb-dap"
118
+
119
+ nil
120
+ end
121
+
122
+ def resolve_launch_program(program)
123
+ path = program.to_s
124
+ return { ok: false, error: "launch requires a non-empty 'program' argument" } if path.empty?
125
+
126
+ expanded = File.expand_path(path)
127
+ if expanded.end_with?(".mt")
128
+ return { ok: false, error: "Milk Tea program not found: #{expanded}" } unless File.file?(expanded)
129
+
130
+ tmp_dir = Dir.mktmpdir("milk-tea-dap")
131
+ @tmp_dirs << tmp_dir
132
+ output_path = File.join(tmp_dir, File.basename(expanded, ".mt"))
133
+
134
+ begin
135
+ Build.build(expanded, output_path:, debug: true, debug_guards: false)
136
+ rescue BuildError => e
137
+ return { ok: false, error: e.message }
138
+ end
139
+
140
+ return { ok: true, runnable_path: output_path }
141
+ end
142
+
143
+ return { ok: false, error: "Program not found: #{expanded}" } unless File.file?(expanded)
144
+
145
+ { ok: true, runnable_path: expanded }
146
+ end
147
+
148
+ def resolve_debug_program(command, program)
149
+ return resolve_launch_program(program) if command == "launch"
150
+
151
+ path = program.to_s
152
+ return { ok: true, runnable_path: nil } if path.empty?
153
+
154
+ resolve_launch_program(path)
155
+ end
156
+
157
+ def backend_start_arguments(command, arguments, resolved)
158
+ rewritten = arguments.dup
159
+
160
+ rewritten.delete("backend")
161
+ rewritten.delete("adapterPath")
162
+
163
+ if resolved[:runnable_path]
164
+ rewritten["program"] = resolved[:runnable_path]
165
+ elsif rewritten["program"].to_s.empty?
166
+ rewritten.delete("program")
167
+ end
168
+
169
+ rewritten["stopOnEntry"] = true if command == "launch"
170
+
171
+ rewritten["cwd"] = File.expand_path(rewritten["cwd"]) if rewritten["cwd"].is_a?(String) && !rewritten["cwd"].empty?
172
+ rewritten["coreFile"] = File.expand_path(rewritten["coreFile"]) if rewritten["coreFile"].is_a?(String) && !rewritten["coreFile"].empty?
173
+ rewritten
174
+ end
175
+
176
+ def start_runtime_if_needed
177
+ return if @session.runtime_started?
178
+ return unless @session.runnable_path
179
+
180
+ @session.mark_runtime_started!
181
+ runnable_path = @session.runnable_path
182
+ command = [runnable_path, *@session.program_args]
183
+ chdir = File.dirname(File.expand_path(@session.program_path || runnable_path))
184
+
185
+ Thread.new do
186
+ Open3.popen3(*command, chdir:) do |stdin, stdout, stderr, wait_thr|
187
+ stdin.close
188
+ @runtime_mutex.synchronize { @runtime_pid = wait_thr.pid }
189
+
190
+ out_thread = Thread.new do
191
+ stdout.each_line do |line|
192
+ write_event("output", { category: "stdout", output: line })
193
+ end
194
+ end
195
+
196
+ err_thread = Thread.new do
197
+ stderr.each_line do |line|
198
+ write_event("output", { category: "stderr", output: line })
199
+ end
200
+ end
201
+
202
+ out_thread.join
203
+ err_thread.join
204
+
205
+ status = wait_thr.value
206
+ exit_code = status.exited? ? status.exitstatus : 1
207
+ @session.terminate!
208
+
209
+ write_event("terminated")
210
+ write_event("exited", { exitCode: exit_code })
211
+ @session.request_exit!
212
+ end
213
+ rescue StandardError => e
214
+ @session.terminate!
215
+ write_event("output", { category: "stderr", output: "DAP runtime error: #{e.message}\n" })
216
+ write_event("terminated")
217
+ write_event("exited", { exitCode: 1 })
218
+ @session.request_exit!
219
+ ensure
220
+ @runtime_mutex.synchronize do
221
+ @runtime_pid = nil
222
+ @runtime_paused = false
223
+ end
224
+ end
225
+ end
226
+
227
+ def pause_runtime_if_running
228
+ pid = @runtime_mutex.synchronize { @runtime_pid }
229
+ return false if pid.nil?
230
+
231
+ Process.kill("STOP", pid)
232
+ @runtime_mutex.synchronize { @runtime_paused = true }
233
+ true
234
+ rescue StandardError
235
+ false
236
+ end
237
+
238
+ def continue_runtime_if_paused
239
+ pid, paused = @runtime_mutex.synchronize { [@runtime_pid, @runtime_paused] }
240
+ return unless paused
241
+ return if pid.nil?
242
+
243
+ Process.kill("CONT", pid)
244
+ @runtime_mutex.synchronize { @runtime_paused = false }
245
+ rescue StandardError
246
+ nil
247
+ end
248
+
249
+ def terminate_runtime_if_running
250
+ pid = @runtime_mutex.synchronize { @runtime_pid }
251
+ return if pid.nil?
252
+
253
+ Process.kill("TERM", pid)
254
+ @runtime_mutex.synchronize { @runtime_paused = false }
255
+ rescue StandardError
256
+ nil
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end