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,917 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module PrettyPrinter
5
+ class ASTFormatter < BaseFormatter
6
+ IF_EXPRESSION_PRECEDENCE = 5
7
+ POSTFIX_PRECEDENCE = 90
8
+ UNARY_PRECEDENCE = 80
9
+ IS_PRECEDENCE = 25
10
+
11
+ def format(node, trivia: [])
12
+ @comment_map = build_comment_map(trivia)
13
+ emit_source_file(node)
14
+ finish
15
+ end
16
+
17
+ private
18
+
19
+ def build_comment_map(trivia)
20
+ @blank_line_set = {}
21
+ map = {}
22
+ trivia.each do |t|
23
+ case t.kind
24
+ when :comment
25
+ (map[t.line] ||= []) << t.text.strip
26
+ when :blank_line
27
+ @blank_line_set[t.line] = true
28
+ end
29
+ end
30
+ map
31
+ end
32
+
33
+ def flush_leading_comments_before(stmt_line)
34
+ return unless stmt_line
35
+
36
+ @comment_map.keys.select { |line| line < stmt_line }.sort.each do |line_no|
37
+ @comment_map.delete(line_no)&.each { |text| line(text) }
38
+ end
39
+ end
40
+
41
+ def attach_inline_comment(line_no, header_idx)
42
+ return unless line_no && header_idx < @lines.length
43
+ return unless @comment_map.key?(line_no)
44
+
45
+ comments = @comment_map.delete(line_no)
46
+ return if comments.empty?
47
+
48
+ @lines[header_idx] = "#{@lines[header_idx]} #{comments.first}"
49
+ end
50
+
51
+ def emit_source_file(source_file)
52
+ @current_module_kind = source_file.module_kind
53
+ if source_file.module_kind == :raw_module
54
+ flush_leading_comments_before(source_file.line) if source_file.line
55
+ line("external")
56
+ attach_inline_comment(source_file.line, @lines.length - 1)
57
+ blank_line unless source_file.imports.empty? && source_file.directives.empty? && source_file.declarations.empty?
58
+ end
59
+
60
+ emit_module_body(source_file)
61
+ end
62
+
63
+ def emit_module_body(source_file)
64
+ wrote_section = false
65
+
66
+ unless source_file.imports.empty?
67
+ source_file.imports.each do |import|
68
+ flush_leading_comments_before(import.line)
69
+ line(render_import(import))
70
+ attach_inline_comment(import.line, @lines.length - 1)
71
+ end
72
+ wrote_section = true
73
+ end
74
+
75
+ unless source_file.directives.empty?
76
+ blank_line if wrote_section
77
+ source_file.directives.each { |directive| line(render_directive(directive)) }
78
+ wrote_section = true
79
+ end
80
+
81
+ if source_file.declarations.empty?
82
+ flush_remaining_comments
83
+ return
84
+ end
85
+
86
+ blank_line if wrote_section
87
+ source_file.declarations.each_with_index do |declaration, index|
88
+ emit_declaration(declaration)
89
+ next_decl = source_file.declarations[index + 1]
90
+ if next_decl && declaration_separator_required?(declaration, next_decl)
91
+ blank_line
92
+ end
93
+ end
94
+
95
+ flush_remaining_comments
96
+ end
97
+
98
+ def flush_remaining_comments
99
+ sorted = @comment_map.keys.sort
100
+ sorted.each do |l|
101
+ @comment_map.delete(l)&.each { |text| line(text) }
102
+ end
103
+ end
104
+
105
+ BLOCK_DECLARATION_TYPES = [
106
+ AST::FunctionDef, AST::ForeignFunctionDecl, AST::InterfaceDecl, AST::ExtendingBlock,
107
+ AST::StructDecl, AST::UnionDecl, AST::EnumDecl, AST::FlagsDecl, AST::VariantDecl,
108
+ ].freeze
109
+
110
+ def block_declaration?(declaration)
111
+ BLOCK_DECLARATION_TYPES.any? { |t| declaration.is_a?(t) }
112
+ end
113
+
114
+ def declaration_separator_required?(declaration, next_decl)
115
+ if @current_module_kind == :raw_module
116
+ return raw_module_separator_required?(declaration, next_decl)
117
+ end
118
+
119
+ block_declaration?(declaration) || block_declaration?(next_decl)
120
+ end
121
+
122
+ def raw_module_separator_required?(declaration, next_decl)
123
+ return true if raw_module_block_declaration?(declaration) || raw_module_block_declaration?(next_decl)
124
+
125
+ raw_module_declaration_group(declaration) != raw_module_declaration_group(next_decl)
126
+ end
127
+
128
+ def raw_module_block_declaration?(declaration)
129
+ declaration.is_a?(AST::StructDecl) ||
130
+ declaration.is_a?(AST::UnionDecl) ||
131
+ declaration.is_a?(AST::EnumDecl) ||
132
+ declaration.is_a?(AST::FlagsDecl)
133
+ end
134
+
135
+ def raw_module_declaration_group(declaration)
136
+ case declaration
137
+ when AST::OpaqueDecl, AST::TypeAliasDecl
138
+ :types
139
+ when AST::ConstDecl
140
+ :values
141
+ when AST::ExternFunctionDecl
142
+ :functions
143
+ else
144
+ declaration.class.name
145
+ end
146
+ end
147
+
148
+ def emit_declaration(declaration)
149
+ decl_line = declaration.respond_to?(:line) ? declaration.line : nil
150
+ flush_leading_comments_before(decl_line)
151
+ header_idx = @lines.length
152
+
153
+ case declaration
154
+ when AST::ConstDecl
155
+ emit_const(declaration)
156
+ when AST::VarDecl
157
+ header = "#{visibility_prefix(declaration)}var #{declaration.name}"
158
+ header += ": #{render_type(declaration.type)}" if declaration.type
159
+ if declaration.value
160
+ line("#{header} = #{render_expression(declaration.value)}")
161
+ else
162
+ line(header)
163
+ end
164
+ when AST::EventDecl
165
+ emit_attribute_applications(declaration.attributes)
166
+ line(render_event_declaration(declaration))
167
+ when AST::TypeAliasDecl
168
+ line("#{visibility_prefix(declaration)}type #{declaration.name} = #{render_type(declaration.target)}")
169
+ when AST::AttributeDecl
170
+ text = "#{visibility_prefix(declaration)}attribute[#{declaration.targets.join(', ')}] #{declaration.name}"
171
+ text += "(#{declaration.params.map { |param| render_param(param) }.join(', ')})" unless declaration.params.empty?
172
+ line(text)
173
+ when AST::StaticAssert
174
+ line("static_assert(#{render_expression(declaration.condition)}, #{render_expression(declaration.message)})")
175
+ when AST::StructDecl
176
+ emit_attribute_applications(declaration.attributes)
177
+ header_idx = @lines.length
178
+ header = +""
179
+ header << visibility_prefix(declaration)
180
+ header << "struct #{declaration.name}#{render_struct_params(declaration.lifetime_params, declaration.type_params)}"
181
+ header << render_implements_clause(declaration.implements)
182
+ header << " = c#{declaration.c_name.inspect}" if declaration.c_name
183
+ header << ":"
184
+ line(header)
185
+ with_indent do
186
+ declaration.fields.each do |field|
187
+ emit_attribute_applications(field.attributes)
188
+ line("#{field.name}: #{render_type(field.type)}")
189
+ end
190
+ declaration.nested_types.each do |nested|
191
+ emit_declaration(nested)
192
+ end
193
+ declaration.events.each do |event|
194
+ line(render_event_declaration(event))
195
+ end
196
+ end
197
+ when AST::UnionDecl
198
+ emit_attribute_applications(declaration.attributes)
199
+ header = "#{visibility_prefix(declaration)}union #{declaration.name}"
200
+ header += " = c#{declaration.c_name.inspect}" if declaration.c_name
201
+ line("#{header}:")
202
+ with_indent do
203
+ declaration.fields.each do |field|
204
+ line("#{field.name}: #{render_type(field.type)}")
205
+ end
206
+ end
207
+ when AST::EnumDecl
208
+ emit_attribute_applications(declaration.attributes)
209
+ emit_enum_like("enum", declaration.name, declaration.backing_type, declaration.members, declaration.visibility)
210
+ when AST::FlagsDecl
211
+ emit_attribute_applications(declaration.attributes)
212
+ emit_enum_like("flags", declaration.name, declaration.backing_type, declaration.members, declaration.visibility)
213
+ when AST::VariantDecl
214
+ emit_attribute_applications(declaration.attributes)
215
+ header = "#{visibility_prefix(declaration)}variant #{declaration.name}"
216
+ header += render_type_params(declaration.type_params) if declaration.type_params.any?
217
+ line("#{header}:")
218
+ with_indent do
219
+ declaration.arms.each do |arm|
220
+ arm_text = +"#{arm.name}"
221
+ if arm.fields.any?
222
+ field_strs = arm.fields.map { |f| "#{f.name}: #{render_type(f.type)}" }
223
+ arm_text += "(#{field_strs.join(', ')})"
224
+ end
225
+ line(arm_text)
226
+ end
227
+ end
228
+ when AST::OpaqueDecl
229
+ text = "#{visibility_prefix(declaration)}opaque #{declaration.name}"
230
+ text += render_implements_clause(declaration.implements)
231
+ text += " = c#{declaration.c_name.inspect}" if declaration.c_name
232
+ line(text)
233
+ when AST::InterfaceDecl
234
+ header = "#{visibility_prefix(declaration)}interface #{declaration.name}"
235
+ header += render_type_params(declaration.type_params) if declaration.type_params.any?
236
+ line("#{header}:")
237
+ with_indent do
238
+ declaration.methods.each do |method|
239
+ emit_attribute_applications(method.attributes)
240
+ line(render_interface_method_signature(method))
241
+ end
242
+ end
243
+ when AST::ExtendingBlock
244
+ line("extending #{render_type(declaration.type_name)}:")
245
+ with_indent do
246
+ declaration.methods.each_with_index do |method, index|
247
+ emit_function(method)
248
+ blank_line if index < (declaration.methods.length - 1)
249
+ end
250
+ end
251
+ when AST::FunctionDef
252
+ emit_function(declaration)
253
+ when AST::ExternFunctionDecl
254
+ emit_attribute_applications(declaration.attributes)
255
+ header_idx = @lines.length
256
+ line = "#{render_function_signature(declaration, prefix: 'external ')}"
257
+ line += " = #{render_expression(declaration.mapping)}" if declaration.mapping
258
+ line(line)
259
+ when AST::ForeignFunctionDecl
260
+ emit_attribute_applications(declaration.attributes)
261
+ header_idx = @lines.length
262
+ line("#{render_function_signature(declaration)} = #{render_expression(declaration.mapping)}")
263
+ when AST::WhenStmt
264
+ emit_when(declaration)
265
+ else
266
+ raise ArgumentError, "unsupported AST declaration #{declaration.class.name}"
267
+ end
268
+
269
+ attach_inline_comment(decl_line, header_idx)
270
+ end
271
+
272
+ def emit_enum_like(kind, name, backing_type, members, visibility)
273
+ header = "#{visibility_prefix(visibility)}#{kind} #{name}"
274
+ header += ": #{render_type(backing_type)}" if backing_type
275
+ line(header)
276
+ with_indent do
277
+ members.each do |member|
278
+ text = member.name
279
+ text += " = #{render_expression(member.value)}" if member.value
280
+ line(text)
281
+ end
282
+ end
283
+ end
284
+
285
+ def render_event_declaration(declaration)
286
+ text = +"#{visibility_prefix(declaration)}event #{declaration.name}[#{declaration.capacity}]"
287
+ text << "(#{render_type(declaration.payload_type)})" if declaration.payload_type
288
+ text
289
+ end
290
+
291
+ def emit_function(function)
292
+ emit_attribute_applications(function.attributes)
293
+ line("#{render_function_signature(function)}:")
294
+ with_indent do
295
+ function.body.each do |statement|
296
+ emit_statement(statement)
297
+ end
298
+ end
299
+ end
300
+
301
+ def render_function_signature(function, prefix: "")
302
+ signature_prefix = if function.is_a?(AST::MethodDef)
303
+ case function.kind
304
+ when :editable
305
+ "editable function "
306
+ when :static
307
+ "static function "
308
+ else
309
+ "function "
310
+ end
311
+ elsif function.is_a?(AST::ForeignFunctionDecl)
312
+ "foreign function "
313
+ else
314
+ "function "
315
+ end
316
+ async_prefix = function.respond_to?(:async) && function.async ? "async " : ""
317
+ text = +"#{prefix}#{visibility_prefix(function)}#{async_prefix}#{signature_prefix}#{function.name}#{render_type_params(function.type_params)}(#{render_signature_params(function)})"
318
+ text << " -> #{render_type(function.return_type)}" if function.return_type
319
+ text
320
+ end
321
+
322
+ def visibility_prefix(declaration_or_visibility)
323
+ return "" if @current_module_kind == :raw_module
324
+
325
+ visibility = if declaration_or_visibility.is_a?(Symbol)
326
+ declaration_or_visibility
327
+ elsif declaration_or_visibility.respond_to?(:visibility)
328
+ declaration_or_visibility.visibility
329
+ end
330
+
331
+ visibility == :public ? "public " : ""
332
+ end
333
+
334
+ def render_signature_params(function)
335
+ params = function.params.map { |param| render_param(param) }
336
+ params << "..." if function.respond_to?(:variadic) && function.variadic
337
+ params.join(', ')
338
+ end
339
+
340
+ def render_type_params(type_params)
341
+ return "" if type_params.empty?
342
+
343
+ rendered = type_params.map do |type_param|
344
+ if type_param.is_a?(AST::ValueTypeParam)
345
+ next "#{type_param.name}: #{render_type(type_param.type)}"
346
+ end
347
+
348
+ next type_param.name if type_param.constraints.empty?
349
+
350
+ "#{type_param.name} #{render_type_param_constraints(type_param.constraints)}"
351
+ end
352
+ "[#{rendered.join(', ')}]"
353
+ end
354
+
355
+ def render_struct_params(lifetime_params, type_params)
356
+ parts = []
357
+ parts.push(*lifetime_params) if lifetime_params&.any?
358
+ parts.concat(type_params.map(&:name))
359
+ return "" if parts.empty?
360
+
361
+ "[#{parts.join(', ')}]"
362
+ end
363
+
364
+ def render_type_param_constraints(constraints)
365
+ parts = []
366
+ index = 0
367
+ while index < constraints.length
368
+ constraint = constraints[index]
369
+ if constraint.kind == :interface
370
+ interfaces = [constraint.interface_ref.to_s]
371
+ index += 1
372
+ while index < constraints.length && constraints[index].kind == :interface
373
+ interfaces << constraints[index].interface_ref.to_s
374
+ index += 1
375
+ end
376
+ parts << "implements #{interfaces.join(' and ')}"
377
+ else
378
+ raise "unsupported type parameter constraint #{constraint.kind}"
379
+ end
380
+ end
381
+
382
+ parts.join(' and ')
383
+ end
384
+
385
+ def render_implements_clause(implements)
386
+ return "" if implements.empty?
387
+
388
+ " implements #{implements.map(&:to_s).join(', ')}"
389
+ end
390
+
391
+ def emit_attribute_applications(attributes)
392
+ attributes.each do |attribute|
393
+ line(render_attribute_application(attribute))
394
+ end
395
+ end
396
+
397
+ def render_attribute_application(attribute)
398
+ text = +"@[#{attribute.name}"
399
+ unless attribute.arguments.empty?
400
+ rendered_arguments = attribute.arguments.map do |argument|
401
+ if argument.name
402
+ "#{argument.name} = #{render_expression(argument.value)}"
403
+ else
404
+ render_expression(argument.value)
405
+ end
406
+ end
407
+ text << "(#{rendered_arguments.join(', ')})"
408
+ end
409
+ text << "]"
410
+ text
411
+ end
412
+
413
+ def render_interface_method_signature(method)
414
+ prefix = +""
415
+ prefix << "async " if method.async
416
+ prefix << case method.kind
417
+ when :editable
418
+ "editable function "
419
+ else
420
+ "function "
421
+ end
422
+
423
+ text = "#{prefix}#{method.name}(#{method.params.map { |param| render_param(param) }.join(', ')})"
424
+ text << " -> #{render_type(method.return_type)}" if method.return_type
425
+ text
426
+ end
427
+
428
+ def render_import(import)
429
+ text = "import #{import.path}"
430
+ text += " as #{import.alias_name}" if import.alias_name
431
+ text
432
+ end
433
+
434
+ def render_directive(directive)
435
+ case directive
436
+ when AST::LinkDirective
437
+ "link #{directive.value.inspect}"
438
+ when AST::IncludeDirective
439
+ "include #{directive.value.inspect}"
440
+ when AST::CompilerFlagDirective
441
+ "compiler_flag #{directive.value.inspect}"
442
+ else
443
+ raise ArgumentError, "unsupported AST directive #{directive.class.name}"
444
+ end
445
+ end
446
+
447
+ def emit_statement(statement)
448
+ stmt_line = statement.respond_to?(:line) ? statement.line : nil
449
+ flush_leading_comments_before(stmt_line)
450
+ header_idx = @lines.length
451
+
452
+ case statement
453
+ when AST::LocalDecl
454
+ if statement.destructure_bindings
455
+ text = "#{statement.kind} #{render_destructure_target(statement)}"
456
+ else
457
+ text = "#{statement.kind} #{statement.name}"
458
+ text += ": #{render_type(statement.type)}" if statement.type
459
+ end
460
+ text += " = #{render_expression(statement.value)}" if statement.value
461
+ if statement.else_body
462
+ else_header = statement.else_binding ? "else as #{statement.else_binding.name}:" : "else:"
463
+ line("#{text} #{else_header}")
464
+ with_indent do
465
+ statement.else_body.each { |nested| emit_statement(nested) }
466
+ end
467
+ else
468
+ line(text)
469
+ end
470
+ when AST::Assignment
471
+ line("#{render_expression(statement.target)} #{statement.operator} #{render_expression(statement.value)}")
472
+ when AST::IfStmt
473
+ emit_if(statement)
474
+ when AST::MatchStmt
475
+ prefix = statement.inline ? "inline match" : "match"
476
+ line("#{prefix} #{render_expression(statement.expression)}:")
477
+ with_indent do
478
+ statement.arms.each do |arm|
479
+ binding = arm.binding_name ? " as #{arm.binding_name}" : ""
480
+ line("#{render_expression(arm.pattern)}#{binding}:")
481
+ with_indent do
482
+ arm.body.each { |nested| emit_statement(nested) }
483
+ end
484
+ end
485
+ end
486
+ when AST::UnsafeStmt
487
+ if (inline = render_inline_unsafe(statement))
488
+ line(inline)
489
+ else
490
+ line("unsafe:")
491
+ with_indent do
492
+ statement.body.each { |nested| emit_statement(nested) }
493
+ end
494
+ end
495
+ when AST::StaticAssert
496
+ line("static_assert(#{render_expression(statement.condition)}, #{render_expression(statement.message)})")
497
+ when AST::EmitStmt
498
+ before = @lines.length
499
+ emit_declaration(statement.declaration)
500
+ @lines[before] = @lines[before].sub(/\A(\s*)/, "\\1emit ") if @lines[before]
501
+ when AST::ForStmt
502
+ prefix = +""
503
+ prefix << "parallel " if statement.threaded
504
+ prefix << "inline " if statement.inline
505
+ bindings = statement.bindings.map(&:name).join(", ")
506
+ iterables = statement.iterables.map { |iterable| render_expression(iterable) }.join(", ")
507
+ line("#{prefix}for #{bindings} in #{iterables}:")
508
+ with_indent do
509
+ statement.body.each { |nested| emit_statement(nested) }
510
+ end
511
+ when AST::WhileStmt
512
+ prefix = statement.inline ? "inline while" : "while"
513
+ line("#{prefix} #{render_expression(statement.condition)}:")
514
+ with_indent do
515
+ statement.body.each { |nested| emit_statement(nested) }
516
+ end
517
+ when AST::PassStmt
518
+ line("pass")
519
+ when AST::BreakStmt
520
+ line("break")
521
+ when AST::ContinueStmt
522
+ line("continue")
523
+ when AST::ReturnStmt
524
+ line(statement.value ? "return #{render_expression(statement.value)}" : "return")
525
+ when AST::DeferStmt
526
+ if statement.body
527
+ line("defer:")
528
+ with_indent do
529
+ statement.body.each { |nested| emit_statement(nested) }
530
+ end
531
+ else
532
+ line("defer #{render_expression(statement.expression)}")
533
+ end
534
+ when AST::ExpressionStmt
535
+ line(render_expression(statement.expression))
536
+ when AST::ParallelBlockStmt
537
+ line("parallel:")
538
+ with_indent do
539
+ statement.bodies.each do |body|
540
+ body.each { |nested| emit_statement(nested) }
541
+ end
542
+ end
543
+ when AST::GatherStmt
544
+ line("gather #{statement.handles.map { |handle| render_expression(handle) }.join(', ')}")
545
+ when AST::WhenStmt
546
+ emit_when(statement)
547
+ when AST::ConstDecl
548
+ emit_const(statement)
549
+ else
550
+ raise ArgumentError, "unsupported AST statement #{statement.class.name}"
551
+ end
552
+
553
+ attach_inline_comment(stmt_line, header_idx)
554
+ end
555
+
556
+ def emit_when(statement)
557
+ line("when #{render_expression(statement.discriminant)}:")
558
+ with_indent do
559
+ statement.branches.each do |branch|
560
+ binding = branch.binding_name ? " as #{branch.binding_name}" : ""
561
+ line("#{render_expression(branch.pattern)}#{binding}:")
562
+ with_indent do
563
+ branch.body.each { |nested| emit_block_item(nested) }
564
+ end
565
+ end
566
+ if statement.else_body
567
+ line("else:")
568
+ with_indent do
569
+ statement.else_body.each { |nested| emit_block_item(nested) }
570
+ end
571
+ end
572
+ end
573
+ end
574
+
575
+ # A `when` branch body is statements at function level but declarations at
576
+ # module level; dispatch on the node kind.
577
+ def emit_block_item(node)
578
+ if declaration_node?(node)
579
+ emit_declaration(node)
580
+ else
581
+ emit_statement(node)
582
+ end
583
+ end
584
+
585
+ def declaration_node?(node)
586
+ case node
587
+ when AST::FunctionDef, AST::StructDecl, AST::UnionDecl, AST::EnumDecl,
588
+ AST::FlagsDecl, AST::VariantDecl, AST::OpaqueDecl, AST::InterfaceDecl,
589
+ AST::ExtendingBlock, AST::TypeAliasDecl, AST::AttributeDecl, AST::EventDecl,
590
+ AST::ExternFunctionDecl, AST::ForeignFunctionDecl, AST::VarDecl, AST::MethodDef
591
+ true
592
+ else
593
+ false
594
+ end
595
+ end
596
+
597
+ def render_destructure_target(statement)
598
+ names = statement.destructure_bindings.join(", ")
599
+ type_name = statement.destructure_type_name
600
+ return "(#{names})" unless type_name
601
+
602
+ prefix = type_name.is_a?(Array) ? type_name.join(".") : type_name
603
+ "#{prefix}(#{names})"
604
+ end
605
+
606
+ def emit_const(declaration)
607
+ emit_attribute_applications(declaration.attributes)
608
+ header = "#{visibility_prefix(declaration)}const #{declaration.name}"
609
+ if declaration.block_body
610
+ line("#{header} -> #{render_type(declaration.type)}:")
611
+ with_indent do
612
+ declaration.block_body.each { |nested| emit_statement(nested) }
613
+ end
614
+ else
615
+ header += ": #{render_type(declaration.type)}" if declaration.type
616
+ line("#{header} = #{render_expression(declaration.value)}")
617
+ end
618
+ end
619
+
620
+ def render_inline_unsafe(statement)
621
+ return nil unless statement.body.length == 1
622
+
623
+ nested = statement.body.first
624
+ return nil if nested.is_a?(AST::LocalDecl)
625
+
626
+ inline = render_inline_statement(nested)
627
+ return nil unless inline
628
+ return nil if unsafe_inline_trivia_conflict?(statement, nested)
629
+
630
+ "unsafe: #{inline}"
631
+ end
632
+
633
+ def render_inline_statement(statement)
634
+ case statement
635
+ when AST::LocalDecl
636
+ return nil if statement.else_body
637
+
638
+ text = +"#{statement.kind} #{statement.name}"
639
+ text << ": #{render_type(statement.type)}" if statement.type
640
+ text << " = #{render_expression(statement.value)}" if statement.value
641
+ text
642
+ when AST::Assignment
643
+ "#{render_expression(statement.target)} #{statement.operator} #{render_expression(statement.value)}"
644
+ when AST::StaticAssert
645
+ "static_assert(#{render_expression(statement.condition)}, #{render_expression(statement.message)})"
646
+ when AST::PassStmt
647
+ "pass"
648
+ when AST::BreakStmt
649
+ "break"
650
+ when AST::ContinueStmt
651
+ "continue"
652
+ when AST::ReturnStmt
653
+ statement.value ? "return #{render_expression(statement.value)}" : "return"
654
+ when AST::DeferStmt
655
+ return nil if statement.body
656
+
657
+ "defer #{render_expression(statement.expression)}"
658
+ when AST::ExpressionStmt
659
+ render_expression(statement.expression)
660
+ else
661
+ nil
662
+ end
663
+ end
664
+
665
+ def unsafe_inline_trivia_conflict?(unsafe_stmt, nested)
666
+ unsafe_line = unsafe_stmt.respond_to?(:line) ? unsafe_stmt.line : nil
667
+ nested_line = nested.respond_to?(:line) ? nested.line : nil
668
+ return false unless unsafe_line && nested_line && nested_line > unsafe_line
669
+
670
+ @comment_map.keys.any? { |line| line >= unsafe_line + 1 && line <= nested_line } ||
671
+ @blank_line_set.keys.any? { |line| line >= unsafe_line + 1 && line < nested_line }
672
+ end
673
+
674
+ def emit_if(statement)
675
+ first_branch, *rest = statement.branches
676
+ prefix = statement.inline ? "inline if" : "if"
677
+ line("#{prefix} #{render_expression(first_branch.condition)}:")
678
+ with_indent do
679
+ first_branch.body.each { |nested| emit_statement(nested) }
680
+ end
681
+
682
+ rest.each do |branch|
683
+ line("else if #{render_expression(branch.condition)}:")
684
+ with_indent do
685
+ branch.body.each { |nested| emit_statement(nested) }
686
+ end
687
+ end
688
+
689
+ else_body = statement.else_body || []
690
+ return if else_body.empty?
691
+
692
+ line("else:")
693
+ with_indent do
694
+ else_body.each { |nested| emit_statement(nested) }
695
+ end
696
+ end
697
+
698
+ def render_param(param)
699
+ if param.is_a?(AST::ForeignParam)
700
+ text = +""
701
+ text << "#{param.mode} " unless param.mode == :plain
702
+ text << "#{param.name}: #{render_type(param.type)}"
703
+ text << " as #{render_type(param.boundary_type)}" if param.boundary_type
704
+ return text
705
+ end
706
+
707
+ return param.name unless param.type
708
+
709
+ "#{param.name}: #{render_type(param.type)}"
710
+ end
711
+
712
+ def render_type(type)
713
+ case type
714
+ when AST::TypeRef
715
+ text = type.name.to_s
716
+ args = []
717
+ args << type.lifetime if type.lifetime
718
+ args.concat(type.arguments.map { |argument| render_type_argument(argument.value) })
719
+ text += "[#{args.join(', ')}]" unless args.empty?
720
+ type.nullable ? "#{text}?" : text
721
+ when AST::FunctionType
722
+ "fn(#{type.params.map { |param| render_param(param) }.join(', ')}) -> #{render_type(type.return_type)}"
723
+ when AST::ProcType
724
+ "proc(#{type.params.map { |param| render_param(param) }.join(', ')}) -> #{render_type(type.return_type)}"
725
+ when AST::DynType
726
+ if type.interface.type_arguments.any?
727
+ "dyn[#{type.interface.parts.join('.')}[#{type.interface.type_arguments.map { |a| render_type(a) }.join(', ')}]]"
728
+ else
729
+ "dyn[#{type.interface.parts.join('.')}]"
730
+ end
731
+ when AST::TupleType
732
+ text = "(#{type.element_types.map { |element| render_type(element) }.join(', ')})"
733
+ type.nullable ? "#{text}?" : text
734
+ else
735
+ type.to_s
736
+ end
737
+ end
738
+
739
+ def render_type_argument(argument)
740
+ case argument
741
+ when AST::IntegerLiteral, AST::FloatLiteral
742
+ argument.lexeme
743
+ else
744
+ render_type(argument)
745
+ end
746
+ end
747
+
748
+ def render_expression(expression, parent_precedence = 0)
749
+ case expression
750
+ when AST::Identifier
751
+ expression.name
752
+ when AST::MemberAccess
753
+ wrap("#{render_postfix(expression.receiver)}.#{expression.member}", parent_precedence, POSTFIX_PRECEDENCE)
754
+ when AST::IndexAccess
755
+ wrap("#{render_postfix(expression.receiver)}[#{render_expression(expression.index)}]", parent_precedence, POSTFIX_PRECEDENCE)
756
+ when AST::Specialization
757
+ wrap("#{render_postfix(expression.callee)}[#{expression.arguments.map { |argument| render_type_argument(argument.value) }.join(', ')}]", parent_precedence, POSTFIX_PRECEDENCE)
758
+ when AST::Call
759
+ wrap("#{render_postfix(expression.callee)}(#{expression.arguments.map { |argument| render_argument(argument) }.join(', ')})", parent_precedence, POSTFIX_PRECEDENCE)
760
+ when AST::PrefixCast
761
+ wrap("#{render_type(expression.target_type)}<-#{render_expression(expression.expression, UNARY_PRECEDENCE)}", parent_precedence, UNARY_PRECEDENCE)
762
+ when AST::UnaryOp
763
+ if expression.operator == "?"
764
+ return wrap("#{render_postfix(expression.operand)}?", parent_precedence, POSTFIX_PRECEDENCE)
765
+ end
766
+
767
+ operand = render_expression(expression.operand, UNARY_PRECEDENCE)
768
+ text = %w[not out in inout].include?(expression.operator) ? "#{expression.operator} #{operand}" : "#{expression.operator}#{operand}"
769
+ wrap(text, parent_precedence, UNARY_PRECEDENCE)
770
+ when AST::BinaryOp
771
+ current_precedence = precedence(expression.operator)
772
+ left = render_expression(expression.left, current_precedence)
773
+ right = render_expression(expression.right, current_precedence + 1)
774
+ wrap("#{left} #{expression.operator} #{right}", parent_precedence, current_precedence)
775
+ when AST::RangeExpr
776
+ left = render_expression(expression.start_expr)
777
+ right = render_expression(expression.end_expr)
778
+ "#{left}..#{right}"
779
+ when AST::ExpressionList
780
+ "(#{expression.elements.map { |element| render_list_element(element) }.join(', ')})"
781
+ when AST::IfExpr
782
+ condition = render_expression(expression.condition, IF_EXPRESSION_PRECEDENCE)
783
+ then_expression = render_expression(expression.then_expression, IF_EXPRESSION_PRECEDENCE)
784
+ else_expression = render_expression(expression.else_expression, IF_EXPRESSION_PRECEDENCE)
785
+ wrap("if #{condition}: #{then_expression} else: #{else_expression}", parent_precedence, IF_EXPRESSION_PRECEDENCE)
786
+ when AST::MatchExpr
787
+ sugared = render_is_expression(expression, parent_precedence)
788
+ return sugared if sugared
789
+
790
+ rendered_expression = render_expression(expression.expression, IF_EXPRESSION_PRECEDENCE)
791
+ arm_indent = INDENT * (@indent + 1)
792
+ rendered_arms = expression.arms.map do |arm|
793
+ binding = arm.binding_name ? " as #{arm.binding_name}" : ""
794
+ "#{render_expression(arm.pattern)}#{binding}: #{render_expression(arm.value, IF_EXPRESSION_PRECEDENCE)}"
795
+ end.join("\n#{arm_indent}")
796
+ "match #{rendered_expression}:\n#{arm_indent}#{rendered_arms}"
797
+ when AST::UnsafeExpr
798
+ inner = render_expression(expression.expression, IF_EXPRESSION_PRECEDENCE)
799
+ wrap("unsafe: #{inner}", parent_precedence, IF_EXPRESSION_PRECEDENCE)
800
+ when AST::ProcExpr
801
+ params = expression.params.map { |param| render_param(param) }.join(', ')
802
+ if expression.body.length == 1 && expression.body.first.is_a?(AST::ReturnStmt) && expression.body.first.value
803
+ body_expression = render_expression(expression.body.first.value, IF_EXPRESSION_PRECEDENCE)
804
+ wrap("proc(#{params}) -> #{render_type(expression.return_type)}: #{body_expression}", parent_precedence, IF_EXPRESSION_PRECEDENCE)
805
+ else
806
+ body_lines = capture_lines do
807
+ with_indent do
808
+ expression.body.each { |statement| emit_statement(statement) }
809
+ end
810
+ end
811
+ "proc(#{params}) -> #{render_type(expression.return_type)}:\n#{body_lines.join("\n")}"
812
+ end
813
+ when AST::AwaitExpr
814
+ wrap("await #{render_expression(expression.expression, UNARY_PRECEDENCE)}", parent_precedence, UNARY_PRECEDENCE)
815
+ when AST::SizeofExpr
816
+ "size_of(#{render_type(expression.type)})"
817
+ when AST::AlignofExpr
818
+ "align_of(#{render_type(expression.type)})"
819
+ when AST::OffsetofExpr
820
+ "offset_of(#{render_type(expression.type)}, #{expression.field})"
821
+ when AST::IntegerLiteral, AST::FloatLiteral, AST::StringLiteral, AST::CharLiteral
822
+ expression.lexeme
823
+ when AST::FormatString
824
+ "f\"#{expression.parts.map { |part| render_format_string_part(part) }.join}\""
825
+ when AST::BooleanLiteral
826
+ expression.value ? "true" : "false"
827
+ when AST::NullLiteral
828
+ expression.type ? "null[#{render_type(expression.type)}]" : "null"
829
+ when AST::DetachExpr
830
+ wrap("detach #{render_expression(expression.body.first.expression, UNARY_PRECEDENCE)}", parent_precedence, UNARY_PRECEDENCE)
831
+ else
832
+ raise ArgumentError, "unsupported AST expression #{expression.class.name}"
833
+ end
834
+ end
835
+
836
+ def render_argument(argument)
837
+ return render_expression(argument.value) unless argument.name
838
+
839
+ "#{argument.name} = #{render_expression(argument.value)}"
840
+ end
841
+
842
+ def render_list_element(element)
843
+ return render_argument(element) if element.is_a?(AST::Argument)
844
+
845
+ render_expression(element)
846
+ end
847
+
848
+ def render_format_string_part(part)
849
+ case part
850
+ when AST::FormatTextPart
851
+ escape_format_text(part.value)
852
+ when AST::FormatExprPart
853
+ spec = case part.format_spec&.fetch(:kind)
854
+ when :precision then ":.#{part.format_spec[:value]}"
855
+ when :hex then part.format_spec[:uppercase] ? ":X" : ":x"
856
+ when :oct then part.format_spec[:uppercase] ? ":O" : ":o"
857
+ when :bin then part.format_spec[:uppercase] ? ":B" : ":b"
858
+ end
859
+ "\#{#{render_expression(part.expression)}#{spec}}"
860
+ else
861
+ raise ArgumentError, "unsupported format string part #{part.class.name}"
862
+ end
863
+ end
864
+
865
+ def escape_format_text(value)
866
+ value.gsub(/[\\"\n\r\t\0]/) do |char|
867
+ case char
868
+ when "\\" then "\\\\"
869
+ when '"' then '\\"'
870
+ when "\n" then "\\n"
871
+ when "\r" then "\\r"
872
+ when "\t" then "\\t"
873
+ when "\0" then "\\0"
874
+ end
875
+ end
876
+ end
877
+
878
+ # `expr is Arm` desugars at parse time to `match expr: Arm: true; _: false`;
879
+ # re-sugar that exact shape back to `is` so the formatter round-trips (and a
880
+ # multi-line match does not break when used as a statement condition).
881
+ def render_is_expression(expression, parent_precedence)
882
+ arms = expression.arms
883
+ return nil unless arms.length == 2
884
+
885
+ first, second = arms
886
+ return nil if first.binding_name || second.binding_name
887
+ return nil unless boolean_literal?(first.value, true) && boolean_literal?(second.value, false)
888
+ return nil unless wildcard_pattern?(second.pattern)
889
+ return nil if first.pattern.is_a?(AST::Call) && first.pattern.arguments.any?
890
+
891
+ scrutinee = render_expression(expression.expression, IS_PRECEDENCE)
892
+ arm = render_expression(first.pattern, IS_PRECEDENCE)
893
+ wrap("#{scrutinee} is #{arm}", parent_precedence, IS_PRECEDENCE)
894
+ end
895
+
896
+ def boolean_literal?(node, value)
897
+ node.is_a?(AST::BooleanLiteral) && node.value == value
898
+ end
899
+
900
+ def wildcard_pattern?(node)
901
+ node.is_a?(AST::Identifier) && node.name == "_"
902
+ end
903
+
904
+ def render_postfix(expression)
905
+ return render_expression(expression, POSTFIX_PRECEDENCE) if postfix_expression?(expression)
906
+
907
+ "(#{render_expression(expression)})"
908
+ end
909
+
910
+ def postfix_expression?(expression)
911
+ expression.is_a?(AST::Identifier) || expression.is_a?(AST::MemberAccess) || expression.is_a?(AST::IndexAccess) ||
912
+ expression.is_a?(AST::Specialization) || expression.is_a?(AST::Call) ||
913
+ (expression.is_a?(AST::UnaryOp) && expression.operator == "?")
914
+ end
915
+ end
916
+ end
917
+ end