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,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module ImportedBindings
5
+ class Generator
6
+ module GeneratorMethodSource
7
+ private
8
+
9
+ def load_method_sources(method_specs)
10
+ method_specs.each_with_object({}) do |spec, sources|
11
+ next unless spec[:module_name]
12
+
13
+ key = method_source_key(spec)
14
+ next if sources.key?(key)
15
+
16
+ sources[key] = load_method_source(spec)
17
+ end
18
+ end
19
+
20
+ def load_method_source(spec)
21
+ module_name = spec.fetch(:module_name)
22
+ module_path = resolve_module_path(module_name)
23
+ source_ast = ModuleLoader.new(module_roots: @module_roots).load_file(module_path)
24
+ unless source_ast.module_name&.to_s == module_name
25
+ raise Error, "expected #{module_path} to define module #{module_name}"
26
+ end
27
+
28
+ declarations = index_method_source_declarations(source_ast)
29
+ MethodSource.new(
30
+ module_name:,
31
+ module_path:,
32
+ module_kind: source_ast.module_kind,
33
+ import_alias: spec.fetch(:module_import_alias),
34
+ imports_by_alias: source_ast.imports.to_h { |import| [import.alias_name, import.path.parts.join(".")] },
35
+ public_type_names: declarations[:types].keys,
36
+ functions: declarations[:functions],
37
+ function_order: declarations[:function_order],
38
+ import_specs: [{ module_name:, alias: spec.fetch(:module_import_alias) }],
39
+ )
40
+ end
41
+
42
+ def method_source_import_specs(method_sources)
43
+ method_sources.values.flat_map(&:import_specs)
44
+ end
45
+
46
+ def method_source_key(spec)
47
+ [spec.fetch(:module_name), spec.fetch(:module_import_alias)]
48
+ end
49
+
50
+ def index_method_source_declarations(source_ast)
51
+ types = {}
52
+ type_order = []
53
+ functions = {}
54
+ function_order = []
55
+
56
+ source_ast.declarations.each do |declaration|
57
+ case declaration
58
+ when AST::TypeAliasDecl, AST::StructDecl, AST::UnionDecl, AST::EnumDecl, AST::FlagsDecl, AST::OpaqueDecl
59
+ next unless visible_from_method_source?(declaration, module_kind: source_ast.module_kind)
60
+
61
+ types[declaration.name] = declaration
62
+ type_order << declaration.name
63
+ when AST::ExternFunctionDecl, AST::ForeignFunctionDecl
64
+ next unless visible_from_method_source?(declaration, module_kind: source_ast.module_kind)
65
+
66
+ functions[declaration.name] = declaration
67
+ function_order << declaration.name
68
+ end
69
+ end
70
+
71
+ {
72
+ types:,
73
+ type_order:,
74
+ functions:,
75
+ function_order:,
76
+ }
77
+ end
78
+
79
+ def visible_from_method_source?(declaration, module_kind:)
80
+ return true if module_kind == :raw_module
81
+
82
+ declaration.respond_to?(:visibility) && declaration.visibility == :public
83
+ end
84
+
85
+ def plan_method_source_function(declaration, source:)
86
+ if declaration.respond_to?(:variadic) && declaration.variadic
87
+ raise Error, "method generation for #{declaration.name} in #{source.module_path} cannot use variadic functions"
88
+ end
89
+
90
+ {
91
+ raw_name: declaration.name,
92
+ public_name: declaration.name,
93
+ call_name: "#{source.import_alias}.#{declaration.name}",
94
+ type_params: raw_type_param_names(declaration),
95
+ params: declaration.params.map { |param| project_method_source_param(param, source:) },
96
+ return_type: project_method_source_type(declaration.return_type, source:),
97
+ }
98
+ end
99
+
100
+ def project_method_source_param(param, source:)
101
+ case param
102
+ when AST::ForeignParam
103
+ {
104
+ "name" => param.name,
105
+ "type" => project_method_source_type(param.type, source:),
106
+ "mode" => normalize_method_source_param_mode(param.mode),
107
+ }
108
+ when AST::Param
109
+ {
110
+ "name" => param.name,
111
+ "type" => project_method_source_type(param.type, source:),
112
+ }
113
+ else
114
+ raise Error, "unsupported method source parameter #{param.class.name} in #{source.module_path}"
115
+ end
116
+ end
117
+
118
+ def normalize_method_source_param_mode(mode)
119
+ return nil if mode == :plain || mode.nil?
120
+
121
+ mode.to_s
122
+ end
123
+
124
+ def project_method_source_type(type, source:)
125
+ case type
126
+ when AST::TypeRef
127
+ text = project_method_source_type_name(type.name.to_s, source:)
128
+ unless type.arguments.empty?
129
+ rendered_arguments = type.arguments.map { |argument| project_method_source_type_argument(argument.value, source:) }
130
+ text << "[#{rendered_arguments.join(', ')}]"
131
+ end
132
+ text << "?" if type.nullable
133
+ text
134
+ when AST::FunctionType
135
+ params = type.params.map { |param| project_method_source_function_type_param(param, source:) }.join(', ')
136
+ "fn(#{params}) -> #{project_method_source_type(type.return_type, source:)}"
137
+ else
138
+ raise Error, "unsupported method source type node #{type.class.name} in #{source.module_path}"
139
+ end
140
+ end
141
+
142
+ def project_method_source_function_type_param(param, source:)
143
+ case param
144
+ when AST::Param
145
+ "#{param.name}: #{project_method_source_type(param.type, source:)}"
146
+ when AST::ForeignParam
147
+ text = +""
148
+ text << "#{param.mode} " if param.mode
149
+ text << "#{param.name}: #{project_method_source_type(param.type, source:)}"
150
+ if param.boundary_type
151
+ text << " as #{project_method_source_type(param.boundary_type, source:)}"
152
+ end
153
+ text
154
+ else
155
+ project_method_source_type(param, source:)
156
+ end
157
+ end
158
+
159
+ def project_method_source_type_argument(argument, source:)
160
+ case argument
161
+ when AST::TypeRef, AST::FunctionType
162
+ project_method_source_type(argument, source:)
163
+ when AST::IntegerLiteral
164
+ argument.lexeme
165
+ when AST::Identifier
166
+ argument.name
167
+ else
168
+ raise Error, "unsupported method source type argument #{argument.class.name} in #{source.module_path}"
169
+ end
170
+ end
171
+
172
+ def project_method_source_type_name(raw_name, source:)
173
+ parts = raw_name.split(".")
174
+ if parts.length > 1
175
+ import_alias = parts.first
176
+ imported_name = parts[1..].join(".")
177
+ imported_module_name = source.imports_by_alias[import_alias]
178
+ return rendered_type_name(imported_name) if imported_module_name == @raw_module_name
179
+ return imported_name if imported_module_name == @module_name
180
+ return raw_name if imported_module_name
181
+ end
182
+
183
+ return "#{source.import_alias}.#{raw_name}" if source.public_type_names.include?(raw_name)
184
+
185
+ rendered_type_name(raw_name)
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,286 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module ImportedBindings
5
+ class Generator
6
+ module GeneratorNaming
7
+ OPENGL_TYPED_SUFFIX_TOKENS = [
8
+ ["ui64v", %w[uint64 values]],
9
+ ["i64v", %w[int64 values]],
10
+ ["uiv", %w[uint values]],
11
+ ["iv", %w[int values]],
12
+ ["fi", %w[float int]],
13
+ ["fv", %w[float values]],
14
+ ["dv", %w[double values]],
15
+ ["ubv", %w[ubyte values]],
16
+ ["usv", %w[ushort values]],
17
+ ["bv", %w[byte values]],
18
+ ["sv", %w[short values]],
19
+ ["ui64", %w[uint64]],
20
+ ["i64", %w[int64]],
21
+ ["ui", %w[uint]],
22
+ ["i", %w[int]],
23
+ ["f", %w[float]],
24
+ ["d", %w[double]],
25
+ ["ub", %w[ubyte]],
26
+ ["us", %w[ushort]],
27
+ ["b", %w[byte]],
28
+ ["s", %w[short]],
29
+ ["v", %w[values]],
30
+ ].freeze
31
+ OPENGL_DOMAIN_MARKERS = {
32
+ "i" => "integer",
33
+ "l" => "long",
34
+ "p" => "packed",
35
+ }.freeze
36
+ OPENGL_INDEXED_PARAM_NAMES = %w[buf index mask_number].freeze
37
+ OPENGL_TYPED_ALPHA_STEMS = %w[
38
+ array
39
+ attrib
40
+ block
41
+ boolean
42
+ buffer
43
+ double
44
+ feedback
45
+ float
46
+ framebuffer
47
+ indexed
48
+ integer
49
+ internalformat
50
+ interface
51
+ multisample
52
+ object
53
+ parameter
54
+ pipeline
55
+ pointer
56
+ program
57
+ query
58
+ resource
59
+ shader
60
+ stage
61
+ subroutine
62
+ subroutines
63
+ sync
64
+ uniform
65
+ uniforms
66
+ ].freeze
67
+ OPENGL_TYPED_QUERY_BASES = %w[boolean double float integer].freeze
68
+ OPENGL_NUMERIC_SUFFIX_CONTEXTS = %w[
69
+ attrib
70
+ boolean
71
+ double
72
+ feedback
73
+ float
74
+ indexed
75
+ int
76
+ integer
77
+ integer64
78
+ long
79
+ matrix
80
+ normalized
81
+ object
82
+ packed
83
+ parameter
84
+ uint
85
+ uniform
86
+ ].freeze
87
+
88
+ private
89
+
90
+ def normalize_opengl_snake_case(name)
91
+ tokens = name.to_s.split("_").reject(&:empty?)
92
+ normalized = []
93
+ index = 0
94
+
95
+ while index < tokens.length
96
+ token = tokens[index]
97
+ next_token = tokens[index + 1]
98
+
99
+ case token
100
+ when "getn"
101
+ normalized.concat(%w[get n])
102
+ index += 1
103
+ next
104
+ when "64i"
105
+ if next_token == "v"
106
+ if normalized.last == "integer"
107
+ normalized[-1] = "integer64"
108
+ normalized.concat(%w[indexed values])
109
+ else
110
+ normalized.concat(%w[int64 indexed values])
111
+ end
112
+ index += 2
113
+ next
114
+ end
115
+ when "i"
116
+ if next_token == "v"
117
+ if OPENGL_TYPED_QUERY_BASES.include?(normalized.last)
118
+ normalized.concat(%w[indexed values])
119
+ else
120
+ normalized.concat(%w[int indexed values])
121
+ end
122
+ index += 2
123
+ next
124
+ end
125
+ when "i64"
126
+ if next_token == "v"
127
+ normalized.concat(%w[int64 indexed values])
128
+ index += 2
129
+ next
130
+ end
131
+ when "ui64"
132
+ if next_token == "v"
133
+ normalized.concat(%w[uint64 indexed values])
134
+ index += 2
135
+ next
136
+ end
137
+ when "64v"
138
+ case normalized.last
139
+ when "integer"
140
+ normalized[-1] = "integer64"
141
+ normalized << "values"
142
+ index += 1
143
+ next
144
+ when "int"
145
+ normalized[-1] = "int64"
146
+ normalized << "values"
147
+ index += 1
148
+ next
149
+ when "uint"
150
+ normalized[-1] = "uint64"
151
+ normalized << "values"
152
+ index += 1
153
+ next
154
+ end
155
+ end
156
+
157
+ if (domain_marker = opengl_domain_marker(token, next_token))
158
+ normalized << domain_marker
159
+ index += 1
160
+ next
161
+ end
162
+
163
+ if (expanded = expand_opengl_special_token(token, numeric_context: opengl_numeric_suffix_context?(normalized)))
164
+ normalized.concat(expanded)
165
+ index += 1
166
+ next
167
+ end
168
+
169
+ normalized << token
170
+ index += 1
171
+ end
172
+
173
+ normalized.join("_")
174
+ end
175
+
176
+ def normalize_opengl_terminal_suffix(name, raw_name)
177
+ normalized = if name.end_with?("indexedfv")
178
+ "#{name.delete_suffix('indexedfv')}indexed_float_values"
179
+ elsif name.end_with?("indexedf")
180
+ "#{name.delete_suffix('indexedf')}indexed_float"
181
+ else
182
+ name
183
+ end
184
+
185
+ raw_text = raw_name.to_s
186
+ if raw_text.end_with?("i") && normalized.end_with?("i")
187
+ suffix = opengl_indexed_terminal_variant?(raw_name) ? "indexed" : "int"
188
+ return "#{normalized.delete_suffix('i')}_#{suffix}"
189
+ end
190
+
191
+ return "#{normalized.delete_suffix('f')}_float" if raw_text.end_with?("f") && normalized.end_with?("f")
192
+
193
+ normalized
194
+ end
195
+
196
+ def opengl_indexed_terminal_variant?(raw_name)
197
+ raw_declaration = @raw_function_declarations[raw_name]
198
+ return false unless raw_declaration
199
+
200
+ raw_declaration.params
201
+ .map { |param| snake_case(param.name) }
202
+ .any? { |name| OPENGL_INDEXED_PARAM_NAMES.include?(name) }
203
+ end
204
+
205
+ def opengl_domain_marker(token, next_token)
206
+ return unless OPENGL_DOMAIN_MARKERS.key?(token)
207
+ return unless next_token
208
+ return unless next_token == "format" || next_token == "pointer" || next_token.match?(/\A\d/)
209
+
210
+ OPENGL_DOMAIN_MARKERS.fetch(token)
211
+ end
212
+
213
+ def opengl_numeric_suffix_context?(normalized_tokens)
214
+ last_token = normalized_tokens.last
215
+ last_token && OPENGL_NUMERIC_SUFFIX_CONTEXTS.include?(last_token)
216
+ end
217
+
218
+ def expand_opengl_special_token(token, numeric_context:)
219
+ return %w[integer int values] if token == "iiv"
220
+ return %w[integer uint values] if token == "iuiv"
221
+
222
+ if token.start_with?("n") && token.length > 1
223
+ expanded = expand_opengl_typed_token(token[1..], numeric_context: true)
224
+ return ["normalized", *expanded] if expanded
225
+ end
226
+
227
+ expand_opengl_typed_token(token, numeric_context:)
228
+ end
229
+
230
+ def expand_opengl_typed_token(token, numeric_context:)
231
+ OPENGL_TYPED_SUFFIX_TOKENS.each do |suffix, replacement|
232
+ next unless token.end_with?(suffix)
233
+
234
+ prefix = token.delete_suffix(suffix)
235
+ if prefix.empty?
236
+ return nil if suffix == "v"
237
+ return replacement if numeric_context
238
+
239
+ next
240
+ end
241
+
242
+ if prefix.match?(/\A\d+\z|\A\d+x\d+\z/)
243
+ return nil unless numeric_context
244
+
245
+ return [prefix, *replacement]
246
+ end
247
+
248
+ if prefix.match?(/\A[a-z]+\z/) && OPENGL_TYPED_ALPHA_STEMS.include?(prefix)
249
+ return [prefix, *replacement]
250
+ end
251
+ end
252
+
253
+ nil
254
+ end
255
+
256
+ def snake_case(name)
257
+ name.to_s
258
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\\1_\\2')
259
+ .gsub(/([a-z0-9])([A-Z])/, '\\1_\\2')
260
+ .downcase
261
+ .gsub(/([a-z])(\d+)_d\b/, '\1_\2d')
262
+ end
263
+
264
+ def camelize_binding_name(name)
265
+ parts = name.to_s.split("_").reject(&:empty?)
266
+ return "" if parts.empty?
267
+
268
+ parts.map do |part|
269
+ if part.match?(/\A\d+\z/)
270
+ part
271
+ else
272
+ part[0].upcase + part[1..].to_s.downcase
273
+ end
274
+ end.join
275
+ end
276
+
277
+ def openglize_binding_name(name)
278
+ name.to_s
279
+ .gsub(/([A-Za-z])((?:i64|i|ui)_v)(?=\z|_)/, '\\1_\\2')
280
+ .gsub(/(?<!_)([A-Za-z])(\d[A-Za-z0-9]*)(?=[A-Z_]|\z)/, '\1_\2')
281
+ .gsub(/_(\d)D(?=[A-Z_]|\z)/, '_\1d')
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../tooling/formatter"
5
+ require_relative "../core/token"
6
+ require_relative "../core/types/types"
7
+
8
+ require_relative "imported_bindings/generator"
9
+ require_relative "imported_bindings/method_source"
10
+ require_relative "imported_bindings/naming"
11
+ require_relative "imported_bindings/defaults"
12
+
13
+ module MilkTea
14
+ module ImportedBindings
15
+ class Error < StandardError; end
16
+
17
+ class Binding
18
+ attr_reader :name, :module_name, :binding_path, :raw_module_name, :policy_path, :import_alias
19
+
20
+ def initialize(name:, module_name:, binding_path:, raw_module_name:, policy_path:, import_alias: "c")
21
+ @name = name.to_s
22
+ @module_name = module_name
23
+ @binding_path = File.expand_path(binding_path.to_s)
24
+ @raw_module_name = raw_module_name
25
+ @policy_path = File.expand_path(policy_path.to_s)
26
+ @import_alias = import_alias
27
+ end
28
+
29
+ def task_name
30
+ "imported_bindings:#{name}"
31
+ end
32
+
33
+ def check_task_name
34
+ "imported_bindings:check:#{name}"
35
+ end
36
+
37
+ def generate(module_roots: [MilkTea.root])
38
+ Generator.new(
39
+ module_name:,
40
+ raw_module_name:,
41
+ raw_module_path: resolve_module_path(raw_module_name, module_roots),
42
+ policy_path:,
43
+ import_alias:,
44
+ module_roots:,
45
+ ).generate
46
+ end
47
+
48
+ def write!(module_roots: [MilkTea.root])
49
+ raw_module_path = resolve_module_path(raw_module_name, module_roots)
50
+ source = Generator.new(
51
+ module_name:,
52
+ raw_module_name:,
53
+ raw_module_path:,
54
+ policy_path:,
55
+ import_alias:,
56
+ module_roots:,
57
+ ).generate
58
+ File.write(binding_path, source)
59
+ raw_module_path
60
+ end
61
+
62
+ def check!(module_roots: [MilkTea.root])
63
+ raw_module_path = resolve_module_path(raw_module_name, module_roots)
64
+ actual = Generator.new(
65
+ module_name:,
66
+ raw_module_name:,
67
+ raw_module_path:,
68
+ policy_path:,
69
+ import_alias:,
70
+ module_roots:,
71
+ ).generate
72
+ expected = File.read(binding_path)
73
+
74
+ if expected != actual
75
+ raise Error, <<~MESSAGE
76
+ #{binding_path} is out of date for #{raw_module_path} and #{policy_path}
77
+ Run `rake #{task_name}` to regenerate it.
78
+ MESSAGE
79
+ end
80
+
81
+ MilkTea::ModuleLoader.new(module_roots:).check_file(binding_path)
82
+ raw_module_path
83
+ end
84
+
85
+ private
86
+
87
+ def resolve_module_path(module_name, module_roots)
88
+ relative_path = File.join(*module_name.split(".")) + ".mt"
89
+ candidate = module_roots.lazy.map { |root| File.join(File.expand_path(root.to_s), relative_path) }.find { |path| File.file?(path) }
90
+ raise Error, "module not found: #{module_name}" unless candidate
91
+
92
+ File.expand_path(candidate)
93
+ end
94
+ end
95
+
96
+ class Registry
97
+ include Enumerable
98
+
99
+ def initialize(bindings = [])
100
+ @bindings = {}
101
+ @bindings_by_module_name = {}
102
+ bindings.each { |binding| register(binding) }
103
+ end
104
+
105
+ def register(binding)
106
+ raise Error, "duplicate imported binding #{binding.name}" if @bindings.key?(binding.name)
107
+ raise Error, "duplicate imported binding module #{binding.module_name}" if @bindings_by_module_name.key?(binding.module_name)
108
+
109
+ @bindings[binding.name] = binding
110
+ @bindings_by_module_name[binding.module_name] = binding
111
+ end
112
+
113
+ def fetch(name)
114
+ @bindings.fetch(name.to_s)
115
+ rescue KeyError
116
+ raise Error, "unknown imported binding #{name}"
117
+ end
118
+
119
+ def each(&block)
120
+ @bindings.each_value(&block)
121
+ end
122
+
123
+ def find_by_module_name(module_name)
124
+ @bindings_by_module_name[module_name]
125
+ end
126
+
127
+ def task_names
128
+ map(&:task_name)
129
+ end
130
+
131
+ def check_task_names
132
+ map(&:check_task_name)
133
+ end
134
+ end
135
+
136
+ class Generator
137
+ MethodSource = Data.define(:module_name, :module_path, :module_kind, :import_alias, :imports_by_alias, :public_type_names, :functions, :function_order, :import_specs)
138
+
139
+ def initialize(module_name:, raw_module_name:, raw_module_path:, policy_path:, import_alias:, module_roots:)
140
+ @module_name = module_name
141
+ @raw_module_name = raw_module_name
142
+ @raw_module_path = File.expand_path(raw_module_path)
143
+ @policy_path = File.expand_path(policy_path)
144
+ @import_alias = import_alias
145
+ @module_roots = module_roots.map { |root| File.expand_path(root.to_s) }
146
+ @public_type_names_by_raw_name = {}
147
+ @public_type_kinds_by_raw_name = {}
148
+ @raw_function_declarations = {}
149
+ @raw_type_declarations = {}
150
+ end
151
+
152
+ def generate
153
+ policy = load_policy
154
+ validate_policy!(policy)
155
+
156
+ raw_ast = ModuleLoader.new(module_roots: @module_roots).load_file(@raw_module_path)
157
+ validate_raw_module!(raw_ast)
158
+ declarations = index_raw_declarations(raw_ast)
159
+ import_specs = raw_import_specs(raw_ast)
160
+ validate_import_specs!(import_specs)
161
+ extra_import_specs = policy_import_specs(policy)
162
+ type_spec = normalize_alias_spec(policy["types"], context: "type")
163
+ @native_type_mapping = type_spec[:native_types]
164
+ const_spec = normalize_alias_spec(policy["constants"], context: "constant")
165
+ function_spec = normalize_function_spec(policy["functions"])
166
+ method_specs = normalize_method_specs(policy["methods"])
167
+ method_sources = load_method_sources(method_specs)
168
+ @raw_function_declarations = declarations[:functions]
169
+ @raw_type_declarations = declarations[:types]
170
+ @public_type_names_by_raw_name = build_public_type_names(type_spec, declarations)
171
+ function_entries = plan_foreign_functions(function_spec, declarations)
172
+ generated_import_specs = merge_import_specs(raw_import_specs(raw_ast), extra_import_specs, method_source_import_specs(method_sources))
173
+ validate_import_specs!(generated_import_specs)
174
+ const_lines = emit_const_aliases(const_spec, declarations)
175
+ function_lines = emit_foreign_functions(function_entries)
176
+ method_lines = emit_methods(method_specs, function_entries, declarations, method_sources:)
177
+ type_lines = emit_type_aliases(
178
+ type_spec,
179
+ declarations,
180
+ referenced_lines: const_lines + function_lines + method_lines,
181
+ method_sources:,
182
+ )
183
+
184
+ lines = []
185
+ lines << "# generated by mtc imported-bindings from #{@raw_module_name} using #{policy_label}"
186
+ lines << ""
187
+ lines << "import #{@raw_module_name} as #{@import_alias}"
188
+ generated_import_specs.each do |spec|
189
+ lines << "import #{spec[:module_name]} as #{spec[:alias]}"
190
+ end
191
+
192
+ sections = [
193
+ type_lines,
194
+ const_lines,
195
+ function_lines,
196
+ method_lines,
197
+ ].reject(&:empty?)
198
+
199
+ sections.each do |section_lines|
200
+ lines << ""
201
+ lines.concat(section_lines)
202
+ end
203
+
204
+ source = lines.join("\n") + "\n"
205
+ Formatter.format_source(source, path: generated_module_path, mode: :tidy)
206
+ end
207
+
208
+ private
209
+
210
+ include GeneratorPolicy
211
+ include GeneratorMethodSource
212
+ include GeneratorNaming
213
+ end
214
+ end
215
+ end