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,624 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module Parse
5
+ module Expressions
6
+ private
7
+
8
+ def parse_expression
9
+ return parse_if_expression if match(:if)
10
+ return parse_match_expression if match(:match)
11
+ return parse_unsafe_expression if match(:unsafe)
12
+
13
+ parse_range
14
+ end
15
+
16
+ def parse_range
17
+ expr = parse_or
18
+ return expr unless match(:dot_dot)
19
+
20
+ line = previous.line
21
+ column = expr.respond_to?(:column) ? expr.column : 0
22
+ end_expr = parse_or
23
+ AST::RangeExpr.new(start_expr: expr, end_expr:, line:, column:)
24
+ end
25
+
26
+ def parse_if_expression
27
+ condition = parse_or
28
+ consume(:colon, "expected ':' after condition in if expression")
29
+ then_expression = parse_expression
30
+ consume(:else, "expected 'else' in if expression")
31
+ consume(:colon, "expected ':' after 'else' in if expression")
32
+ else_expression = parse_expression
33
+ AST::IfExpr.new(condition:, then_expression:, else_expression:)
34
+ end
35
+
36
+ def parse_match_expression
37
+ token = previous
38
+ expression = parse_expression
39
+ arms = parse_match_expression_arms
40
+ AST::MatchExpr.new(expression:, arms:, line: token.line, column: token.column, length: token.lexeme.length)
41
+ end
42
+
43
+ def parse_match_expression_arms
44
+ consume(:colon, "expected ':' before match expression arms")
45
+ consume(:newline, "expected newline before match expression arms")
46
+ consume(:indent, "expected indented match expression arms")
47
+
48
+ arms = []
49
+ skip_newlines
50
+ until check(:dedent) || eof?
51
+ arms.concat(parse_match_expression_arm)
52
+ skip_newlines
53
+ end
54
+
55
+ consume(:dedent, "expected end of match expression arms")
56
+ arms
57
+ end
58
+
59
+ def parse_match_expression_arm
60
+ patterns = []
61
+ if match(:else)
62
+ patterns << AST::Identifier.new(name: "_", line: previous.line, column: previous.column)
63
+ else
64
+ patterns << parse_bitwise_xor
65
+ while match(:pipe)
66
+ patterns << parse_bitwise_xor
67
+ end
68
+ end
69
+ binding_token = nil
70
+ binding_name = if match(:as)
71
+ binding_token = consume_name("expected binding name after 'as'")
72
+ binding_token.lexeme
73
+ end
74
+ consume(:colon, "expected ':' after match expression arm pattern")
75
+ value = parse_expression
76
+ consume_end_of_statement unless block_expression?(value)
77
+ patterns.map do |pattern|
78
+ AST::MatchExprArm.new(
79
+ pattern:,
80
+ binding_name:,
81
+ binding_line: binding_token&.line,
82
+ binding_column: binding_token&.column,
83
+ value:,
84
+ )
85
+ end
86
+ end
87
+
88
+ def parse_unsafe_expression
89
+ token = previous
90
+ consume(:colon, "expected ':' after unsafe in expression")
91
+ expression = parse_expression
92
+ AST::UnsafeExpr.new(expression:, line: token.line, column: token.column, length: token.lexeme.length)
93
+ end
94
+
95
+ def parse_or
96
+ parse_left_associative(:parse_and, :or)
97
+ end
98
+
99
+ def parse_and
100
+ parse_left_associative(:parse_not, :and)
101
+ end
102
+
103
+ def parse_not
104
+ if match(:not)
105
+ operator = previous.lexeme
106
+ operand = parse_not
107
+ return AST::UnaryOp.new(operator:, operand:)
108
+ end
109
+
110
+ parse_is
111
+ end
112
+
113
+ def parse_is
114
+ expr = parse_bitwise_or
115
+ while match(:is)
116
+ line = previous.line
117
+ column = previous.column
118
+ arm_pattern = parse_bitwise_or
119
+ if arm_pattern.is_a?(AST::Call) && arm_pattern.arguments.any?
120
+ raise error(previous, "`is` does not support struct pattern bindings; use `match` to destructure payload fields")
121
+ end
122
+
123
+ expr = AST::MatchExpr.new(
124
+ expression: expr,
125
+ arms: [
126
+ AST::MatchExprArm.new(
127
+ pattern: arm_pattern,
128
+ binding_name: nil,
129
+ binding_line: nil,
130
+ binding_column: nil,
131
+ value: AST::BooleanLiteral.new(value: true),
132
+ ),
133
+ AST::MatchExprArm.new(
134
+ pattern: AST::Identifier.new(name: "_", line:, column:),
135
+ binding_name: nil,
136
+ binding_line: nil,
137
+ binding_column: nil,
138
+ value: AST::BooleanLiteral.new(value: false),
139
+ ),
140
+ ],
141
+ line:,
142
+ column:,
143
+ length: previous.lexeme.length,
144
+ )
145
+ end
146
+ expr
147
+ end
148
+
149
+ def parse_bitwise_or
150
+ parse_left_associative(:parse_bitwise_xor, :pipe)
151
+ end
152
+
153
+ def parse_bitwise_xor
154
+ parse_left_associative(:parse_bitwise_and, :caret)
155
+ end
156
+
157
+ def parse_bitwise_and
158
+ parse_left_associative(:parse_equality, :amp)
159
+ end
160
+
161
+ def parse_equality
162
+ parse_left_associative(:parse_comparison, :equal_equal, :bang_equal)
163
+ end
164
+
165
+ def parse_comparison
166
+ parse_left_associative(:parse_shift, :less, :less_equal, :greater, :greater_equal)
167
+ end
168
+
169
+ def parse_shift
170
+ parse_left_associative(:parse_additive, :shift_left, :shift_right)
171
+ end
172
+
173
+ def parse_additive
174
+ parse_left_associative(:parse_multiplicative, :plus, :minus)
175
+ end
176
+
177
+ def parse_multiplicative
178
+ parse_left_associative(:parse_unary, :star, :slash, :percent)
179
+ end
180
+
181
+ def parse_unary
182
+ if (cast_prefix = try_parse_prefix_cast_expression)
183
+ cast_prefix
184
+ elsif match(:unsafe)
185
+ parse_unsafe_expression
186
+ elsif match(:await)
187
+ AST::AwaitExpr.new(expression: parse_unary)
188
+ elsif match(:detach)
189
+ line = previous.line
190
+ column = previous.column
191
+ AST::DetachExpr.new(body: [AST::ExpressionStmt.new(expression: parse_unary)], line:, column:)
192
+ elsif match(:minus, :plus, :tilde, :out, :in, :inout)
193
+ operator = previous.lexeme
194
+ operand = parse_unary
195
+ AST::UnaryOp.new(operator:, operand:)
196
+ else
197
+ parse_postfix
198
+ end
199
+ end
200
+
201
+ def try_parse_prefix_cast_expression
202
+ saved_current = @current
203
+ return nil unless check_name && known_type_like_name?(peek.lexeme)
204
+
205
+ start_token = peek
206
+ expression = nil
207
+ target_type = parse_type_ref
208
+ type_tail = @tokens[@current - 1]
209
+ less_token = peek
210
+ return nil unless less_token.type == :less
211
+
212
+ minus_token = @tokens[@current + 1]
213
+ return nil unless minus_token&.type == :minus
214
+
215
+ unless adjacent_tokens?(type_tail, less_token) && adjacent_tokens?(less_token, minus_token)
216
+ raise error(less_token, "did you mean T<-expr?")
217
+ end
218
+
219
+ advance
220
+ advance
221
+
222
+ expression = parse_unary
223
+ AST::PrefixCast.new(target_type:, expression:, line: start_token.line, column: start_token.column)
224
+ rescue ParseError => e
225
+ raise e if parse_diagnostic_hint?(e)
226
+
227
+ nil
228
+ ensure
229
+ @current = saved_current if expression.nil?
230
+ end
231
+
232
+ def adjacent_tokens?(left, right)
233
+ left.line == right.line && right.column == (left.column + left.lexeme.length)
234
+ end
235
+
236
+ def parse_postfix
237
+ expression = parse_primary
238
+
239
+ loop do
240
+ if match(:dot)
241
+ member_token = consume_name_allowing_keywords("expected member name after '.'")
242
+ expression = AST::MemberAccess.new(
243
+ receiver: expression,
244
+ member: member_token.lexeme,
245
+ line: member_token.line,
246
+ column: member_token.column,
247
+ )
248
+ elsif check(:lbracket)
249
+ if (specialization = try_parse_specialization(expression))
250
+ expression = specialization
251
+ else
252
+ advance
253
+ index = parse_expression
254
+ consume(:rbracket, "expected ']' after index expression")
255
+ expression = AST::IndexAccess.new(receiver: expression, index:)
256
+ end
257
+ elsif match(:lparen)
258
+ expression = AST::Call.new(callee: expression, arguments: parse_call_arguments)
259
+ elsif match(:question)
260
+ expression = AST::UnaryOp.new(operator: "?", operand: expression)
261
+ else
262
+ break
263
+ end
264
+ end
265
+
266
+ expression
267
+ end
268
+
269
+ def try_parse_specialization(expression)
270
+ return nil unless postfix_bracket_starts_specialization?(expression)
271
+
272
+ saved_current = @current
273
+ advance
274
+ arguments = parse_comma_separated_until(:rbracket) do
275
+ AST::TypeArgument.new(value: parse_type_argument)
276
+ end
277
+ consume(:rbracket, "expected ']' after specialization arguments")
278
+
279
+ if match(:lparen)
280
+ call_arguments = parse_call_arguments
281
+ unless specialization_call_target?(expression, arguments, call_arguments)
282
+ @current = saved_current
283
+ return nil
284
+ end
285
+
286
+ return AST::Call.new(callee: AST::Specialization.new(callee: expression, arguments:), arguments: call_arguments)
287
+ end
288
+
289
+ unless specialization_value_target?(expression, arguments)
290
+ @current = saved_current
291
+ return nil
292
+ end
293
+
294
+ AST::Specialization.new(callee: expression, arguments:)
295
+ rescue ParseError => e
296
+ raise e if parse_diagnostic_hint?(e)
297
+
298
+ @current = saved_current
299
+ nil
300
+ end
301
+
302
+ def parse_call_arguments
303
+ arguments = []
304
+ unless check(:rparen)
305
+ loop do
306
+ arguments << parse_call_argument
307
+ break unless match(:comma)
308
+ break if check(:rparen)
309
+ end
310
+ end
311
+ consume(:rparen, "expected ')' after call arguments")
312
+ arguments
313
+ end
314
+
315
+ def parse_call_argument
316
+ if check_name && check_next(:equal)
317
+ name = advance.lexeme
318
+ consume(:equal, "expected '=' after named argument name")
319
+ AST::Argument.new(name:, value: parse_expression)
320
+ else
321
+ AST::Argument.new(name: nil, value: parse_expression)
322
+ end
323
+ end
324
+
325
+ def parse_primary
326
+ if match(:size_of)
327
+ parse_sizeof_expr
328
+ elsif match(:align_of)
329
+ parse_alignof_expr
330
+ elsif match(:offset_of)
331
+ parse_offsetof_expr
332
+ elsif match(:members_of) || match(:attributes_of) || match(:field_of) || match(:callable_of) || match(:attribute_of) || match(:has_attribute) || match(:attribute_arg) || match(:fields_of)
333
+ AST::Identifier.new(name: previous.lexeme, line: previous.line, column: previous.column)
334
+ elsif match(:proc)
335
+ parse_proc_expr
336
+ elsif match_name
337
+ AST::Identifier.new(name: previous.lexeme, line: previous.line, column: previous.column)
338
+ elsif match(:integer)
339
+ AST::IntegerLiteral.new(lexeme: previous.lexeme, value: previous.literal)
340
+ elsif match(:float)
341
+ AST::FloatLiteral.new(lexeme: previous.lexeme, value: previous.literal)
342
+ elsif match(:char_literal)
343
+ AST::CharLiteral.new(lexeme: previous.lexeme, value: previous.literal, line: previous.line, column: previous.column)
344
+ elsif match(:string)
345
+ parse_adjacent_string_literal(previous, cstring: false)
346
+ elsif match(:cstring)
347
+ parse_adjacent_string_literal(previous, cstring: true)
348
+ elsif match(:fstring)
349
+ parse_format_string_literal(previous.literal)
350
+ elsif match(:true)
351
+ AST::BooleanLiteral.new(value: true)
352
+ elsif match(:false)
353
+ AST::BooleanLiteral.new(value: false)
354
+ elsif match(:null)
355
+ line = previous.line
356
+ column = previous.column
357
+ type = nil
358
+ if match(:lbracket)
359
+ type = parse_type_ref
360
+ consume(:rbracket, "expected ']' after typed null literal")
361
+ end
362
+ AST::NullLiteral.new(type:, line:, column:)
363
+ elsif match(:lparen)
364
+ line = previous.line
365
+ column = previous.column
366
+ first = if check_name && check_next(:equal)
367
+ name_token = advance
368
+ consume(:equal, "expected '=' after named tuple field")
369
+ AST::Argument.new(name: name_token.lexeme, value: parse_expression)
370
+ else
371
+ parse_expression
372
+ end
373
+ if match(:comma)
374
+ elements = [first]
375
+ loop do
376
+ if check_name && check_next(:equal)
377
+ name_token = advance
378
+ consume(:equal, "expected '=' after named tuple field")
379
+ value = parse_expression
380
+ elements << AST::Argument.new(name: name_token.lexeme, value:)
381
+ else
382
+ elements << parse_expression
383
+ end
384
+ break unless match(:comma)
385
+ break if check(:rparen)
386
+ end
387
+ consume(:rparen, "expected ')' after tuple elements")
388
+ AST::ExpressionList.new(elements:, line:, column:)
389
+ else
390
+ consume(:rparen, "expected ')' after expression")
391
+ first
392
+ end
393
+ elsif keyword_token?(peek)
394
+ advance
395
+ AST::Identifier.new(name: previous.lexeme, line: previous.line, column: previous.column)
396
+ else
397
+ raise error(peek, "expected expression")
398
+ end
399
+ end
400
+
401
+ def parse_adjacent_string_literal(first_token, cstring:)
402
+ lexeme = +first_token.lexeme
403
+ value = +first_token.literal
404
+ all_cstring = cstring
405
+
406
+ while match(:string, :cstring)
407
+ token = previous
408
+ lexeme << token.lexeme
409
+ value << token.literal
410
+ all_cstring &&= token.type == :cstring
411
+ end
412
+
413
+ AST::StringLiteral.new(lexeme:, value:, cstring: all_cstring)
414
+ end
415
+
416
+ def parse_proc_expr
417
+ consume(:lparen, "expected '(' after proc")
418
+ params = parse_comma_separated_until(:rparen) { parse_function_type_param }
419
+
420
+ consume(:rparen, "expected ')' after proc parameters")
421
+ consume(:arrow, "expected '->' after proc parameters")
422
+ return_type = parse_type_ref
423
+ consume(:colon, "expected ':' before proc body")
424
+ body = if match(:newline)
425
+ consume(:indent, "expected indented block")
426
+ statements = parse_statement_block_body
427
+ consume(:dedent, "expected end of block")
428
+ statements
429
+ else
430
+ [AST::ReturnStmt.new(value: parse_expression)]
431
+ end
432
+ AST::ProcExpr.new(params:, return_type:, body:)
433
+ end
434
+
435
+ def parse_sizeof_expr
436
+ consume(:lparen, "expected '(' after size_of")
437
+ type = parse_type_ref
438
+ consume(:rparen, "expected ')' after size_of type")
439
+ AST::SizeofExpr.new(type:)
440
+ end
441
+
442
+ def parse_alignof_expr
443
+ consume(:lparen, "expected '(' after align_of")
444
+ type = parse_type_ref
445
+ consume(:rparen, "expected ')' after align_of type")
446
+ AST::AlignofExpr.new(type:)
447
+ end
448
+
449
+ def parse_offsetof_expr
450
+ consume(:lparen, "expected '(' after offset_of")
451
+ type = parse_type_ref
452
+ consume(:comma, "expected ',' after offset_of type")
453
+ field = consume_name("expected field name in offset_of").lexeme
454
+ consume(:rparen, "expected ')' after offset_of field")
455
+ AST::OffsetofExpr.new(type:, field:)
456
+ end
457
+
458
+ def parse_format_string_literal(parts)
459
+ AST::FormatString.new(parts: parts.map { |part| parse_format_string_part(part) })
460
+ end
461
+
462
+ def parse_format_string_part(part)
463
+ case part.fetch(:kind)
464
+ when :text
465
+ AST::FormatTextPart.new(value: part.fetch(:value))
466
+ when :expr
467
+ format_spec = parse_format_spec(part.fetch(:format_spec))
468
+ AST::FormatExprPart.new(
469
+ expression: parse_embedded_expression(part.fetch(:source), line: part.fetch(:line), column: part.fetch(:column)),
470
+ format_spec:,
471
+ )
472
+ else
473
+ raise error(peek, "unsupported format string part #{part.inspect}")
474
+ end
475
+ end
476
+
477
+ def parse_format_spec(spec_str)
478
+ return nil if spec_str.nil? || spec_str.empty?
479
+
480
+ trimmed = spec_str.strip
481
+ if (m = trimmed.match(/\A\.(\d+)\z/))
482
+ { kind: :precision, value: m[1].to_i }
483
+ elsif trimmed == "x"
484
+ { kind: :hex, uppercase: false }
485
+ elsif trimmed == "X"
486
+ { kind: :hex, uppercase: true }
487
+ elsif trimmed == "o"
488
+ { kind: :oct, uppercase: false }
489
+ elsif trimmed == "O"
490
+ { kind: :oct, uppercase: true }
491
+ elsif trimmed == "b"
492
+ { kind: :bin, uppercase: false }
493
+ elsif trimmed == "B"
494
+ { kind: :bin, uppercase: true }
495
+ else
496
+ raise error(peek, "unsupported format spec '#{trimmed}': expected .N for float precision (e.g. :.2), :x/:X, :o/:O, or :b/:B")
497
+ end
498
+ end
499
+
500
+ def parse_embedded_expression(source, line:, column:)
501
+ # Interpolation source may carry surrounding whitespace (e.g. `#{ x }`);
502
+ # strip it so leading whitespace is not re-lexed as indentation. Shift
503
+ # the column by the stripped leading width to keep error positions exact.
504
+ leading = source.length - source.lstrip.length
505
+ stripped = source.strip
506
+ tokens = Lexer.lex(stripped, path: @path).map do |token|
507
+ Token.new(
508
+ type: token.type,
509
+ lexeme: token.lexeme,
510
+ literal: token.literal,
511
+ line: token.line + line - 1,
512
+ column: token.column + column - 1 + leading,
513
+ start_offset: token.start_offset,
514
+ end_offset: token.end_offset,
515
+ leading_trivia: token.leading_trivia,
516
+ trailing_trivia: token.trailing_trivia,
517
+ )
518
+ end
519
+
520
+ parser = self.class.send(:new, tokens, path: @path)
521
+ parser.instance_variable_set(:@known_type_names, @known_type_names.dup)
522
+ parser.instance_variable_set(:@known_import_aliases, @known_import_aliases.dup)
523
+ parser.instance_variable_set(:@known_generic_callable_names, @known_generic_callable_names.dup)
524
+ parser.instance_variable_set(:@current_type_param_names, @current_type_param_names.dup)
525
+
526
+ expression = parser.send(:parse_expression)
527
+ parser.send(:skip_newlines)
528
+ raise parser.send(:error, parser.send(:peek), "expected end of interpolation") unless parser.send(:eof?)
529
+
530
+ expression
531
+ end
532
+
533
+ def parse_left_associative(operand_method, *operator_types)
534
+ expression = send(operand_method)
535
+ while match(*operator_types)
536
+ operator = previous.lexeme
537
+ right = send(operand_method)
538
+ expression = AST::BinaryOp.new(operator:, left: expression, right:)
539
+ end
540
+ expression
541
+ end
542
+
543
+ def postfix_bracket_starts_specialization?(expression)
544
+ specialization_target?(expression) && matching_rbracket_index(@current)
545
+ end
546
+
547
+ def specialization_target?(expression)
548
+ builtin_specialization_target?(expression) || aggregate_specialization_target?(expression)
549
+ end
550
+
551
+ def builtin_specialization_target?(expression)
552
+ expression.is_a?(AST::Identifier) && %w[array reinterpret span zero ptr const_ptr own ref adapt equal hash order].include?(expression.name)
553
+ end
554
+
555
+ def parse_diagnostic_hint?(error)
556
+ error.message.include?("did you mean T<-expr?")
557
+ end
558
+
559
+ def aggregate_specialization_target?(expression)
560
+ case expression
561
+ when AST::Identifier
562
+ true
563
+ when AST::MemberAccess
564
+ true
565
+ else
566
+ false
567
+ end
568
+ end
569
+
570
+ def specialization_call_target?(expression, arguments, call_arguments)
571
+ if expression.is_a?(AST::Identifier) && %w[default zero].include?(expression.name) &&
572
+ !known_type_like_name?(expression.name) && !generic_callable_specialization_target?(expression)
573
+ return false
574
+ end
575
+
576
+ return true if builtin_specialization_target?(expression)
577
+ return true if aggregate_specialization_target?(expression) && call_arguments.all?(&:name)
578
+ return true if generic_callable_specialization_target?(expression) && arguments.all? { |argument| explicit_specialization_argument?(argument.value) }
579
+ return true if imported_member_specialization_target?(expression) && arguments.all? { |argument| explicit_specialization_argument?(argument.value) }
580
+
581
+ aggregate_specialization_target?(expression) && arguments.all? { |argument| definite_type_argument?(argument.value) }
582
+ end
583
+
584
+ def specialization_value_target?(expression, arguments)
585
+ return true if expression.is_a?(AST::Identifier) && expression.name == "zero" && arguments.all? { |argument| explicit_specialization_argument?(argument.value) }
586
+ return true if aggregate_specialization_target?(expression) && arguments.all? { |argument| definite_type_argument?(argument.value) }
587
+ return true if generic_callable_specialization_target?(expression) && arguments.all? { |argument| explicit_specialization_argument?(argument.value) }
588
+ return true if imported_member_specialization_target?(expression) && arguments.all? { |argument| explicit_specialization_argument?(argument.value) }
589
+
590
+ false
591
+ end
592
+
593
+ def generic_callable_specialization_target?(expression)
594
+ expression.is_a?(AST::Identifier) && @known_generic_callable_names.key?(expression.name)
595
+ end
596
+
597
+ def imported_member_specialization_target?(expression)
598
+ expression.is_a?(AST::MemberAccess) && expression.receiver.is_a?(AST::Identifier) && @known_import_aliases.key?(expression.receiver.name)
599
+ end
600
+
601
+ def explicit_specialization_argument?(value)
602
+ definite_type_argument?(value) ||
603
+ potential_named_literal_type_argument?(value) ||
604
+ value.is_a?(AST::IntegerLiteral) ||
605
+ value.is_a?(AST::FloatLiteral)
606
+ end
607
+
608
+ def potential_named_literal_type_argument?(value)
609
+ value.is_a?(AST::TypeRef) && value.arguments.empty? && !value.nullable
610
+ end
611
+
612
+ def definite_type_argument?(value)
613
+ case value
614
+ when AST::FunctionType
615
+ true
616
+ when AST::TypeRef
617
+ known_type_like_name?(value.name.parts.first)
618
+ else
619
+ false
620
+ end
621
+ end
622
+ end
623
+ end
624
+ end