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,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module MilkTea
6
+ VERSION = "0.2.0"
7
+
8
+ def self.root
9
+ @root ||= Pathname.new(File.expand_path("../..", __dir__))
10
+ end
11
+
12
+ def self.data_root
13
+ @data_root ||= begin
14
+ if File.writable?(root.to_s)
15
+ root
16
+ else
17
+ xdg_cache = ENV.fetch("XDG_CACHE_HOME", File.join(Dir.home, ".cache"))
18
+ Pathname.new(File.join(xdg_cache, "milk_tea"))
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.writable_root_for(root)
24
+ resolved = Pathname.new(File.expand_path(root.to_s))
25
+ if resolved.to_s == MilkTea.root.to_s
26
+ data_root
27
+ else
28
+ resolved
29
+ end
30
+ end
31
+
32
+ def self.host_platform
33
+ return :windows if /mswin|mingw|cygwin/ === RUBY_PLATFORM
34
+ return :darwin if /darwin/ === RUBY_PLATFORM
35
+
36
+ :linux
37
+ end
38
+
39
+ # Unified diagnostic value shared by the linter, sema, and all ControlFlow analyses.
40
+ # severity: :error | :warning | :hint | :info
41
+ Diagnostic = Data.define(:path, :line, :column, :length, :code, :message, :severity, :symbol_name) do
42
+ def initialize(path:, line:, column: nil, length: nil, code:, message:, severity: :warning, symbol_name: nil) = super
43
+
44
+ def error? = severity == :error
45
+ def warning? = severity == :warning
46
+ def hint? = severity == :hint
47
+ def info? = severity == :info
48
+ end
49
+ end
@@ -0,0 +1,398 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module Bindgen
5
+ class Generator
6
+ module GeneratorAstParser
7
+ private
8
+
9
+ def dump_ast
10
+ Tempfile.create(["milk-tea-bindgen", ".c"]) do |translation_unit|
11
+ @translation_unit_path = translation_unit.path
12
+ write_translation_unit_prelude(translation_unit)
13
+ translation_unit.flush
14
+ @active_macro_names = preprocessed_macro_names(translation_unit.path)
15
+ macro_probe_declarations.each do |declaration|
16
+ translation_unit.write(declaration)
17
+ translation_unit.write("\n")
18
+ end
19
+ translation_unit.flush
20
+ translation_unit.close
21
+
22
+ command = [
23
+ @clang,
24
+ "-x",
25
+ "c",
26
+ "-fno-builtin",
27
+ *@clang_args,
28
+ "-Xclang",
29
+ "-ast-dump=json",
30
+ "-fsyntax-only",
31
+ translation_unit.path,
32
+ ]
33
+ stdout, stderr, status = Open3.capture3(*command)
34
+ begin
35
+ return JSON.parse(stdout)
36
+ rescue JSON::ParserError
37
+ details = [stdout, stderr].reject(&:empty?).join
38
+ raise BindgenError, details.empty? ? "clang bindgen failed" : "clang bindgen failed:\n#{details}" unless status.success?
39
+
40
+ raise
41
+ end
42
+ end
43
+ rescue Errno::ENOENT
44
+ raise BindgenError, "clang not found: #{@clang}"
45
+ rescue JSON::ParserError => e
46
+ raise BindgenError, "failed to parse clang AST JSON: #{e.message}"
47
+ end
48
+
49
+ def write_translation_unit_prelude(translation_unit)
50
+ @bindgen_defines.each do |define|
51
+ name, value = define.split("=", 2)
52
+ if value.nil?
53
+ translation_unit.write("#define #{name}\n")
54
+ else
55
+ translation_unit.write("#define #{name} #{value}\n")
56
+ end
57
+ end
58
+
59
+ translation_unit.write(%(#include #{@header_path.dump}\n))
60
+ @bindgen_include_directives.each do |directive|
61
+ translation_unit.write(%(#include #{directive.dump}\n))
62
+ end
63
+ end
64
+
65
+ def extract_top_level_header_nodes(ast)
66
+ seen_ids = {}
67
+
68
+ Array(ast["inner"]).flat_map do |node|
69
+ next [] unless node.is_a?(Hash)
70
+
71
+ nodes = []
72
+ nodes << node if interesting_top_level_kind?(node["kind"]) && node_from_header?(node)
73
+ nodes.concat(typedef_owned_tag_nodes(node)) if node["kind"] == "TypedefDecl"
74
+ nodes
75
+ end.filter do |node|
76
+ node_id = node["id"] || node.object_id
77
+ next false if seen_ids.key?(node_id)
78
+
79
+ seen_ids[node_id] = true
80
+ end
81
+ end
82
+
83
+ def typedef_owned_tag_nodes(node)
84
+ Array(node["inner"]).flat_map do |child|
85
+ typedef_owned_tag_nodes_from(child)
86
+ end
87
+ end
88
+
89
+ def typedef_owned_tag_nodes_from(node)
90
+ return [] unless node.is_a?(Hash)
91
+
92
+ nodes = []
93
+ if %w[RecordDecl EnumDecl].include?(node["kind"]) && node_from_header?(node)
94
+ nodes << node
95
+ end
96
+
97
+ Array(node["inner"]).each do |child|
98
+ nodes.concat(typedef_owned_tag_nodes_from(child))
99
+ end
100
+ nodes
101
+ end
102
+
103
+ def interesting_top_level_kind?(kind)
104
+ %w[TypedefDecl RecordDecl EnumDecl VarDecl FunctionDecl].include?(kind)
105
+ end
106
+
107
+ def node_from_header?(node)
108
+ source_files = node_source_files(node).map { |path| File.expand_path(path) }
109
+ return true if source_files.include?(@translation_unit_path)
110
+ return true if source_files.any? { |path| tracked_header_path?(path) }
111
+
112
+ return false unless source_files.empty?
113
+
114
+ include_files = node_include_files(node).map { |path| File.expand_path(path) }
115
+ return true if include_files.include?(@translation_unit_path)
116
+
117
+ include_files.any? { |path| tracked_header_path?(path) }
118
+ end
119
+
120
+ def tracked_header_path?(path)
121
+ return true if @tracked_header_paths.include?(path)
122
+
123
+ @tracked_header_prefixes.any? do |prefix|
124
+ path == prefix || path.start_with?(prefix + File::SEPARATOR)
125
+ end
126
+ end
127
+
128
+ def allowed_declaration_name?(name)
129
+ return false unless name
130
+ return false if @excluded_declaration_names.include?(name)
131
+ return true if @declaration_name_prefixes.empty?
132
+
133
+ @declaration_name_prefixes.any? { |prefix| name.start_with?(prefix) }
134
+ end
135
+
136
+ def node_source_files(node)
137
+ [
138
+ node.dig("loc", "file"),
139
+ node.dig("loc", "spellingLoc", "file"),
140
+ node.dig("loc", "expansionLoc", "file"),
141
+ node.dig("range", "begin", "file"),
142
+ node.dig("range", "end", "file"),
143
+ node.dig("range", "begin", "spellingLoc", "file"),
144
+ node.dig("range", "begin", "expansionLoc", "file"),
145
+ node.dig("range", "end", "spellingLoc", "file"),
146
+ node.dig("range", "end", "expansionLoc", "file"),
147
+ ].compact.uniq
148
+ end
149
+
150
+ def node_include_files(node)
151
+ [
152
+ node.dig("loc", "includedFrom", "file"),
153
+ node.dig("loc", "spellingLoc", "includedFrom", "file"),
154
+ node.dig("range", "begin", "includedFrom", "file"),
155
+ node.dig("range", "end", "includedFrom", "file"),
156
+ node.dig("range", "begin", "spellingLoc", "includedFrom", "file"),
157
+ node.dig("range", "end", "spellingLoc", "includedFrom", "file"),
158
+ ].compact.uniq
159
+ end
160
+
161
+ def build_alias_maps(nodes)
162
+ nodes.each do |node|
163
+ next unless node["kind"] == "TypedefDecl"
164
+
165
+ target = typedef_target(node)
166
+ next unless target
167
+
168
+ case target[:kind]
169
+ when "RecordDecl"
170
+ @record_aliases[target[:id]] = node["name"]
171
+ if target[:name] && !target[:name].empty?
172
+ @record_aliases_by_tag_name[target[:name]] ||= node["name"]
173
+ end
174
+ when "EnumDecl"
175
+ @enum_aliases[target[:id]] = node["name"]
176
+ end
177
+ end
178
+ end
179
+
180
+ def index_referenceable_record_declarations(ast)
181
+ indexed = {}
182
+ indexed_by_id = {}
183
+ queue = Array(ast["inner"]).dup
184
+
185
+ until queue.empty?
186
+ node = queue.shift
187
+ next unless node.is_a?(Hash)
188
+
189
+ if node["kind"] == "RecordDecl" && %w[struct union].include?(node["tagUsed"])
190
+ if node["name"] && !node["name"].empty?
191
+ existing = indexed[node["name"]]
192
+ if existing.nil? || (!record_complete_definition?(existing) && record_complete_definition?(node))
193
+ indexed[node["name"]] = node
194
+ end
195
+ end
196
+ indexed_by_id[node["id"]] = node if node["id"]
197
+ end
198
+
199
+ queue.concat(Array(node["inner"]))
200
+ end
201
+
202
+ @referenceable_record_declarations = indexed
203
+ @referenceable_record_declarations_by_id = indexed_by_id
204
+ end
205
+
206
+ def macro_probe_declarations
207
+ macro_constant_candidates.map do |name|
208
+ %(static const __typeof__(#{name}) #{macro_probe_name(name)} = #{name};)
209
+ end
210
+ end
211
+
212
+ def macro_constant_candidates
213
+ return @macro_constant_candidates if defined?(@macro_constant_candidates)
214
+
215
+ definitions = {}
216
+ each_macro_definition do |name, body|
217
+ definitions[name] = body
218
+ end
219
+
220
+ @macro_constant_candidates = definitions.filter_map do |name, body|
221
+ next unless macro_constant_candidate?(name, body)
222
+
223
+ name
224
+ end
225
+ end
226
+
227
+ def each_macro_definition
228
+ logical_line = +""
229
+ current_file = nil
230
+
231
+ preprocessed_macro_source.each_line do |line|
232
+ current = line.delete_suffix("\n")
233
+ if logical_line.empty?
234
+ logical_line = current
235
+ else
236
+ logical_line << current.lstrip
237
+ end
238
+
239
+ if logical_line.end_with?("\\")
240
+ logical_line = logical_line.delete_suffix("\\") + " "
241
+ next
242
+ end
243
+
244
+ line_marker = logical_line.match(/\A#\s+\d+\s+"([^"]+)"/)
245
+ if line_marker
246
+ current_file = normalize_preprocessor_path(line_marker[1])
247
+ logical_line = +""
248
+ next
249
+ end
250
+
251
+ match = logical_line.match(/\A\s*#define\s+([A-Za-z_][A-Za-z0-9_]*)(.*)\z/)
252
+ if match && current_file && tracked_header_path?(current_file)
253
+ name = match[1]
254
+ suffix = match[2]
255
+ yield(name, suffix.strip) unless suffix.start_with?("(")
256
+ end
257
+
258
+ logical_line = +""
259
+ end
260
+ end
261
+
262
+ def macro_constant_candidate?(name, body)
263
+ return false unless name.match?(/\A[A-Z][A-Z0-9_]*\z/)
264
+ return false unless allowed_declaration_name?(name)
265
+ return false unless active_macro_name?(name)
266
+
267
+ normalized = normalize_macro_body(body)
268
+ return false if normalized.empty?
269
+ return false if normalized.match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/)
270
+ return false if normalized.include?('"') || normalized.include?("'")
271
+ return false if contains_disallowed_macro_call?(normalized)
272
+
273
+ first_token = normalized[/\A[A-Za-z_][A-Za-z0-9_]*/]
274
+ return false if first_token && NON_VALUE_MACRO_TOKENS.include?(first_token)
275
+
276
+ normalized.match?(/\A[A-Za-z0-9_()+\-*\/%<>&|~^.,{}\[\]:?\s]+\z/)
277
+ end
278
+
279
+ def active_macro_name?(name)
280
+ @active_macro_names.include?(name)
281
+ end
282
+
283
+ def preprocessed_macro_names(translation_unit_path)
284
+ command = [
285
+ @clang,
286
+ "-x",
287
+ "c",
288
+ "-fno-builtin",
289
+ *@clang_args,
290
+ "-dM",
291
+ "-E",
292
+ translation_unit_path,
293
+ ]
294
+ stdout, stderr, status = Open3.capture3(*command)
295
+ unless status.success?
296
+ details = [stdout, stderr].reject(&:empty?).join
297
+ raise BindgenError, details.empty? ? "clang bindgen macro probe failed" : "clang bindgen macro probe failed:\n#{details}"
298
+ end
299
+
300
+ stdout.each_line.filter_map do |line|
301
+ match = line.match(/\A#define\s+([A-Za-z_][A-Za-z0-9_]*)\b/)
302
+ match[1] if match
303
+ end.to_set
304
+ end
305
+
306
+ def contains_disallowed_macro_call?(source)
307
+ index = 0
308
+
309
+ while index < source.length
310
+ match = source.match(/\b[A-Za-z_][A-Za-z0-9_]*\s*\(/, index)
311
+ return false unless match
312
+
313
+ callee = match[0][/\A[A-Za-z_][A-Za-z0-9_]*/]
314
+ open_index = match[0].rindex("(") + match.begin(0)
315
+ close_index = matching_paren_index(source, open_index)
316
+ return true unless close_index
317
+
318
+ return true unless callee&.match?(/\A[A-Z][A-Z0-9_]*\z/)
319
+
320
+ index = close_index + 1
321
+ end
322
+
323
+ false
324
+ end
325
+
326
+ def matching_paren_index(source, open_index)
327
+ depth = 0
328
+ index = open_index
329
+
330
+ while index < source.length
331
+ case source[index]
332
+ when "("
333
+ depth += 1
334
+ when ")"
335
+ depth -= 1
336
+ return index if depth.zero?
337
+ end
338
+
339
+ index += 1
340
+ end
341
+
342
+ nil
343
+ end
344
+
345
+ def normalize_macro_body(body)
346
+ body.gsub(%r{/\*.*?\*/}, " ").sub(%r{//.*\z}, "").strip
347
+ end
348
+
349
+ def macro_probe_name(name)
350
+ "#{MACRO_CONST_PREFIX}#{name}"
351
+ end
352
+
353
+ def macro_probe_declaration?(node)
354
+ node["name"].to_s.start_with?(MACRO_CONST_PREFIX)
355
+ end
356
+
357
+ def constant_name_for(node)
358
+ name = node["name"].to_s
359
+ macro_probe_declaration?(node) ? name.delete_prefix(MACRO_CONST_PREFIX) : name
360
+ end
361
+
362
+ def typedef_target(node)
363
+ inner = Array(node["inner"]).find { |child| %w[RecordType EnumType].include?(child["kind"]) }
364
+ decl = inner&.dig("decl")
365
+ return unless decl
366
+
367
+ { id: decl["id"], kind: decl["kind"], name: decl["name"] }
368
+ end
369
+
370
+ def preprocessed_macro_source
371
+ command = [
372
+ @clang,
373
+ "-x",
374
+ "c",
375
+ "-fno-builtin",
376
+ *@clang_args,
377
+ "-E",
378
+ "-dD",
379
+ @translation_unit_path,
380
+ ]
381
+ stdout, stderr, status = Open3.capture3(*command)
382
+ unless status.success?
383
+ details = [stdout, stderr].reject(&:empty?).join
384
+ raise BindgenError, details.empty? ? "clang bindgen macro dump failed" : "clang bindgen macro dump failed:\n#{details}"
385
+ end
386
+
387
+ stdout
388
+ end
389
+
390
+ def normalize_preprocessor_path(path)
391
+ return nil if path.start_with?("<") && path.end_with?(">")
392
+
393
+ File.expand_path(path)
394
+ end
395
+ end
396
+ end
397
+ end
398
+ end