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,356 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tomlrb"
4
+
5
+ module MilkTea
6
+ class PackageManifestError < StandardError; end
7
+
8
+ class PackageManifest
9
+ DependencyView = Data.define(:name, :version, :version_req, :git, :git_rev, :git_subdir, :path) do
10
+ def path_dependency?
11
+ !path.nil?
12
+ end
13
+
14
+ def git_dependency?
15
+ !git.nil?
16
+ end
17
+
18
+ def registry?
19
+ !version_req.nil? && !path_dependency? && !git_dependency?
20
+ end
21
+
22
+ def exact_registry_version?
23
+ registry? && !version.nil?
24
+ end
25
+ end
26
+ DataView = Data.define(:root_dir, :manifest_path, :package_name, :package_version, :package_kind, :source_root, :source_path, :profile, :platform, :output_path, :assets_paths, :html_template_path, :dependencies)
27
+
28
+ def self.default_package_name_for_root(root_dir)
29
+ snake_case_identifier(File.basename(File.expand_path(root_dir)))
30
+ end
31
+
32
+ def self.snake_case_identifier(name)
33
+ name.to_s
34
+ .gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
35
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
36
+ .tr("- ", "__")
37
+ .gsub(/_+/, "_")
38
+ .downcase
39
+ end
40
+
41
+ def self.load(path)
42
+ new(path).load
43
+ end
44
+
45
+ def self.manifest_exists_for?(path)
46
+ path = File.expand_path(path)
47
+ current = File.directory?(path) ? path : File.dirname(path)
48
+ loop do
49
+ return true if File.file?(File.join(current, "package.toml"))
50
+ parent = File.dirname(current)
51
+ break if parent == current
52
+ current = parent
53
+ end
54
+ false
55
+ end
56
+
57
+ def initialize(path)
58
+ @path = File.expand_path(path)
59
+ end
60
+
61
+ def load
62
+ manifest_path, root_dir = resolve_manifest_path(@path)
63
+ raise PackageManifestError, "package manifest not found for #{@path}" unless manifest_path
64
+
65
+ config = parse_manifest(manifest_path)
66
+ package = config.fetch("package", {})
67
+ build = config.fetch("build", {})
68
+ profile_config = config.fetch("profile", {})
69
+ platform_config = config.fetch("platform", {})
70
+
71
+ package_name = package["name"]
72
+ if package_name.nil? || package_name.empty?
73
+ package_name = self.class.default_package_name_for_root(root_dir)
74
+ end
75
+ package_version = package["version"]
76
+ package_version = package_version.to_s unless package_version.nil?
77
+ package_version = nil if package_version == ""
78
+
79
+ package_kind = normalize_package_kind(package["kind"] || build["type"] || default_package_kind(build))
80
+ source_root_value = package["source_root"] || default_source_root(root_dir)
81
+ source_root = File.expand_path(source_root_value.to_s, root_dir)
82
+ unless File.directory?(source_root)
83
+ raise PackageManifestError, "package.source_root not found: #{source_root_value} (resolved to #{source_root})"
84
+ end
85
+
86
+ profile_name = normalize_profile_name(build["profile"] || profile_config["default"])
87
+ platform_name = normalize_platform_name(build["platform"] || platform_config["default"])
88
+
89
+ source_path = resolve_source_path(root_dir, build, package_kind)
90
+
91
+ output_path = build["output"]
92
+ output_path = File.expand_path(output_path.to_s, root_dir) if output_path
93
+
94
+ assets_paths = parse_assets_paths(build["assets"], root_dir)
95
+
96
+ html_template_path = build["html_template"]
97
+ if html_template_path
98
+ html_template_path = File.expand_path(html_template_path.to_s, root_dir)
99
+ unless File.file?(html_template_path)
100
+ raise PackageManifestError, "build.html_template not found: #{build["html_template"]} (resolved to #{html_template_path})"
101
+ end
102
+ end
103
+
104
+ dependencies = parse_dependencies(config.fetch("dependencies", {}), root_dir:, manifest_path:)
105
+
106
+ DataView.new(
107
+ root_dir:,
108
+ manifest_path:,
109
+ package_name:,
110
+ package_version:,
111
+ package_kind:,
112
+ source_root:,
113
+ source_path:,
114
+ profile: profile_name,
115
+ platform: platform_name,
116
+ output_path:,
117
+ assets_paths:,
118
+ html_template_path:,
119
+ dependencies:,
120
+ )
121
+ end
122
+
123
+ private
124
+
125
+ def parse_assets_paths(raw_assets, root_dir)
126
+ return [] unless raw_assets
127
+
128
+ asset_paths = (raw_assets.is_a?(Array) ? raw_assets : [raw_assets]).map do |raw_asset|
129
+ asset_path = File.expand_path(raw_asset.to_s, root_dir)
130
+ unless File.exist?(asset_path)
131
+ raise PackageManifestError, "build.assets not found: #{raw_asset} (resolved to #{asset_path})"
132
+ end
133
+
134
+ asset_path
135
+ end
136
+
137
+ duplicate_basenames = asset_paths.group_by { |asset_path| File.basename(asset_path) }
138
+ .select { |_basename, paths| paths.length > 1 }
139
+ .keys
140
+ .sort
141
+ return asset_paths if duplicate_basenames.empty?
142
+
143
+ raise PackageManifestError, "build.assets entries must have unique basenames: #{duplicate_basenames.join(", ")}"
144
+ end
145
+
146
+ def resolve_manifest_path(path)
147
+ if File.directory?(path)
148
+ manifest_path = File.join(path, "package.toml")
149
+ raise PackageManifestError, "package manifest not found: #{manifest_path}" unless File.file?(manifest_path)
150
+
151
+ return [manifest_path, path]
152
+ end
153
+
154
+ if File.basename(path) == "package.toml"
155
+ raise PackageManifestError, "package manifest not found: #{path}" unless File.file?(path)
156
+
157
+ return [path, File.dirname(path)]
158
+ end
159
+
160
+ current = File.dirname(path)
161
+ loop do
162
+ manifest_path = File.join(current, "package.toml")
163
+ return [manifest_path, current] if File.file?(manifest_path)
164
+
165
+ parent = File.dirname(current)
166
+ break if parent == current
167
+
168
+ current = parent
169
+ end
170
+
171
+ [nil, nil]
172
+ end
173
+
174
+ def parse_manifest(path)
175
+ data = Tomlrb.load_file(path)
176
+ raise PackageManifestError, "package.toml root must be a table: #{path}" unless data.is_a?(Hash)
177
+
178
+ data
179
+ rescue StandardError => e
180
+ raise PackageManifestError, "invalid package.toml at #{path}: #{e.message}"
181
+ end
182
+
183
+ def default_package_kind(build)
184
+ "application"
185
+ end
186
+
187
+ def default_source_root(root_dir)
188
+ conventional_source_root = File.join(root_dir, "src")
189
+ return "src" if File.directory?(conventional_source_root)
190
+
191
+ "."
192
+ end
193
+
194
+ def resolve_source_path(root_dir, build, package_kind)
195
+ return nil if package_kind == :library
196
+
197
+ if build["entry"]
198
+ entry = build["entry"].to_s
199
+ source_path = File.expand_path(entry, root_dir)
200
+ unless File.file?(source_path)
201
+ raise PackageManifestError, "build.entry not found: #{entry} (resolved to #{source_path})"
202
+ end
203
+
204
+ return source_path
205
+ end
206
+
207
+ default_source_path = File.expand_path("src/main.mt", root_dir)
208
+ return default_source_path if File.file?(default_source_path)
209
+
210
+ requested_path = File.expand_path(@path)
211
+ return requested_path if File.file?(requested_path) && path_within_root?(requested_path, root_dir)
212
+
213
+ nil
214
+ end
215
+
216
+ def path_within_root?(path, root_dir)
217
+ normalized_root = File.expand_path(root_dir)
218
+ path == normalized_root || path.start_with?(normalized_root + File::SEPARATOR)
219
+ end
220
+
221
+ def parse_dependencies(raw_dependencies, root_dir:, manifest_path:)
222
+ return [] if raw_dependencies.nil?
223
+ unless raw_dependencies.is_a?(Hash)
224
+ raise PackageManifestError, "dependencies must be a table in #{manifest_path}"
225
+ end
226
+
227
+ raw_dependencies.map do |name, spec|
228
+ parse_dependency(name, spec, root_dir:, manifest_path:)
229
+ end
230
+ end
231
+
232
+ def parse_dependency(name, spec, root_dir:, manifest_path:)
233
+ case spec
234
+ when String
235
+ return registry_dependency(name, spec, manifest_path:)
236
+ when Hash
237
+ version = normalize_optional_dependency_value(spec["version"])
238
+ git = normalize_optional_dependency_value(spec["git"])
239
+ git_rev = normalize_optional_dependency_value(spec["rev"])
240
+ git_subdir = normalize_optional_dependency_value(spec["subdir"])
241
+ path_value = normalize_optional_dependency_value(spec["path"])
242
+
243
+ if git_rev && !git
244
+ raise PackageManifestError, "dependency #{name} in #{manifest_path} declares rev without git"
245
+ end
246
+
247
+ if git_subdir && !git
248
+ raise PackageManifestError, "dependency #{name} in #{manifest_path} declares subdir without git"
249
+ end
250
+
251
+ if version && git
252
+ raise PackageManifestError,
253
+ "dependency #{name} in #{manifest_path} cannot combine version with git resolution"
254
+ end
255
+
256
+ declared_sources = []
257
+ declared_sources << "git" if git
258
+ declared_sources << "path" if path_value
259
+ declared_sources << "version" if version && !path_value && !git
260
+
261
+ if declared_sources.empty?
262
+ raise PackageManifestError, "dependency #{name} in #{manifest_path} must declare version, git, or path"
263
+ end
264
+
265
+ if declared_sources.length > 1
266
+ raise PackageManifestError,
267
+ "dependency #{name} in #{manifest_path} must choose exactly one of version, git, or path"
268
+ end
269
+
270
+ if path_value
271
+ path = File.expand_path(path_value, root_dir)
272
+ unless File.directory?(path)
273
+ raise PackageManifestError, "dependency #{name} path not found: #{spec["path"]} (resolved to #{path})"
274
+ end
275
+
276
+ version_req = version ? normalize_registry_dependency_requirement(version, name:, manifest_path:) : nil
277
+ exact_version = version_req&.exact_version&.to_s
278
+ return DependencyView.new(name.to_s, exact_version, version_req, nil, nil, nil, path)
279
+ end
280
+
281
+ if git
282
+ unless git_rev
283
+ raise PackageManifestError,
284
+ "dependency #{name} in #{manifest_path} uses git resolution but is missing rev"
285
+ end
286
+
287
+ return DependencyView.new(name.to_s, nil, nil, git, git_rev, git_subdir, nil)
288
+ end
289
+
290
+ registry_dependency(name, version, manifest_path:)
291
+ else
292
+ raise PackageManifestError, "dependency #{name} in #{manifest_path} has unsupported type #{spec.class}"
293
+ end
294
+ end
295
+
296
+ def registry_dependency(name, raw_requirement, manifest_path:)
297
+ version_req = normalize_registry_dependency_requirement(raw_requirement, name:, manifest_path:)
298
+ exact_version = version_req.exact_version&.to_s
299
+
300
+ DependencyView.new(name.to_s, exact_version, version_req, nil, nil, nil, nil)
301
+ end
302
+
303
+ def normalize_optional_dependency_value(value)
304
+ text = value.to_s.strip
305
+ text.empty? ? nil : text
306
+ end
307
+
308
+ def normalize_registry_dependency_requirement(value, name:, manifest_path:)
309
+ text = normalize_optional_dependency_value(value)
310
+ if text.nil?
311
+ raise PackageManifestError, "dependency #{name} in #{manifest_path} must declare a version"
312
+ end
313
+
314
+ PackageVersionReq.parse(text, label: "dependency #{name} in #{manifest_path} version requirement")
315
+ rescue PackageVersionError => e
316
+ raise PackageManifestError, e.message
317
+ end
318
+
319
+ def normalize_package_kind(value)
320
+ case value.to_s
321
+ when "", "application", "app", "executable"
322
+ :application
323
+ when "library", "lib"
324
+ :library
325
+ else
326
+ raise PackageManifestError, "unknown package kind #{value}; expected application|library"
327
+ end
328
+ end
329
+
330
+ def normalize_profile_name(value)
331
+ case value.to_s
332
+ when "", "debug", "dev"
333
+ :debug
334
+ when "release", "rel"
335
+ :release
336
+ else
337
+ raise PackageManifestError, "unknown profile #{value}; expected debug|release"
338
+ end
339
+ end
340
+
341
+ def normalize_platform_name(value)
342
+ case value.to_s
343
+ when "", "linux"
344
+ :linux
345
+ when "windows", "win", "win32"
346
+ :windows
347
+ when "wasm", "web", "html5", "browser"
348
+ :wasm
349
+ when "darwin", "macos", "osx"
350
+ :darwin
351
+ else
352
+ raise PackageManifestError, "unknown platform #{value}; expected linux|windows|wasm|darwin"
353
+ end
354
+ end
355
+ end
356
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tomlrb"
4
+
5
+ module MilkTea
6
+ class PackageManifestEditorError < StandardError; end
7
+
8
+ class PackageManifestEditor
9
+ attr_reader :manifest_path
10
+
11
+ def initialize(path)
12
+ manifest = PackageManifest.load(path)
13
+ @manifest_path = manifest.manifest_path
14
+ rescue PackageManifestError => e
15
+ raise PackageManifestEditorError, e.message
16
+ end
17
+
18
+ def add_dependency(name, raw_spec)
19
+ config = parse_manifest
20
+ dependencies = normalize_dependencies(config)
21
+ dependencies[name] = raw_spec
22
+ write_dependencies(dependencies)
23
+ end
24
+
25
+ def remove_dependency(name)
26
+ config = parse_manifest
27
+ dependencies = normalize_dependencies(config)
28
+ raise PackageManifestEditorError, "dependency #{name} not found in #{@manifest_path}" unless dependencies.key?(name)
29
+
30
+ dependencies.delete(name)
31
+ write_dependencies(dependencies)
32
+ end
33
+
34
+ private
35
+
36
+ def parse_manifest
37
+ data = Tomlrb.load_file(@manifest_path)
38
+ raise PackageManifestEditorError, "package.toml root must be a table: #{@manifest_path}" unless data.is_a?(Hash)
39
+
40
+ data
41
+ rescue StandardError => e
42
+ raise PackageManifestEditorError, "invalid package.toml at #{@manifest_path}: #{e.message}"
43
+ end
44
+
45
+ def normalize_dependencies(config)
46
+ dependencies = config["dependencies"]
47
+ return {} if dependencies.nil?
48
+ raise PackageManifestEditorError, "dependencies must be a table in #{@manifest_path}" unless dependencies.is_a?(Hash)
49
+
50
+ dependencies.dup
51
+ end
52
+
53
+ def write_dependencies(dependencies)
54
+ source = File.read(@manifest_path)
55
+ replacement = dependencies.empty? ? nil : render_dependencies_section(dependencies)
56
+ updated_source = replace_dependencies_section(source, replacement)
57
+ PackageAtomicWrite.write(@manifest_path, updated_source)
58
+ rescue SystemCallError => e
59
+ raise PackageManifestEditorError, "failed to update #{@manifest_path}: #{e.message}"
60
+ end
61
+
62
+ def render_dependencies_section(dependencies)
63
+ lines = ["[dependencies]\n"]
64
+ dependencies.each do |name, spec|
65
+ lines << "#{toml_string(name)} = #{render_dependency_spec(spec)}\n"
66
+ end
67
+ lines.join
68
+ end
69
+
70
+ def render_dependency_spec(spec)
71
+ case spec
72
+ when String
73
+ toml_string(spec)
74
+ when Hash
75
+ pairs = spec.map do |key, value|
76
+ "#{key} = #{toml_string(value)}"
77
+ end
78
+ "{ #{pairs.join(', ')} }"
79
+ else
80
+ raise PackageManifestEditorError, "unsupported dependency spec type #{spec.class}"
81
+ end
82
+ end
83
+
84
+ def replace_dependencies_section(source, replacement)
85
+ lines = source.lines
86
+ start_index = lines.index { |line| line.match?(/^\[dependencies\]\s*$/) }
87
+ if start_index
88
+ end_index = lines[(start_index + 1)..]&.index { |line| line.start_with?("[") }
89
+ end_index = end_index ? start_index + 1 + end_index : lines.length
90
+ before = lines[0...start_index]
91
+ after = lines[end_index..] || []
92
+ return rebuild_source(before, replacement, after)
93
+ end
94
+
95
+ return source if replacement.nil?
96
+
97
+ rebuilt = source.dup
98
+ rebuilt << "\n" unless rebuilt.empty? || rebuilt.end_with?("\n")
99
+ rebuilt << "\n" unless rebuilt.empty? || rebuilt.end_with?("\n\n")
100
+ rebuilt << replacement
101
+ rebuilt
102
+ end
103
+
104
+ def rebuild_source(before, replacement, after)
105
+ rebuilt = before.join
106
+ rebuilt = rebuilt.rstrip
107
+ if replacement
108
+ rebuilt << "\n\n" unless rebuilt.empty?
109
+ rebuilt << replacement
110
+ end
111
+
112
+ trailing = after.join.lstrip
113
+ unless trailing.empty?
114
+ rebuilt << "\n" unless rebuilt.empty? || rebuilt.end_with?("\n")
115
+ rebuilt << "\n" unless rebuilt.end_with?("\n\n")
116
+ rebuilt << trailing
117
+ else
118
+ rebuilt << "\n" unless rebuilt.empty? || rebuilt.end_with?("\n")
119
+ end
120
+ rebuilt
121
+ end
122
+
123
+ def toml_string(value)
124
+ escaped = value.to_s
125
+ .gsub('\\', '\\\\')
126
+ .gsub('"', '\\"')
127
+ .gsub("\n", '\\n')
128
+ .gsub("\r", '\\r')
129
+ .gsub("\t", '\\t')
130
+
131
+ %Q("#{escaped}")
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class PackageRegistryMetadataProviderError < StandardError; end
5
+
6
+ class PackageRegistryMetadataProvider
7
+ def initialize(registry_store: PackageRegistryStore.new)
8
+ @registry_store = registry_store
9
+ end
10
+
11
+ def available_versions(package_name)
12
+ @registry_store.available_versions(package_name).map do |version_text|
13
+ PackageVersion.parse(version_text, label: "registry package #{package_name} version")
14
+ end.sort.reverse
15
+ rescue PackageRegistryStoreError, PackageVersionError => e
16
+ raise PackageRegistryMetadataProviderError, e.message
17
+ end
18
+
19
+ def manifest_for(package_name, version)
20
+ version_text = version.to_s
21
+ package_root = @registry_store.published_root_for(package_name, version_text, sync: true)
22
+
23
+ manifest = PackageManifest.load(package_root)
24
+ if manifest.package_name != package_name || manifest.package_version != version_text
25
+ raise PackageRegistryMetadataProviderError,
26
+ "registry package at #{manifest.manifest_path} resolved to #{manifest.package_name}@#{manifest.package_version.inspect}; expected #{package_name}@#{version_text}"
27
+ end
28
+
29
+ manifest
30
+ rescue PackageManifestError, PackageRegistryStoreError => e
31
+ raise PackageRegistryMetadataProviderError, e.message
32
+ end
33
+ end
34
+ end