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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class CSTFormatter
5
+ def self.format(cst)
6
+ cst.reconstruct
7
+ end
8
+
9
+ def self.format_normalized(cst)
10
+ cst.reconstruct_normalized
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,456 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module DebugInfoFormatter
5
+ # AST node types rendered as compact single-line (no subtree expansion).
6
+ COMPACT_AST_TYPES = %w[
7
+ QualifiedName TypeRef TypeArgument Identifier
8
+ IntegerLiteral FloatLiteral StringLiteral BooleanLiteral NullLiteral
9
+ FormatString FormatTextPart FormatExprPart
10
+ ].to_set.freeze
11
+
12
+ module_function
13
+
14
+ def format_all(content:, tokens:, ast:, parse_errors:, facts:, snapshot:, path:, semantic_entries: nil)
15
+ sections = []
16
+ sections << format_header(content, path)
17
+ sections << format_tokens(tokens, semantic_entries:)
18
+ sections << format_ast(ast)
19
+ sections << format_parse_errors(parse_errors)
20
+ sections << format_facts(facts)
21
+ sections << format_bindings(facts)
22
+ sections << format_diagnostics(snapshot)
23
+ sections.join("\n\n")
24
+ end
25
+
26
+ # ── Header ────────────────────────────────────────────────────────────────────
27
+
28
+ def format_header(content, path)
29
+ lines = content.count("\n") + 1
30
+ bytes = content.bytesize
31
+ ["File: #{path}", "Content: #{lines} lines, #{bytes} bytes"].join("\n")
32
+ end
33
+
34
+ # ── Tokens ─────────────────────────────────────────────────────────────────────
35
+
36
+ def format_tokens(tokens, semantic_entries: nil)
37
+ return '── Tokens 0 ──' if tokens.nil? || tokens.empty?
38
+
39
+ max_len = tokens.reject { |t| [:newline, :indent, :dedent].include?(t.type) }
40
+ .map { |t| t.lexeme.length }
41
+ .max || 0
42
+ max_len = [max_len, 28].min
43
+
44
+ type_counts = tokens.group_by(&:type).transform_values(&:length).sort_by { |_k, v| -v }
45
+ summary = type_counts.map { |t, n| "#{n} #{t}" }.join(', ')
46
+
47
+ sema_map = build_sema_map(semantic_entries)
48
+
49
+ lines = ["── Tokens #{tokens.length} (#{summary}) " + '─' * 50]
50
+ tokens.each do |tok|
51
+ lex = tok.lexeme.inspect
52
+ type_str = tok.type.to_s
53
+ end_col = tok.type == :eof ? tok.column : tok.column + tok.lexeme.length - 1
54
+ loc = "Ln #{tok.line.to_s.rjust(2)} Col #{tok.column.to_s.rjust(3)}-#{end_col}"
55
+ sema_col = if sema_map.any?
56
+ entry = sema_map[[tok.line, tok.column]]
57
+ entry ? "#{entry[:type]}#{entry[:modifiers].any? ? " (#{entry[:modifiers].join(',')})" : ''}" : ''
58
+ else
59
+ ''
60
+ end
61
+ lines << format(' %3d:%-3d %-*s %-16s %s %-22s %s',
62
+ tok.line, tok.column,
63
+ max_len + 2, lex,
64
+ type_str,
65
+ loc,
66
+ sema_col,
67
+ (tok.type == :eof ? '◄' : ''))
68
+ end
69
+ lines.join("\n")
70
+ end
71
+
72
+ def build_sema_map(entries)
73
+ return {} unless entries.is_a?(Array) && !entries.empty?
74
+
75
+ entries.each_with_object({}) do |e, map|
76
+ key = [e[:line] + 1, e[:start_char] + 1]
77
+ map[key] = e
78
+ end
79
+ end
80
+
81
+ # ── AST ────────────────────────────────────────────────────────────────────────
82
+
83
+ def format_ast(ast)
84
+ return '── AST (not available) ──' unless ast
85
+
86
+ counts = %w[imports directives declarations].filter_map do |m|
87
+ child = ast.public_send(m) if ast.respond_to?(m)
88
+ "#{child.length} #{m}" if child
89
+ end
90
+ summary = counts.any? ? counts.join(', ') : ''
91
+ buf = ["── AST 1 SourceFile, #{summary} " + '─' * 50]
92
+ format_ast_node(ast, prefix: '', is_last: true, buf: buf)
93
+ buf.join("\n")
94
+ end
95
+
96
+ def format_ast_node(node, prefix:, is_last:, buf:)
97
+ return if node.nil?
98
+
99
+ if node.is_a?(Array)
100
+ node.each_with_index { |item, i| format_ast_node(item, prefix:, is_last: i == node.length - 1, buf:) }
101
+ return
102
+ end
103
+
104
+ unless ast_node?(node)
105
+ buf << "#{prefix}#{is_last ? '└─ ' : '├─ '}#{value_repr(node)}"
106
+ return
107
+ end
108
+
109
+ if compact_ast_node?(node)
110
+ buf << "#{prefix}#{is_last ? '└─ ' : '├─ '}#{compact_ast_repr(node)}"
111
+ return
112
+ end
113
+
114
+ connector = is_last ? '└─ ' : '├─ '
115
+ header = ast_node_header(node)
116
+ buf << "#{prefix}#{connector}#{header}"
117
+
118
+ child_prefix = is_last ? ' ' : '│ '
119
+
120
+ if node.respond_to?(:members)
121
+ active = node.members.select { |m| m.is_a?(Symbol) }
122
+ active -= %i[line column length module_kind visibility name]
123
+
124
+ active.each_with_index do |member, i|
125
+ child = node.public_send(member)
126
+ next if skip_child_value?(child)
127
+
128
+ child_is_last = active.drop(i + 1).none? { |m| keep_child?(node.public_send(m)) }
129
+
130
+ if array_of_ast_nodes?(child)
131
+ label = "#{symbol_icon(child.length)} #{member} (#{child.length})"
132
+ label += ':' if child.length > 0
133
+ buf << "#{prefix}#{child_prefix}#{child_is_last ? '└─ ' : '├─ '}#{label}"
134
+ child.each_with_index do |item, j|
135
+ format_ast_node(item, prefix: prefix + child_prefix + (child_is_last ? ' ' : '│ '),
136
+ is_last: j == child.length - 1, buf:)
137
+ end
138
+ elsif child.is_a?(Array)
139
+ extra = simple_value_array?(child) ? " #{compact_array_repr(child)}" : ''
140
+ label = "#{symbol_icon(child.length)} #{member} (#{child.length})#{extra}"
141
+ buf << "#{prefix}#{child_prefix}#{child_is_last ? '└─ ' : '├─ '}#{label}"
142
+ elsif ast_node?(child)
143
+ if compact_ast_node?(child)
144
+ buf << "#{prefix}#{child_prefix}#{child_is_last ? '└─ ' : '├─ '}#{member}: #{compact_ast_repr(child)}"
145
+ else
146
+ format_ast_node(child, prefix: prefix + child_prefix, is_last: child_is_last, buf:)
147
+ end
148
+ else
149
+ buf << "#{prefix}#{child_prefix}#{child_is_last ? '└─ ' : '├─ '}#{member}: #{value_repr(child)}"
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ def compact_ast_node?(node)
156
+ COMPACT_AST_TYPES.include?(node.class.name.split('::').last)
157
+ end
158
+
159
+ def compact_ast_repr(node)
160
+ kind = node.class.name.split('::').last
161
+ case kind
162
+ when 'QualifiedName'
163
+ path = node.parts.join('.')
164
+ unless node.type_arguments.empty?
165
+ args = node.type_arguments.map { |a| type_arg_str(a) }.join(', ')
166
+ path = "#{path}<#{args}>"
167
+ end
168
+ "QualifiedName(#{path})"
169
+ when 'TypeRef'
170
+ name = node.name
171
+ args = node.arguments.map { |a| type_arg_str(a) }.join(', ')
172
+ name = "#{name}[#{args}]" unless args.empty?
173
+ name = "?#{name}" if node.nullable
174
+ name
175
+ when 'TypeArgument'
176
+ "TypeArg(#{compact_ast_repr(node.value)})"
177
+ when 'Identifier'
178
+ "id:#{node.name}"
179
+ when 'StringLiteral'
180
+ node.value.inspect
181
+ when 'IntegerLiteral'
182
+ node.value.to_s
183
+ when 'FloatLiteral'
184
+ node.value.to_s
185
+ when 'BooleanLiteral'
186
+ node.value.to_s
187
+ when 'NullLiteral'
188
+ 'null'
189
+ when 'FormatString'
190
+ "f#{node.parts.map { |p| value_repr(p) }.join}"
191
+ when 'FormatTextPart'
192
+ node.value.inspect
193
+ when 'FormatExprPart'
194
+ "{#{node.expression.respond_to?(:name) ? node.expression.name : 'expr'}}"
195
+ else
196
+ kind
197
+ end
198
+ end
199
+
200
+ def type_arg_str(arg)
201
+ return value_repr(arg) unless arg.respond_to?(:value)
202
+
203
+ val = arg.value
204
+ ast_node?(val) && compact_ast_node?(val) ? compact_ast_repr(val) : value_repr(val)
205
+ end
206
+
207
+ def ast_node_header(node)
208
+ name_attr = node.respond_to?(:name) ? node.name : nil
209
+ loc = if node.respond_to?(:line) && node.line
210
+ col = node.respond_to?(:column) && node.column ? ", Col #{node.column}" : ''
211
+ " [Ln #{node.line}#{col}]"
212
+ else
213
+ ''
214
+ end
215
+ kind = node.class.name.split('::').last
216
+ name_attr ? "#{kind} ◆ #{name_attr}#{loc}" : "#{kind}#{loc}"
217
+ end
218
+
219
+ def ast_node?(val)
220
+ val.class.name.to_s.start_with?('MilkTea::AST::')
221
+ rescue StandardError
222
+ false
223
+ end
224
+
225
+ def array_of_ast_nodes?(val)
226
+ val.is_a?(Array) && val.any? { |e| ast_node?(e) }
227
+ end
228
+
229
+ def skip_child_value?(val)
230
+ val.nil? || val == false || val == [] || val == {} || val == :private
231
+ end
232
+
233
+ def keep_child?(val)
234
+ return false if skip_child_value?(val)
235
+ return true if val.is_a?(Array) && val.any?
236
+ return true if ast_node?(val) || val.class.name.to_s.start_with?('MilkTea::')
237
+
238
+ true
239
+ end
240
+
241
+ def symbol_icon(count)
242
+ count > 0 ? '◆' : '◇'
243
+ end
244
+
245
+ def compact_array_repr(arr)
246
+ arr.map { |e| value_repr(e) }.join(', ')
247
+ end
248
+
249
+ def simple_value_array?(arr)
250
+ arr.all? { |e| e.is_a?(String) || e.is_a?(Symbol) || e.is_a?(Numeric) || e.nil? }
251
+ end
252
+
253
+ def value_repr(val)
254
+ case val
255
+ when nil then 'nil'
256
+ when true then 'true'
257
+ when false then 'false'
258
+ when String then val.length < 60 ? val.inspect : "#{val[0..56].inspect}..."
259
+ when Integer, Float then val.to_s
260
+ when Symbol then ":#{val}"
261
+ when Array then val.empty? ? '[]' : "[#{val.length}]"
262
+ when Hash then "{#{val.length} entries}"
263
+ else
264
+ klass = val.class.name&.split('::')&.last || val.class.to_s
265
+ if val.respond_to?(:name) && val.name
266
+ "#{klass}(#{value_repr(val.name)})"
267
+ elsif val.respond_to?(:to_s) && val.method(:to_s).owner != Object
268
+ s = val.to_s
269
+ s.length < 60 ? s : "#{s[0..56]}..."
270
+ else
271
+ klass
272
+ end
273
+ end
274
+ end
275
+
276
+ # ── Parse Errors ────────────────────────────────────────────────────────────────
277
+
278
+ def format_parse_errors(errors)
279
+ return '── Parse Errors 0 ──' if errors.nil? || errors.empty?
280
+
281
+ buf = ["── Parse Errors #{errors.length} " + '─' * 55]
282
+ errors.each do |e|
283
+ line = e.respond_to?(:line) ? e.line : '?'
284
+ col = e.respond_to?(:column) ? e.column : '?'
285
+ buf << " ERROR [Ln #{line}, Col #{col}]: #{e.message}"
286
+ end
287
+ buf.join("\n")
288
+ end
289
+
290
+ # ── Facts ───────────────────────────────────────────────────────────────────────
291
+
292
+ def format_facts(facts)
293
+ return '── Facts (not available) ──' unless facts
294
+
295
+ counts = []
296
+ counts << "#{facts.types.length} types" unless facts.types.empty?
297
+ counts << "#{facts.functions.length} functions" unless facts.functions.empty?
298
+ counts << "#{facts.values.length} values" unless facts.values.empty?
299
+ counts << "#{facts.methods.length} method_sets" unless facts.methods.empty?
300
+ counts << "#{facts.interfaces.length} interfaces" unless facts.interfaces.empty?
301
+ counts << "#{facts.imports.length} imports" unless facts.imports.empty?
302
+ summary = counts.any? ? counts.join(', ') : 'empty'
303
+
304
+ buf = ["── Facts module #{facts.module_name.inspect} #{summary} " + '─' * 50]
305
+
306
+ unless facts.types.empty?
307
+ buf << ''
308
+ buf << ' Types:'
309
+ facts.types.each { |name, type| buf << " #{name.ljust(20)} → #{type_repr(type)}" }
310
+ end
311
+
312
+ unless facts.interfaces.empty?
313
+ buf << ''
314
+ buf << ' Interfaces:'
315
+ facts.interfaces.each do |name, iface|
316
+ methods = iface.respond_to?(:methods) ? iface.methods.keys.join(', ') : '?'
317
+ buf << " #{name} [methods: #{methods}]"
318
+ end
319
+ end
320
+
321
+ unless facts.functions.empty?
322
+ buf << ''
323
+ buf << ' Functions:'
324
+ facts.functions.each { |name, b| buf.concat(format_function_binding(name, b, indent: ' ')) }
325
+ end
326
+
327
+ unless facts.values.empty?
328
+ buf << ''
329
+ buf << ' Values:'
330
+ facts.values.each { |name, b| buf << " #{name.ljust(20)} → #{value_binding_repr(b)}" }
331
+ end
332
+
333
+ unless facts.methods.empty?
334
+ buf << ''
335
+ buf << ' Methods:'
336
+ facts.methods.each do |receiver_type, methods|
337
+ buf << " on #{type_repr(receiver_type)}:"
338
+ methods.each { |name, b| buf.concat(format_function_binding(name, b, indent: ' ')) }
339
+ end
340
+ end
341
+
342
+ unless facts.imports.empty?
343
+ buf << ''
344
+ buf << ' Imports:'
345
+ facts.imports.each do |import_name, module_binding|
346
+ count = module_binding.types.length + module_binding.functions.length + module_binding.values.length
347
+ buf << " #{import_name.ljust(24)} → ModuleBinding (exported symbols: #{count})"
348
+ end
349
+ end
350
+
351
+ buf << ''
352
+ buf << " local_completion_frames: #{facts.local_completion_frames.length}"
353
+ buf << " callable_value_identifier_sites: #{facts.callable_value_identifier_sites.length}"
354
+ buf << " callable_value_member_access_sites: #{facts.callable_value_member_access_sites.length}"
355
+ buf << " required_unsafe_lines: #{facts.required_unsafe_lines.inspect}"
356
+ buf.join("\n")
357
+ end
358
+
359
+ def format_function_binding(name, binding, indent:)
360
+ buf = []
361
+ buf << "#{indent}#{name}"
362
+ buf << "#{indent} type: #{type_repr(binding.type)}"
363
+ unless binding.body_params.empty?
364
+ buf << "#{indent} params (#{binding.body_params.length}):"
365
+ binding.body_params.each { |p| buf << "#{indent} #{value_binding_repr(p)}" }
366
+ end
367
+ buf << "#{indent} return_type: #{type_repr(binding.body_return_type)}" if binding.body_return_type
368
+ buf << "#{indent} external: #{binding.external}"
369
+ buf << "#{indent} async: #{binding.async}"
370
+ buf << "#{indent} const: #{binding.ast.respond_to?(:const) ? binding.ast.const : false}" if binding.ast
371
+ buf << "#{indent} ast: #{ast_node_header(binding.ast)}" if binding.ast
372
+ buf << "#{indent} instances: #{binding.instances.length}" unless binding.instances.empty?
373
+ unless binding.type_params.empty?
374
+ buf << "#{indent} type_params (#{binding.type_params.length}):"
375
+ binding.type_params.each { |tp| buf << "#{indent} #{value_repr(tp)}" }
376
+ end
377
+ buf
378
+ end
379
+
380
+ def type_repr(type)
381
+ return 'nil' unless type
382
+ return type.to_s if type.respond_to?(:to_s)
383
+
384
+ type.class.name&.split('::')&.last || type.class.to_s
385
+ end
386
+
387
+ def value_binding_repr(vb)
388
+ mutable = vb.mutable ? 'mut' : ' '
389
+ kind = vb.kind.to_s
390
+ type = vb.flow_type || vb.storage_type
391
+ "ValueBinding[id: #{vb.id}] #{mutable} #{kind.ljust(6)} #{vb.name}: #{type_repr(type)}"
392
+ end
393
+
394
+ # ── Bindings ────────────────────────────────────────────────────────────────────
395
+
396
+ def format_bindings(facts)
397
+ return '── Bindings (not available) ──' unless facts&.binding_resolution
398
+
399
+ res = facts.binding_resolution
400
+ counts = []
401
+ counts << "#{res.identifier_binding_ids.length} ident→id" unless res.identifier_binding_ids.empty?
402
+ counts << "#{res.declaration_binding_ids.length} decl→id" unless res.declaration_binding_ids.empty?
403
+ counts << "#{res.binding_types.length} id→type" if res.respond_to?(:binding_types) && !res.binding_types.empty?
404
+ summary = counts.any? ? counts.join(', ') : 'empty'
405
+
406
+ buf = ["── Bindings #{summary} " + '─' * 60]
407
+
408
+ unless res.identifier_binding_ids.empty?
409
+ buf << " identifier_binding_ids (#{res.identifier_binding_ids.length}):"
410
+ res.identifier_binding_ids.take(30).each { |node_id, bid| buf << " obj #{node_id} → binding_id: #{bid}" }
411
+ buf << " ... (#{res.identifier_binding_ids.length - 30} more)" if res.identifier_binding_ids.length > 30
412
+ end
413
+
414
+ unless res.declaration_binding_ids.empty?
415
+ buf << " declaration_binding_ids (#{res.declaration_binding_ids.length}):"
416
+ res.declaration_binding_ids.take(30).each { |node_id, bid| buf << " obj #{node_id} → binding_id: #{bid}" }
417
+ buf << " ... (#{res.declaration_binding_ids.length - 30} more)" if res.declaration_binding_ids.length > 30
418
+ end
419
+
420
+ if res.respond_to?(:binding_types) && !res.binding_types.empty?
421
+ buf << " binding_types (#{res.binding_types.length}):"
422
+ res.binding_types.each { |bid, type| buf << " binding_id: #{bid} → #{type_repr(type)}" }
423
+ end
424
+
425
+ buf.join("\n")
426
+ end
427
+
428
+ # ── Diagnostics ─────────────────────────────────────────────────────────────────
429
+
430
+ def format_diagnostics(snapshot)
431
+ return '── Diagnostics (not available) ──' unless snapshot
432
+
433
+ diags = snapshot.diagnostics
434
+ return '── Diagnostics 0 ──' if diags.nil? || diags.empty?
435
+
436
+ error_count = diags.count { |d| !d.respond_to?(:severity) || d.severity == :error }
437
+ warn_count = diags.count { |d| d.respond_to?(:severity) && d.severity == :warning }
438
+ info_count = diags.length - error_count - warn_count
439
+ parts = []
440
+ parts << "#{error_count} errors" if error_count > 0
441
+ parts << "#{warn_count} warnings" if warn_count > 0
442
+ parts << "#{info_count} info" if info_count > 0
443
+
444
+ buf = ["── Diagnostics #{diags.length} (#{parts.join(', ')}) " + '─' * 50]
445
+ diags.each do |d|
446
+ severity = d.respond_to?(:severity) ? d.severity.to_s.upcase : '?'
447
+ code = d.respond_to?(:code) ? d.code : '?'
448
+ line = d.respond_to?(:line) ? d.line : '?'
449
+ col = d.respond_to?(:column) ? d.column : '?'
450
+ msg = d.respond_to?(:message) ? d.message : d.to_s
451
+ buf << " #{severity[0]} #{severity.ljust(7)} #{code} [Ln #{line}, Col #{col}]: #{msg}"
452
+ end
453
+ buf.join("\n")
454
+ end
455
+ end
456
+ end