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,408 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "socket"
5
+ require "tmpdir"
6
+
7
+ module MilkTea
8
+ class RunError < StandardError; end
9
+
10
+ class Run
11
+ class WasmPreviewServer
12
+ HOST = "127.0.0.1"
13
+ DEFAULT_IDLE_TIMEOUT = 600
14
+
15
+ def initialize(root_dir:, host: HOST, port: nil, idle_timeout: DEFAULT_IDLE_TIMEOUT)
16
+ @root_dir = File.expand_path(root_dir)
17
+ @host = host
18
+ @port = port
19
+ @idle_timeout = idle_timeout
20
+ @thread = nil
21
+ @server = nil
22
+ @running = false
23
+ end
24
+
25
+ attr_reader :host, :port
26
+
27
+ def start
28
+ listen!
29
+ @thread = Thread.new { serve_forever(trap_signals: false) }
30
+ self
31
+ rescue StandardError
32
+ stop
33
+ raise
34
+ end
35
+
36
+ def stop
37
+ @running = false
38
+ if @thread
39
+ wake_server
40
+ @thread.join(1)
41
+ end
42
+ @thread = nil
43
+ close_server
44
+ nil
45
+ end
46
+
47
+ def listen!
48
+ return self if @server
49
+
50
+ @server = TCPServer.new(@host, @port || 0)
51
+ @port = @server.addr[1]
52
+ self
53
+ end
54
+
55
+ def url_for(entry_name)
56
+ raise RunError, "wasm preview server has not been started" unless @port
57
+
58
+ "http://#{@host}:#{@port}/#{entry_name}"
59
+ end
60
+
61
+ def serve_forever(trap_signals: true)
62
+ listen!
63
+ @running = true
64
+ previous_handlers = trap_signals ? install_signal_handlers : nil
65
+ last_request_at = Time.now
66
+
67
+ while @running
68
+ if @idle_timeout.positive? && Time.now - last_request_at > @idle_timeout
69
+ break
70
+ end
71
+
72
+ readable, = IO.select([@server], nil, nil, 0.25)
73
+ next unless readable
74
+
75
+ client = @server.accept
76
+ Thread.new(client) do |socket|
77
+ begin
78
+ handle_client(socket)
79
+ ensure
80
+ last_request_at = Time.now
81
+ socket.close unless socket.closed?
82
+ end
83
+ end
84
+ end
85
+ ensure
86
+ close_server
87
+ restore_signal_handlers(previous_handlers) if previous_handlers
88
+ end
89
+
90
+ private
91
+
92
+ def handle_client(socket)
93
+ request_line = socket.gets
94
+ return unless request_line
95
+
96
+ method, target, = request_line.split(" ", 3)
97
+ while (header = socket.gets)
98
+ break if header == "\r\n"
99
+ end
100
+
101
+ unless ["GET", "HEAD"].include?(method)
102
+ write_response(socket, 405, "Method Not Allowed", "method not allowed\n")
103
+ return
104
+ end
105
+
106
+ path = resolve_request_path(target)
107
+ unless path && File.file?(path)
108
+ write_response(socket, 404, "Not Found", "not found\n")
109
+ return
110
+ end
111
+
112
+ body = File.binread(path)
113
+ headers = {
114
+ "Content-Type" => mime_type_for(path),
115
+ "Content-Length" => body.bytesize.to_s,
116
+ "Cache-Control" => "no-cache",
117
+ "Cross-Origin-Opener-Policy" => "same-origin",
118
+ "Cross-Origin-Embedder-Policy" => "require-corp",
119
+ "Connection" => "close",
120
+ }
121
+
122
+ socket.write("HTTP/1.1 200 OK\r\n")
123
+ headers.each { |name, value| socket.write("#{name}: #{value}\r\n") }
124
+ socket.write("\r\n")
125
+ socket.write(body) if method == "GET"
126
+ end
127
+
128
+ def resolve_request_path(target)
129
+ request_path = target.to_s.split("?", 2).first
130
+ request_path = "/" if request_path.empty?
131
+ relative = request_path == "/" ? default_entry_name : request_path.delete_prefix("/")
132
+ candidate = File.expand_path(relative, @root_dir)
133
+ return nil unless candidate.start_with?(@root_dir + File::SEPARATOR) || candidate == @root_dir
134
+
135
+ candidate
136
+ end
137
+
138
+ def default_entry_name
139
+ html_entries = Dir.children(@root_dir).grep(/\.html\z/).sort
140
+ html_entries.first || "index.html"
141
+ end
142
+
143
+ def mime_type_for(path)
144
+ case File.extname(path)
145
+ when ".html"
146
+ "text/html; charset=utf-8"
147
+ when ".js"
148
+ "text/javascript; charset=utf-8"
149
+ when ".css"
150
+ "text/css; charset=utf-8"
151
+ when ".json", ".mtdbg.json"
152
+ "application/json; charset=utf-8"
153
+ when ".wasm"
154
+ "application/wasm"
155
+ when ".wav"
156
+ "audio/wav"
157
+ when ".png"
158
+ "image/png"
159
+ when ".ppm"
160
+ "image/x-portable-pixmap"
161
+ else
162
+ "application/octet-stream"
163
+ end
164
+ end
165
+
166
+ def write_response(socket, status, message, body)
167
+ body ||= ""
168
+ socket.write("HTTP/1.1 #{status} #{message}\r\n")
169
+ socket.write("Content-Type: text/plain; charset=utf-8\r\n")
170
+ socket.write("Content-Length: #{body.bytesize}\r\n")
171
+ socket.write("Cross-Origin-Opener-Policy: same-origin\r\n")
172
+ socket.write("Cross-Origin-Embedder-Policy: require-corp\r\n")
173
+ socket.write("Connection: close\r\n")
174
+ socket.write("\r\n")
175
+ socket.write(body)
176
+ end
177
+
178
+ def install_signal_handlers
179
+ {
180
+ "TERM" => Signal.trap("TERM") { @running = false },
181
+ "INT" => Signal.trap("INT") { @running = false },
182
+ }
183
+ end
184
+
185
+ def restore_signal_handlers(previous_handlers)
186
+ previous_handlers.each do |signal_name, handler|
187
+ Signal.trap(signal_name, handler)
188
+ end
189
+ rescue ArgumentError
190
+ nil
191
+ end
192
+
193
+ def wake_server
194
+ return unless @server && @port
195
+
196
+ socket = TCPSocket.new(@host, @port)
197
+ socket.close
198
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
199
+ nil
200
+ end
201
+
202
+ def close_server
203
+ @server&.close
204
+ rescue IOError, Errno::EBADF
205
+ nil
206
+ ensure
207
+ @server = nil
208
+ end
209
+ end
210
+
211
+ Result = Data.define(:stdout, :stderr, :exit_status, :output_path, :c_path, :compiler, :link_flags, :platform, :bundle_root, :archive_path, :cached)
212
+
213
+ def self.run(path, output_path: nil, cc: ENV.fetch("CC", "cc"), keep_c_path: nil, module_roots: nil, package_graph: nil, frontend: nil, profile: nil, platform: nil, bundle: false, archive: false, browser_opener: nil, preview_server_class: nil, preview_started: nil, argv: [], no_cache: false, kind: :executable, debug_guards: false)
214
+ new(path, output_path:, cc:, keep_c_path:, module_roots:, package_graph:, frontend:, profile:, platform:, bundle:, archive:, browser_opener:, preview_server_class:, preview_started:, argv:, no_cache:, kind:, debug_guards:).run
215
+ end
216
+
217
+ def initialize(path, output_path:, cc:, keep_c_path:, module_roots: nil, package_graph: nil, frontend: nil, profile: nil, platform: nil, bundle: false, archive: false, browser_opener: nil, preview_server_class: nil, preview_started: nil, argv: [], no_cache: false, kind: :executable, debug_guards: false)
218
+ @input_path = File.expand_path(path)
219
+ @output_path = output_path ? File.expand_path(output_path) : nil
220
+ @cc = cc
221
+ @keep_c_path = keep_c_path ? File.expand_path(keep_c_path) : nil
222
+ @module_roots = module_roots
223
+ @project_root = resolve_project_root
224
+ @package_graph = package_graph
225
+ @frontend = frontend
226
+ @profile = profile
227
+ @platform = platform
228
+ @bundle = bundle
229
+ @archive = archive
230
+ @browser_opener = browser_opener || method(:open_browser)
231
+ @preview_server_class = preview_server_class || WasmPreviewServer
232
+ @preview_started = preview_started
233
+ @argv = argv
234
+ @no_cache = no_cache
235
+ @debug_guards = debug_guards
236
+ end
237
+
238
+ def run
239
+ if @output_path
240
+ return run_binary(@output_path)
241
+ end
242
+
243
+ if wasm_target_requested?
244
+ return run_binary(nil)
245
+ end
246
+
247
+ Dir.mktmpdir("milk-tea-run") do |dir|
248
+ binary_path = File.join(dir, File.basename(@input_path, ".mt"))
249
+ run_binary(binary_path)
250
+ end
251
+ end
252
+
253
+ private
254
+
255
+ def resolve_project_root
256
+ candidate = File.directory?(@input_path) ? @input_path : File.dirname(@input_path)
257
+ return candidate if File.directory?(candidate)
258
+
259
+ Array(@module_roots).map { |root| File.expand_path(root.to_s) }.find { |root| File.directory?(root) } || Dir.pwd
260
+ end
261
+
262
+ def run_binary(binary_path)
263
+ build_result = Build.build(
264
+ @input_path,
265
+ output_path: binary_path,
266
+ cc: @cc,
267
+ keep_c_path: @keep_c_path,
268
+ module_roots: @module_roots,
269
+ package_graph: @package_graph,
270
+ frontend: @frontend,
271
+ profile: @profile,
272
+ platform: @platform,
273
+ bundle: @bundle,
274
+ archive: @archive,
275
+ no_cache: @no_cache,
276
+ debug_guards: @debug_guards,
277
+ )
278
+ return run_wasm_preview(build_result) if build_result.platform == :wasm
279
+
280
+ unless build_result.platform == host_platform
281
+ raise RunError, "run target platform is #{build_result.platform}; host platform is #{host_platform}"
282
+ end
283
+
284
+ out_buf = String.new
285
+ err_buf = String.new
286
+ status = nil
287
+ live = $stdout.tty?
288
+
289
+ cmd = [build_result.output_path, *@argv]
290
+ cmd = ["stdbuf", "-oL", "-eL", *cmd] if host_platform == :linux
291
+
292
+ Open3.popen3(*cmd, chdir: @project_root) do |stdin, stdout, stderr, wait_thr|
293
+ stdin.close
294
+
295
+ out_thread = Thread.new do
296
+ Thread.current.report_on_exception = false
297
+ while (chunk = stdout.readpartial(4096))
298
+ $stdout.write(chunk) if live
299
+ $stdout.flush if live
300
+ out_buf << chunk
301
+ end
302
+ rescue EOFError, IOError
303
+ end
304
+
305
+ err_thread = Thread.new do
306
+ Thread.current.report_on_exception = false
307
+ while (chunk = stderr.readpartial(4096))
308
+ $stderr.write(chunk) if live
309
+ $stderr.flush if live
310
+ err_buf << chunk
311
+ end
312
+ rescue EOFError, IOError
313
+ end
314
+
315
+ begin
316
+ status = wait_thr.value
317
+ rescue Interrupt
318
+ Process.kill("TERM", wait_thr.pid) rescue nil
319
+ begin
320
+ status = wait_thr.value
321
+ rescue Interrupt
322
+ Process.kill("KILL", wait_thr.pid) rescue nil
323
+ status = wait_thr.value
324
+ end
325
+ end
326
+
327
+ out_thread.join
328
+ err_thread.join
329
+ end
330
+
331
+ Result.new(
332
+ stdout: out_buf,
333
+ stderr: err_buf,
334
+ exit_status: process_exit_status(status),
335
+ output_path: @output_path,
336
+ c_path: build_result.c_path,
337
+ compiler: build_result.compiler,
338
+ link_flags: build_result.link_flags,
339
+ platform: build_result.platform,
340
+ bundle_root: build_result.bundle_root,
341
+ archive_path: build_result.archive_path,
342
+ cached: build_result.cached,
343
+ )
344
+ rescue Errno::ENOENT
345
+ raise RunError, "built program not found: #{binary_path}"
346
+ end
347
+
348
+ def process_exit_status(status)
349
+ return status.exitstatus if status.exited?
350
+ return 128 + status.termsig if status.signaled?
351
+
352
+ 1
353
+ end
354
+
355
+ def run_wasm_preview(build_result)
356
+ preview_server = @preview_server_class.new(root_dir: File.dirname(build_result.output_path), idle_timeout: 0)
357
+ preview_server.listen! if preview_server.respond_to?(:listen!)
358
+ url = preview_server.url_for(File.basename(build_result.output_path))
359
+ startup_message = "serving #{url} (press Ctrl-C to stop)\n"
360
+ @browser_opener.call(url)
361
+ @preview_started&.call(startup_message)
362
+ preview_server.serve_forever
363
+
364
+ Result.new(
365
+ stdout: startup_message,
366
+ stderr: "",
367
+ exit_status: 0,
368
+ output_path: build_result.output_path,
369
+ c_path: build_result.c_path,
370
+ compiler: build_result.compiler,
371
+ link_flags: build_result.link_flags,
372
+ platform: build_result.platform,
373
+ bundle_root: build_result.bundle_root,
374
+ archive_path: build_result.archive_path,
375
+ cached: build_result.cached,
376
+ )
377
+ rescue StandardError
378
+ preview_server&.stop if preview_server&.respond_to?(:stop)
379
+ raise
380
+ end
381
+
382
+ def open_browser(url)
383
+ command = browser_open_command(url)
384
+ pid = Process.spawn(*command, out: File::NULL, err: File::NULL)
385
+ Process.detach(pid)
386
+ rescue Errno::ENOENT
387
+ raise RunError, "browser launcher not found: #{command.first}"
388
+ end
389
+
390
+ def browser_open_command(url)
391
+ return ["cmd", "/c", "start", "", url] if host_platform == :windows
392
+
393
+ ["xdg-open", url]
394
+ end
395
+
396
+ def wasm_target_requested?
397
+ return true if @platform && @platform.to_s == "wasm"
398
+
399
+ PackageManifest.load(@input_path).platform == :wasm
400
+ rescue PackageManifestError
401
+ false
402
+ end
403
+
404
+ def host_platform
405
+ MilkTea.host_platform
406
+ end
407
+ end
408
+ end
@@ -0,0 +1,48 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Milk Tea App</title>
7
+ <style>
8
+ html, body {
9
+ margin: 0;
10
+ height: 100%;
11
+ background: #111;
12
+ color: #f5f5f5;
13
+ font-family: monospace;
14
+ }
15
+
16
+ body {
17
+ overflow: hidden;
18
+ }
19
+
20
+ #canvas {
21
+ display: block;
22
+ width: 100vw;
23
+ height: 100vh;
24
+ border: 0;
25
+ background: #000;
26
+ }
27
+
28
+ #output {
29
+ position: fixed;
30
+ left: 0;
31
+ right: 0;
32
+ bottom: 0;
33
+ max-height: 30vh;
34
+ margin: 0;
35
+ padding: 0.75rem;
36
+ overflow: auto;
37
+ white-space: pre-wrap;
38
+ background: rgba(0, 0, 0, 0.72);
39
+ }
40
+ </style>
41
+ </head>
42
+ <body>
43
+ {{{ MILK_TEA_CANVAS }}}
44
+ {{{ MILK_TEA_OUTPUT }}}
45
+ {{{ MILK_TEA_BOOTSTRAP }}}
46
+ {{{ SCRIPT }}}
47
+ </body>
48
+ </html>
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class ToolchainCLI
5
+ def self.start(argv = ARGV, out:, err:, help_printer:)
6
+ new(argv, out:, err:, help_printer:).start
7
+ end
8
+
9
+ def initialize(argv, out:, err:, help_printer:)
10
+ @argv = argv.dup
11
+ @out = out
12
+ @err = err
13
+ @help_printer = help_printer
14
+ end
15
+
16
+ def start
17
+ subcommand = @argv.shift
18
+ unless subcommand
19
+ @err.puts("missing toolchain subcommand")
20
+ print_help
21
+ return 1
22
+ end
23
+
24
+ case subcommand
25
+ when "bootstrap"
26
+ bootstrap_command
27
+ when "doctor"
28
+ doctor_command
29
+ when "tools"
30
+ tools_command
31
+ else
32
+ @err.puts("unknown toolchain subcommand #{subcommand}")
33
+ print_help
34
+ 1
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def print_help
41
+ @help_printer.call(@err)
42
+ end
43
+
44
+ def bootstrap_command
45
+ if @argv.any?
46
+ @err.puts("unknown toolchain option #{@argv.first}")
47
+ print_help
48
+ return 1
49
+ end
50
+
51
+ require_relative "../bindings"
52
+
53
+ results = UpstreamSources.bootstrap_all!
54
+ results.each do |result|
55
+ verb = result.status == :present ? "kept" : "bootstrapped"
56
+ @out.puts("#{verb} #{result.source.name} -> #{result.path}")
57
+ end
58
+ 0
59
+ end
60
+
61
+ def doctor_command
62
+ if @argv.any?
63
+ @err.puts("unknown toolchain option #{@argv.first}")
64
+ print_help
65
+ return 1
66
+ end
67
+
68
+ require_relative "../bindings"
69
+
70
+ cc = ENV.fetch("CC", "cc")
71
+ ar = ENV.fetch("AR", "ar")
72
+ checks = []
73
+
74
+ checks << ["ruby", true, RUBY_DESCRIPTION]
75
+ checks << ["cc", executable_available?(cc), cc]
76
+ checks << ["ar", executable_available?(ar), ar]
77
+ checks << ["bundle", executable_available?("bundle"), "bundle"]
78
+ checks << ["git", executable_available?("git"), "git"]
79
+ checks << ["clang", executable_available?(ENV.fetch("CLANG", "clang")), ENV.fetch("CLANG", "clang")]
80
+ checks << ["cmake", executable_available?(ENV.fetch("CMAKE", "cmake")), ENV.fetch("CMAKE", "cmake")]
81
+ checks << ["ninja", executable_available?("ninja"), "ninja"]
82
+
83
+ UpstreamSources.default_sources(root: MilkTea.root).each do |source|
84
+ missing = source.sentinel_paths.reject do |relative_path|
85
+ File.exist?(source.checkout_root.join(relative_path))
86
+ end
87
+ if missing.empty?
88
+ checks << ["source/#{source.name}", true, source.checkout_root.to_s]
89
+ else
90
+ checks << ["source/#{source.name}", false, "missing #{missing.first} under #{source.checkout_root}"]
91
+ end
92
+ end
93
+
94
+ RawBindings.default_registry(root: MilkTea.root)
95
+ .select { |binding| binding.module_name.start_with?("std.c.") }
96
+ .sort_by(&:name)
97
+ .each do |binding|
98
+ begin
99
+ header_path = binding.header_path(env: ENV)
100
+ checks << ["binding/#{binding.name}", true, "#{binding.module_name} -> #{header_path}"]
101
+ rescue RawBindings::Error => header_error
102
+ unless checks.assoc("cc")[1]
103
+ checks << ["binding/#{binding.name}", false, "skipped (missing C compiler)"]
104
+ next
105
+ end
106
+
107
+ unless binding.respond_to?(:prepare!)
108
+ checks << ["binding/#{binding.name}", false, header_error.message]
109
+ next
110
+ end
111
+
112
+ begin
113
+ binding.prepare!(env: ENV, cc:)
114
+ header_path = binding.header_path(env: ENV)
115
+ checks << ["binding/#{binding.name}", true, "#{binding.module_name} -> #{header_path}"]
116
+ rescue RawBindings::Error => e
117
+ checks << ["binding/#{binding.name}", false, e.message]
118
+ end
119
+ end
120
+ end
121
+
122
+ checks.each do |name, ok, detail|
123
+ @out.puts("#{ok ? 'ok' : 'fail'} #{name}: #{detail}")
124
+ end
125
+
126
+ checks.all? { |_, ok, _| ok } ? 0 : 1
127
+ end
128
+
129
+ def tools_command
130
+ if @argv.any?
131
+ @err.puts("unknown toolchain option #{@argv.first}")
132
+ print_help
133
+ return 1
134
+ end
135
+
136
+ require_relative "../bindings"
137
+
138
+ results = VendoredTools.build_all!(root: MilkTea.root)
139
+ results.each do |result|
140
+ @out.puts("built #{result[:tool].name} -> #{result[:binary]}")
141
+ end
142
+ 0
143
+ rescue VendoredTool::Error => e
144
+ @err.puts(e.message)
145
+ 1
146
+ end
147
+
148
+ def executable_available?(program)
149
+ return File.file?(program) && File.executable?(program) if program.include?(File::SEPARATOR)
150
+
151
+ ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |entry|
152
+ candidate = File.join(entry, program)
153
+ File.file?(candidate) && File.executable?(candidate)
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,7 @@
1
+ <% @title = "Not Found" %>
2
+
3
+ <div class="hero" style="text-align:center">
4
+ <h1 style="font-size:4rem">404</h1>
5
+ <p class="tagline">Page not found.</p>
6
+ <p><a href="/">Back to documentation</a></p>
7
+ </div>