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,756 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module Parse
5
+ module Statements
6
+ private
7
+
8
+ def check_inline_stmt_start?
9
+ return false unless check(:inline)
10
+
11
+ next_idx = @current + 1
12
+ return false if next_idx >= @tokens.length
13
+
14
+ next_token = @tokens[next_idx]
15
+ %i[for while match if].include?(next_token.type)
16
+ end
17
+
18
+ def check_parallel_for_start?
19
+ return false unless check(:parallel)
20
+
21
+ next_idx = @current + 1
22
+ return false if next_idx >= @tokens.length
23
+
24
+ @tokens[next_idx].type == :for
25
+ end
26
+
27
+ def check_parallel_block_start?
28
+ return false unless check(:parallel)
29
+
30
+ next_idx = @current + 1
31
+ return false if next_idx >= @tokens.length
32
+
33
+ @tokens[next_idx].type == :colon
34
+ end
35
+
36
+ def check_when_start?
37
+ check(:when)
38
+ end
39
+
40
+ def parse_statement
41
+ if match(:let)
42
+ parse_local_decl(:let)
43
+ elsif match(:var)
44
+ parse_local_decl(:var)
45
+ elsif match(:if)
46
+ parse_if_stmt
47
+ elsif match(:match)
48
+ parse_match_stmt
49
+ elsif match(:unsafe)
50
+ parse_unsafe_stmt
51
+ elsif match(:static_assert)
52
+ parse_static_assert
53
+ elsif match(:emit)
54
+ parse_emit_stmt
55
+ elsif match(:for)
56
+ parse_for_stmt
57
+ elsif check_parallel_for_start?
58
+ advance
59
+ parse_parallel_for_stmt
60
+ elsif check_parallel_block_start?
61
+ advance
62
+ parse_parallel_block_stmt
63
+ elsif match(:gather)
64
+ parse_gather_stmt
65
+ elsif match(:while)
66
+ parse_while_stmt
67
+ elsif match(:pass)
68
+ parse_pass_stmt
69
+ elsif match(:break)
70
+ parse_break_stmt
71
+ elsif match(:continue)
72
+ parse_continue_stmt
73
+ elsif match(:return)
74
+ parse_return_stmt
75
+ elsif match(:defer)
76
+ parse_defer_stmt
77
+ elsif check_inline_stmt_start?
78
+ advance
79
+ parse_inline_stmt
80
+ elsif check_when_start?
81
+ advance
82
+ parse_when_stmt
83
+ else
84
+ parse_assignment_or_expression_stmt
85
+ end
86
+ end
87
+
88
+ def parse_local_decl(kind)
89
+ line = previous.line
90
+ name_token = nil
91
+ name = nil
92
+ var_type = nil
93
+
94
+ destructure_bindings = nil
95
+ destructure_type_name = nil
96
+ if check(:lparen)
97
+ destructure_bindings = parse_destructure_pattern
98
+ elsif check_name && check_next(:lparen)
99
+ destructure_type_name = advance.lexeme
100
+ destructure_bindings = parse_destructure_pattern
101
+ elsif check_name && check_next(:dot)
102
+ parts = [advance.lexeme]
103
+ while check(:dot)
104
+ advance
105
+ parts << consume_name("expected type name after '.' in destructure pattern").lexeme
106
+ end
107
+ if match(:lparen)
108
+ destructure_type_name = parts.length == 1 ? parts[0].dup : parts
109
+ destructure_bindings = parse_destructure_pattern
110
+ else
111
+ raise error(@tokens[@current], "expected '(' after type name in destructure pattern")
112
+ end
113
+ else
114
+ name_token = consume_name("expected local variable name")
115
+ name = name_token.lexeme
116
+ end
117
+
118
+ var_type = match(:colon) ? parse_type_ref : nil
119
+ value = nil
120
+ else_binding = nil
121
+ else_body = nil
122
+ else_started = false
123
+
124
+ if destructure_bindings
125
+ consume(:equal, "expected '=' after destructure pattern")
126
+ value = parse_expression
127
+ consume_end_of_statement
128
+ elsif match(:equal)
129
+ value = parse_expression
130
+ if match(:else)
131
+ else_started = true
132
+
133
+ if match(:as)
134
+ binding_token = consume_name("expected error binding name after 'as'")
135
+ else_binding = AST::Identifier.new(name: binding_token.lexeme, line: binding_token.line, column: binding_token.column)
136
+ end
137
+
138
+ else_body = parse_block
139
+ else
140
+ consume_end_of_statement unless block_expression?(value)
141
+ end
142
+ else
143
+ raise error(name_token, "local declaration without initializer requires a type") unless var_type
144
+
145
+ consume_end_of_statement
146
+ end
147
+
148
+ AST::LocalDecl.new(kind:, name:, type: var_type, value:, else_binding:, else_body:, line:, column: name_token&.column || line, destructure_bindings:, destructure_type_name:)
149
+ rescue ParseError => e
150
+ raise unless @recovery_errors && name
151
+
152
+ @recovery_errors << e
153
+ synchronize_to_statement_boundary
154
+
155
+ if else_started
156
+ return AST::LocalDecl.new(
157
+ kind:,
158
+ name:,
159
+ type: var_type,
160
+ value:,
161
+ else_binding:,
162
+ else_body: nil,
163
+ line:,
164
+ column: name_token.column,
165
+ recovered_else: true,
166
+ )
167
+ end
168
+
169
+ AST::LocalDecl.new(
170
+ kind:,
171
+ name:,
172
+ type: var_type,
173
+ value: recovery_error_expr(e),
174
+ else_binding: nil,
175
+ else_body: nil,
176
+ line:,
177
+ column: name_token.column,
178
+ )
179
+ end
180
+
181
+ def parse_destructure_pattern
182
+ match(:lparen)
183
+ bindings = []
184
+ loop do
185
+ token = consume_name("expected binding name in destructure pattern")
186
+ bindings << token.lexeme
187
+ break unless match(:comma)
188
+ end
189
+ consume(:rparen, "expected ')' after destructure pattern")
190
+ bindings
191
+ end
192
+
193
+ def parse_if_stmt
194
+ line = previous.line
195
+ branches = [parse_if_branch(previous)]
196
+
197
+ while check(:else) && check_next(:if)
198
+ advance
199
+ advance
200
+ branches << parse_if_branch(previous)
201
+ end
202
+
203
+ else_line = nil
204
+ else_column = nil
205
+ else_body = if match(:else)
206
+ else_line = previous.line
207
+ else_column = previous.column
208
+ parse_else_branch_body
209
+ end
210
+ AST::IfStmt.new(branches:, else_body:, line:, else_line:, else_column:)
211
+ end
212
+
213
+ def parse_if_branch(token)
214
+ condition = nil
215
+ condition = parse_expression
216
+ body = if inline_block_body?
217
+ consume(:colon, "expected ':' after if condition")
218
+ @in_inline_block_body = true
219
+ result = [parse_statement]
220
+ @in_inline_block_body = false
221
+ result
222
+ else
223
+ parse_block
224
+ end
225
+ AST::IfBranch.new(
226
+ condition:,
227
+ body:,
228
+ line: token.line,
229
+ column: token.column,
230
+ length: token.lexeme.length,
231
+ )
232
+ rescue ParseError => e
233
+ raise unless @recovery_errors
234
+
235
+ @recovery_errors << e
236
+ recovered_body = synchronize_to_statement_boundary
237
+ raise unless recovered_body
238
+
239
+ AST::IfBranch.new(
240
+ condition: condition || recovery_error_expr(e),
241
+ body: recovered_body,
242
+ line: token.line,
243
+ column: token.column,
244
+ length: token.lexeme.length,
245
+ )
246
+ end
247
+
248
+ def parse_else_branch_body
249
+ if inline_block_body?
250
+ consume(:colon, "expected ':' before else body")
251
+ @in_inline_block_body = true
252
+ result = [parse_statement]
253
+ @in_inline_block_body = false
254
+ result
255
+ else
256
+ parse_block
257
+ end
258
+ rescue ParseError => e
259
+ raise unless @recovery_errors
260
+
261
+ @recovery_errors << e
262
+ recovered_body = synchronize_to_statement_boundary
263
+ raise unless recovered_body
264
+
265
+ recovered_body
266
+ end
267
+
268
+ def inline_block_body?
269
+ check(:colon) && @current + 1 < @tokens.length && @tokens[@current + 1].type != :newline
270
+ end
271
+
272
+ def parse_match_stmt
273
+ token = previous
274
+ line = token.line
275
+ expression = nil
276
+ arms = []
277
+ expression = parse_expression
278
+ arms = parse_match_arms(arms)
279
+ if arms.first&.is_a?(AST::MatchExprArm)
280
+ expr = AST::MatchExpr.new(expression:, arms:, line:, column: token.column, length: token.lexeme.length)
281
+ AST::ExpressionStmt.new(expression: expr, line:)
282
+ else
283
+ AST::MatchStmt.new(expression:, arms:, line:, column: token.column, length: token.lexeme.length)
284
+ end
285
+ rescue ParseError => e
286
+ raise unless @recovery_errors
287
+
288
+ @recovery_errors << e
289
+ recovered_arms = synchronize_to_match_arm_boundary
290
+ target_line = line
291
+ if recovered_arms
292
+ stmt =
293
+ if recovered_arms.first&.is_a?(AST::MatchExprArm)
294
+ expr = AST::MatchExpr.new(expression: expression || recovery_error_expr(e), arms: arms + recovered_arms, line: target_line, column: token.column, length: token.lexeme.length)
295
+ AST::ExpressionStmt.new(expression: expr, line: target_line)
296
+ else
297
+ AST::MatchStmt.new(expression: expression || recovery_error_expr(e), arms: arms + recovered_arms, line: target_line, column: token.column, length: token.lexeme.length)
298
+ end
299
+ return stmt
300
+ end
301
+
302
+ recovery_error_stmt(e)
303
+ end
304
+
305
+ def parse_match_arms(arms = [])
306
+ consume(:colon, "expected ':' before block")
307
+ consume(:newline, "expected newline before block")
308
+ consume(:indent, "expected indented block")
309
+
310
+ parse_match_arm_body(arms)
311
+
312
+ consume(:dedent, "expected end of block")
313
+ arms
314
+ end
315
+
316
+ def parse_match_arm_body(arms = [])
317
+ skip_newlines
318
+ until check(:dedent) || eof?
319
+ arms.concat(parse_match_arm)
320
+ skip_newlines
321
+ end
322
+
323
+ arms
324
+ end
325
+
326
+ def parse_match_arm
327
+ patterns = []
328
+ binding_token = nil
329
+ binding_name = nil
330
+
331
+ if match(:else)
332
+ patterns << AST::Identifier.new(name: "_", line: previous.line, column: previous.column)
333
+ else
334
+ patterns << parse_bitwise_xor
335
+ while match(:pipe)
336
+ patterns << parse_bitwise_xor
337
+ end
338
+ end
339
+ binding_name = if match(:as)
340
+ binding_token = consume_name("expected binding name after 'as'")
341
+ binding_token.lexeme
342
+ end
343
+
344
+ if match_arm_expr_form?
345
+ consume(:colon, "expected ':' after match expression arm pattern")
346
+ value = parse_expression
347
+ consume_end_of_statement unless block_expression?(value)
348
+ patterns.map do |pattern|
349
+ AST::MatchExprArm.new(
350
+ pattern:,
351
+ binding_name:,
352
+ binding_line: binding_token&.line,
353
+ binding_column: binding_token&.column,
354
+ value:,
355
+ )
356
+ end
357
+ else
358
+ body = parse_block
359
+ patterns.map do |pattern|
360
+ AST::MatchArm.new(
361
+ pattern:,
362
+ binding_name:,
363
+ binding_line: binding_token&.line,
364
+ binding_column: binding_token&.column,
365
+ body:,
366
+ )
367
+ end
368
+ end
369
+ rescue ParseError => e
370
+ raise unless @recovery_errors
371
+
372
+ @recovery_errors << e
373
+ recovered_body = synchronize_to_statement_boundary
374
+ return [AST::MatchArm.new(
375
+ pattern: patterns.first || recovery_error_expr(e),
376
+ binding_name:,
377
+ binding_line: binding_token&.line,
378
+ binding_column: binding_token&.column,
379
+ body: recovered_body,
380
+ )] if recovered_body
381
+
382
+ raise
383
+ end
384
+
385
+ def match_arm_expr_form?
386
+ check(:colon) && @current + 1 < @tokens.length && @tokens[@current + 1].type != :newline
387
+ end
388
+
389
+ def parse_unsafe_stmt
390
+ token = previous
391
+ consume(:colon, "expected ':' after unsafe")
392
+ body = if match(:newline)
393
+ consume(:indent, "expected indented block")
394
+
395
+ statements = parse_statement_block_body
396
+ consume(:dedent, "expected end of block")
397
+ statements
398
+ else
399
+ statement = parse_statement
400
+ raise ParseError.new("inline unsafe local declarations must use expression form", token:, path: @path) if statement.is_a?(AST::LocalDecl)
401
+
402
+ [statement]
403
+ end
404
+ AST::UnsafeStmt.new(body:, line: token.line, column: token.column, length: token.lexeme.length)
405
+ end
406
+
407
+ def parse_static_assert
408
+ line = previous.line
409
+ consume(:lparen, "expected '(' after static_assert")
410
+ condition = parse_expression
411
+ consume(:comma, "expected ',' after static_assert condition")
412
+ message = parse_expression
413
+ consume(:rparen, "expected ')' after static_assert message")
414
+ consume_end_of_statement
415
+ AST::StaticAssert.new(condition:, message:, line:)
416
+ end
417
+
418
+ def parse_emit_stmt
419
+ line = previous.line
420
+ column = previous.column
421
+ decl = parse_declaration
422
+ AST::EmitStmt.new(declaration: decl, line:, column:)
423
+ rescue ParseError => e
424
+ raise unless @recovery_errors
425
+
426
+ @recovery_errors << e
427
+ synchronize_to_statement_boundary
428
+ AST::EmitStmt.new(declaration: AST::ErrorExpr.new(message: e.message), line:, column:)
429
+ end
430
+
431
+ def parse_for_stmt
432
+ line = previous.line
433
+ bindings = []
434
+ iterables = nil
435
+ loop do
436
+ name_token = consume_name("expected loop variable name")
437
+ bindings << AST::ForBinding.new(name: name_token.lexeme, line: name_token.line, column: name_token.column)
438
+ break unless match(:comma)
439
+ end
440
+ consume(:in, "expected 'in' in for loop")
441
+ iterables = [parse_expression]
442
+ iterables << parse_expression while match(:comma)
443
+ body = parse_block
444
+ AST::ForStmt.new(bindings:, iterables:, body:, line:, column: bindings.first.column)
445
+ rescue ParseError => e
446
+ raise unless @recovery_errors
447
+
448
+ @recovery_errors << e
449
+ recovered_body = synchronize_to_statement_boundary
450
+ return recovery_error_block_stmt(
451
+ e,
452
+ recovered_body,
453
+ header_type: :for,
454
+ header_bindings: bindings.empty? ? nil : bindings,
455
+ header_iterables: iterables&.any? ? iterables : nil,
456
+ ) if recovered_body
457
+
458
+ recovery_error_stmt(e)
459
+ end
460
+
461
+ def parse_parallel_for_stmt
462
+ consume(:for, "expected 'for' after 'parallel'")
463
+ line = previous.line
464
+ name_token = consume_name("expected loop variable name")
465
+ bindings = [AST::ForBinding.new(name: name_token.lexeme, line: name_token.line, column: name_token.column)]
466
+ consume(:in, "expected 'in' in parallel for loop")
467
+ iterables = [parse_expression]
468
+ body = parse_block
469
+ AST::ForStmt.new(bindings:, iterables:, body:, threaded: true, line:, column: bindings.first.column)
470
+ rescue ParseError => e
471
+ raise unless @recovery_errors
472
+
473
+ @recovery_errors << e
474
+ recovered_body = synchronize_to_statement_boundary
475
+ return recovery_error_block_stmt(e, recovered_body, header_type: :for) if recovered_body
476
+
477
+ recovery_error_stmt(e)
478
+ end
479
+
480
+ def parse_parallel_block_stmt
481
+ line = previous.line
482
+ column = previous.column
483
+ consume(:colon, "expected ':' after 'parallel'")
484
+ consume(:newline, "expected newline after 'parallel:'")
485
+ consume(:indent, "expected indented block after 'parallel:'")
486
+ statements = parse_statement_block_body
487
+ consume(:dedent, "expected end of parallel block")
488
+ bodies = statements.map { |stmt| [stmt] }
489
+ AST::ParallelBlockStmt.new(bodies:, line:, column:)
490
+ rescue ParseError => e
491
+ raise unless @recovery_errors
492
+
493
+ @recovery_errors << e
494
+ recovered_body = synchronize_to_statement_boundary
495
+ return recovery_error_block_stmt(e, recovered_body, header_type: :parallel) if recovered_body
496
+
497
+ recovery_error_stmt(e)
498
+ end
499
+
500
+ def parse_gather_stmt
501
+ line = previous.line
502
+ column = previous.column
503
+ handles = []
504
+ handle = parse_expression
505
+ handles << handle
506
+ while match(:comma)
507
+ handle = parse_expression
508
+ handles << handle
509
+ end
510
+ AST::GatherStmt.new(handles:, line:, column:)
511
+ end
512
+
513
+ def parse_while_stmt
514
+ token = previous
515
+ line = token.line
516
+ condition = parse_expression
517
+ body = parse_block
518
+ AST::WhileStmt.new(condition:, body:, line:, column: token.column, length: token.lexeme.length)
519
+ rescue ParseError => e
520
+ raise unless @recovery_errors
521
+
522
+ @recovery_errors << e
523
+ recovered_body = synchronize_to_statement_boundary
524
+ return AST::WhileStmt.new(
525
+ condition: condition || recovery_error_expr(e),
526
+ body: recovered_body,
527
+ line:,
528
+ column: token.column,
529
+ length: token.lexeme.length,
530
+ ) if recovered_body
531
+
532
+ recovery_error_stmt(e)
533
+ end
534
+
535
+ def parse_pass_stmt
536
+ token = previous
537
+ line = token.line
538
+ consume_end_of_statement
539
+ AST::PassStmt.new(line:, column: token.column, length: token.lexeme.length)
540
+ end
541
+
542
+ def parse_break_stmt
543
+ token = previous
544
+ line = token.line
545
+ consume_end_of_statement
546
+ AST::BreakStmt.new(line:, column: token.column, length: token.lexeme.length)
547
+ end
548
+
549
+ def parse_continue_stmt
550
+ token = previous
551
+ line = token.line
552
+ consume_end_of_statement
553
+ AST::ContinueStmt.new(line:, column: token.column, length: token.lexeme.length)
554
+ end
555
+
556
+ def parse_return_stmt
557
+ token = previous
558
+ line = token.line
559
+ value = check(:newline) ? nil : parse_expression
560
+ consume_end_of_statement unless block_expression?(value)
561
+ AST::ReturnStmt.new(value:, line:, column: token.column, length: token.lexeme.length)
562
+ end
563
+
564
+ def parse_defer_stmt
565
+ token = previous
566
+ line = token.line
567
+ if check(:colon)
568
+ body = parse_block
569
+ AST::DeferStmt.new(expression: nil, body:, line:, column: token.column, length: token.lexeme.length)
570
+ else
571
+ expression = parse_expression
572
+ consume_end_of_statement unless block_expression?(expression)
573
+ AST::DeferStmt.new(expression:, body: nil, line:, column: token.column, length: token.lexeme.length)
574
+ end
575
+ end
576
+
577
+ def parse_when_decl
578
+ token = previous
579
+ line = token.line
580
+ discriminant = parse_expression
581
+ branches = parse_decl_when_arms
582
+ else_body = if check(:else)
583
+ if check_next(:newline) || check_next(:indent)
584
+ parse_declaration_block
585
+ end
586
+ end
587
+ AST::WhenStmt.new(discriminant:, branches:, else_body:, line:, column: token.column, length: token.lexeme.length)
588
+ rescue ParseError => e
589
+ raise unless @recovery_errors
590
+
591
+ @recovery_errors << e
592
+ recovered_body = synchronize_to_statement_boundary
593
+ return recovery_error_block_stmt(e, recovered_body, header_type: :when) if recovered_body
594
+
595
+ recovery_error_stmt(e)
596
+ end
597
+
598
+ def parse_decl_when_arms
599
+ consume(:colon, "expected ':' before block")
600
+ consume(:newline, "expected newline before block")
601
+ consume(:indent, "expected indented block")
602
+
603
+ arms = []
604
+ skip_newlines
605
+ until check(:dedent) || eof?
606
+ pattern = parse_expression
607
+ binding_name = if match(:as)
608
+ binding_token = consume_name("expected binding name after 'as'")
609
+ binding_token.lexeme
610
+ end
611
+ body = parse_declaration_block
612
+ binding_token ||= previous
613
+ arms << AST::WhenBranch.new(
614
+ pattern:,
615
+ binding_name:,
616
+ binding_line: binding_token.line,
617
+ binding_column: binding_token.column,
618
+ body:,
619
+ )
620
+ skip_newlines
621
+ end
622
+
623
+ consume(:dedent, "expected end of block")
624
+ arms
625
+ end
626
+
627
+ def parse_when_stmt
628
+ token = previous
629
+ line = token.line
630
+ discriminant = parse_expression
631
+ branches = parse_match_arms([])
632
+ else_body = if check(:else)
633
+ if check_next(:newline) || check_next(:indent)
634
+ parse_else_branch_body
635
+ end
636
+ end
637
+ AST::WhenStmt.new(discriminant:, branches:, else_body:, line:, column: token.column, length: token.lexeme.length)
638
+ rescue ParseError => e
639
+ raise unless @recovery_errors
640
+
641
+ @recovery_errors << e
642
+ recovered_body = synchronize_to_statement_boundary
643
+ return recovery_error_block_stmt(e, recovered_body, header_type: :when) if recovered_body
644
+
645
+ recovery_error_stmt(e)
646
+ end
647
+
648
+ def parse_inline_stmt
649
+ token = previous
650
+ if match(:for)
651
+ parse_inline_for_stmt(token)
652
+ elsif match(:while)
653
+ parse_inline_while_stmt(token)
654
+ elsif match(:match)
655
+ parse_inline_match_stmt(token)
656
+ elsif match(:if)
657
+ parse_inline_if_stmt(token)
658
+ else
659
+ raise error(peek, "expected for, while, match, or if after inline")
660
+ end
661
+ end
662
+
663
+ def parse_inline_for_stmt(_inline_token)
664
+ line = previous.line
665
+ bindings = []
666
+ iterables = nil
667
+ loop do
668
+ name_token = consume_name("expected loop variable name")
669
+ bindings << AST::ForBinding.new(name: name_token.lexeme, line: name_token.line, column: name_token.column)
670
+ break unless match(:comma)
671
+ end
672
+ consume(:in, "expected 'in' in for loop")
673
+ iterables = [parse_expression]
674
+ iterables << parse_expression while match(:comma)
675
+ body = parse_block
676
+ AST::ForStmt.new(bindings:, iterables:, body:, inline: true, line:, column: bindings.first.column)
677
+ rescue ParseError => e
678
+ raise unless @recovery_errors
679
+
680
+ @recovery_errors << e
681
+ recovered_body = synchronize_to_statement_boundary
682
+ return recovery_error_block_stmt(e, recovered_body, header_type: :for) if recovered_body
683
+
684
+ recovery_error_stmt(e)
685
+ end
686
+
687
+ def parse_inline_while_stmt(inline_token)
688
+ line = inline_token.line
689
+ condition = parse_expression
690
+ body = parse_block
691
+ AST::WhileStmt.new(condition:, body:, inline: true, line:, column: inline_token.column, length: inline_token.lexeme.length)
692
+ rescue ParseError => e
693
+ raise unless @recovery_errors
694
+
695
+ @recovery_errors << e
696
+ recovered_body = synchronize_to_statement_boundary
697
+ return AST::WhileStmt.new(
698
+ condition: condition || recovery_error_expr(e),
699
+ body: recovered_body,
700
+ line:,
701
+ column: inline_token.column,
702
+ length: inline_token.lexeme.length,
703
+ ) if recovered_body
704
+
705
+ recovery_error_stmt(e)
706
+ end
707
+
708
+ def parse_inline_match_stmt(inline_token)
709
+ token = previous
710
+ line = token.line
711
+ arms = []
712
+ expression = parse_expression
713
+ arms = parse_match_arms(arms)
714
+ AST::MatchStmt.new(expression:, arms:, inline: true, line:, column: token.column, length: token.lexeme.length)
715
+ rescue ParseError => e
716
+ raise unless @recovery_errors
717
+
718
+ @recovery_errors << e
719
+ recovered_arms = synchronize_to_match_arm_boundary
720
+ return AST::MatchStmt.new(expression: expression || recovery_error_expr(e), arms: arms + recovered_arms, inline: true, line:, column: token.column, length: token.lexeme.length) if recovered_arms
721
+
722
+ recovery_error_stmt(e)
723
+ end
724
+
725
+ def parse_inline_if_stmt(inline_token)
726
+ token = previous
727
+ line = token.line
728
+ branches = [parse_if_branch(token)]
729
+
730
+ while check(:else) && check_next(:if)
731
+ advance
732
+ advance
733
+ branches << parse_if_branch(previous)
734
+ end
735
+
736
+ else_body = match(:else) ? parse_else_branch_body : nil
737
+ AST::IfStmt.new(branches:, else_body:, inline: true, line:)
738
+ end
739
+
740
+ def parse_assignment_or_expression_stmt
741
+ line = peek.line
742
+ expression = parse_expression
743
+ if match(*Token::ASSIGNMENT_TYPES)
744
+ operator = previous.lexeme
745
+ column = previous.column
746
+ value = parse_expression
747
+ consume_end_of_statement unless block_expression?(value)
748
+ AST::Assignment.new(target: expression, operator:, value:, line:, column:)
749
+ else
750
+ consume_end_of_statement unless block_expression?(expression)
751
+ AST::ExpressionStmt.new(expression:, line:)
752
+ end
753
+ end
754
+ end
755
+ end
756
+ end