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,1114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module ImportedBindings
5
+ class Generator
6
+ module GeneratorPolicy
7
+ private
8
+
9
+ def load_policy
10
+ JSON.parse(File.read(@policy_path))
11
+ rescue Errno::ENOENT
12
+ raise Error, "imported binding policy not found: #{@policy_path}"
13
+ rescue JSON::ParserError => e
14
+ raise Error, "failed to parse imported binding policy #{@policy_path}: #{e.message}"
15
+ end
16
+
17
+ def validate_policy!(policy)
18
+ unless policy.is_a?(Hash)
19
+ raise Error, "imported binding policy #{@policy_path} must be a JSON object"
20
+ end
21
+
22
+ if policy.key?("extra_source")
23
+ raise Error, "extra_source in #{@policy_path} is no longer supported; move helper code into a normal .mt module"
24
+ end
25
+
26
+ validate_allowed_keys!(policy, %w[module_name raw_module_name raw_import_alias imports types constants functions methods], context: "imported binding policy")
27
+
28
+ policy_module_name = policy.fetch("module_name")
29
+ if policy_module_name != @module_name
30
+ raise Error, "imported binding policy #{@policy_path} targets #{policy_module_name}, expected #{@module_name}"
31
+ end
32
+
33
+ policy_raw_module_name = policy.fetch("raw_module_name")
34
+ if policy_raw_module_name != @raw_module_name
35
+ raise Error, "imported binding policy #{@policy_path} expects raw module #{policy_raw_module_name}, expected #{@raw_module_name}"
36
+ end
37
+
38
+ import_alias = policy["raw_import_alias"]
39
+ return unless import_alias && import_alias != @import_alias
40
+
41
+ raise Error, "imported binding policy #{@policy_path} expects import alias #{import_alias}, expected #{@import_alias}"
42
+ end
43
+
44
+ def validate_raw_module!(raw_ast)
45
+ unless raw_ast.module_kind == :raw_module && raw_ast.module_name&.to_s == @raw_module_name
46
+ raise Error, "expected #{@raw_module_path} to be external file #{@raw_module_name}"
47
+ end
48
+ end
49
+
50
+ def raw_import_specs(raw_ast)
51
+ raw_ast.imports.map do |import|
52
+ {
53
+ module_name: import.path.parts.join("."),
54
+ alias: import.alias_name,
55
+ }
56
+ end
57
+ end
58
+
59
+ def policy_import_specs(policy)
60
+ entries = policy["imports"] || []
61
+ raise Error, "imports in #{@policy_path} must be an array" unless entries.is_a?(Array)
62
+
63
+ entries.map.with_index do |entry, index|
64
+ raise Error, "imports[#{index}] in #{@policy_path} must be an object" unless entry.is_a?(Hash)
65
+
66
+ validate_allowed_keys!(entry, %w[module_name alias], context: "policy import")
67
+
68
+ module_name = entry.fetch("module_name")
69
+ alias_name = entry.fetch("alias")
70
+ raise Error, "policy import module_name in #{@policy_path} must be a non-empty string" unless module_name.is_a?(String) && !module_name.empty?
71
+ raise Error, "policy import alias in #{@policy_path} must be a non-empty string" unless alias_name.is_a?(String) && !alias_name.empty?
72
+
73
+ {
74
+ module_name:,
75
+ alias: alias_name,
76
+ }
77
+ end
78
+ end
79
+
80
+ def generated_module_path
81
+ "#{@module_name.tr('.', '/')}" + ".mt"
82
+ end
83
+
84
+ def validate_import_specs!(import_specs)
85
+ duplicate_module = duplicate_name(import_specs.map { |spec| spec[:module_name] })
86
+ raise Error, "duplicate import #{duplicate_module} in #{@policy_path}" if duplicate_module
87
+
88
+ duplicate_alias = duplicate_name(([@import_alias] + import_specs.map { |spec| spec[:alias] }))
89
+ raise Error, "duplicate import alias #{duplicate_alias} in #{@policy_path}" if duplicate_alias
90
+ end
91
+
92
+ def merge_import_specs(*groups)
93
+ seen = {}
94
+
95
+ groups.flatten.compact.each_with_object([]) do |spec, merged|
96
+ key = [spec[:module_name], spec[:alias]]
97
+ next if seen[key]
98
+
99
+ seen[key] = true
100
+ merged << spec
101
+ end
102
+ end
103
+
104
+ def index_raw_declarations(raw_ast)
105
+ types = {}
106
+ type_order = []
107
+ values = {}
108
+ value_order = []
109
+ functions = {}
110
+ function_order = []
111
+
112
+ raw_ast.declarations.each do |declaration|
113
+ case declaration
114
+ when AST::TypeAliasDecl, AST::StructDecl, AST::UnionDecl, AST::EnumDecl, AST::FlagsDecl, AST::OpaqueDecl
115
+ types[declaration.name] = declaration
116
+ type_order << declaration.name
117
+ when AST::ConstDecl
118
+ values[declaration.name] = declaration
119
+ value_order << declaration.name
120
+ when AST::ExternFunctionDecl
121
+ functions[declaration.name] = declaration
122
+ function_order << declaration.name
123
+ end
124
+ end
125
+
126
+ {
127
+ types:,
128
+ type_order:,
129
+ values:,
130
+ value_order:,
131
+ functions:,
132
+ function_order:,
133
+ }
134
+ end
135
+
136
+ def emit_type_aliases(spec, declarations, referenced_lines:, method_sources:)
137
+ overrides = index_alias_overrides(spec[:overrides], declarations[:types], context: "type")
138
+ seen_public_names = {}
139
+ selected_names = resolve_selected_names(spec, declarations[:types], declarations[:type_order], context: "type")
140
+
141
+ if spec[:include] == :all
142
+ shadowed_public_type_names = method_sources.values.flat_map(&:public_type_names).uniq
143
+ selected_names = selected_names.reject do |raw_name|
144
+ override = overrides[raw_name]
145
+ next false if override
146
+
147
+ public_name = alias_public_name(raw_name, spec:, override:, binding_kind: :type)
148
+ shadowed_public_type_names.include?(public_name) && !unqualified_type_name_referenced?(public_name, referenced_lines)
149
+ end
150
+ end
151
+
152
+ selected_names.map do |raw_name|
153
+ raw_declaration = declarations[:types].fetch(raw_name)
154
+ override = overrides[raw_name]
155
+ public_name = alias_public_name(raw_name, spec:, override:, binding_kind: :type)
156
+ raise Error, "duplicate generated type #{public_name} in #{@policy_path}" if seen_public_names.key?(public_name)
157
+
158
+ seen_public_names[public_name] = true
159
+ case public_type_kind(raw_name, override:, raw_declaration:)
160
+ when :alias
161
+ mapping = override && override["mapping"] || @native_type_mapping[raw_name] || "#{@import_alias}.#{raw_name}"
162
+ "public type #{public_name} = #{mapping}"
163
+ when :opaque
164
+ opaque_c_name = raw_declaration.c_name || raw_name
165
+ "public opaque #{public_name} = c#{opaque_c_name.inspect}"
166
+ else
167
+ raise Error, "unsupported generated public type kind for #{raw_name} in #{@policy_path}"
168
+ end
169
+ end
170
+ end
171
+
172
+ def unqualified_type_name_referenced?(name, lines)
173
+ pattern = /(?<![A-Za-z0-9_.])#{Regexp.escape(name)}(?![A-Za-z0-9_])/
174
+
175
+ lines.any? { |line| line.match?(pattern) }
176
+ end
177
+
178
+ def emit_const_aliases(spec, declarations)
179
+ overrides = index_alias_overrides(spec[:overrides], declarations[:values], context: "constant")
180
+ seen_public_names = {}
181
+
182
+ resolve_selected_names(spec, declarations[:values], declarations[:value_order], context: "constant").map do |raw_name|
183
+ raw_declaration = declarations[:values].fetch(raw_name)
184
+ override = overrides[raw_name]
185
+ public_name = alias_public_name(raw_name, spec:, override:, binding_kind: :value)
186
+ raise Error, "duplicate generated constant #{public_name} in #{@policy_path}" if seen_public_names.key?(public_name)
187
+
188
+ seen_public_names[public_name] = true
189
+ const_type = override && override["type"] || render_type(raw_declaration.type)
190
+ mapping = override && override["mapping"] || "#{@import_alias}.#{raw_name}"
191
+ "public const #{public_name}: #{const_type} = #{mapping}"
192
+ end
193
+ end
194
+
195
+ def emit_foreign_functions(function_entries)
196
+ function_entries.map do |entry|
197
+ build_foreign_signature(
198
+ entry.fetch(:public_name),
199
+ type_params: entry.fetch(:type_params),
200
+ params: entry.fetch(:params).map { |param| render_foreign_param(param) },
201
+ return_type: entry.fetch(:return_type),
202
+ mapping: entry.fetch(:mapping),
203
+ variadic: entry.fetch(:variadic, false),
204
+ )
205
+ end
206
+ end
207
+
208
+ def emit_methods(method_specs, function_entries, declarations, method_sources:)
209
+ return [] if method_specs.empty?
210
+
211
+ entries_by_raw_name = function_entries.group_by { |entry| entry.fetch(:raw_name) }
212
+ available_entries = entries_by_raw_name.transform_values(&:first)
213
+ ordered_names = declarations[:function_order].select { |raw_name| entries_by_raw_name.key?(raw_name) }
214
+ lines = []
215
+
216
+ method_specs.each do |spec|
217
+ if spec[:module_name]
218
+ source = method_sources.fetch(method_source_key(spec))
219
+ source_entries = source.functions
220
+ selected_names = resolve_selected_names(spec, source_entries, source.function_order, context: "method function")
221
+ next if selected_names.empty?
222
+
223
+ seen_method_names = {}
224
+ lines << "" unless lines.empty?
225
+ lines << "extending #{spec.fetch(:type)}:"
226
+
227
+ selected_names.each do |source_name|
228
+ method_name, wrapper_lines = render_method_wrapper(plan_method_source_function(source_entries.fetch(source_name), source:), spec:)
229
+ if seen_method_names.key?(method_name)
230
+ raise Error, "duplicate generated method #{method_name} in #{@policy_path}"
231
+ end
232
+
233
+ seen_method_names[method_name] = true
234
+ wrapper_lines.each { |line| lines << " #{line}" }
235
+ end
236
+
237
+ next
238
+ end
239
+
240
+ selected_names = resolve_selected_names(spec, available_entries, ordered_names, context: "method function")
241
+ next if selected_names.empty?
242
+
243
+ seen_method_names = {}
244
+ lines << "" unless lines.empty?
245
+ lines << "extending #{spec.fetch(:type)}:"
246
+
247
+ selected_names.each do |raw_name|
248
+ generated_entries = entries_by_raw_name.fetch(raw_name)
249
+ if generated_entries.length != 1
250
+ raise Error, "method generation for #{raw_name} in #{@policy_path} does not support multiple generated signatures"
251
+ end
252
+
253
+ method_name, wrapper_lines = render_method_wrapper(generated_entries.first, spec:)
254
+ if seen_method_names.key?(method_name)
255
+ raise Error, "duplicate generated method #{method_name} in #{@policy_path}"
256
+ end
257
+
258
+ seen_method_names[method_name] = true
259
+ wrapper_lines.each { |line| lines << " #{line}" }
260
+ end
261
+ end
262
+
263
+ lines
264
+ end
265
+
266
+ def plan_foreign_functions(spec, declarations)
267
+ validate_known_raw_names!(spec[:exclude], declarations[:functions], context: "function")
268
+ validate_known_raw_names!(spec[:include], declarations[:functions], context: "function") unless spec[:include] == :all
269
+
270
+ overrides = index_function_overrides(spec[:overrides], declarations[:functions])
271
+ seen_public_names = {}
272
+
273
+ declarations[:function_order].flat_map do |raw_name|
274
+ selected = selected_by_spec?(raw_name, spec) || overrides.key?(raw_name)
275
+ next [] unless selected
276
+ next [] if spec[:exclude].include?(raw_name)
277
+
278
+ raw_declaration = declarations[:functions].fetch(raw_name)
279
+ if raw_declaration.variadic
280
+ if overrides.key?(raw_name)
281
+ generated_entries = overrides.fetch(raw_name).map do |entry|
282
+ unless entry.key?("mapping")
283
+ raise Error, "variadic raw function #{raw_name} in #{@raw_module_name} requires an explicit mapping override"
284
+ end
285
+
286
+ plan_overridden_function(entry, raw_declaration, spec:)
287
+ end
288
+
289
+ generated_entries.each do |entry|
290
+ public_name = entry.fetch(:public_name)
291
+ raise Error, "duplicate generated function #{public_name} in #{@policy_path}" if seen_public_names.key?(public_name)
292
+
293
+ seen_public_names[public_name] = true
294
+ end
295
+
296
+ next generated_entries
297
+ end
298
+ end
299
+
300
+ generated_entries = if overrides.key?(raw_name)
301
+ overrides.fetch(raw_name).map do |entry|
302
+ plan_overridden_function(entry, raw_declaration, spec:)
303
+ end
304
+ else
305
+ [plan_pass_through_foreign_function(raw_declaration, spec:)]
306
+ end
307
+
308
+ generated_entries.each do |entry|
309
+ public_name = entry.fetch(:public_name)
310
+ raise Error, "duplicate generated function #{public_name} in #{@policy_path}" if seen_public_names.key?(public_name)
311
+
312
+ seen_public_names[public_name] = true
313
+ end
314
+
315
+ generated_entries
316
+ end
317
+ end
318
+
319
+ def normalize_alias_spec(value, context:)
320
+ case value
321
+ when nil
322
+ {
323
+ include: :all,
324
+ include_prefixes: [],
325
+ exclude: [],
326
+ overrides: [],
327
+ rename_rules: [],
328
+ strip_prefix: nil,
329
+ native_types: {},
330
+ }
331
+ when Array
332
+ {
333
+ include: normalize_name_list(value, context:, label: "include"),
334
+ include_prefixes: [],
335
+ exclude: [],
336
+ overrides: [],
337
+ rename_rules: [],
338
+ strip_prefix: nil,
339
+ native_types: {},
340
+ }
341
+ when Hash
342
+ allowed_keys = %w[include include_prefixes exclude overrides rename_rules strip_prefix native_types]
343
+ validate_allowed_keys!(value, allowed_keys, context: "#{context} section")
344
+ native_types = value["native_types"] || {}
345
+ raise Error, "native_types in #{@policy_path} must be an object" unless native_types.is_a?(Hash)
346
+ native_types.each do |raw_type, native_type|
347
+ raise Error, "native_types key '#{raw_type}' in #{@policy_path} must be a non-empty string" unless native_type.is_a?(String) && !native_type.empty?
348
+ end
349
+ {
350
+ include: default_include(value, context:),
351
+ include_prefixes: normalize_prefix_list(value["include_prefixes"], context:),
352
+ exclude: normalize_name_list(value["exclude"], context:, label: "exclude"),
353
+ overrides: normalize_alias_overrides(value["overrides"], context:),
354
+ rename_rules: normalize_rename_rules(value["rename_rules"], context:),
355
+ strip_prefix: normalize_strip_prefix(value["strip_prefix"], context:),
356
+ native_types:,
357
+ }
358
+ else
359
+ raise Error, "#{context} section in #{@policy_path} must be an array or object"
360
+ end
361
+ end
362
+
363
+ def normalize_function_spec(value)
364
+ case value
365
+ when nil
366
+ {
367
+ include: [],
368
+ include_prefixes: [],
369
+ exclude: [],
370
+ overrides: [],
371
+ rename_rules: [],
372
+ strip_prefix: nil,
373
+ }
374
+ when Array
375
+ if value.all? { |entry| entry.is_a?(String) }
376
+ {
377
+ include: normalize_name_list(value, context: "function", label: "include"),
378
+ include_prefixes: [],
379
+ exclude: [],
380
+ overrides: [],
381
+ rename_rules: [],
382
+ strip_prefix: nil,
383
+ }
384
+ else
385
+ {
386
+ include: [],
387
+ include_prefixes: [],
388
+ exclude: [],
389
+ overrides: normalize_function_overrides(value),
390
+ rename_rules: [],
391
+ strip_prefix: nil,
392
+ }
393
+ end
394
+ when Hash
395
+ validate_allowed_keys!(value, %w[include include_prefixes exclude overrides rename_rules strip_prefix], context: "function section")
396
+ {
397
+ include: default_include(value, context: "function"),
398
+ include_prefixes: normalize_prefix_list(value["include_prefixes"], context: "function"),
399
+ exclude: normalize_name_list(value["exclude"], context: "function", label: "exclude"),
400
+ overrides: normalize_function_overrides(value["overrides"]),
401
+ rename_rules: normalize_rename_rules(value["rename_rules"], context: "function"),
402
+ strip_prefix: normalize_strip_prefix(value["strip_prefix"], context: "function"),
403
+ }
404
+ else
405
+ raise Error, "function section in #{@policy_path} must be an array or object"
406
+ end
407
+ end
408
+
409
+ def normalize_method_specs(value)
410
+ return [] if value.nil?
411
+ raise Error, "methods section in #{@policy_path} must be an array" unless value.is_a?(Array)
412
+
413
+ value.map do |entry|
414
+ raise Error, "methods entries in #{@policy_path} must be objects" unless entry.is_a?(Hash)
415
+
416
+ validate_allowed_keys!(entry, %w[type receiver_types include include_prefixes exclude rename_rules strip_prefix module_name module_import_alias], context: "methods entry")
417
+
418
+ type = normalize_method_type(entry["type"])
419
+ {
420
+ type:,
421
+ receiver_types: normalize_method_receiver_types(entry["receiver_types"], type:),
422
+ include: default_include(entry, context: "method"),
423
+ include_prefixes: normalize_prefix_list(entry["include_prefixes"], context: "method"),
424
+ exclude: normalize_name_list(entry["exclude"], context: "method", label: "exclude"),
425
+ rename_rules: normalize_rename_rules(entry["rename_rules"], context: "method"),
426
+ strip_prefix: normalize_strip_prefix(entry["strip_prefix"], context: "method"),
427
+ module_name: normalize_method_module_name(entry["module_name"]),
428
+ module_import_alias: normalize_method_module_import_alias(entry["module_import_alias"], module_name: entry["module_name"]),
429
+ }
430
+ end
431
+ end
432
+
433
+ def normalize_method_module_name(value)
434
+ return nil if value.nil?
435
+ raise Error, "methods entry module_name in #{@policy_path} must be a string" unless value.is_a?(String)
436
+ raise Error, "methods entry module_name in #{@policy_path} cannot be empty" if value.empty?
437
+
438
+ value
439
+ end
440
+
441
+ def normalize_method_module_import_alias(value, module_name:)
442
+ return nil if module_name.nil? && value.nil?
443
+ raise Error, "methods entry module_import_alias in #{@policy_path} requires module_name" if module_name.nil?
444
+ return module_name.split(".").last if value.nil?
445
+ raise Error, "methods entry module_import_alias in #{@policy_path} must be a string" unless value.is_a?(String)
446
+ raise Error, "methods entry module_import_alias in #{@policy_path} cannot be empty" if value.empty?
447
+
448
+ value
449
+ end
450
+
451
+ def normalize_method_type(value)
452
+ raise Error, "methods entry type in #{@policy_path} must be a string" unless value.is_a?(String)
453
+ raise Error, "methods entry type in #{@policy_path} cannot be empty" if value.empty?
454
+
455
+ value
456
+ end
457
+
458
+ def normalize_method_receiver_types(value, type:)
459
+ return [type] if value.nil?
460
+
461
+ normalize_name_list(value, context: "method receiver type", label: "method receiver type")
462
+ end
463
+
464
+ def normalize_alias_overrides(value, context:)
465
+ return [] if value.nil?
466
+ raise Error, "#{context} overrides in #{@policy_path} must be an array" unless value.is_a?(Array)
467
+
468
+ value.each do |entry|
469
+ raise Error, "#{context} overrides in #{@policy_path} must be objects" unless entry.is_a?(Hash)
470
+ end
471
+
472
+ value
473
+ end
474
+
475
+ def normalize_strip_prefix(value, context:)
476
+ return nil if value.nil?
477
+ raise Error, "#{context} strip_prefix in #{@policy_path} must be a string" unless value.is_a?(String)
478
+ raise Error, "#{context} strip_prefix in #{@policy_path} cannot be empty" if value.empty?
479
+
480
+ value
481
+ end
482
+
483
+ def normalize_rename_rules(value, context:)
484
+ return [] if value.nil?
485
+ raise Error, "#{context} rename_rules in #{@policy_path} must be an array" unless value.is_a?(Array)
486
+
487
+ value.map do |entry|
488
+ raise Error, "#{context} rename_rules in #{@policy_path} must be objects" unless entry.is_a?(Hash)
489
+
490
+ validate_allowed_keys!(entry, %w[kind match replace_with], context: "#{context} rename rule")
491
+
492
+ kind = entry.fetch("kind")
493
+ unless %w[prefix replace camelize opengl].include?(kind)
494
+ raise Error, "#{context} rename rule kind in #{@policy_path} must be prefix, replace, camelize, or opengl"
495
+ end
496
+
497
+ if %w[camelize opengl].include?(kind)
498
+ next {
499
+ kind: kind.to_sym,
500
+ match: nil,
501
+ replace_with: nil,
502
+ }
503
+ end
504
+
505
+ match = entry.fetch("match")
506
+ raise Error, "#{context} rename rule match in #{@policy_path} must be a string" unless match.is_a?(String)
507
+ raise Error, "#{context} rename rule match in #{@policy_path} cannot be empty" if match.empty?
508
+
509
+ replace_with = entry.fetch("replace_with", "")
510
+ raise Error, "#{context} rename rule replace_with in #{@policy_path} must be a string" unless replace_with.is_a?(String)
511
+
512
+ {
513
+ kind: kind.to_sym,
514
+ match:,
515
+ replace_with:,
516
+ }
517
+ end
518
+ end
519
+
520
+ def normalize_include(value, context:)
521
+ return :all if value.nil? || value == "all"
522
+
523
+ normalize_name_list(value, context:, label: "include")
524
+ end
525
+
526
+ def default_include(value, context:)
527
+ return normalize_include(value["include"], context:) if value.key?("include")
528
+ return [] if value.key?("include_prefixes")
529
+
530
+ :all
531
+ end
532
+
533
+ def normalize_prefix_list(value, context:)
534
+ return [] if value.nil?
535
+ raise Error, "include_prefixes #{context}s in #{@policy_path} must be an array" unless value.is_a?(Array)
536
+
537
+ prefixes = value.map do |entry|
538
+ raise Error, "include_prefixes #{context} entries in #{@policy_path} must be strings" unless entry.is_a?(String)
539
+ raise Error, "include_prefixes #{context} entries in #{@policy_path} cannot be empty" if entry.empty?
540
+
541
+ entry
542
+ end
543
+
544
+ duplicate = duplicate_name(prefixes)
545
+ raise Error, "duplicate include_prefixes #{context} #{duplicate} in #{@policy_path}" if duplicate
546
+
547
+ prefixes
548
+ end
549
+
550
+ def normalize_name_list(value, context:, label:)
551
+ return [] if value.nil?
552
+ raise Error, "#{label} #{context}s in #{@policy_path} must be an array" unless value.is_a?(Array)
553
+
554
+ names = value.map do |entry|
555
+ raise Error, "#{label} #{context} entries in #{@policy_path} must be strings" unless entry.is_a?(String)
556
+
557
+ entry
558
+ end
559
+
560
+ duplicate = duplicate_name(names)
561
+ raise Error, "duplicate #{label} #{context} #{duplicate} in #{@policy_path}" if duplicate
562
+
563
+ names
564
+ end
565
+
566
+ def normalize_function_overrides(value)
567
+ return [] if value.nil?
568
+ raise Error, "function overrides in #{@policy_path} must be an array" unless value.is_a?(Array)
569
+
570
+ value.each do |entry|
571
+ raise Error, "function overrides in #{@policy_path} must be objects" unless entry.is_a?(Hash)
572
+ end
573
+
574
+ value
575
+ end
576
+
577
+ def duplicate_name(names)
578
+ names.tally.each do |name, count|
579
+ return name if count > 1
580
+ end
581
+
582
+ nil
583
+ end
584
+
585
+ def validate_allowed_keys!(value, allowed_keys, context:)
586
+ unknown_keys = value.keys - allowed_keys
587
+ return if unknown_keys.empty?
588
+
589
+ raise Error, "#{context} in #{@policy_path} has unknown keys: #{unknown_keys.join(', ')}"
590
+ end
591
+
592
+ def resolve_selected_names(spec, declarations_by_name, ordered_names, context:)
593
+ validate_known_raw_names!(spec[:exclude], declarations_by_name, context:)
594
+
595
+ selected_names = if spec[:include] == :all
596
+ ordered_names.dup
597
+ else
598
+ validate_known_raw_names!(spec[:include], declarations_by_name, context:)
599
+ ordered_names.select { |raw_name| selected_by_spec?(raw_name, spec) }
600
+ end
601
+
602
+ selected_names.reject { |raw_name| spec[:exclude].include?(raw_name) }
603
+ end
604
+
605
+ def selected_by_spec?(raw_name, spec)
606
+ return true if spec[:include] == :all
607
+
608
+ spec[:include].include?(raw_name) || spec[:include_prefixes].any? { |prefix| raw_name.start_with?(prefix) }
609
+ end
610
+
611
+ def validate_known_raw_names!(names, declarations_by_name, context:)
612
+ names.each do |name|
613
+ next if declarations_by_name.key?(name)
614
+
615
+ raise Error, "unknown raw #{context} #{name} in #{@raw_module_name}"
616
+ end
617
+ end
618
+
619
+ def build_public_type_names(spec, declarations)
620
+ overrides = index_alias_overrides(spec[:overrides], declarations[:types], context: "type")
621
+ public_names = {}
622
+ seen_public_names = {}
623
+ @public_type_kinds_by_raw_name = {}
624
+
625
+ resolve_selected_names(spec, declarations[:types], declarations[:type_order], context: "type").each do |raw_name|
626
+ raw_declaration = declarations[:types].fetch(raw_name)
627
+ override = overrides[raw_name]
628
+ public_name = alias_public_name(raw_name, spec:, override:, binding_kind: :type)
629
+ raise Error, "duplicate generated type #{public_name} in #{@policy_path}" if seen_public_names.key?(public_name)
630
+
631
+ seen_public_names[public_name] = true
632
+ @public_type_kinds_by_raw_name[raw_name] = public_type_kind(raw_name, override:, raw_declaration:)
633
+ public_names[raw_name] = public_name
634
+ end
635
+
636
+ public_names
637
+ end
638
+
639
+ def index_alias_overrides(entries, declarations_by_name, context:)
640
+ overrides = {}
641
+ allowed_keys = context == "constant" ? %w[raw name type mapping] : %w[raw name mapping kind]
642
+
643
+ entries.each do |entry|
644
+ validate_allowed_keys!(entry, allowed_keys, context: "#{context} override")
645
+
646
+ raw_name = entry.fetch("raw")
647
+ raise Error, "#{context} override raw names in #{@policy_path} must be strings" unless raw_name.is_a?(String)
648
+ raise Error, "unknown raw #{context} #{raw_name} in #{@raw_module_name}" unless declarations_by_name.key?(raw_name)
649
+ raise Error, "duplicate #{context} override #{raw_name} in #{@policy_path}" if overrides.key?(raw_name)
650
+
651
+ overrides[raw_name] = entry
652
+ end
653
+
654
+ overrides
655
+ end
656
+
657
+ def alias_public_name(raw_name, spec:, override:, binding_kind:)
658
+ return override["name"] if override && override.key?("name")
659
+
660
+ default_public_name(raw_name, spec:, context: "generated name", binding_kind:)
661
+ end
662
+
663
+ def public_type_kind(raw_name, override:, raw_declaration:)
664
+ kind = override && override["kind"]
665
+ return :alias if kind.nil?
666
+ raise Error, "type override kind for #{raw_name} in #{@policy_path} must be alias or opaque" unless %w[alias opaque].include?(kind)
667
+
668
+ return :alias if kind == "alias"
669
+
670
+ unless raw_declaration.is_a?(AST::OpaqueDecl)
671
+ raise Error, "type override #{raw_name} in #{@policy_path} can only use kind opaque for raw opaque declarations"
672
+ end
673
+
674
+ if override.key?("mapping")
675
+ raise Error, "type override #{raw_name} in #{@policy_path} cannot use mapping with kind opaque"
676
+ end
677
+
678
+ :opaque
679
+ end
680
+
681
+ def default_public_name(raw_name, spec:, context:, binding_kind:)
682
+ transformed = apply_rename_rules(raw_name, spec[:rename_rules], context:)
683
+ transformed = strip_prefix(transformed, spec[:strip_prefix], context:) if spec[:strip_prefix]
684
+ raise Error, "#{context} in #{@policy_path} cannot be empty" if transformed.empty?
685
+
686
+ sanitize_generated_binding_name(transformed, binding_kind:)
687
+ end
688
+
689
+ def sanitize_generated_binding_name(name, binding_kind:)
690
+ return name unless generated_binding_name_conflict?(name, binding_kind:)
691
+
692
+ "#{name}_"
693
+ end
694
+
695
+ def apply_rename_rules(name, rules, context:)
696
+ transformed = name
697
+
698
+ rules.each do |rule|
699
+ case rule[:kind]
700
+ when :prefix
701
+ next unless transformed.start_with?(rule[:match])
702
+
703
+ transformed = rule[:replace_with] + transformed.delete_prefix(rule[:match])
704
+ when :replace
705
+ transformed = transformed.gsub(rule[:match], rule[:replace_with])
706
+ when :camelize
707
+ transformed = camelize_binding_name(transformed)
708
+ when :opengl
709
+ transformed = openglize_binding_name(transformed)
710
+ else
711
+ raise Error, "unsupported #{context} rename rule #{rule[:kind]} in #{@policy_path}"
712
+ end
713
+ end
714
+
715
+ raise Error, "#{context} in #{@policy_path} cannot be empty" if transformed.empty?
716
+
717
+ transformed
718
+ end
719
+
720
+ def strip_prefix(name, prefix, context:)
721
+ stripped = prefix && name.start_with?(prefix) ? name.delete_prefix(prefix) : name
722
+ raise Error, "#{context} in #{@policy_path} cannot be empty" if stripped.empty?
723
+
724
+ stripped
725
+ end
726
+
727
+ def index_function_overrides(entries, declarations_by_name)
728
+ overrides = {}
729
+
730
+ entries.each do |entry|
731
+ validate_allowed_keys!(entry, %w[raw name type_params params return_type mapping], context: "function override")
732
+
733
+ raw_name = entry.fetch("raw")
734
+ raise Error, "function override raw names in #{@policy_path} must be strings" unless raw_name.is_a?(String)
735
+ raise Error, "unknown raw function #{raw_name} in #{@raw_module_name}" unless declarations_by_name.key?(raw_name)
736
+
737
+ (overrides[raw_name] ||= []) << entry
738
+ end
739
+
740
+ overrides
741
+ end
742
+
743
+ def render_overridden_function(entry, raw_declaration, spec:)
744
+ render_overridden_foreign_function(entry, raw_declaration, spec:)
745
+ end
746
+
747
+ def render_overridden_foreign_function(entry, raw_declaration, spec:)
748
+ raw_name = raw_declaration.name
749
+ function_name = entry["name"] || foreign_function_name(raw_name, spec:)
750
+ type_params = override_type_param_names(entry, raw_declaration)
751
+ params = override_param_specs(entry, raw_declaration).map { |param| render_foreign_param(param) }
752
+ return_type = entry["return_type"] || render_public_foreign_type(raw_declaration.return_type)
753
+ mapping = entry["mapping"] || "#{@import_alias}.#{raw_name}"
754
+
755
+ [function_name, [build_foreign_signature(function_name, type_params:, params:, return_type:, mapping:, variadic: false)]]
756
+ end
757
+
758
+ def render_pass_through_foreign_function(raw_declaration, spec:)
759
+ function_name = foreign_function_name(raw_declaration.name, spec:)
760
+ params = raw_declaration.params.map { |param| render_heuristic_foreign_param(param) }
761
+ return_type = render_public_foreign_type(raw_declaration.return_type)
762
+ mapping = "#{@import_alias}.#{raw_declaration.name}"
763
+
764
+ [function_name, [build_foreign_signature(function_name, type_params: raw_type_param_names(raw_declaration), params:, return_type:, mapping:, variadic: raw_declaration.variadic)]]
765
+ end
766
+
767
+ def override_type_param_names(entry, raw_declaration)
768
+ if entry.key?("type_params")
769
+ normalize_name_list(entry["type_params"], context: "function type parameter", label: "function type parameter")
770
+ else
771
+ raw_type_param_names(raw_declaration)
772
+ end
773
+ end
774
+
775
+ def override_param_specs(entry, raw_declaration)
776
+ if entry.key?("params")
777
+ Array(entry["params"]).map do |param|
778
+ param = param.dup
779
+ param["name"] = generated_foreign_param_name(param.fetch("name"))
780
+ param["type"] = apply_native_type_mapping(param["type"])
781
+ param
782
+ end
783
+ else
784
+ raw_declaration.params.map do |param|
785
+ {
786
+ "name" => generated_foreign_param_name(param.name),
787
+ "type" => render_public_foreign_type(param.type),
788
+ }
789
+ end
790
+ end
791
+ end
792
+
793
+ def apply_native_type_mapping(type_str)
794
+ return type_str unless type_str.is_a?(String)
795
+ return type_str if @native_type_mapping.empty?
796
+
797
+ type_str.gsub(/\b([A-Z][A-Za-z0-9_]*)\b/) do |match|
798
+ @native_type_mapping[match] || match
799
+ end
800
+ end
801
+
802
+ def plan_overridden_function(entry, raw_declaration, spec:)
803
+ raw_name = raw_declaration.name
804
+ return_type = entry["return_type"] || render_public_foreign_type(raw_declaration.return_type)
805
+ return_type = apply_native_type_mapping(return_type) if return_type
806
+ {
807
+ raw_name:,
808
+ public_name: entry["name"] || foreign_function_name(raw_name, spec:),
809
+ type_params: override_type_param_names(entry, raw_declaration),
810
+ params: override_param_specs(entry, raw_declaration),
811
+ return_type:,
812
+ mapping: entry["mapping"] || "#{@import_alias}.#{raw_name}",
813
+ variadic: false,
814
+ }
815
+ end
816
+
817
+ def plan_pass_through_foreign_function(raw_declaration, spec:)
818
+ {
819
+ raw_name: raw_declaration.name,
820
+ public_name: foreign_function_name(raw_declaration.name, spec:),
821
+ type_params: raw_type_param_names(raw_declaration),
822
+ params: raw_declaration.params.map { |param| heuristic_foreign_param_spec(param) },
823
+ return_type: render_public_foreign_type(raw_declaration.return_type),
824
+ mapping: "#{@import_alias}.#{raw_declaration.name}",
825
+ variadic: raw_declaration.variadic,
826
+ }
827
+ end
828
+
829
+ def foreign_function_name(raw_name, spec:)
830
+ normalize_generated_public_value_name(
831
+ default_public_name(raw_name, spec:, context: "generated function name", binding_kind: :value),
832
+ raw_name:,
833
+ spec:,
834
+ )
835
+ end
836
+
837
+ def render_method_wrapper(function_entry, spec:)
838
+ raw_name = function_entry.fetch(:raw_name)
839
+ method_name = normalize_generated_public_value_name(
840
+ default_public_name(raw_name, spec:, context: "generated method name", binding_kind: :value),
841
+ raw_name:,
842
+ spec:,
843
+ )
844
+ if function_entry.fetch(:variadic, false)
845
+ raise Error, "method generation for #{raw_name} in #{@policy_path} cannot wrap variadic functions"
846
+ end
847
+ method_kind = method_kind(function_entry, spec:, raw_name:)
848
+ method_params = method_kind == :static ? function_entry.fetch(:params) : function_entry.fetch(:params).drop(1)
849
+ if method_params.any? { |param| param["mode"] }
850
+ raise Error, "method generation for #{raw_name} in #{@policy_path} cannot wrap out/inout non-receiver parameters; exclude it from methods"
851
+ end
852
+ call_args = []
853
+ call_args << "this" unless method_kind == :static
854
+ call_args.concat(method_params.map { |param| param.fetch("name") })
855
+ call_expression = build_call_expression(function_entry.fetch(:call_name, function_entry.fetch(:public_name)), type_params: function_entry.fetch(:type_params), args: call_args)
856
+ body_line = function_entry.fetch(:return_type) == "void" ? call_expression : "return #{call_expression}"
857
+
858
+ [
859
+ method_name,
860
+ [
861
+ build_method_signature(
862
+ method_name,
863
+ type_params: function_entry.fetch(:type_params),
864
+ params: method_params.map { |param| render_method_param(param) },
865
+ return_type: function_entry.fetch(:return_type),
866
+ kind: method_kind,
867
+ ),
868
+ " #{body_line}",
869
+ ],
870
+ ]
871
+ end
872
+
873
+ def normalize_generated_public_value_name(name, raw_name:, spec:)
874
+ normalized = snake_case(name)
875
+ return normalized unless spec[:rename_rules].any? { |rule| rule[:kind] == :opengl }
876
+
877
+ normalize_opengl_terminal_suffix(normalize_opengl_snake_case(normalized), raw_name)
878
+ end
879
+
880
+ def method_kind(function_entry, spec:, raw_name:)
881
+ first_param = function_entry.fetch(:params).first
882
+ return :static unless first_param
883
+ return :static unless method_receiver_type_match?(first_param.fetch("type"), spec.fetch(:receiver_types))
884
+
885
+ case first_param["mode"]
886
+ when nil
887
+ :instance
888
+ when "inout"
889
+ :editable
890
+ else
891
+ raise Error, "method generation for #{raw_name} in #{@policy_path} cannot use receiver mode #{first_param["mode"].inspect}"
892
+ end
893
+ end
894
+
895
+ def method_receiver_type_match?(param_type, receiver_types)
896
+ return true if receiver_types.include?(param_type)
897
+
898
+ receiver_types.any? { |rt| @native_type_mapping[rt] == param_type }
899
+ end
900
+
901
+ def build_foreign_signature(name, type_params:, params:, return_type:, mapping:, variadic: false, visibility: :public)
902
+ signature = +""
903
+ signature << "public " if visibility == :public
904
+ signature << "foreign function #{name}"
905
+ signature << render_type_params(type_params)
906
+ rendered_params = params.dup
907
+ rendered_params << "..." if variadic
908
+ signature << "(#{rendered_params.join(', ')}) -> #{return_type}"
909
+ signature << " = #{mapping}"
910
+ signature
911
+ end
912
+
913
+ def build_method_signature(name, type_params:, params:, return_type:, kind:, visibility: :public)
914
+ signature = +""
915
+ signature << "public " if visibility == :public
916
+ signature << case kind
917
+ when :static
918
+ "static function "
919
+ when :editable
920
+ "editable function "
921
+ else
922
+ "function "
923
+ end
924
+ signature << name
925
+ signature << render_type_params(type_params)
926
+ signature << "(#{params.join(', ')}) -> #{return_type}:"
927
+ signature
928
+ end
929
+
930
+ def build_call_expression(name, type_params:, args:)
931
+ expression = name.dup
932
+ expression << render_type_params(type_params)
933
+ expression << "(#{args.join(', ')})"
934
+ expression
935
+ end
936
+
937
+ def raw_type_param_names(raw_declaration)
938
+ Array(raw_declaration.type_params).map(&:name)
939
+ end
940
+
941
+ def render_raw_foreign_param(param)
942
+ "#{generated_foreign_param_name(param.name)}: #{render_public_foreign_type(param.type)}"
943
+ end
944
+
945
+ def render_method_param(param)
946
+ "#{param.fetch("name")}: #{param.fetch("type")}"
947
+ end
948
+
949
+ def render_heuristic_foreign_param(param)
950
+ type = param.type
951
+
952
+ if type.is_a?(AST::TypeRef) && type.name.to_s == "cstr" && type.arguments.empty? && !type.nullable
953
+ name = generated_foreign_param_name(param.name)
954
+ return render_foreign_param({ "name" => name, "type" => "str", "boundary_type" => "cstr" })
955
+ end
956
+
957
+ render_raw_foreign_param(param)
958
+ end
959
+
960
+ def heuristic_foreign_param_spec(param)
961
+ type = param.type
962
+
963
+ if type.is_a?(AST::TypeRef) && type.name.to_s == "cstr" && type.arguments.empty? && !type.nullable
964
+ name = generated_foreign_param_name(param.name)
965
+ return { "name" => name, "type" => "str", "boundary_type" => "cstr" }
966
+ end
967
+
968
+ { "name" => generated_foreign_param_name(param.name), "type" => render_public_foreign_type(param.type) }
969
+ end
970
+
971
+ def generated_foreign_param_name(name)
972
+ normalized = snake_case(name)
973
+ return normalized unless generated_binding_name_conflict?(normalized, binding_kind: :value)
974
+
975
+ "#{normalized}_"
976
+ end
977
+
978
+ def generated_binding_name_conflict?(name, binding_kind:)
979
+ return true if Token::KEYWORDS.key?(name)
980
+
981
+ case binding_kind
982
+ when :type
983
+ Types::RESERVED_TYPE_BINDING_NAMES.include?(name)
984
+ when :value
985
+ Types::RESERVED_VALUE_TYPE_NAMES.include?(name)
986
+ else
987
+ raise Error, "unsupported generated binding kind #{binding_kind.inspect} in #{@policy_path}"
988
+ end
989
+ end
990
+
991
+ def render_public_foreign_type(type)
992
+ projected_handle = project_public_handle_type(type)
993
+ return projected_handle if projected_handle
994
+
995
+ render_type(type)
996
+ end
997
+
998
+ def project_public_handle_type(type)
999
+ return unless type.is_a?(AST::TypeRef)
1000
+ return unless %w[ptr const_ptr].include?(type.name.to_s)
1001
+ return unless type.arguments.length == 1
1002
+
1003
+ pointee = type.arguments.first.value
1004
+ return unless pointee.is_a?(AST::TypeRef)
1005
+ return unless pointee.arguments.empty?
1006
+ return unless @public_type_kinds_by_raw_name[pointee.name.to_s] == :opaque
1007
+
1008
+ text = rendered_type_name(pointee.name.to_s).dup
1009
+ text << "?" if type.nullable
1010
+ text
1011
+ end
1012
+
1013
+ def render_foreign_param(param)
1014
+ unless param.is_a?(Hash)
1015
+ raise Error, "function parameters in #{@policy_path} must be objects"
1016
+ end
1017
+
1018
+ text = +""
1019
+ mode = param["mode"]
1020
+ text << "#{mode} " if mode
1021
+ text << "#{param.fetch("name")}: #{param.fetch("type")}"
1022
+ boundary_type = param["boundary_type"]
1023
+ text << " as #{boundary_type}" if boundary_type
1024
+ text
1025
+ end
1026
+
1027
+ def render_type_params(type_params)
1028
+ return "" if type_params.empty?
1029
+
1030
+ "[#{type_params.join(', ')}]"
1031
+ end
1032
+
1033
+ def render_type(type)
1034
+ case type
1035
+ when AST::TypeRef
1036
+ text = rendered_type_name(type.name.to_s).dup
1037
+ unless type.arguments.empty?
1038
+ rendered_arguments = type.arguments.map { |argument| render_type_argument(argument.value) }
1039
+ text << "[#{rendered_arguments.join(', ')}]"
1040
+ end
1041
+ text << "?" if type.nullable
1042
+ text
1043
+ when AST::FunctionType
1044
+ params = type.params.map { |param| render_function_type_param(param) }.join(', ')
1045
+ "fn(#{params}) -> #{render_public_foreign_type(type.return_type)}"
1046
+ else
1047
+ raise Error, "unsupported raw type node #{type.class.name} in #{@raw_module_path}"
1048
+ end
1049
+ end
1050
+
1051
+ def render_function_type_param(param)
1052
+ case param
1053
+ when AST::Param
1054
+ "#{param.name}: #{render_public_foreign_type(param.type)}"
1055
+ when AST::ForeignParam
1056
+ render_foreign_param({ "name" => param.name, "type" => render_public_foreign_type(param.type), "mode" => param.mode, "boundary_type" => param.boundary_type && render_public_foreign_type(param.boundary_type) })
1057
+ else
1058
+ render_type(param)
1059
+ end
1060
+ end
1061
+
1062
+ def render_type_argument(argument)
1063
+ case argument
1064
+ when AST::TypeRef, AST::FunctionType
1065
+ render_type(argument)
1066
+ when AST::IntegerLiteral
1067
+ argument.lexeme
1068
+ when AST::Identifier
1069
+ argument.name
1070
+ else
1071
+ raise Error, "unsupported raw type argument #{argument.class.name} in #{@raw_module_path}"
1072
+ end
1073
+ end
1074
+
1075
+ def rendered_type_name(raw_name, seen = [])
1076
+ return @native_type_mapping[raw_name] if @native_type_mapping.key?(raw_name)
1077
+
1078
+ bare_name = raw_name.split(".").last
1079
+ return @native_type_mapping[bare_name] if @native_type_mapping.key?(bare_name)
1080
+
1081
+ public_name = @public_type_names_by_raw_name[raw_name]
1082
+ return public_name if public_name
1083
+
1084
+ raise Error, "cyclic raw type alias #{raw_name} in #{@raw_module_path}" if seen.include?(raw_name)
1085
+
1086
+ raw_declaration = @raw_type_declarations[raw_name]
1087
+ if raw_declaration.is_a?(AST::TypeAliasDecl) &&
1088
+ raw_declaration.target.is_a?(AST::TypeRef) &&
1089
+ raw_declaration.target.arguments.empty? &&
1090
+ !raw_declaration.target.nullable
1091
+ return rendered_type_name(raw_declaration.target.name.to_s, seen + [raw_name])
1092
+ end
1093
+
1094
+ raw_name
1095
+ end
1096
+
1097
+ def policy_label
1098
+ root_prefix = MilkTea.root.to_s + "/"
1099
+ return @policy_path.delete_prefix(root_prefix) if @policy_path.start_with?(root_prefix)
1100
+
1101
+ File.basename(@policy_path)
1102
+ end
1103
+
1104
+ def resolve_module_path(module_name)
1105
+ relative_path = File.join(*module_name.split(".")) + ".mt"
1106
+ candidate = @module_roots.lazy.map { |root| File.join(root, relative_path) }.find { |path| File.file?(path) }
1107
+ raise Error, "module not found: #{module_name}" unless candidate
1108
+
1109
+ File.expand_path(candidate)
1110
+ end
1111
+ end
1112
+ end
1113
+ end
1114
+ end