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,387 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module Bindgen
5
+ class Generator
6
+ module GeneratorEmitter
7
+ private
8
+
9
+ def emitted_name(raw_name)
10
+ return raw_name unless @strip_leading_underscores
11
+
12
+ raw_name.sub(/\A_+/, "")
13
+ end
14
+
15
+ def emit_module(declarations)
16
+ declarations = synthetic_declarations_for(declarations) + declarations
17
+
18
+ lines = []
19
+ lines << "# generated by mtc bindgen from #{@header_path}"
20
+ lines << "external"
21
+ lines << ""
22
+
23
+ @module_imports.each do |import|
24
+ lines << %(import #{import.fetch(:module_name)} as #{import.fetch(:alias)})
25
+ end
26
+
27
+ directives = []
28
+ @link_libraries.each do |library|
29
+ directives << %(link #{library.dump})
30
+ end
31
+ includes = @include_directives && !@include_directives.empty? ? @include_directives : [File.basename(@header_path)]
32
+ includes.each do |include_name|
33
+ directives << %(include #{include_name.dump})
34
+ end
35
+ lines.concat(directives)
36
+
37
+ declarations.each do |declaration|
38
+ lines.concat(emit_declaration(declaration))
39
+ end
40
+
41
+ source = lines.join("\n") + "\n"
42
+ Formatter.format_source(source, path: generated_module_path, mode: :tidy)
43
+ end
44
+
45
+ def generated_module_path
46
+ "#{@module_name.tr('.', '/')}" + ".mt"
47
+ end
48
+
49
+ def emit_declaration(declaration)
50
+ case declaration[:kind]
51
+ when "struct", "union"
52
+ emit_aggregate_declaration(declaration[:kind], declaration[:name], declaration[:node])
53
+ when "opaque"
54
+ name = emitted_name(declaration[:name])
55
+ line = "opaque #{name}"
56
+ line += " = c#{declaration[:linkage_name].inspect}" if declaration[:linkage_name]
57
+ [line]
58
+ when "enum", "flags"
59
+ emit_enum_declaration(declaration[:kind], emitted_name(declaration[:name]), declaration[:node])
60
+ when "type_alias"
61
+ ["type #{emitted_name(declaration[:name])} = #{declaration[:mapped_type]}"]
62
+ when "const"
63
+ ["const #{emitted_name(declaration[:name])}: #{declaration[:type]} = #{declaration[:value]}"]
64
+ when "function"
65
+ emit_function_declaration(declaration)
66
+ else
67
+ raise BindgenError, "unsupported bindgen declaration kind #{declaration[:kind]}"
68
+ end
69
+ end
70
+
71
+ def emit_aggregate_declaration(kind, name, node)
72
+ explicit_c_name = aggregate_explicit_c_name(name, node)
73
+ stripped = emitted_name(name)
74
+ header = "#{kind} #{stripped}"
75
+ header += " = c#{explicit_c_name.inspect}" if explicit_c_name
76
+ header += ":"
77
+ lines = [header]
78
+ fields = Array(node["inner"]).select { |child| child["kind"] == "FieldDecl" }
79
+ fields.each do |field|
80
+ field_type = aggregate_field_type(field, owner_name: name, aggregate_node: node)
81
+ mt_name, = bindgen_field_name(emitted_name(aggregate_field_name(field, aggregate_node: node)))
82
+ lines << " #{mt_name}: #{field_type}"
83
+ end
84
+ lines
85
+ end
86
+
87
+ def bindgen_field_name(name)
88
+ name = emitted_name(name)
89
+ return [name, nil] unless Token::KEYWORDS.key?(name)
90
+
91
+ ["#{name}_", nil]
92
+ end
93
+
94
+ def bindgen_param_name(name)
95
+ name = emitted_name(name)
96
+ return [name, nil] unless generated_binding_name_conflict?(name)
97
+
98
+ ["#{name}_", nil]
99
+ end
100
+
101
+ def generated_binding_name_conflict?(name)
102
+ Token::KEYWORDS.key?(name) || Types::RESERVED_VALUE_TYPE_NAMES.include?(name)
103
+ end
104
+
105
+ def discover_synthetic_aggregate_dependencies(declarations)
106
+ pending = declarations.select { |declaration| %w[struct union].include?(declaration[:kind]) }.map do |declaration|
107
+ [declaration[:name], declaration[:node]]
108
+ end
109
+ seen = {}
110
+
111
+ until pending.empty?
112
+ owner_name, aggregate_node = pending.shift
113
+ key = [owner_name, aggregate_node["id"]]
114
+ next if seen[key]
115
+
116
+ seen[key] = true
117
+
118
+ Array(aggregate_node["inner"]).select { |child| child["kind"] == "FieldDecl" }.each do |field|
119
+ anonymous_record = anonymous_record_decl_for_field(field, aggregate_node)
120
+ next unless anonymous_record
121
+
122
+ synthetic_name = synthetic_aggregate_name(owner_name, field, aggregate_node)
123
+ unless @synthetic_declarations.any? { |declaration| declaration[:name] == synthetic_name }
124
+ @synthetic_declarations << { kind: anonymous_record.fetch("tagUsed"), name: synthetic_name, node: anonymous_record }
125
+ @aggregate_declarations[synthetic_name] = anonymous_record
126
+ end
127
+
128
+ pending << [synthetic_name, anonymous_record]
129
+ end
130
+ end
131
+ end
132
+
133
+ def discover_synthetic_field_type_dependencies(declarations)
134
+ pending = declarations.select { |declaration| %w[struct union].include?(declaration[:kind]) }.map do |declaration|
135
+ [declaration[:name], declaration[:node]]
136
+ end
137
+ pending.concat(
138
+ @synthetic_declarations.select { |declaration| %w[struct union].include?(declaration[:kind]) }.map do |declaration|
139
+ [declaration[:name], declaration[:node]]
140
+ end,
141
+ )
142
+ seen = {}
143
+
144
+ until pending.empty?
145
+ owner_name, aggregate_node = pending.shift
146
+ key = [owner_name, aggregate_node["id"]]
147
+ next if seen[key]
148
+
149
+ seen[key] = true
150
+
151
+ Array(aggregate_node["inner"]).select { |child| child["kind"] == "FieldDecl" }.each do |field|
152
+ aggregate_field_type(field, owner_name:, aggregate_node:)
153
+
154
+ anonymous_record = anonymous_record_decl_for_field(field, aggregate_node)
155
+ next unless anonymous_record
156
+
157
+ pending << [synthetic_aggregate_name(owner_name, field, aggregate_node), anonymous_record]
158
+ end
159
+ end
160
+ end
161
+
162
+ def aggregate_field_type(field, owner_name:, aggregate_node:)
163
+ override = field_type_override(owner_name, field["name"])
164
+ return override if override
165
+
166
+ anonymous_record = anonymous_record_decl_for_field(field, aggregate_node)
167
+ return synthetic_aggregate_name(owner_name, field, aggregate_node) if anonymous_record
168
+
169
+ map_type_node(field, context: "field #{owner_name}.#{field["name"]}")
170
+ end
171
+
172
+ def aggregate_field_name(field, aggregate_node:)
173
+ name = field["name"]
174
+ return name if name && !name.empty?
175
+
176
+ anonymous_record = anonymous_record_decl_for_field(field, aggregate_node)
177
+ return name unless anonymous_record
178
+
179
+ field_index = Array(aggregate_node["inner"]).select { |child| child["kind"] == "FieldDecl" }.index { |child| child["id"] == field["id"] } || 0
180
+ "anonymous_#{anonymous_record.fetch("tagUsed")}_#{field_index}"
181
+ end
182
+
183
+ def synthetic_aggregate_name(owner_name, field, aggregate_node)
184
+ "#{owner_name}_#{aggregate_field_name(field, aggregate_node:)}"
185
+ end
186
+
187
+ def anonymous_record_decl_for_field(field, aggregate_node)
188
+ qual_type = type_qual_type(field)
189
+ return unless qual_type
190
+
191
+ tag_match = qual_type.match(/\A(struct|union)\b/)
192
+ return unless tag_match
193
+ return unless qual_type.include?("(unnamed at ") || qual_type.include?("(anonymous at ")
194
+
195
+ field_begin = source_location_key(field.dig("range", "begin"))
196
+ return unless field_begin
197
+
198
+ expected_tag = tag_match[1]
199
+ Array(aggregate_node["inner"]).find do |child|
200
+ next false unless child["kind"] == "RecordDecl"
201
+ next false unless child["tagUsed"] == expected_tag
202
+
203
+ source_location_key(child["loc"]) == field_begin
204
+ end
205
+ end
206
+
207
+ def source_location_key(location)
208
+ return unless location
209
+
210
+ [
211
+ location["offset"],
212
+ location.dig("includedFrom", "file"),
213
+ location["file"],
214
+ ]
215
+ end
216
+
217
+ def emit_enum_declaration(kind, name, node)
218
+ members = enum_member_values(node)
219
+ backing_type = if members.empty?
220
+ "int"
221
+ else
222
+ map_c_type(members.first.dig(:node, "type", "qualType"), context: "enum #{name}")
223
+ end
224
+ lines = ["#{kind} #{name}: #{backing_type}"]
225
+ members.each do |member|
226
+ lines << " #{member[:node]["name"]} = #{member[:value]}"
227
+ end
228
+ lines
229
+ end
230
+
231
+ def enum_member_values(node)
232
+ next_value = 0
233
+
234
+ Array(node["inner"]).filter_map do |child|
235
+ next unless child["kind"] == "EnumConstantDecl"
236
+
237
+ explicit_value = integer_value(child)
238
+ value = explicit_value ? Integer(explicit_value, 10) : next_value
239
+ next_value = value + 1
240
+ { node: child, value: }
241
+ end
242
+ end
243
+
244
+ def integer_value(node)
245
+ constant = Array(node["inner"]).find { |child| child["kind"] == "ConstantExpr" }
246
+ return constant["value"] if constant && constant.key?("value")
247
+
248
+ literal = Array(node["inner"]).find { |child| child["kind"] == "IntegerLiteral" }
249
+ literal&.[]("value")
250
+ end
251
+
252
+ def emit_function_declaration(declaration)
253
+ original_name = declaration[:name]
254
+ stripped_name = emitted_name(original_name)
255
+ params = declaration[:params].map do |param|
256
+ param_name, = bindgen_param_name(param[:name])
257
+ "#{param_name}: #{param[:type]}"
258
+ end
259
+ params << "..." if declaration[:variadic]
260
+ line = "external function #{stripped_name}(#{params.join(', ')}) -> #{declaration[:return_type]}"
261
+ if stripped_name != original_name
262
+ line += " = c#{original_name.inspect}"
263
+ end
264
+ [line]
265
+ end
266
+
267
+ def lower_constant_expression(node, expected_type:, context:)
268
+ raise BindgenError, "missing initializer for #{context}" unless node
269
+
270
+ case node["kind"]
271
+ when "IntegerLiteral"
272
+ integer_value = node.fetch("value")
273
+ typed_null = pointer_zero_literal(expected_type, integer_value)
274
+ return typed_null if typed_null
275
+
276
+ integer_value
277
+ when "FloatingLiteral"
278
+ emit_float_value(node.fetch("value"), expected_type)
279
+ when "StringLiteral"
280
+ %(c#{node.fetch("value")})
281
+ when "ImplicitValueInitExpr"
282
+ lower_zero_value(expected_type:, context:)
283
+ when "ImplicitCastExpr", "ConstantExpr", "CompoundLiteralExpr", "ParenExpr"
284
+ child = Array(node["inner"]).first
285
+ lower_constant_expression(child, expected_type:, context:)
286
+ when "UnaryOperator"
287
+ operator = node["opcode"]
288
+ operand = lower_constant_expression(Array(node["inner"]).first, expected_type:, context:)
289
+ "#{operator}#{operand}"
290
+ when "InitListExpr"
291
+ lower_init_list_expression(node, expected_type:, context:)
292
+ else
293
+ raise BindgenError, "unsupported constant initializer #{node["kind"]} for #{context}"
294
+ end
295
+ end
296
+
297
+ def lower_init_list_expression(node, expected_type:, context:)
298
+ values = Array(node["inner"])
299
+
300
+ aggregate = @aggregate_declarations[expected_type]
301
+ return lower_aggregate_init_list(values, aggregate:, expected_type:, context:) if aggregate
302
+
303
+ element_type, length = parse_array_type(expected_type)
304
+ raise BindgenError, "unsupported aggregate constant type #{expected_type} for #{context}" unless element_type
305
+ raise BindgenError, "initializer field count mismatch for #{context}" if values.length > length
306
+
307
+ arguments = (0...length).map do |index|
308
+ value = values[index]
309
+ if value
310
+ lower_constant_expression(value, expected_type: element_type, context: "element #{index} of #{context}")
311
+ else
312
+ lower_zero_value(expected_type: element_type, context: "element #{index} of #{context}")
313
+ end
314
+ end
315
+ "#{expected_type}(#{arguments.join(', ')})"
316
+ end
317
+
318
+ def emit_float_value(value, expected_type)
319
+ value.match?(/[.eE]/) ? value : "#{value}.0"
320
+ end
321
+
322
+ def lower_aggregate_init_list(values, aggregate:, expected_type:, context:)
323
+ fields = Array(aggregate["inner"]).select { |child| child["kind"] == "FieldDecl" }
324
+ raise BindgenError, "initializer field count mismatch for #{context}" if values.length > fields.length
325
+
326
+ arguments = fields.each_with_index.map do |field, index|
327
+ field_type = map_type_node(field, context: "field #{expected_type}.#{field["name"]}")
328
+ value = values[index]
329
+ lowered = if value
330
+ lower_constant_expression(value, expected_type: field_type, context: "field #{field["name"]} of #{context}")
331
+ else
332
+ lower_zero_value(expected_type: field_type, context: "field #{field["name"]} of #{context}")
333
+ end
334
+ "#{field["name"]} = #{lowered}"
335
+ end
336
+ "#{expected_type}(#{arguments.join(', ')})"
337
+ end
338
+
339
+ def lower_zero_value(expected_type:, context:)
340
+ return "false" if expected_type == "bool"
341
+ return "0" if %w[char byte ubyte short ushort int uint long ulong ptr_int ptr_uint].include?(expected_type)
342
+ return "0.0" if %w[float double].include?(expected_type)
343
+ return "null[ptr[char]]" if expected_type == "cstr"
344
+ return "null" if expected_type == "cstr?"
345
+ return "null[#{expected_type}]" if expected_type.start_with?("ptr[") || expected_type.start_with?("const_ptr[")
346
+
347
+ if @aggregate_declarations.key?(expected_type)
348
+ return lower_aggregate_init_list([], aggregate: @aggregate_declarations.fetch(expected_type), expected_type:, context:)
349
+ end
350
+
351
+ element_type, length = parse_array_type(expected_type)
352
+ if element_type
353
+ values = Array.new(length) do |index|
354
+ lower_zero_value(expected_type: element_type, context: "element #{index} of #{context}")
355
+ end
356
+ return "#{expected_type}(#{values.join(', ')})"
357
+ end
358
+
359
+ "#{expected_type}<-0"
360
+ end
361
+
362
+ def pointer_zero_literal(expected_type, value)
363
+ return nil unless Integer(value, 10).zero?
364
+
365
+ return "null[ptr[char]]" if expected_type == "cstr"
366
+ return "null" if expected_type == "cstr?"
367
+ return "null[#{expected_type}]" if expected_type.start_with?("ptr[") || expected_type.start_with?("const_ptr[")
368
+
369
+ nil
370
+ rescue ArgumentError
371
+ nil
372
+ end
373
+
374
+ def parse_array_type(type)
375
+ return [nil, nil] unless type.start_with?("array[") && type.end_with?("]")
376
+
377
+ parts = split_top_level_csv(type.delete_prefix("array[").delete_suffix("]"))
378
+ return [nil, nil] unless parts.length == 2
379
+
380
+ [parts[0], Integer(parts[1], 10)]
381
+ rescue ArgumentError
382
+ [nil, nil]
383
+ end
384
+ end
385
+ end
386
+ end
387
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module Bindgen
5
+ class Generator
6
+ module GeneratorOverrides
7
+ private
8
+
9
+ def function_param_type_override(function_name, param_name)
10
+ @function_param_type_overrides.dig(function_name, param_name)
11
+ end
12
+
13
+ def function_return_type_override(function_name)
14
+ @function_return_type_overrides[function_name]
15
+ end
16
+
17
+ def field_type_override(type_name, field_name)
18
+ @field_type_overrides.dig(type_name, field_name)
19
+ end
20
+
21
+ def normalize_function_param_type_overrides(overrides)
22
+ return {} if overrides.nil?
23
+
24
+ raise BindgenError, "function_param_type_overrides must be a hash" unless overrides.is_a?(Hash)
25
+
26
+ overrides.each_with_object({}) do |(function_name, param_overrides), normalized|
27
+ unless function_name.is_a?(String) || function_name.is_a?(Symbol)
28
+ raise BindgenError, "function_param_type_overrides function names must be strings or symbols"
29
+ end
30
+
31
+ raise BindgenError, "function_param_type_overrides for #{function_name} must be a hash" unless param_overrides.is_a?(Hash)
32
+
33
+ normalized[function_name.to_s] = param_overrides.each_with_object({}) do |(param_name, type), params|
34
+ unless param_name.is_a?(String) || param_name.is_a?(Symbol)
35
+ raise BindgenError, "function_param_type_overrides parameter names must be strings or symbols"
36
+ end
37
+
38
+ raise BindgenError, "function_param_type_overrides for #{function_name}.#{param_name} must be a non-empty string" unless type.is_a?(String) && !type.empty?
39
+
40
+ params[param_name.to_s] = type
41
+ end.freeze
42
+ end.freeze
43
+ end
44
+
45
+ def normalize_field_type_overrides(overrides)
46
+ return {} if overrides.nil?
47
+
48
+ raise BindgenError, "field_type_overrides must be a hash" unless overrides.is_a?(Hash)
49
+
50
+ overrides.each_with_object({}) do |(type_name, field_overrides), normalized|
51
+ unless type_name.is_a?(String) || type_name.is_a?(Symbol)
52
+ raise BindgenError, "field_type_overrides type names must be strings or symbols"
53
+ end
54
+
55
+ raise BindgenError, "field_type_overrides for #{type_name} must be a hash" unless field_overrides.is_a?(Hash)
56
+
57
+ normalized[type_name.to_s] = field_overrides.each_with_object({}) do |(field_name, type), fields|
58
+ unless field_name.is_a?(String) || field_name.is_a?(Symbol)
59
+ raise BindgenError, "field_type_overrides field names must be strings or symbols"
60
+ end
61
+
62
+ raise BindgenError, "field_type_overrides for #{type_name}.#{field_name} must be a non-empty string" unless type.is_a?(String) && !type.empty?
63
+
64
+ fields[field_name.to_s] = type
65
+ end.freeze
66
+ end.freeze
67
+ end
68
+
69
+ def normalize_module_imports(imports)
70
+ return [] if imports.nil?
71
+ raise BindgenError, "module_imports must be an array" unless imports.is_a?(Array)
72
+
73
+ imports.map do |entry|
74
+ raise BindgenError, "module_imports entries must be hashes" unless entry.is_a?(Hash)
75
+
76
+ module_name = entry.fetch(:module_name) { entry.fetch("module_name", nil) }
77
+ import_alias = entry.fetch(:alias) { entry.fetch("alias", nil) }
78
+ raise BindgenError, "module_imports module_name must be a non-empty string" unless module_name.is_a?(String) && !module_name.empty?
79
+ raise BindgenError, "module_imports alias must be a non-empty string" unless import_alias.is_a?(String) && !import_alias.empty?
80
+
81
+ { module_name:, alias: import_alias }
82
+ end.freeze
83
+ end
84
+
85
+ def normalize_type_overrides(overrides)
86
+ return {} if overrides.nil?
87
+ raise BindgenError, "type_overrides must be a hash" unless overrides.is_a?(Hash)
88
+
89
+ overrides.each_with_object({}) do |(type_name, mapped_type), normalized|
90
+ unless type_name.is_a?(String) || type_name.is_a?(Symbol)
91
+ raise BindgenError, "type_overrides type names must be strings or symbols"
92
+ end
93
+
94
+ raise BindgenError, "type_overrides for #{type_name} must be a non-empty string" unless mapped_type.is_a?(String) && !mapped_type.empty?
95
+
96
+ normalized[type_name.to_s] = mapped_type
97
+ end.freeze
98
+ end
99
+
100
+ def normalize_type_name_overrides(overrides)
101
+ return {} if overrides.nil?
102
+ raise BindgenError, "type_name_overrides must be a hash" unless overrides.is_a?(Hash)
103
+
104
+ overrides.each_with_object({}) do |(type_name, visible_name), normalized|
105
+ unless type_name.is_a?(String) || type_name.is_a?(Symbol)
106
+ raise BindgenError, "type_name_overrides type names must be strings or symbols"
107
+ end
108
+ unless visible_name.is_a?(String) && !visible_name.empty?
109
+ raise BindgenError, "type_name_overrides for #{type_name} must be a non-empty string"
110
+ end
111
+
112
+ normalized[type_name.to_s] = visible_name
113
+ end.freeze
114
+ end
115
+
116
+ def normalize_function_return_type_overrides(overrides)
117
+ return {} if overrides.nil?
118
+
119
+ raise BindgenError, "function_return_type_overrides must be a hash" unless overrides.is_a?(Hash)
120
+
121
+ overrides.each_with_object({}) do |(function_name, type), normalized|
122
+ unless function_name.is_a?(String) || function_name.is_a?(Symbol)
123
+ raise BindgenError, "function_return_type_overrides function names must be strings or symbols"
124
+ end
125
+
126
+ raise BindgenError, "function_return_type_overrides for #{function_name} must be a non-empty string" unless type.is_a?(String) && !type.empty?
127
+
128
+ normalized[function_name.to_s] = type
129
+ end.freeze
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end