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,629 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "pathname"
6
+
7
+ module MilkTea
8
+ module Steamworks
9
+ class Error < StandardError; end
10
+
11
+ SOURCE_NAME = "steamworks_sdk"
12
+ HELPER_HEADER_BASENAME = "steamworks.h"
13
+ SDK_ROOT_ENV_VAR = "STEAMWORKS_SDK_ROOT"
14
+ API_JSON_ENV_VAR = "STEAMWORKS_API_JSON"
15
+ API_JSON_RELATIVE_PATH = "public/steam/steam_api.json"
16
+
17
+ REDISTRIBUTABLE_DIRECTORY_BY_PLATFORM = {
18
+ linux: "redistributable_bin/linux64",
19
+ darwin: "redistributable_bin/osx",
20
+ windows: "redistributable_bin/win64",
21
+ }.freeze
22
+
23
+ LINK_LIBRARY_NAME_BY_PLATFORM = {
24
+ linux: "steam_api",
25
+ darwin: "steam_api",
26
+ windows: "steam_api64",
27
+ }.freeze
28
+
29
+ IMPORT_LIBRARY_BASENAME_BY_PLATFORM = {
30
+ linux: "libsteam_api.so",
31
+ darwin: "libsteam_api.dylib",
32
+ windows: "steam_api64.lib",
33
+ }.freeze
34
+
35
+ RUNTIME_LIBRARY_BASENAME_BY_PLATFORM = {
36
+ linux: "libsteam_api.so",
37
+ darwin: "libsteam_api.dylib",
38
+ windows: "steam_api64.dll",
39
+ }.freeze
40
+
41
+ module_function
42
+
43
+ def helper_header_path(root: MilkTea.root)
44
+ root.join("std/c/#{HELPER_HEADER_BASENAME}")
45
+ end
46
+
47
+ def source(root: MilkTea.root)
48
+ UpstreamSources.default_sources(root:).find { |entry| entry.name == SOURCE_NAME } ||
49
+ raise(Error, "missing upstream source definition for #{SOURCE_NAME}")
50
+ end
51
+
52
+ def host_platform
53
+ MilkTea.host_platform
54
+ end
55
+
56
+ def default_link_libraries(platform: host_platform)
57
+ [LINK_LIBRARY_NAME_BY_PLATFORM.fetch(platform)]
58
+ end
59
+
60
+ def sdk_root(root: MilkTea.root, env: ENV, bootstrap: false)
61
+ candidates = []
62
+ env_root = env[SDK_ROOT_ENV_VAR]
63
+ candidates << canonical_sdk_root(env_root) if env_root && !env_root.empty?
64
+
65
+ third_party_sdk = MilkTea.data_root.join("third_party/steamworks-sdk")
66
+ candidates << canonical_sdk_root(third_party_sdk) if File.directory?(third_party_sdk)
67
+
68
+ upstream_root = source(root:).checkout_root
69
+ candidates << canonical_sdk_root(upstream_root) if File.directory?(upstream_root)
70
+
71
+ if bootstrap && candidates.none? { |path| sdk_layout_present?(path) }
72
+ source(root:).bootstrap!
73
+ candidates << canonical_sdk_root(upstream_root)
74
+ end
75
+
76
+ candidates.find { |path| sdk_layout_present?(path) }
77
+ end
78
+
79
+ def api_json_path(root: MilkTea.root, env: ENV, bootstrap: false)
80
+ explicit_path = env[API_JSON_ENV_VAR]
81
+ if explicit_path && !explicit_path.empty?
82
+ candidate = Pathname.new(File.expand_path(explicit_path))
83
+ return candidate if File.file?(candidate)
84
+
85
+ raise Error, "Steamworks metadata not found: #{candidate}"
86
+ end
87
+
88
+ resolved_sdk_root = sdk_root(root:, env:, bootstrap:)
89
+ return unless resolved_sdk_root
90
+
91
+ candidate = resolved_sdk_root.join(API_JSON_RELATIVE_PATH)
92
+ return candidate if File.file?(candidate)
93
+
94
+ raise Error, "Steamworks metadata not found under #{resolved_sdk_root}"
95
+ end
96
+
97
+ def redistributable_directory(root: MilkTea.root, env: ENV, platform: host_platform, bootstrap: false)
98
+ resolved_sdk_root = sdk_root(root:, env:, bootstrap:)
99
+ return unless resolved_sdk_root
100
+
101
+ directory = resolved_sdk_root.join(REDISTRIBUTABLE_DIRECTORY_BY_PLATFORM.fetch(platform))
102
+ return directory if File.directory?(directory)
103
+
104
+ nil
105
+ end
106
+
107
+ def import_library_path(root: MilkTea.root, env: ENV, platform: host_platform, bootstrap: false)
108
+ directory = redistributable_directory(root:, env:, platform:, bootstrap:)
109
+ return unless directory
110
+
111
+ candidate = directory.join(IMPORT_LIBRARY_BASENAME_BY_PLATFORM.fetch(platform))
112
+ return candidate if File.file?(candidate)
113
+
114
+ nil
115
+ end
116
+
117
+ def runtime_library_path(root: MilkTea.root, env: ENV, platform: host_platform, bootstrap: false)
118
+ directory = redistributable_directory(root:, env:, platform:, bootstrap:)
119
+ return unless directory
120
+
121
+ candidate = directory.join(RUNTIME_LIBRARY_BASENAME_BY_PLATFORM.fetch(platform))
122
+ return candidate if File.file?(candidate)
123
+
124
+ nil
125
+ end
126
+
127
+ def prepare!(root: MilkTea.root, env: ENV)
128
+ path = helper_header_path(root:)
129
+ existing = File.exist?(path)
130
+ json_path = api_json_path(root:, env:, bootstrap: !existing)
131
+ return path.to_s if json_path.nil? && existing
132
+ raise Error, "Steamworks metadata unavailable; set #{API_JSON_ENV_VAR} or #{SDK_ROOT_ENV_VAR}" unless json_path
133
+
134
+ generated = Generator.new(json_path:).generate
135
+ FileUtils.mkdir_p(path.dirname)
136
+ return path.to_s if existing && File.read(path) == generated
137
+
138
+ File.write(path, generated)
139
+ path.to_s
140
+ end
141
+
142
+ def canonical_sdk_root(path)
143
+ root = Pathname.new(File.expand_path(path.to_s))
144
+ sdk_child = root.join("sdk")
145
+ return sdk_child if sdk_layout_present?(sdk_child)
146
+
147
+ root
148
+ end
149
+ private_class_method :canonical_sdk_root
150
+
151
+ def sdk_layout_present?(root)
152
+ File.file?(root.join(API_JSON_RELATIVE_PATH))
153
+ end
154
+ private_class_method :sdk_layout_present?
155
+
156
+ class Generator
157
+ HEADER_GUARD = "MT_LANG_STEAMWORKS_H"
158
+
159
+ MANUAL_OPAQUE_TYPES = %w[
160
+ CCallbackBase
161
+ CallbackMsg_t
162
+ ISteamNetworkingConnectionSignaling
163
+ ISteamNetworkingSignalingRecvContext
164
+ ScePadTriggerEffectParam
165
+ SteamDatagramRelayAuthTicket
166
+ ].freeze
167
+
168
+ SPECIAL_TYPE_REWRITES = {
169
+ "CGameID" => "uint64_gameid",
170
+ "CSteamID" => "uint64_steamid",
171
+ "SteamInputActionEvent_t::AnalogAction_t" => "SteamInputActionEvent_t_AnalogAction_t",
172
+ "SteamInputActionEvent_t::DigitalAction_t" => "SteamInputActionEvent_t_DigitalAction_t",
173
+ }.freeze
174
+
175
+ MANUAL_FUNCTION_POINTER_TYPEDEFS = {
176
+ "SteamAPIWarningMessageHook_t" => ["void", ["int", "const char *"]],
177
+ }.freeze
178
+
179
+ MANUAL_SUPPORT_STRUCT_DEFINITIONS = [
180
+ "typedef struct SteamInputActionEvent_t_AnalogAction_t {",
181
+ " InputAnalogActionHandle_t actionHandle;",
182
+ " InputAnalogActionData_t analogActionData;",
183
+ "} SteamInputActionEvent_t_AnalogAction_t;",
184
+ "",
185
+ "typedef struct SteamInputActionEvent_t_DigitalAction_t {",
186
+ " InputDigitalActionHandle_t actionHandle;",
187
+ " InputDigitalActionData_t digitalActionData;",
188
+ "} SteamInputActionEvent_t_DigitalAction_t;",
189
+ ].freeze
190
+
191
+ MANUAL_FUNCTIONS = [
192
+ { return_type: "ESteamAPIInitResult", name: "SteamAPI_InitFlat", params: [{ name: "pOutErrMsg", type: "SteamErrMsg *" }] },
193
+ { return_type: "void", name: "SteamAPI_Shutdown", params: [] },
194
+ { return_type: "bool", name: "SteamAPI_RestartAppIfNecessary", params: [{ name: "unOwnAppID", type: "uint32" }] },
195
+ { return_type: "void", name: "SteamAPI_ReleaseCurrentThreadMemory", params: [] },
196
+ { return_type: "void", name: "SteamAPI_WriteMiniDump", params: [{ name: "uStructuredExceptionCode", type: "uint32" }, { name: "pvExceptionInfo", type: "void *" }, { name: "uBuildID", type: "uint32" }] },
197
+ { return_type: "void", name: "SteamAPI_SetMiniDumpComment", params: [{ name: "pchMsg", type: "const char *" }] },
198
+ { return_type: "bool", name: "SteamAPI_IsSteamRunning", params: [] },
199
+ { return_type: "const char *", name: "SteamAPI_GetSteamInstallPath", params: [] },
200
+ { return_type: "void", name: "SteamAPI_SetTryCatchCallbacks", params: [{ name: "bTryCatchCallbacks", type: "bool" }] },
201
+ { return_type: "bool", name: "SteamAPI_InitSafe", params: [] },
202
+ { return_type: "void", name: "SteamAPI_UseBreakpadCrashHandler", params: [{ name: "pchVersion", type: "const char *" }, { name: "pchDate", type: "const char *" }, { name: "pchTime", type: "const char *" }, { name: "bFullMemoryDumps", type: "bool" }, { name: "pvContext", type: "void *" }, { name: "pfnPreMinidumpCallback", type: "PFNPreMinidumpCallback" }] },
203
+ { return_type: "void", name: "SteamAPI_SetBreakpadAppID", params: [{ name: "unAppID", type: "uint32" }] },
204
+ { return_type: "void", name: "SteamAPI_ManualDispatch_Init", params: [] },
205
+ { return_type: "void", name: "SteamAPI_ManualDispatch_RunFrame", params: [{ name: "hSteamPipe", type: "HSteamPipe" }] },
206
+ { return_type: "bool", name: "SteamAPI_ManualDispatch_GetNextCallback", params: [{ name: "hSteamPipe", type: "HSteamPipe" }, { name: "pCallbackMsg", type: "CallbackMsg_t *" }] },
207
+ { return_type: "void", name: "SteamAPI_ManualDispatch_FreeLastCallback", params: [{ name: "hSteamPipe", type: "HSteamPipe" }] },
208
+ { return_type: "bool", name: "SteamAPI_ManualDispatch_GetAPICallResult", params: [{ name: "hSteamPipe", type: "HSteamPipe" }, { name: "hSteamAPICall", type: "SteamAPICall_t" }, { name: "pCallback", type: "void *" }, { name: "cubCallback", type: "int" }, { name: "iCallbackExpected", type: "int" }, { name: "pbFailed", type: "bool *" }] },
209
+ { return_type: "ESteamAPIInitResult", name: "SteamInternal_SteamAPI_Init", params: [{ name: "pszInternalCheckInterfaceVersions", type: "const char *" }, { name: "pOutErrMsg", type: "SteamErrMsg *" }] },
210
+ { return_type: "void", name: "SteamAPI_RunCallbacks", params: [] },
211
+ { return_type: "void", name: "SteamGameServer_RunCallbacks", params: [] },
212
+ { return_type: "HSteamPipe", name: "SteamAPI_GetHSteamPipe", params: [] },
213
+ { return_type: "HSteamUser", name: "SteamAPI_GetHSteamUser", params: [] },
214
+ { return_type: "HSteamPipe", name: "SteamGameServer_GetHSteamPipe", params: [] },
215
+ { return_type: "HSteamUser", name: "SteamGameServer_GetHSteamUser", params: [] },
216
+ { return_type: "void *", name: "SteamInternal_ContextInit", params: [{ name: "pContextInitData", type: "void *" }] },
217
+ { return_type: "void *", name: "SteamInternal_CreateInterface", params: [{ name: "ver", type: "const char *" }] },
218
+ { return_type: "void *", name: "SteamInternal_FindOrCreateUserInterface", params: [{ name: "hSteamUser", type: "HSteamUser" }, { name: "pszVersion", type: "const char *" }] },
219
+ { return_type: "void *", name: "SteamInternal_FindOrCreateGameServerInterface", params: [{ name: "hSteamUser", type: "HSteamUser" }, { name: "pszVersion", type: "const char *" }] },
220
+ { return_type: "void", name: "SteamAPI_RegisterCallback", params: [{ name: "pCallback", type: "CCallbackBase *" }, { name: "iCallback", type: "int" }] },
221
+ { return_type: "void", name: "SteamAPI_UnregisterCallback", params: [{ name: "pCallback", type: "CCallbackBase *" }] },
222
+ { return_type: "void", name: "SteamAPI_RegisterCallResult", params: [{ name: "pCallback", type: "CCallbackBase *" }, { name: "hAPICall", type: "SteamAPICall_t" }] },
223
+ { return_type: "void", name: "SteamAPI_UnregisterCallResult", params: [{ name: "pCallback", type: "CCallbackBase *" }, { name: "hAPICall", type: "SteamAPICall_t" }] },
224
+ { return_type: "void", name: "SteamGameServer_Shutdown", params: [] },
225
+ { return_type: "bool", name: "SteamGameServer_BSecure", params: [] },
226
+ { return_type: "uint64", name: "SteamGameServer_GetSteamID", params: [] },
227
+ { return_type: "ESteamAPIInitResult", name: "SteamInternal_GameServer_Init_V2", params: [{ name: "unIP", type: "uint32" }, { name: "usGamePort", type: "uint16" }, { name: "usQueryPort", type: "uint16" }, { name: "eServerMode", type: "EServerMode" }, { name: "pchVersionString", type: "const char *" }, { name: "pszInternalCheckInterfaceVersions", type: "const char *" }, { name: "pOutErrMsg", type: "SteamErrMsg *" }] },
228
+ ].freeze
229
+
230
+ def initialize(json_path: nil, json_source: nil, source_label: nil)
231
+ raise ArgumentError, "json_path or json_source is required" if json_path.nil? && json_source.nil?
232
+
233
+ @json_source = json_source || File.read(json_path)
234
+ @source_label = source_label || File.expand_path(json_path.to_s)
235
+ end
236
+
237
+ def generate
238
+ lines = []
239
+ lines << "/* generated by mtc steamworks from #{@source_label} */"
240
+ lines << "#ifndef #{HEADER_GUARD}"
241
+ lines << "#define #{HEADER_GUARD}"
242
+ lines << ""
243
+ lines << "#include <stdbool.h>"
244
+ lines << "#include <stddef.h>"
245
+ lines << "#include <stdint.h>"
246
+ lines << ""
247
+ lines << "#ifdef __cplusplus"
248
+ lines << 'extern "C" {'
249
+ lines << "#endif"
250
+ lines << ""
251
+ lines.concat(emit_forward_declarations)
252
+ lines << "" unless lines.last.empty?
253
+ lines.concat(emit_typedefs)
254
+ lines << "" unless lines.last.empty?
255
+ lines.concat(emit_flat_aliases)
256
+ lines << "" unless lines.last.empty?
257
+ lines.concat(emit_enums)
258
+ lines << "" unless lines.last.empty?
259
+ lines.concat(emit_structs)
260
+ lines << "" unless lines.last.empty?
261
+ lines.concat(emit_manual_function_pointer_typedefs)
262
+ lines << "" unless lines.last.empty?
263
+ lines.concat(emit_constants)
264
+ lines << "" unless lines.last.empty?
265
+ lines.concat(emit_manual_functions)
266
+ lines << "" unless lines.last.empty?
267
+ lines.concat(emit_accessors)
268
+ lines << "" unless lines.last.empty?
269
+ lines.concat(emit_interface_methods)
270
+ lines << "" unless lines.last.empty?
271
+ lines.concat(emit_struct_methods)
272
+ lines << "" unless lines.last.empty?
273
+ lines.concat(emit_inline_wrappers)
274
+ lines << ""
275
+ lines << "#ifdef __cplusplus"
276
+ lines << "}"
277
+ lines << "#endif"
278
+ lines << ""
279
+ lines << "#endif"
280
+ lines.join("\n") + "\n"
281
+ end
282
+
283
+ private
284
+
285
+ def metadata
286
+ @metadata ||= JSON.parse(@json_source)
287
+ rescue JSON::ParserError => e
288
+ raise Error, "failed to parse Steamworks metadata #{@source_label}: #{e.message}"
289
+ end
290
+
291
+ def typedefs
292
+ metadata.fetch("typedefs", [])
293
+ end
294
+
295
+ def interfaces
296
+ metadata.fetch("interfaces", [])
297
+ end
298
+
299
+ def structs
300
+ metadata.fetch("structs", [])
301
+ end
302
+
303
+ def callback_structs
304
+ metadata.fetch("callback_structs", [])
305
+ end
306
+
307
+ def all_enums
308
+ @all_enums ||= begin
309
+ enums = {}
310
+ metadata.fetch("enums", []).each { |entry| enums[entry.fetch("enumname")] ||= entry }
311
+ [interfaces, structs, callback_structs].each do |group|
312
+ group.each do |entry|
313
+ Array(entry["enums"]).each do |enum_entry|
314
+ enums[enum_entry.fetch("enumname")] ||= enum_entry
315
+ end
316
+ end
317
+ end
318
+ enums.values
319
+ end
320
+ end
321
+
322
+ def scoped_type_rewrites
323
+ @scoped_type_rewrites ||= begin
324
+ rewrites = {}
325
+ [interfaces, structs, callback_structs].each do |group|
326
+ group.each do |entry|
327
+ Array(entry["enums"]).each do |enum_entry|
328
+ fqname = enum_entry["fqname"]
329
+ rewrites[fqname] = enum_entry.fetch("enumname") if fqname
330
+ end
331
+ end
332
+ end
333
+ rewrites
334
+ end
335
+ end
336
+
337
+ def emit_forward_declarations
338
+ lines = []
339
+ opaque_types = []
340
+ opaque_types.concat(interfaces.map { |entry| entry.fetch("classname") })
341
+ opaque_types.concat(MANUAL_OPAQUE_TYPES)
342
+ opaque_types.uniq.sort.each do |name|
343
+ lines << "typedef struct #{name} #{name};"
344
+ end
345
+
346
+ record_names = structs.map { |entry| entry.fetch("struct") } + callback_structs.map { |entry| entry.fetch("struct") }
347
+ record_names.uniq.sort.each do |name|
348
+ lines << "typedef struct #{name} #{name};"
349
+ end
350
+
351
+ lines
352
+ end
353
+
354
+ def emit_typedefs
355
+ typedefs.filter_map { |entry| emit_typedef(entry) }
356
+ end
357
+
358
+ def emit_typedef(entry)
359
+ name = entry.fetch("typedef")
360
+ source = entry.fetch("type")
361
+
362
+ return if function_pointer_type?(source)
363
+
364
+ if (match = split_array_type(source))
365
+ return "typedef #{normalize_type(match[0])} #{name}[#{match[1]}];"
366
+ end
367
+
368
+ "typedef #{normalize_type(source)} #{name};"
369
+ end
370
+
371
+ def emit_manual_function_pointer_typedefs
372
+ lines = []
373
+
374
+ typedefs.each do |entry|
375
+ next unless function_pointer_type?(entry.fetch("type"))
376
+
377
+ match = entry.fetch("type").strip.match(/\A(.+?)\(\s*\*\s*\)\s*\((.*)\)\z/)
378
+ return_type = normalize_type(match[1])
379
+ params = normalize_function_pointer_params(match[2])
380
+ lines << "typedef #{return_type} (*#{entry.fetch('typedef')})(#{params.join(', ')});"
381
+ end
382
+
383
+ MANUAL_FUNCTION_POINTER_TYPEDEFS.each do |name, (return_type, params)|
384
+ lines << "typedef #{normalize_type(return_type)} (*#{name})(#{params.map { |param| normalize_type(param) }.join(', ')});"
385
+ end
386
+
387
+ lines
388
+ end
389
+
390
+ def emit_flat_aliases
391
+ [
392
+ "typedef uint64 uint64_steamid;",
393
+ "typedef uint64 uint64_gameid;",
394
+ ]
395
+ end
396
+
397
+ def emit_enums
398
+ all_enums.flat_map do |entry|
399
+ lines = []
400
+ name = entry.fetch("enumname")
401
+ lines << "typedef enum #{name} {"
402
+ Array(entry.fetch("values", [])).each do |value|
403
+ lines << " #{value.fetch('name')} = #{value.fetch('value')},"
404
+ end
405
+ lines << "} #{name};"
406
+ lines << ""
407
+ lines
408
+ end[0..-2] || []
409
+ end
410
+
411
+ def emit_structs
412
+ regular_entries = []
413
+ delayed_entries = []
414
+
415
+ (structs + callback_structs).each do |entry|
416
+ if entry.fetch("struct") == "SteamInputActionEvent_t"
417
+ delayed_entries << entry
418
+ else
419
+ regular_entries << entry
420
+ end
421
+ end
422
+
423
+ lines = regular_entries.flat_map { |entry| emit_generic_struct(entry) + [""] }
424
+ unless delayed_entries.empty?
425
+ lines.concat(MANUAL_SUPPORT_STRUCT_DEFINITIONS)
426
+ lines << ""
427
+ delayed_entries.each do |entry|
428
+ lines.concat(emit_generic_struct(entry))
429
+ lines << ""
430
+ end
431
+ end
432
+
433
+ lines[0..-2] || []
434
+ end
435
+
436
+ def emit_generic_struct(entry)
437
+ name = entry.fetch("struct")
438
+ return emit_steam_input_action_event_struct(entry) if name == "SteamInputActionEvent_t"
439
+
440
+ fields = Array(entry.fetch("fields", []))
441
+ lines = []
442
+ lines << "struct #{name} {"
443
+ if fields.empty?
444
+ lines << " char _mt_dummy;"
445
+ end
446
+ fields.each do |field|
447
+ lines << " #{format_declarator(normalize_type(field.fetch('fieldtype')), field.fetch('fieldname'))};"
448
+ end
449
+ lines << "};"
450
+ lines
451
+ end
452
+
453
+ def emit_steam_input_action_event_struct(entry)
454
+ fields = Array(entry.fetch("fields", []))
455
+ lines = []
456
+ lines << "struct SteamInputActionEvent_t {"
457
+ fields.each do |field|
458
+ next if field.fetch("fieldname") == "analogAction"
459
+
460
+ lines << " #{format_declarator(normalize_type(field.fetch('fieldtype')), field.fetch('fieldname'))};"
461
+ end
462
+ lines << " union {"
463
+ lines << " SteamInputActionEvent_t_AnalogAction_t analogAction;"
464
+ lines << " SteamInputActionEvent_t_DigitalAction_t digitalAction;"
465
+ lines << " };"
466
+ lines << "};"
467
+ lines
468
+ end
469
+
470
+ def emit_constants
471
+ lines = []
472
+ metadata.fetch("consts", []).each do |entry|
473
+ lines << emit_constant(name: entry.fetch("constname"), type: entry.fetch("consttype"), value: entry.fetch("constval"))
474
+ end
475
+
476
+ structs.each do |entry|
477
+ Array(entry["consts"]).each do |const_entry|
478
+ scoped_name = "#{entry.fetch('struct')}_#{const_entry.fetch('constname')}"
479
+ lines << emit_constant(name: scoped_name, type: const_entry.fetch("consttype"), value: const_entry.fetch("constval"))
480
+ end
481
+ end
482
+
483
+ callback_structs.each do |entry|
484
+ callback_name = entry.fetch("struct")
485
+ if entry["callback_id"]
486
+ lines << emit_constant(name: "#{callback_name}_k_iCallback", type: "int", value: entry.fetch("callback_id").to_s)
487
+ end
488
+ Array(entry["consts"]).each do |const_entry|
489
+ scoped_name = "#{callback_name}_#{const_entry.fetch('constname')}"
490
+ lines << emit_constant(name: scoped_name, type: const_entry.fetch("consttype"), value: const_entry.fetch("constval"))
491
+ end
492
+ end
493
+
494
+ lines
495
+ end
496
+
497
+ def emit_constant(name:, type:, value:)
498
+ normalized_type = normalize_type(type)
499
+ return "static const #{normalized_type} #{name} = #{value};" if normalized_type == "float"
500
+
501
+ "enum { #{name} = #{value} };"
502
+ end
503
+
504
+ def emit_manual_functions
505
+ MANUAL_FUNCTIONS.map do |entry|
506
+ emit_function(entry.fetch(:return_type), entry.fetch(:name), entry.fetch(:params))
507
+ end
508
+ end
509
+
510
+ def emit_accessors
511
+ interfaces.flat_map do |entry|
512
+ interface_name = entry.fetch("classname")
513
+ Array(entry["accessors"]).map do |accessor|
514
+ emit_function("#{interface_name} *", accessor.fetch("name_flat"), [])
515
+ end
516
+ end
517
+ end
518
+
519
+ def emit_interface_methods
520
+ interfaces.flat_map do |entry|
521
+ interface_name = entry.fetch("classname")
522
+ Array(entry.fetch("methods", [])).map do |method|
523
+ params = [{ name: "self", type: "#{interface_name} *" }]
524
+ params.concat(Array(method["params"]).map { |param| { name: param.fetch("paramname"), type: param["paramtype_flat"] || param.fetch("paramtype") } })
525
+ emit_function(method["returntype_flat"] || method.fetch("returntype"), method.fetch("methodname_flat"), params)
526
+ end
527
+ end
528
+ end
529
+
530
+ def emit_struct_methods
531
+ structs.flat_map do |entry|
532
+ struct_name = entry.fetch("struct")
533
+ Array(entry["methods"]).map do |method|
534
+ params = [{ name: "self", type: "#{struct_name} *" }]
535
+ params.concat(Array(method["params"]).map { |param| { name: param.fetch("paramname"), type: param["paramtype_flat"] || param.fetch("paramtype") } })
536
+ emit_function(method["returntype_flat"] || method.fetch("returntype"), method.fetch("methodname_flat"), params)
537
+ end
538
+ end
539
+ end
540
+
541
+ def emit_inline_wrappers
542
+ lines = []
543
+ lines << "static inline bool SteamAPI_Init(void) {"
544
+ lines << " return SteamAPI_InitFlat(NULL) == k_ESteamAPIInitResult_OK;"
545
+ lines << "}"
546
+ lines << ""
547
+ lines << "static inline void SteamGameServer_ReleaseCurrentThreadMemory(void) {"
548
+ lines << " SteamAPI_ReleaseCurrentThreadMemory();"
549
+ lines << "}"
550
+
551
+ accessor_wrappers = interfaces.flat_map do |entry|
552
+ interface_name = entry.fetch("classname")
553
+ Array(entry["accessors"]).flat_map do |accessor|
554
+ wrapper_name = accessor.fetch("name_flat").sub(/_v[0-9A-Za-z]+\z/, "")
555
+ [
556
+ "",
557
+ "static inline #{interface_name} * #{wrapper_name}(void) {",
558
+ " return #{accessor.fetch('name_flat')}();",
559
+ "}",
560
+ ]
561
+ end
562
+ end
563
+
564
+ lines.concat(accessor_wrappers)
565
+ lines
566
+ end
567
+
568
+ def emit_function(return_type, name, params)
569
+ signature = params.map { |param| format_declarator(normalize_type(param.fetch(:type)), param.fetch(:name)) }
570
+ signature = ["void"] if signature.empty?
571
+ "#{normalize_type(return_type)} #{name}(#{signature.join(', ')});"
572
+ end
573
+
574
+ def normalize_type(type)
575
+ value = type.to_s.strip
576
+ return value if value.empty?
577
+
578
+ if (match = split_array_type(value))
579
+ return "#{normalize_type(match[0])}[#{match[1]}]"
580
+ end
581
+
582
+ scoped_type_rewrites.each do |source, target|
583
+ value = value.gsub(source, target)
584
+ end
585
+
586
+ SPECIAL_TYPE_REWRITES.each do |source, target|
587
+ value = value.gsub(source, target)
588
+ end
589
+
590
+ value = value.gsub(/\b(?:class|struct|enum)\s+/, "")
591
+ value = value.gsub(/\s*&/, " *")
592
+ value = value.gsub(/\s*\*\s*/, " *")
593
+ value = value.gsub(/\s+/, " ").strip
594
+ value
595
+ end
596
+
597
+ def format_declarator(type, name)
598
+ if (match = type.match(/\A(.+?)\(\s*\*\s*\)\s*\((.*)\)\z/))
599
+ params = normalize_function_pointer_params(match[2]).join(', ')
600
+ return "#{normalize_type(match[1])} (*#{name})(#{params})"
601
+ end
602
+
603
+ if (match = split_array_type(type))
604
+ return "#{match[0]} #{name}[#{match[1]}]"
605
+ end
606
+
607
+ "#{type} #{name}"
608
+ end
609
+
610
+ def normalize_function_pointer_params(source)
611
+ params = source.strip
612
+ return ["void"] if params.empty? || params == "void"
613
+
614
+ params.split(/\s*,\s*/).map { |param| normalize_type(param) }
615
+ end
616
+
617
+ def function_pointer_type?(source)
618
+ source.to_s.strip.match?(/\A.+?\(\s*\*\s*\)\s*\(.*\)\z/)
619
+ end
620
+
621
+ def split_array_type(source)
622
+ match = source.strip.match(/\A(.+?)\s*\[(.+)\]\z/)
623
+ return unless match
624
+
625
+ [match[1].strip, match[2].strip]
626
+ end
627
+ end
628
+ end
629
+ end