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,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../compile_time/const_eval"
4
+
5
+ module MilkTea
6
+ module ControlFlow
7
+ class ConstantPropagation
8
+ ConstVal = Data.define(:value)
9
+
10
+ UNDEF = :undef
11
+ NAC = :nac
12
+
13
+ Result = Data.define(:in_states, :out_states) do
14
+ def constant_at(node_id, key)
15
+ entry = in_states[node_id]
16
+ return nil unless entry
17
+
18
+ v = entry[key]
19
+ v.is_a?(ConstVal) ? v.value : nil
20
+ end
21
+ end
22
+
23
+ def self.solve(graph, binding_resolution: nil, strict_binding_ids: false)
24
+ result = Dataflow.solve(
25
+ graph,
26
+ direction: :forward,
27
+ initial: -> { {} },
28
+ join: lambda do |states|
29
+ return {} if states.empty?
30
+
31
+ states.reduce do |acc, state|
32
+ keys = acc.keys | state.keys
33
+ keys.each_with_object({}) do |k, merged|
34
+ a = acc[k] || UNDEF
35
+ b = state[k] || UNDEF
36
+ merged[k] = join_lattice(a, b)
37
+ end
38
+ end
39
+ end,
40
+ transfer: lambda do |node, in_state|
41
+ out = in_state.dup
42
+ node.writes_info.each do |write|
43
+ key = write[:binding_key]
44
+ val = eval_const(node.statement, write, in_state, binding_resolution:, strict_binding_ids:)
45
+ out[key] = val
46
+ end
47
+ out
48
+ end,
49
+ boundary_in: { graph.entry_id => {} }
50
+ )
51
+
52
+ Result.new(in_states: result.in_states, out_states: result.out_states)
53
+ end
54
+
55
+ def self.join_lattice(a, b)
56
+ return b if a == UNDEF
57
+ return a if b == UNDEF
58
+ return NAC if a == NAC || b == NAC
59
+
60
+ a == b ? a : NAC
61
+ end
62
+ private_class_method :join_lattice
63
+
64
+ def self.eval_const(statement, _write, in_state, binding_resolution:, strict_binding_ids:)
65
+ case statement
66
+ when AST::LocalDecl
67
+ eval_expr_const(statement.value, in_state, binding_resolution:, strict_binding_ids:)
68
+ when AST::Assignment
69
+ eval_assignment_const(statement, in_state, binding_resolution:, strict_binding_ids:)
70
+ else
71
+ NAC
72
+ end
73
+ end
74
+ private_class_method :eval_const
75
+
76
+ def self.eval_assignment_const(statement, in_state, binding_resolution:, strict_binding_ids:)
77
+ return NAC unless statement.target.is_a?(AST::Identifier)
78
+
79
+ rhs = eval_expr_const(statement.value, in_state, binding_resolution:, strict_binding_ids:)
80
+ return NAC unless rhs.is_a?(ConstVal)
81
+
82
+ return rhs if statement.operator == "="
83
+
84
+ lhs = eval_expr_const(statement.target, in_state, binding_resolution:, strict_binding_ids:)
85
+ return NAC unless lhs.is_a?(ConstVal)
86
+
87
+ begin
88
+ v = case statement.operator
89
+ when "+=" then lhs.value + rhs.value
90
+ when "-=" then lhs.value - rhs.value
91
+ when "*=" then lhs.value * rhs.value
92
+ when "/=" then rhs.value.zero? ? (return NAC) : lhs.value / rhs.value
93
+ when "%=" then rhs.value.zero? ? (return NAC) : lhs.value % rhs.value
94
+ when "&=" then lhs.value & rhs.value
95
+ when "|=" then lhs.value | rhs.value
96
+ when "^=" then lhs.value ^ rhs.value
97
+ when "<<=" then lhs.value << rhs.value
98
+ when ">>=" then lhs.value >> rhs.value
99
+ else return NAC
100
+ end
101
+ ConstVal.new(v)
102
+ rescue ZeroDivisionError, RangeError
103
+ NAC
104
+ end
105
+ end
106
+ private_class_method :eval_assignment_const
107
+
108
+ def self.eval_expr_const(expr, state, binding_resolution:, strict_binding_ids:)
109
+ return NAC if expr.nil?
110
+
111
+ value = ConstEval.evaluate(
112
+ expr,
113
+ resolve_identifier: lambda do |identifier_expression|
114
+ key = identifier_key(identifier_expression, binding_resolution:, strict_binding_ids:)
115
+ next unless key
116
+
117
+ state_value = state[key]
118
+ state_value.is_a?(ConstVal) ? state_value.value : nil
119
+ end,
120
+ resolve_member_access: nil,
121
+ )
122
+ value.nil? ? NAC : ConstVal.new(value)
123
+ end
124
+ private_class_method :eval_expr_const
125
+
126
+ def self.constant_value_of(expr, in_state, binding_resolution: nil, strict_binding_ids: false)
127
+ result = send(:eval_expr_const, expr, in_state, binding_resolution:, strict_binding_ids:)
128
+ result.is_a?(ConstVal) ? result.value : nil
129
+ end
130
+
131
+ def self.identifier_key(identifier_expression, binding_resolution:, strict_binding_ids:)
132
+ if binding_resolution && (id = binding_resolution.identifier_binding_ids[identifier_expression.object_id])
133
+ return id
134
+ end
135
+
136
+ return nil if strict_binding_ids
137
+
138
+ identifier_expression.name
139
+ end
140
+ private_class_method :identifier_key
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module ControlFlow
5
+ class Dataflow
6
+ Result = Data.define(:in_states, :out_states)
7
+
8
+ def self.solve(graph, direction:, initial:, join:, transfer: nil, edge_transfer: nil, boundary_in: {}, boundary_out: {})
9
+ raise ArgumentError, "direction must be :forward or :backward" unless %i[forward backward].include?(direction)
10
+ raise ArgumentError, "provide either transfer: or edge_transfer:" if transfer.nil? && edge_transfer.nil?
11
+
12
+ in_states = {}
13
+ out_states = {}
14
+ edge_out = {} # [from_id, to_id] => state (only when edge_transfer given)
15
+ all_ids = graph.ids
16
+ all_ids.each do |id|
17
+ in_states[id] = initial.call
18
+ out_states[id] = initial.call
19
+ end
20
+
21
+ changed = true
22
+ while changed
23
+ changed = false
24
+ iteration_ids = direction == :forward ? graph.rpo_ids : all_ids.reverse
25
+ iteration_ids.each do |id|
26
+ node = graph.nodes[id]
27
+
28
+ if direction == :forward
29
+ incoming =
30
+ if boundary_in.key?(id)
31
+ boundary_in[id]
32
+ elsif edge_transfer
33
+ join.call(node.preds.map { |pred| edge_out[[pred, id]] || initial.call })
34
+ else
35
+ join.call(node.preds.map { |pred| out_states[pred] })
36
+ end
37
+
38
+ if edge_transfer
39
+ node.succs.each do |succ|
40
+ label = graph.edge_label(id, succ)
41
+ new_edge = edge_transfer.call(node, incoming, succ, label)
42
+ old_edge = edge_out[[id, succ]]
43
+ if old_edge != new_edge
44
+ edge_out[[id, succ]] = new_edge
45
+ changed = true
46
+ end
47
+ end
48
+ new_out = node.succs.empty? ? initial.call : join.call(node.succs.map { |s| edge_out[[id, s]] || initial.call })
49
+ else
50
+ new_out = boundary_out.fetch(id) { transfer.call(node, incoming) }
51
+ end
52
+
53
+ if in_states[id] != incoming || out_states[id] != new_out
54
+ in_states[id] = incoming
55
+ out_states[id] = new_out
56
+ changed = true
57
+ end
58
+ else
59
+ outgoing = boundary_out.fetch(id) { join.call(node.succs.map { |succ| in_states[succ] }) }
60
+ incoming = boundary_in.fetch(id) { transfer.call(node, outgoing) }
61
+ if in_states[id] != incoming || out_states[id] != outgoing
62
+ in_states[id] = incoming
63
+ out_states[id] = outgoing
64
+ changed = true
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ Result.new(in_states:, out_states:)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module ControlFlow
5
+ class DefiniteAssignment
6
+ ReadBeforeAssignment = Data.define(:node_id, :binding_key, :line, :column, :length)
7
+ Result = Data.define(:definitely_assigned_in, :definitely_assigned_out, :read_before_assignment)
8
+
9
+ def self.solve(graph, initially_assigned: Set.new)
10
+ initially_assigned = initially_assigned.dup
11
+ universe = initial_universe(graph, initially_assigned)
12
+
13
+ result = Dataflow.solve(
14
+ graph,
15
+ direction: :forward,
16
+ initial: -> { universe.dup },
17
+ join: lambda do |states|
18
+ if states.empty?
19
+ universe.dup
20
+ else
21
+ states.reduce(universe.dup) { |acc, state| acc & state }
22
+ end
23
+ end,
24
+ transfer: lambda do |node, in_state|
25
+ in_state | node.writes
26
+ end,
27
+ boundary_in: { graph.entry_id => initially_assigned.dup }
28
+ )
29
+
30
+ read_before_assignment = []
31
+ graph.each_node do |node|
32
+ in_state = result.in_states[node.id]
33
+ if node.reads_info.empty?
34
+ node.reads.each do |binding_key|
35
+ next if in_state.include?(binding_key)
36
+
37
+ read_before_assignment << ReadBeforeAssignment.new(
38
+ node_id: node.id,
39
+ binding_key:,
40
+ line: node.line,
41
+ column: nil,
42
+ length: nil,
43
+ )
44
+ end
45
+ next
46
+ end
47
+
48
+ node.reads_info.each do |read_site|
49
+ next if in_state.include?(read_site.binding_key)
50
+
51
+ read_before_assignment << ReadBeforeAssignment.new(
52
+ node_id: node.id,
53
+ binding_key: read_site.binding_key,
54
+ line: read_site.line || node.line,
55
+ column: read_site.column,
56
+ length: read_site.length,
57
+ )
58
+ end
59
+ end
60
+
61
+ Result.new(
62
+ definitely_assigned_in: result.in_states,
63
+ definitely_assigned_out: result.out_states,
64
+ read_before_assignment:
65
+ )
66
+ end
67
+
68
+ def self.initial_universe(graph, initially_assigned)
69
+ universe = initially_assigned.dup
70
+ graph.each_node do |node|
71
+ universe.merge(node.writes)
72
+ universe.merge(node.reads)
73
+ end
74
+ universe
75
+ end
76
+ private_class_method :initial_universe
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module ControlFlow
5
+ class Graph
6
+ attr_accessor :entry_id, :exit_id
7
+ attr_reader :nodes
8
+
9
+ def initialize
10
+ @nodes = {}
11
+ @next_id = 1
12
+ @entry_id = nil
13
+ @exit_id = nil
14
+ @edge_labels = {} # Hash[(from_id, to_id) → :true_branch | :false_branch]
15
+ @edge_refinements = {} # Hash[(from_id, to_id) → Hash[binding_key → :non_null | :null]]
16
+ end
17
+
18
+ def add_node(kind:, statement: nil, line: nil, reads: Set.new, reads_info: [], writes: Set.new, writes_info: [])
19
+ id = @next_id
20
+ @next_id += 1
21
+ @nodes[id] = Node.new(
22
+ id:,
23
+ kind:,
24
+ statement:,
25
+ line:,
26
+ reads: reads.dup,
27
+ reads_info: reads_info.map(&:dup),
28
+ writes: writes.dup,
29
+ writes_info: writes_info.map(&:dup),
30
+ succs: [],
31
+ preds: []
32
+ )
33
+ id
34
+ end
35
+
36
+ def add_edge(from, to, label: nil)
37
+ return unless @nodes[from] && @nodes[to]
38
+
39
+ @nodes[from].succs << to unless @nodes[from].succs.include?(to)
40
+ @nodes[to].preds << from unless @nodes[to].preds.include?(from)
41
+ @edge_labels[[from, to]] = label if label
42
+ end
43
+
44
+ def edge_label(from, to)
45
+ @edge_labels[[from, to]]
46
+ end
47
+
48
+ def edge_refinement(from, to)
49
+ @edge_refinements[[from, to]]
50
+ end
51
+
52
+ def set_edge_refinement(from, to, refinement)
53
+ @edge_refinements[[from, to]] = refinement
54
+ end
55
+
56
+ def each_node
57
+ return enum_for(:each_node) unless block_given?
58
+
59
+ @nodes.each_value { |node| yield node }
60
+ end
61
+
62
+ def ids
63
+ @nodes.keys
64
+ end
65
+
66
+ def rpo_ids
67
+ visited = {}
68
+ order = []
69
+
70
+ dfs = lambda do |id|
71
+ return if visited[id]
72
+
73
+ visited[id] = true
74
+ node = @nodes[id]
75
+ node&.succs&.each { |succ| dfs.call(succ) }
76
+ order.unshift(id)
77
+ end
78
+
79
+ dfs.call(@entry_id) if @entry_id
80
+ order
81
+ end
82
+
83
+ def read_bindings
84
+ keys = Set.new
85
+ each_node { |node| keys.merge(node.reads) }
86
+ keys
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module ControlFlow
5
+ class Liveness
6
+ Result = Data.define(:live_in, :live_out)
7
+
8
+ def self.solve(graph)
9
+ result = Dataflow.solve(
10
+ graph,
11
+ direction: :backward,
12
+ initial: -> { Set.new },
13
+ join: lambda do |states|
14
+ states.reduce(Set.new) { |acc, state| acc | state }
15
+ end,
16
+ transfer: lambda do |node, live_out|
17
+ node.reads | (live_out - node.writes)
18
+ end,
19
+ )
20
+ Result.new(live_in: result.in_states, live_out: result.out_states)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module ControlFlow
5
+ class NullabilityFlow
6
+ Result = Data.define(:in_states, :out_states, :stmt_to_node_id) do
7
+ def nonnull_before(stmt)
8
+ node_id = stmt_to_node_id[stmt.object_id]
9
+ return Set.new unless node_id
10
+
11
+ in_states[node_id] || Set.new
12
+ end
13
+ end
14
+
15
+ def self.solve(graph)
16
+ stmt_to_node_id = {}
17
+ graph.each_node { |n| stmt_to_node_id[n.statement.object_id] = n.id if n.statement }
18
+
19
+ result = Dataflow.solve(
20
+ graph,
21
+ direction: :forward,
22
+ initial: -> { Set.new },
23
+ join: lambda do |states|
24
+ return Set.new if states.empty?
25
+
26
+ states.reduce { |acc, s| acc & s }
27
+ end,
28
+ edge_transfer: lambda do |node, in_state, succ_id, _edge_label|
29
+ state = in_state - node.writes
30
+ refs = graph.edge_refinement(node.id, succ_id) || {}
31
+ refs.each do |key, ref|
32
+ ref == :non_null ? (state = state | Set[key]) : (state = state - Set[key])
33
+ end
34
+ state
35
+ end,
36
+ boundary_in: { graph.entry_id => Set.new }
37
+ )
38
+
39
+ Result.new(in_states: result.in_states, out_states: result.out_states, stmt_to_node_id:)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module ControlFlow
5
+ class Reachability
6
+ Result = Data.define(:reachable_ids)
7
+
8
+ def self.solve(graph)
9
+ reachable = Set.new
10
+ queue = [graph.entry_id]
11
+ until queue.empty?
12
+ id = queue.shift
13
+ next if reachable.include?(id)
14
+
15
+ reachable << id
16
+ (graph.nodes[id]&.succs || []).each { |succ| queue << succ }
17
+ end
18
+ Result.new(reachable_ids: reachable)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module ControlFlow
5
+ class Termination
6
+ def self.block_always_terminates?(statements, **builder_options)
7
+ return false if statements.nil? || statements.empty?
8
+
9
+ graph = Builder.new(**builder_options).build(statements)
10
+ reachability = Reachability.solve(graph)
11
+ !reachability.reachable_ids.include?(graph.exit_id)
12
+ end
13
+
14
+ def self.loop_body_always_exits?(statements, **builder_options)
15
+ return false if statements.nil? || statements.empty?
16
+
17
+ graph = Builder.new(**builder_options).build_loop_body(statements)
18
+ reachability = Reachability.solve(graph)
19
+ !reachability.reachable_ids.include?(graph.exit_id)
20
+ end
21
+
22
+ def self.block_always_terminates_in_loop?(statements, **builder_options)
23
+ return false if statements.nil? || statements.empty?
24
+
25
+ graph = Builder.new(**builder_options).build_loop_branch(statements)
26
+ reachability = Reachability.solve(graph)
27
+ !reachability.reachable_ids.include?(graph.exit_id)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ require_relative "control_flow/graph"
6
+ require_relative "control_flow/builder"
7
+ require_relative "control_flow/dataflow"
8
+ require_relative "control_flow/liveness"
9
+ require_relative "control_flow/definite_assignment"
10
+ require_relative "control_flow/reachability"
11
+ require_relative "control_flow/termination"
12
+ require_relative "control_flow/nullability_flow"
13
+ require_relative "control_flow/constant_propagation"
14
+
15
+ module MilkTea
16
+ module ControlFlow
17
+ BindingResolution = Data.define(:identifier_binding_ids, :declaration_binding_ids, :mutating_argument_identifier_ids)
18
+ ReadSite = Data.define(:binding_key, :line, :column, :length)
19
+
20
+ Node = Struct.new(
21
+ :id,
22
+ :kind,
23
+ :statement,
24
+ :line,
25
+ :reads,
26
+ :reads_info,
27
+ :writes,
28
+ :writes_info,
29
+ :succs,
30
+ :preds,
31
+ keyword_init: true
32
+ )
33
+ end
34
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module CST
5
+ SourceFile = Data.define(:source, :tokens, :trivia) do
6
+ def reconstruct
7
+ reconstruct_from_tokens
8
+ end
9
+
10
+ def reconstruct_normalized
11
+ reconstruct_from_tokens(normalize_spaces: true)
12
+ end
13
+
14
+ def reconstruct_from_tokens(normalize_spaces: false)
15
+ return "" if tokens.empty?
16
+
17
+ tokens.each_with_object(+"") do |token, result|
18
+ append_trivia(result, token.leading_trivia, normalize_spaces:, leading: true)
19
+ result << segment_text(token.start_offset, token.end_offset, token.lexeme) unless token.eof?
20
+ append_trivia(result, token.trailing_trivia, normalize_spaces:, leading: false)
21
+ end
22
+ end
23
+
24
+ def append_trivia(result, entries, normalize_spaces:, leading:)
25
+ entries.each do |entry|
26
+ if normalize_spaces && entry.kind == :space && (!leading || entry.column > 1)
27
+ result << " "
28
+ else
29
+ result << segment_text(entry.start_offset, entry.end_offset, entry.text)
30
+ end
31
+ end
32
+ end
33
+
34
+ def segment_text(start_offset, end_offset, fallback)
35
+ return fallback unless source
36
+ return "" if end_offset <= start_offset
37
+
38
+ fallback_text = fallback.dup.force_encoding(source.encoding)
39
+ segment = source.byteslice(start_offset, end_offset - start_offset)
40
+ return fallback_text unless segment
41
+ segment = segment.dup.force_encoding(source.encoding)
42
+ return fallback_text if segment != fallback_text
43
+
44
+ segment
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class CSTBuilder
5
+ def self.build(source, path: nil)
6
+ new(source, path:).build
7
+ end
8
+
9
+ def initialize(source, path: nil)
10
+ @source = source
11
+ @path = path
12
+ end
13
+
14
+ def build
15
+ lexed = Lexer.lex_with_trivia(@source, path: @path)
16
+ CST::SourceFile.new(source: @source, tokens: lexed.tokens, trivia: lexed.trivia)
17
+ end
18
+ end
19
+ end