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,271 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module Parse
5
+ module Types
6
+ private
7
+
8
+ def parse_params(allow_variadic: false)
9
+ parse_parameter_list(allow_variadic:) { parse_param }
10
+ end
11
+
12
+ def parse_signature_params
13
+ parse_parameter_list { parse_param }
14
+ end
15
+
16
+ def parse_foreign_params(allow_variadic: false)
17
+ parse_parameter_list(allow_variadic:) { parse_foreign_param }
18
+ end
19
+
20
+ def parse_parameter_list(allow_variadic: false)
21
+ consume(:lparen, "expected '('")
22
+ params = []
23
+ variadic = false
24
+
25
+ unless check(:rparen)
26
+ loop do
27
+ if allow_variadic && match(:ellipsis)
28
+ variadic = true
29
+ break
30
+ end
31
+
32
+ params << yield
33
+ break unless match(:comma)
34
+ break if check(:rparen)
35
+ end
36
+ end
37
+
38
+ consume(:rparen, "expected ')' after parameters")
39
+ return [params, variadic] if allow_variadic
40
+
41
+ params
42
+ end
43
+
44
+ def parse_param
45
+ name_token = consume_name("expected parameter name")
46
+ raise error(name_token, "expected ':' and parameter type") unless match(:colon)
47
+
48
+ param_type = parse_type_ref
49
+
50
+ AST::Param.new(name: name_token.lexeme, type: param_type, line: name_token.line, column: name_token.column)
51
+ rescue ParseError => e
52
+ raise unless @recovery_errors
53
+
54
+ @recovery_errors << e
55
+ param_name = name_token&.lexeme || "error"
56
+ error_type = recovery_error_expr(e)
57
+ AST::Param.new(name: param_name, type: error_type, line: name_token&.line || 1, column: name_token&.column || 1)
58
+ end
59
+
60
+ def parse_foreign_param
61
+ mode = if foreign_param_qualifier_mode?
62
+ advance.type
63
+ else
64
+ :plain
65
+ end
66
+ name_token = consume_name("expected parameter name")
67
+ raise error(name_token, "expected ':' and parameter type") unless match(:colon)
68
+
69
+ param_type = parse_type_ref
70
+ boundary_type = match(:as) ? parse_type_ref : nil
71
+
72
+ AST::ForeignParam.new(name: name_token.lexeme, type: param_type, mode:, boundary_type:)
73
+ rescue ParseError => e
74
+ raise unless @recovery_errors
75
+
76
+ @recovery_errors << e
77
+ param_name = name_token&.lexeme || "error"
78
+ error_type = recovery_error_expr(e)
79
+ AST::ForeignParam.new(name: param_name, type: error_type, mode: mode || :plain, boundary_type: nil)
80
+ end
81
+
82
+ def parse_type_ref
83
+ return parse_function_type_ref if match(:fn)
84
+ return parse_proc_type_ref if match(:proc)
85
+ return parse_dyn_type_ref if match(:dyn)
86
+ return parse_tuple_type_ref if check(:lparen)
87
+
88
+ first_token = peek
89
+ if match(:at)
90
+ lt_token = consume_name("expected lifetime name after @")
91
+ return AST::TypeRef.new(name: AST::QualifiedName.new(parts: ["@#{lt_token.lexeme}"]), arguments: [], nullable: false, lifetime: nil, line: first_token.line, column: first_token.column, length: lt_token.lexeme.length + 1)
92
+ end
93
+ name = parse_qualified_name
94
+ arguments = []
95
+ lifetime = nil
96
+ if match(:lbracket)
97
+ if check(:at) && name.to_s == "ref"
98
+ match(:at)
99
+ lt_token = consume_name("expected lifetime name after @")
100
+ lifetime = "@#{lt_token.lexeme}"
101
+ consume(:comma, "expected ',' after lifetime in type arguments")
102
+ end
103
+ arguments = parse_comma_separated_until(:rbracket) do
104
+ AST::TypeArgument.new(value: parse_type_argument)
105
+ end
106
+ consume(:rbracket, "expected ']' after type arguments")
107
+ end
108
+
109
+ nullable = match(:question)
110
+ type_name = name.to_s
111
+ length = type_name.length + (nullable ? 1 : 0)
112
+ AST::TypeRef.new(name:, arguments:, nullable:, lifetime:, line: first_token.line, column: first_token.column, length:)
113
+ end
114
+
115
+ def parse_function_type_ref
116
+ parse_callable_type_ref(keyword: "fn", param_context: "function type parameters") do |params, return_type|
117
+ AST::FunctionType.new(params:, return_type:)
118
+ end
119
+ end
120
+
121
+ def parse_proc_type_ref
122
+ parse_callable_type_ref(keyword: "proc", param_context: "proc type parameters") do |params, return_type|
123
+ AST::ProcType.new(params:, return_type:)
124
+ end
125
+ end
126
+
127
+ def parse_dyn_type_ref
128
+ first_token = previous
129
+ consume(:lbracket, "expected '[' after dyn")
130
+ interface = parse_qualified_name_with_type_arguments
131
+ consume(:rbracket, "expected ']' after interface name")
132
+ nullable = match(:question)
133
+ AST::DynType.new(interface:, nullable:, line: first_token.line, column: first_token.column, length: 3)
134
+ end
135
+
136
+ def parse_tuple_type_ref
137
+ match(:lparen)
138
+ first_type = parse_type_ref
139
+ if match(:comma)
140
+ element_types = [first_type]
141
+ loop do
142
+ element_types << parse_type_ref
143
+ break unless match(:comma)
144
+ end
145
+ consume(:rparen, "expected ')' after tuple type elements")
146
+ nullable = match(:question)
147
+ AST::TupleType.new(element_types:, nullable:)
148
+ else
149
+ consume(:rparen, "expected ')' after type")
150
+ first_type
151
+ end
152
+ end
153
+
154
+ def parse_callable_type_ref(keyword:, param_context:)
155
+ consume(:lparen, "expected '(' after #{keyword}")
156
+ params = parse_comma_separated_until(:rparen) { parse_function_type_param }
157
+
158
+ consume(:rparen, "expected ')' after #{param_context}")
159
+ consume(:arrow, "expected '->' after #{param_context}")
160
+ return_type = parse_type_ref
161
+ yield params, return_type
162
+ end
163
+
164
+ def parse_function_type_param
165
+ name_token = consume_name("expected function type parameter name")
166
+ name = name_token.lexeme
167
+ consume(:colon, "expected ':' after function type parameter name")
168
+ type = parse_type_ref
169
+ AST::Param.new(name:, type:, line: name_token.line, column: name_token.column)
170
+ end
171
+
172
+ def parse_type_argument
173
+ if match(:integer)
174
+ token = previous
175
+ AST::IntegerLiteral.new(lexeme: token.lexeme, value: token.literal)
176
+ elsif match(:float)
177
+ token = previous
178
+ AST::FloatLiteral.new(lexeme: token.lexeme, value: token.literal)
179
+ else
180
+ parse_type_ref
181
+ end
182
+ end
183
+
184
+ def parse_declaration_type_params
185
+ return [] unless match(:lbracket)
186
+
187
+ params = parse_comma_separated_until(:rbracket) do
188
+ name_token = consume_name("expected type parameter name")
189
+ name = name_token.lexeme
190
+ if match(:colon)
191
+ value_type = parse_type_ref
192
+ AST::ValueTypeParam.new(
193
+ name:,
194
+ type: value_type,
195
+ line: name_token.line,
196
+ column: name_token.column,
197
+ length: name.length,
198
+ )
199
+ else
200
+ constraints = parse_type_param_constraints
201
+ AST::TypeParam.new(
202
+ name:,
203
+ constraints:,
204
+ line: name_token.line,
205
+ column: name_token.column,
206
+ length: name.length,
207
+ )
208
+ end
209
+ end
210
+
211
+ consume(:rbracket, "expected ']' after type parameters")
212
+ params
213
+ end
214
+
215
+ def parse_type_param_constraints
216
+ constraints = []
217
+ interface_constraint_mode = false
218
+
219
+ constraint, interface_constraint_mode = parse_type_param_constraint(interface_constraint_mode)
220
+ return constraints unless constraint
221
+
222
+ constraints << constraint
223
+
224
+ while match(:and)
225
+ constraint, interface_constraint_mode = parse_type_param_constraint(interface_constraint_mode)
226
+ raise error(peek, "expected constraint after 'and'") unless constraint
227
+
228
+ constraints << constraint
229
+ end
230
+
231
+ constraints
232
+ end
233
+
234
+ def parse_type_param_constraint(interface_constraint_mode)
235
+ if match(:implements)
236
+ return [AST::TypeParamConstraint.new(kind: :interface, interface_ref: parse_qualified_name_with_type_arguments), true]
237
+ end
238
+
239
+ return [AST::TypeParamConstraint.new(kind: :interface, interface_ref: parse_qualified_name_with_type_arguments), true] if interface_constraint_mode
240
+
241
+ [nil, interface_constraint_mode]
242
+ end
243
+
244
+ def parse_implements_clause
245
+ return [] unless match(:implements)
246
+
247
+ implements = [parse_qualified_name_with_type_arguments]
248
+ implements << parse_qualified_name_with_type_arguments while match(:comma)
249
+ implements
250
+ end
251
+
252
+ def parse_qualified_name_with_type_arguments
253
+ first_token = consume_path_component("expected identifier")
254
+ parts = [first_token.lexeme]
255
+ while match(:dot)
256
+ parts << consume_path_component("expected identifier after '.'").lexeme
257
+ end
258
+ type_arguments = if match(:lbracket)
259
+ args = parse_comma_separated_until(:rbracket) do
260
+ parse_type_ref
261
+ end
262
+ consume(:rbracket, "expected ']' after type arguments")
263
+ args
264
+ else
265
+ []
266
+ end
267
+ AST::QualifiedName.new(parts:, type_arguments:, line: first_token.line, column: first_token.column)
268
+ end
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,400 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "parser/blocks"
4
+ require_relative "parser/recovery"
5
+ require_relative "parser/types"
6
+ require_relative "parser/attributes"
7
+ require_relative "parser/expressions"
8
+ require_relative "parser/declarations"
9
+ require_relative "parser/statements"
10
+
11
+ module MilkTea
12
+ class ParseError < StandardError
13
+ attr_reader :token, :path
14
+
15
+ def initialize(message, token:, path: nil)
16
+ @token = token
17
+ @path = path
18
+
19
+ location = [path, "#{token.line}:#{token.column}"].compact.join(":")
20
+ super(location.empty? ? message : "#{message} at #{location}")
21
+ end
22
+
23
+ def line
24
+ @token.line
25
+ end
26
+
27
+ def column
28
+ @token.column
29
+ end
30
+
31
+ def code
32
+ "parse/error"
33
+ end
34
+ end
35
+
36
+ class Parser
37
+ ParseRecoveryResult = Data.define(:ast, :errors) do
38
+ def initialize(ast:, errors: []) = super
39
+ end
40
+
41
+ include Parse::Blocks
42
+ include Parse::Recovery
43
+ include Parse::Types
44
+ include Parse::Attributes
45
+ include Parse::Expressions
46
+ include Parse::Declarations
47
+ include Parse::Statements
48
+
49
+ def self.parse(source = nil, path: nil, tokens: nil)
50
+ token_stream = tokens || Lexer.lex(source, path: path)
51
+ new(token_stream, path: path).parse
52
+ end
53
+
54
+ def self.parse_collecting_errors(source = nil, path: nil, tokens: nil)
55
+ if tokens
56
+ return new(tokens, path: path).parse_collecting_errors
57
+ end
58
+
59
+ lex_errors = []
60
+ token_stream = Lexer.lex(source, path: path, recovery_errors: lex_errors)
61
+ parse_result = new(token_stream, path: path).parse_collecting_errors
62
+ ParseRecoveryResult.new(ast: parse_result.ast, errors: lex_errors + parse_result.errors)
63
+ end
64
+
65
+ def initialize(tokens, path: nil)
66
+ @tokens = tokens.is_a?(SyntaxTokenStream) ? tokens : SyntaxTokenStream.new(tokens)
67
+ @path = path
68
+ @current = 0
69
+ @known_type_names = {}
70
+ @known_import_aliases = {}
71
+ @known_generic_callable_names = {}
72
+ @current_type_param_names = []
73
+ @in_inline_block_body = false
74
+ seed_known_names
75
+ end
76
+
77
+ def parse
78
+ parse_source_file
79
+ end
80
+
81
+ def parse_collecting_errors
82
+ errors = @recovery_errors ? @recovery_errors.dup : []
83
+ previous_recovery_errors = @recovery_errors
84
+ @recovery_errors = errors
85
+ ast = parse_source_file(errors:)
86
+ ParseRecoveryResult.new(ast:, errors:)
87
+ rescue ParseError => e
88
+ errors ||= []
89
+ errors << e
90
+ ParseRecoveryResult.new(ast: nil, errors:)
91
+ ensure
92
+ @recovery_errors = previous_recovery_errors
93
+ end
94
+
95
+ private
96
+
97
+ BUILTIN_ATTRIBUTE_NAME_LEXEMES = %w[packed align].freeze
98
+
99
+ def parse_source_file(errors: nil)
100
+ skip_newlines
101
+
102
+ module_name = nil
103
+ module_kind = :module
104
+ module_line = nil
105
+ imports = []
106
+ directives = []
107
+ declarations = []
108
+
109
+ if external_file_header?
110
+ advance
111
+ module_line = previous.line
112
+ module_kind = :raw_module
113
+ consume(:newline, "expected newline after external") unless eof?
114
+ skip_newlines
115
+ imports, directives, declarations = parse_raw_module_body(errors:)
116
+ skip_newlines
117
+ raise error(peek, "expected end of file after external declarations") unless eof?
118
+
119
+ return AST.assign_node_ids(AST::SourceFile.new(module_name:, module_kind:, imports:, directives:, declarations:, line: module_line))
120
+ end
121
+
122
+ while match(:import)
123
+ if errors
124
+ begin
125
+ imports << parse_import
126
+ rescue ParseError => e
127
+ errors << e
128
+ synchronize_to_top_level_boundary
129
+ end
130
+ else
131
+ imports << parse_import
132
+ end
133
+ skip_newlines
134
+ end
135
+
136
+ until eof?
137
+ if errors
138
+ begin
139
+ declarations << parse_declaration
140
+ rescue ParseError => e
141
+ errors << e
142
+ synchronize_to_top_level_boundary
143
+ end
144
+ else
145
+ declarations << parse_declaration
146
+ end
147
+ skip_newlines
148
+ end
149
+
150
+ AST.assign_node_ids(AST::SourceFile.new(module_name:, module_kind:, imports:, directives:, declarations:, line: module_line))
151
+ end
152
+
153
+ def external_file_header?
154
+ return false unless check(:external)
155
+
156
+ next_token = @tokens[@current + 1]
157
+ next_token.nil? || %i[newline eof].include?(next_token.type)
158
+ end
159
+
160
+ def match(*types)
161
+ return false unless types.any? { |type| check(type) }
162
+
163
+ advance
164
+ true
165
+ end
166
+
167
+ def consume(type, message)
168
+ return advance if check(type)
169
+
170
+ raise error(%i[rparen rbracket rbrace].include?(type) ? previous : peek, message)
171
+ end
172
+
173
+ def consume_name(message)
174
+ if keyword_token?(peek)
175
+ name_role = message.sub(/\A(?:expected|required)\s+/, "")
176
+ clearer_message = if name_role == message
177
+ message
178
+ else
179
+ "keyword '#{peek.lexeme}' cannot be used as #{name_role}"
180
+ end
181
+ raise error(peek, clearer_message)
182
+ end
183
+
184
+ consume(:identifier, message)
185
+ end
186
+
187
+ def consume_name_allowing_keywords(message)
188
+ if check(:identifier)
189
+ advance
190
+ elsif keyword_token?(peek)
191
+ advance
192
+ else
193
+ raise error(peek, message)
194
+ end
195
+ end
196
+
197
+ def keyword_token?(token)
198
+ token && Token::KEYWORDS.key?(token.lexeme)
199
+ end
200
+
201
+ def check(type)
202
+ return false if eof?
203
+
204
+ peek.type == type
205
+ end
206
+
207
+ def check_name
208
+ !eof? && peek.type == :identifier
209
+ end
210
+
211
+ def check_next(type)
212
+ return false if (@current + 1) >= @tokens.length
213
+
214
+ @tokens[@current + 1].type == type
215
+ end
216
+
217
+ def match_name
218
+ return false unless check_name
219
+
220
+ advance
221
+ true
222
+ end
223
+
224
+ def advance
225
+ @current += 1 unless eof?
226
+ previous
227
+ end
228
+
229
+ def eof?
230
+ peek.type == :eof
231
+ end
232
+
233
+ def peek
234
+ @tokens[@current]
235
+ end
236
+
237
+ def previous
238
+ @tokens[@current - 1]
239
+ end
240
+
241
+ def error(token, message)
242
+ ParseError.new(message, token:, path: @path)
243
+ end
244
+
245
+ def skip_newlines
246
+ advance while check(:newline)
247
+ end
248
+
249
+ def consume_end_of_statement
250
+ return if @in_inline_block_body
251
+
252
+ consume(:newline, "expected end of statement")
253
+ end
254
+
255
+ def block_expression?(expression)
256
+ expression.is_a?(AST::ProcExpr) || expression.is_a?(AST::MatchExpr)
257
+ end
258
+
259
+ def parse_qualified_name
260
+ parts = [consume_path_component("expected identifier").lexeme]
261
+ while match(:dot)
262
+ parts << consume_path_component("expected identifier after '.'").lexeme
263
+ end
264
+ AST::QualifiedName.new(parts:)
265
+ end
266
+
267
+ def consume_path_component(message)
268
+ return advance if !eof? && (peek.type == :identifier || Token::KEYWORDS.value?(peek.type))
269
+
270
+ raise error(peek, message)
271
+ end
272
+
273
+ def parse_comma_separated_until(closing_type)
274
+ items = []
275
+
276
+ unless check(closing_type)
277
+ loop do
278
+ items << yield
279
+ break unless match(:comma)
280
+ break if check(closing_type)
281
+ end
282
+ end
283
+
284
+ items
285
+ end
286
+
287
+ def foreign_param_qualifier_mode?
288
+ return false unless %i[out in inout consuming].include?(peek.type)
289
+
290
+ @tokens[@current + 1]&.type == :identifier
291
+ end
292
+
293
+ def legacy_layout_modifier_start?(token)
294
+ token&.type == :identifier && BUILTIN_ATTRIBUTE_NAME_LEXEMES.include?(token.lexeme)
295
+ end
296
+
297
+ def known_type_like_name?(name)
298
+ @known_type_names.key?(name) || @known_import_aliases.key?(name) || @current_type_param_names.include?(name)
299
+ end
300
+
301
+ def with_type_param_names(names)
302
+ saved_names = @current_type_param_names
303
+ @current_type_param_names = @current_type_param_names + names
304
+ yield
305
+ ensure
306
+ @current_type_param_names = saved_names
307
+ end
308
+
309
+ def seed_known_names
310
+ MilkTea::BUILTIN_TYPE_NAMES.each { |name| @known_type_names[name] = true }
311
+
312
+ depth = 0
313
+ index = 0
314
+ while index < @tokens.length
315
+ token = @tokens[index]
316
+ case token.type
317
+ when :indent
318
+ depth += 1
319
+ when :dedent
320
+ depth -= 1 if depth.positive?
321
+ when :import
322
+ index = seed_import_alias(index + 1) if depth.zero?
323
+ when :function
324
+ if depth.zero?
325
+ name_token = @tokens[index + 1]
326
+ type_param_token = @tokens[index + 2]
327
+ if type_name_token?(name_token) && type_param_token&.type == :lbracket
328
+ @known_generic_callable_names[name_token.lexeme] = true
329
+ end
330
+ end
331
+ when :async
332
+ if depth.zero? && @tokens[index + 1]&.type == :function
333
+ name_token = @tokens[index + 2]
334
+ type_param_token = @tokens[index + 3]
335
+ if type_name_token?(name_token) && type_param_token&.type == :lbracket
336
+ @known_generic_callable_names[name_token.lexeme] = true
337
+ end
338
+ end
339
+ when :foreign
340
+ if depth.zero? && @tokens[index + 1]&.type == :function
341
+ name_token = @tokens[index + 2]
342
+ type_param_token = @tokens[index + 3]
343
+ if type_name_token?(name_token) && type_param_token&.type == :lbracket
344
+ @known_generic_callable_names[name_token.lexeme] = true
345
+ end
346
+ end
347
+ when :struct, :union, :enum, :flags, :opaque, :type, :variant
348
+ if depth.zero?
349
+ name_token = @tokens[index + 1]
350
+ @known_type_names[name_token.lexeme] = true if type_name_token?(name_token)
351
+ end
352
+ end
353
+
354
+ index += 1
355
+ end
356
+ end
357
+
358
+ def seed_import_alias(start_index)
359
+ cursor = start_index
360
+ last_part = nil
361
+
362
+ while cursor < @tokens.length && @tokens[cursor].type != :newline
363
+ token = @tokens[cursor]
364
+ if token.type == :as
365
+ alias_token = @tokens[cursor + 1]
366
+ @known_import_aliases[alias_token.lexeme] = true if type_name_token?(alias_token)
367
+ return cursor
368
+ end
369
+
370
+ last_part = token.lexeme if type_name_token?(token)
371
+ cursor += 1
372
+ end
373
+
374
+ @known_import_aliases[last_part] = true if last_part
375
+ cursor
376
+ end
377
+
378
+ def type_name_token?(token)
379
+ token&.type == :identifier
380
+ end
381
+
382
+ def matching_rbracket_index(start_index)
383
+ depth = 0
384
+ index = start_index
385
+
386
+ while index < @tokens.length
387
+ case @tokens[index].type
388
+ when :lbracket
389
+ depth += 1
390
+ when :rbracket
391
+ depth -= 1
392
+ return index if depth.zero?
393
+ end
394
+ index += 1
395
+ end
396
+
397
+ nil
398
+ end
399
+ end
400
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class PreludeInstaller
5
+ PRELUDE_MODULE_PATHS = %w[std.option std.result].freeze
6
+
7
+ def initialize(resolve_module_path:, check_block:, bind_block:)
8
+ @resolve_module_path = resolve_module_path
9
+ @check_block = check_block
10
+ @bind_block = bind_block
11
+ end
12
+
13
+ def install_prelude_modules!(ast, modules, importer_path: nil, collecting_errors: false)
14
+ current_module_name = ast.module_name&.to_s
15
+ return if PRELUDE_MODULE_PATHS.include?(current_module_name)
16
+
17
+ PRELUDE_MODULE_PATHS.each do |module_path|
18
+ next if modules.key?(module_path)
19
+
20
+ begin
21
+ import_path = @resolve_module_path.call(module_path, importer_path:)
22
+ import_analysis = @check_block.call(import_path, collecting_errors)
23
+ modules[module_path] = @bind_block.call(import_analysis)
24
+ rescue ModuleLoadError
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end