mt-lang 0.3.17 → 0.3.20

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/lib/milk_tea/base.rb +1 -1
  3. data/lib/milk_tea/core/c_backend/expressions.rb +34 -23
  4. data/lib/milk_tea/core/c_backend/feature_detection.rb +25 -45
  5. data/lib/milk_tea/core/c_backend/type_collectors.rb +18 -30
  6. data/lib/milk_tea/core/c_backend/type_declaration.rb +0 -6
  7. data/lib/milk_tea/core/c_backend.rb +49 -39
  8. data/lib/milk_tea/core/compile_time.rb +98 -72
  9. data/lib/milk_tea/core/intrinsics.rb +7 -0
  10. data/lib/milk_tea/core/lexer.rb +46 -35
  11. data/lib/milk_tea/core/lowering/block.rb +9 -0
  12. data/lib/milk_tea/core/lowering/calls.rb +2 -0
  13. data/lib/milk_tea/core/lowering/declarations.rb +1 -1
  14. data/lib/milk_tea/core/lowering/functions.rb +11 -8
  15. data/lib/milk_tea/core/lowering/resolve.rb +10 -3
  16. data/lib/milk_tea/core/lowering/scans.rb +13 -18
  17. data/lib/milk_tea/core/module_binder.rb +9 -10
  18. data/lib/milk_tea/core/module_loader.rb +38 -42
  19. data/lib/milk_tea/core/module_path_resolver.rb +1 -4
  20. data/lib/milk_tea/core/parser/declarations.rb +43 -19
  21. data/lib/milk_tea/core/parser/expressions.rb +4 -7
  22. data/lib/milk_tea/core/parser/statements.rb +5 -5
  23. data/lib/milk_tea/core/parser.rb +26 -0
  24. data/lib/milk_tea/core/semantic_analyzer/calls.rb +24 -31
  25. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +20 -23
  26. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +117 -85
  27. data/lib/milk_tea/core/semantic_analyzer/statements.rb +19 -41
  28. data/lib/milk_tea/core/semantic_analyzer.rb +56 -37
  29. data/lib/milk_tea/lsp/server/semantic_tokens.rb +6 -0
  30. data/lib/milk_tea/tooling/cli/commands/bindgen.rb +11 -0
  31. data/lib/milk_tea/tooling/cli/commands/build.rb +37 -0
  32. data/lib/milk_tea/tooling/cli/commands/cache.rb +46 -0
  33. data/lib/milk_tea/tooling/cli/commands/check.rb +116 -0
  34. data/lib/milk_tea/tooling/cli/commands/command_base.rb +8 -0
  35. data/lib/milk_tea/tooling/cli/commands/completions.rb +48 -0
  36. data/lib/milk_tea/tooling/cli/commands/dap.rb +58 -0
  37. data/lib/milk_tea/tooling/cli/commands/debug.rb +77 -0
  38. data/lib/milk_tea/tooling/cli/commands/deps.rb +17 -0
  39. data/lib/milk_tea/tooling/cli/commands/docs.rb +58 -0
  40. data/lib/milk_tea/tooling/cli/commands/emit_c.rb +64 -0
  41. data/lib/milk_tea/tooling/cli/commands/format.rb +199 -0
  42. data/lib/milk_tea/tooling/cli/commands/lex.rb +46 -0
  43. data/lib/milk_tea/tooling/cli/commands/lint.rb +248 -0
  44. data/lib/milk_tea/tooling/cli/commands/lower.rb +50 -0
  45. data/lib/milk_tea/tooling/cli/commands/lsp.rb +43 -0
  46. data/lib/milk_tea/tooling/cli/commands/new.rb +26 -0
  47. data/lib/milk_tea/tooling/cli/commands/parse.rb +51 -0
  48. data/lib/milk_tea/tooling/cli/commands/run.rb +99 -0
  49. data/lib/milk_tea/tooling/cli/commands/snapshot.rb +117 -0
  50. data/lib/milk_tea/tooling/cli/commands/test.rb +557 -0
  51. data/lib/milk_tea/tooling/cli/commands/toolchain.rb +16 -0
  52. data/lib/milk_tea/tooling/cli.rb +101 -1893
  53. metadata +24 -2
@@ -45,6 +45,13 @@ module MilkTea
45
45
  "with" => :simd_lane_with,
46
46
  }.freeze
47
47
 
48
+ SPECIALIZED_RECEIVERS = {
49
+ "str_buffer" => { predicate: :str_buffer_type?, kinds: STR_BUFFER_METHOD_KINDS },
50
+ "event" => { predicate: :event_type?, kinds: EVENT_METHOD_KINDS },
51
+ "atomic" => { predicate: :atomic_type?, kinds: ATOMIC_METHOD_KINDS },
52
+ "simd" => { predicate: :simd_type?, kinds: SIMD_METHOD_KINDS },
53
+ }.freeze
54
+
48
55
  def type_ref_from_specialization(expression)
49
56
  case expression.callee
50
57
  when AST::Identifier
@@ -279,43 +279,17 @@ module MilkTea
279
279
  break
280
280
  end
281
281
 
282
- if char == "c" && heredoc_start?(line, index, cstring: true)
283
- return lex_heredoc(lines, line_index, index, line_number, line_offset, cstring: true)
284
- end
285
-
286
- if char == "c" && line[index + 1] == "<" && line[index + 2] == "-" && identifier_start?(line[index + 3])
287
- error = LexError.new("expected '<<-' for heredoc string; did you mean 'c<<-#{identifier_start_token(line, index + 3)}'?", line: line_number, column: index + 1, path: @path)
288
- if @recovery_errors
289
- @recovery_errors << error
290
- else
291
- raise error
282
+ if (char == "c" || char == "f")
283
+ result = dispatch_cf_prefix(char, line, index, line_number, line_offset, lines:, line_index:)
284
+ if result
285
+ if result[0] == :return
286
+ return result[1]
287
+ else
288
+ index = result[1]
289
+ next
290
+ end
292
291
  end
293
292
  end
294
- if char == "c" && line[index + 1] == '"'
295
- result = lex_string(lines, line_index, line, index, line_number, line_offset:, cstring: true)
296
- return result.consumed_lines if result.consumed_lines > 1
297
-
298
- index = result.next_index
299
- next
300
- end
301
-
302
- if char == "f" && heredoc_start?(line, index, format: true)
303
- return lex_heredoc(lines, line_index, index, line_number, line_offset, cstring: false, format: true)
304
- end
305
-
306
- if char == "f" && line[index + 1] == "<" && line[index + 2] == "-" && identifier_start?(line[index + 3])
307
- error = LexError.new("expected '<<-' for heredoc string; did you mean 'f<<-#{identifier_start_token(line, index + 3)}'?", line: line_number, column: index + 1, path: @path)
308
- if @recovery_errors
309
- @recovery_errors << error
310
- else
311
- raise error
312
- end
313
- end
314
-
315
- if char == "f" && line[index + 1] == '"'
316
- index = lex_format_string(line, index, line_number, line_offset:)
317
- next
318
- end
319
293
 
320
294
  if char == "<" && heredoc_start?(line, index)
321
295
  return lex_heredoc(lines, line_index, index, line_number, line_offset, cstring: false)
@@ -391,6 +365,43 @@ module MilkTea
391
365
  end
392
366
  end
393
367
 
368
+ def dispatch_cf_prefix(char, line, index, line_number, line_offset, lines:, line_index:)
369
+ return nil unless char == "c" || char == "f"
370
+
371
+ is_c = char == "c"
372
+ is_f = char == "f"
373
+
374
+ if heredoc_start?(line, index, cstring: is_c, format: is_f)
375
+ n = lex_heredoc(lines, line_index, index, line_number, line_offset, cstring: is_c, format: is_f)
376
+ return [:return, n]
377
+ end
378
+
379
+ if line[index + 1] == "<" && line[index + 2] == "-" && identifier_start?(line[index + 3])
380
+ error = LexError.new("expected '<<-' for heredoc string; did you mean '#{char}<<-#{identifier_start_token(line, index + 3)}'?", line: line_number, column: index + 1, path: @path)
381
+ if @recovery_errors
382
+ @recovery_errors << error
383
+ else
384
+ raise error
385
+ end
386
+ return nil
387
+ end
388
+
389
+ if line[index + 1] == '"'
390
+ if is_c
391
+ string_result = lex_string(lines, line_index, line, index, line_number, line_offset:, cstring: true)
392
+ if string_result.consumed_lines > 1
393
+ return [:return, string_result.consumed_lines]
394
+ end
395
+ return [:next, string_result.next_index]
396
+ end
397
+
398
+ new_index = lex_format_string(line, index, line_number, line_offset:)
399
+ return [:next, new_index]
400
+ end
401
+
402
+ nil
403
+ end
404
+
394
405
  def token(type, lexeme, literal, line, column, start_offset:, end_offset:)
395
406
  Token.new(
396
407
  type:,
@@ -1,5 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "expressions"
4
+ require_relative "loops"
5
+ require_relative "proc"
6
+ require_relative "resolve"
7
+ require_relative "utils"
8
+ require_relative "foreign_cstr"
9
+ require_relative "declarations"
10
+ require_relative "calls"
11
+
3
12
  module MilkTea
4
13
  module LowererBlock
5
14
  def lower_block(statements, env:, active_defers:, return_type:, loop_flow:, allow_return: true)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "dyn"
4
+
3
5
  module MilkTea
4
6
  module LowererCalls
5
7
  def lower_call(expression, env:, type:)
@@ -78,7 +78,7 @@ module MilkTea
78
78
  end
79
79
  IR::AggregateLiteral.new(type:, fields:)
80
80
  else
81
- IR::IntegerLiteral.new(value: 0, type:)
81
+ raise LoweringError.new("unsupported const value type #{const_value.class}", line: 0, column: 0, path: @ctx.current_analysis_path)
82
82
  end
83
83
  end
84
84
 
@@ -1,14 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "resolve"
4
+ require_relative "utils"
5
+ require_relative "block"
6
+ require_relative "async/normalization"
7
+ require_relative "async/lowering"
8
+
3
9
  module MilkTea
4
10
  module LowererFunctions
5
11
  def lower_functions
6
12
  lowered = []
13
+ cursor = 0
14
+ declarations = @ctx.ast.declarations
7
15
 
8
- changed = true
9
- while changed
10
- changed = false
11
-
16
+ while cursor < declarations.length
12
17
  expanded_declarations.each do |decl|
13
18
  case decl
14
19
  when AST::FunctionDef
@@ -21,7 +26,6 @@ module MilkTea
21
26
 
22
27
  lowered << lower_function_decl(instance)
23
28
  @artifacts.lowered_function_linkage_names[linkage_name] = true
24
- changed = true
25
29
  end
26
30
  else
27
31
  linkage_name = function_binding_c_name(binding, module_name: @ctx.module_name)
@@ -37,7 +41,6 @@ module MilkTea
37
41
  @artifacts.lowered_function_linkage_names[entrypoint.linkage_name] = true
38
42
  end
39
43
  end
40
- changed = true
41
44
  end
42
45
  when AST::ExtendingBlock
43
46
  receiver_type = resolve_extending_receiver_type(@ctx.analysis, decl.type_name)
@@ -56,7 +59,6 @@ module MilkTea
56
59
 
57
60
  lowered << lower_function_decl(instance, receiver_type:)
58
61
  @artifacts.lowered_function_linkage_names[linkage_name] = true
59
- changed = true
60
62
  end
61
63
  else
62
64
  linkage_name = function_binding_c_name(binding, module_name: @ctx.module_name, receiver_type:)
@@ -64,11 +66,12 @@ module MilkTea
64
66
 
65
67
  lowered << lower_function_decl(binding, receiver_type:)
66
68
  @artifacts.lowered_function_linkage_names[linkage_name] = true
67
- changed = true
68
69
  end
69
70
  end
70
71
  end
71
72
  end
73
+
74
+ cursor = declarations.length
72
75
  end
73
76
 
74
77
  lowered
@@ -1518,12 +1518,19 @@ module MilkTea
1518
1518
  def resolve_type_argument_ref(type_ref, type_params:)
1519
1519
  return resolve_type_ref(type_ref, type_params:) unless literal_type_argument_name_candidate?(type_ref)
1520
1520
 
1521
- resolve_type_ref(type_ref, type_params:)
1522
- rescue LoweringError => error
1521
+ result = try_resolve_type_ref(type_ref, type_params:)
1522
+ return result if result
1523
+
1523
1524
  literal_type_argument = resolve_named_literal_type_argument(type_ref)
1524
1525
  return literal_type_argument if literal_type_argument
1525
1526
 
1526
- raise error
1527
+ resolve_type_ref(type_ref, type_params:)
1528
+ end
1529
+
1530
+ def try_resolve_type_ref(type_ref, type_params:)
1531
+ resolve_type_ref(type_ref, type_params:)
1532
+ rescue LoweringError
1533
+ nil
1527
1534
  end
1528
1535
 
1529
1536
  def literal_type_argument_name_candidate?(type_ref)
@@ -1,26 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "functions"
4
+ require_relative "resolve"
5
+ require_relative "utils"
6
+
3
7
  module MilkTea
4
8
  module LowererScans
5
9
  def collect_structs
6
- @ctx.ast.declarations.each do |decl|
7
- case decl
8
- when AST::WhenStmt
9
- body = lower_when_chosen_body(decl)
10
- body&.each { |nested| collect_struct_from_decl(nested) }
11
- when AST::OpaqueDecl
12
- @ctx.opaque_types[decl.name] = @ctx.types.fetch(decl.name)
13
- when AST::StructDecl
14
- @ctx.struct_types[decl.name] = @ctx.types.fetch(decl.name)
15
- collect_nested_structs(decl)
16
- when AST::UnionDecl
17
- @ctx.union_types[decl.name] = @ctx.types.fetch(decl.name)
18
- end
19
- end
10
+ @ctx.ast.declarations.each { |decl| collect_one_struct_decl(decl) }
20
11
  end
21
12
 
22
- def collect_struct_from_decl(decl)
13
+ def collect_one_struct_decl(decl)
23
14
  case decl
15
+ when AST::WhenStmt
16
+ body = lower_when_chosen_body(decl)
17
+ body&.each { |nested| collect_one_struct_decl(nested) }
24
18
  when AST::OpaqueDecl
25
19
  @ctx.opaque_types[decl.name] = @ctx.types.fetch(decl.name)
26
20
  when AST::StructDecl
@@ -28,12 +22,13 @@ module MilkTea
28
22
  collect_nested_structs(decl)
29
23
  when AST::UnionDecl
30
24
  @ctx.union_types[decl.name] = @ctx.types.fetch(decl.name)
31
- when AST::WhenStmt
32
- body = lower_when_chosen_body(decl)
33
- body&.each { |nested| collect_struct_from_decl(nested) }
34
25
  end
35
26
  end
36
27
 
28
+ def collect_struct_from_decl(decl)
29
+ collect_one_struct_decl(decl)
30
+ end
31
+
37
32
  def lower_when_chosen_body(decl)
38
33
  val = compile_time_const_value(decl.discriminant)
39
34
  return nil if val.nil?
@@ -136,17 +136,16 @@ module MilkTea
136
136
  imported_interface_binding?(interface, analysis.imports)
137
137
  end
138
138
 
139
+ BUILTIN_EXPORTABLE_RECEIVER_TYPES = %w[
140
+ StringView Primitive Vector Matrix Quaternion SoA Simd Span Task Dyn
141
+ ].map { |n| "Types::#{n}" }.freeze
142
+
143
+ def builtin_exportable_receiver?(receiver_type)
144
+ BUILTIN_EXPORTABLE_RECEIVER_TYPES.any? { |name| receiver_type.class.to_s.end_with?(name.split("::").last) }
145
+ end
146
+
139
147
  def exported_method_receiver?(receiver_type, analysis, exported_types)
140
- return true if receiver_type.is_a?(Types::StringView)
141
- return true if receiver_type.is_a?(Types::Primitive)
142
- return true if receiver_type.is_a?(Types::Vector)
143
- return true if receiver_type.is_a?(Types::Matrix)
144
- return true if receiver_type.is_a?(Types::Quaternion)
145
- return true if receiver_type.is_a?(Types::SoA)
146
- return true if receiver_type.is_a?(Types::Simd)
147
- return true if receiver_type.is_a?(Types::Span)
148
- return true if receiver_type.is_a?(Types::Task)
149
- return true if receiver_type.is_a?(Types::Dyn)
148
+ return true if builtin_exportable_receiver?(receiver_type)
150
149
  return true if exported_types.value?(receiver_type)
151
150
  return true if imported_receiver_type?(receiver_type, analysis.imports)
152
151
  return exported_method_receiver?(receiver_type.base, analysis, exported_types) if receiver_type.is_a?(Types::Nullable)
@@ -317,48 +317,14 @@ module MilkTea
317
317
  end
318
318
 
319
319
  def imported_modules_for_ast(ast, importer_path: nil)
320
- modules = {}
321
-
322
- ast.imports.each do |import|
323
- import_path = @path_resolver.resolve_module_path(import.path.to_s, importer_path:, importer_module_name: ast.module_name.to_s)
324
-
325
- if @forward_bindings.key?(import_path)
326
- modules[import.path.to_s] = @forward_bindings[import_path]
327
- else
328
- import_analysis = check_path(import_path)
329
- modules[import.path.to_s] = @binder.module_binding(import_analysis)
330
- end
331
- end
332
-
333
- @async_runtime_installer.install_async_runtime_dependency!(ast, modules, importer_path:, collecting_errors: false)
334
- @prelude_installer.install_prelude_modules!(ast, modules, importer_path:, collecting_errors: false)
335
- modules.freeze
320
+ resolve_imports_for_ast(ast, importer_path:, collecting: false)
336
321
  end
337
322
 
338
- def build_global_import_index(ast)
339
- index = {}
340
- current_imports = ast.imports.map { |import| import.path.to_s }.to_set
341
-
342
- @analysis_cache.each_value do |analysis|
343
- next unless analysis
344
- next unless analysis.module_name
345
-
346
- mod_name = analysis.module_name.to_s
347
- next if current_imports.include?(mod_name)
348
- next if mod_name == ast.module_name.to_s
349
-
350
- types = analysis.respond_to?(:types) ? analysis.types : {}
351
- types.each_key do |type_name|
352
- type_str = type_name.to_s
353
- index[type_str] ||= []
354
- index[type_str] << mod_name unless index[type_str].include?(mod_name)
355
- end
356
- end
357
-
358
- index
323
+ def imported_modules_for_ast_collecting_errors(ast, importer_path: nil)
324
+ resolve_imports_for_ast(ast, importer_path:, collecting: true)
359
325
  end
360
326
 
361
- def imported_modules_for_ast_collecting_errors(ast, importer_path: nil)
327
+ def resolve_imports_for_ast(ast, importer_path:, collecting: false)
362
328
  modules = {}
363
329
  errors = []
364
330
 
@@ -369,27 +335,55 @@ module MilkTea
369
335
  if @forward_bindings.key?(import_path)
370
336
  modules[import.path.to_s] = @forward_bindings[import_path]
371
337
  else
372
- import_analysis = check_path_collecting_errors(import_path)
338
+ import_analysis = collecting ? check_path_collecting_errors(import_path) : check_path(import_path)
373
339
  modules[import.path.to_s] = @binder.module_binding(import_analysis)
374
340
  end
375
341
  rescue ModuleLoadError, PackageLockError, SemanticError => e
342
+ raise unless collecting
376
343
  errors << ImportResolutionError.new(import:, error: e)
377
344
  end
378
345
  end
379
346
 
380
347
  begin
381
- @async_runtime_installer.install_async_runtime_dependency!(ast, modules, importer_path:, collecting_errors: true)
348
+ @async_runtime_installer.install_async_runtime_dependency!(ast, modules, importer_path:, collecting_errors: collecting)
382
349
  rescue ModuleLoadError, PackageLockError => e
350
+ raise unless collecting
383
351
  errors << ImportResolutionError.new(import: nil, error: e)
384
352
  end
385
353
 
386
354
  begin
387
- @prelude_installer.install_prelude_modules!(ast, modules, importer_path:, collecting_errors: true)
355
+ @prelude_installer.install_prelude_modules!(ast, modules, importer_path:, collecting_errors: collecting)
388
356
  rescue ModuleLoadError, PackageLockError => e
357
+ raise unless collecting
389
358
  errors << ImportResolutionError.new(import: nil, error: e)
390
359
  end
391
360
 
392
- ImportResolution.new(modules: modules.freeze, errors: errors.freeze)
361
+ return ImportResolution.new(modules: modules.freeze, errors: errors.freeze) if collecting
362
+
363
+ modules.freeze
364
+ end
365
+
366
+ def build_global_import_index(ast)
367
+ index = {}
368
+ current_imports = ast.imports.map { |import| import.path.to_s }.to_set
369
+
370
+ @analysis_cache.each_value do |analysis|
371
+ next unless analysis
372
+ next unless analysis.module_name
373
+
374
+ mod_name = analysis.module_name.to_s
375
+ next if current_imports.include?(mod_name)
376
+ next if mod_name == ast.module_name.to_s
377
+
378
+ types = analysis.respond_to?(:types) ? analysis.types : {}
379
+ types.each_key do |type_name|
380
+ type_str = type_name.to_s
381
+ index[type_str] ||= []
382
+ index[type_str] << mod_name unless index[type_str].include?(mod_name)
383
+ end
384
+ end
385
+
386
+ index
393
387
  end
394
388
 
395
389
  # Errors collected per analyzed import path during collecting-mode checks.
@@ -543,6 +537,8 @@ module MilkTea
543
537
  types[decl.name] = Types::Flags.new(decl.name, module_name:)
544
538
  when AST::OpaqueDecl
545
539
  types[decl.name] = Types::Opaque.new(decl.name, module_name:, external: false)
540
+ when AST::UnionDecl
541
+ types[decl.name] = Types::Union.new(decl.name, module_name:)
546
542
  end
547
543
  end
548
544
 
@@ -119,10 +119,7 @@ module MilkTea
119
119
  return @package_manifest_cache[manifest_path] if @package_manifest_cache.key?(manifest_path)
120
120
 
121
121
  @package_manifest_cache[manifest_path] = PackageManifest.load(path)
122
- rescue PackageManifestError
123
- @package_manifest_cache[manifest_path] = nil if manifest_path
124
- nil
125
- end
122
+ end
126
123
 
127
124
  def package_namespace_match?(module_name, package_name)
128
125
  module_name == package_name || module_name.start_with?("#{package_name}.")
@@ -18,6 +18,39 @@ module MilkTea
18
18
  AST::Import.new(path:, alias_name:, line:, column: local_column, length: local_name.length)
19
19
  end
20
20
 
21
+ DECL_KIND = {
22
+ var: { method: :parse_var_decl, reject_attrs: "var" },
23
+ type: { method: :parse_type_alias_decl, reject_attrs: "type" },
24
+ union: { method: :parse_union_decl },
25
+ enum: { method: :parse_enum_decl, enum_class: AST::EnumDecl },
26
+ flags: { method: :parse_enum_decl, enum_class: AST::FlagsDecl },
27
+ opaque: { method: :parse_opaque_decl, reject_attrs: "opaque" },
28
+ interface: { method: :parse_interface_decl, reject_attrs: "interface" },
29
+ event: { method: :parse_event_decl },
30
+ attribute: { method: :parse_attribute_decl, reject_attrs: "attribute" },
31
+ }.freeze
32
+
33
+ def dispatch_decl_kind(kind, visibility:, attributes:)
34
+ info = DECL_KIND.fetch(kind)
35
+ reject_attributes!(attributes, info[:reject_attrs]) if info[:reject_attrs]
36
+
37
+ if info[:enum_class]
38
+ send(info[:method], info[:enum_class], visibility:, attributes:)
39
+ elsif info[:reject_attrs]
40
+ send(info[:method], visibility:)
41
+ else
42
+ send(info[:method], visibility:, attributes:)
43
+ end
44
+ end
45
+
46
+ def parse_const_or_function_decl(visibility:, attributes:)
47
+ if match(:function)
48
+ parse_function_def(visibility:, const: true, attributes:)
49
+ else
50
+ parse_const_decl(visibility:, attributes:)
51
+ end
52
+ end
53
+
21
54
  def parse_declaration
22
55
  attributes = parse_attribute_applications
23
56
  visibility, visibility_token = parse_visibility
@@ -25,38 +58,29 @@ module MilkTea
25
58
  if builtin_attribute_identifier?(peek)
26
59
  raise error(peek, "layout modifiers must use attributes like @[packed] or @[align(...)]")
27
60
  elsif match(:attribute)
28
- reject_attributes!(attributes, "attribute")
29
- parse_attribute_decl(visibility:)
61
+ dispatch_decl_kind(:attribute, visibility:, attributes:)
30
62
  elsif match(:const)
31
- if match(:function)
32
- parse_function_def(visibility:, const: true, attributes:)
33
- else
34
- parse_const_decl(visibility:, attributes:)
35
- end
63
+ parse_const_or_function_decl(visibility:, attributes:)
36
64
  elsif match(:var)
37
- reject_attributes!(attributes, "var")
38
- parse_var_decl(visibility:)
65
+ dispatch_decl_kind(:var, visibility:, attributes:)
39
66
  elsif match(:event)
40
- parse_event_decl(visibility:, attributes:)
67
+ dispatch_decl_kind(:event, visibility:, attributes:)
41
68
  elsif match(:type)
42
- reject_attributes!(attributes, "type")
43
- parse_type_alias_decl(visibility:)
69
+ dispatch_decl_kind(:type, visibility:, attributes:)
44
70
  elsif match(:struct)
45
71
  parse_struct_decl(visibility:, attributes:)
46
72
  elsif match(:union)
47
- parse_union_decl(visibility:, attributes:)
73
+ dispatch_decl_kind(:union, visibility:, attributes:)
48
74
  elsif match(:enum)
49
- parse_enum_decl(AST::EnumDecl, visibility:, attributes:)
75
+ dispatch_decl_kind(:enum, visibility:, attributes:)
50
76
  elsif match(:flags)
51
- parse_enum_decl(AST::FlagsDecl, visibility:, attributes:)
77
+ dispatch_decl_kind(:flags, visibility:, attributes:)
52
78
  elsif match(:variant)
53
79
  parse_variant_decl(visibility:, attributes:)
54
80
  elsif match(:interface)
55
- reject_attributes!(attributes, "interface")
56
- parse_interface_decl(visibility:)
81
+ dispatch_decl_kind(:interface, visibility:, attributes:)
57
82
  elsif match(:opaque)
58
- reject_attributes!(attributes, "opaque")
59
- parse_opaque_decl(visibility:)
83
+ dispatch_decl_kind(:opaque, visibility:, attributes:)
60
84
  elsif match(:extending)
61
85
  reject_attributes!(attributes, "extending")
62
86
  raise error(visibility_token, "public is not allowed on extending blocks") if visibility == :public
@@ -35,14 +35,14 @@ module MilkTea
35
35
  consume(:newline, "expected newline after 'if condition:' in if expression")
36
36
  consume(:indent, "expected indented body in if expression")
37
37
  then_expression = parse_expression
38
- consume_end_of_statement unless block_expression?(then_expression)
38
+ finish_expression_statement(then_expression)
39
39
  consume(:dedent, "expected end of if expression body")
40
40
  consume(:else, "expected 'else' in if expression")
41
41
  consume(:colon, "expected ':' after 'else' in if expression")
42
42
  consume(:newline, "expected newline after 'else:' in if expression")
43
43
  consume(:indent, "expected indented else body in if expression")
44
44
  else_expression = parse_expression
45
- consume_end_of_statement unless block_expression?(else_expression)
45
+ finish_expression_statement(else_expression)
46
46
  consume(:dedent, "expected end of else expression body")
47
47
  end
48
48
 
@@ -97,7 +97,7 @@ module MilkTea
97
97
  end
98
98
  consume(:colon, "expected ':' after match expression arm pattern")
99
99
  value = parse_expression
100
- consume_end_of_statement unless block_expression?(value)
100
+ finish_expression_statement(value)
101
101
  patterns.map do |pattern|
102
102
  AST::MatchExprArm.new(
103
103
  pattern:,
@@ -549,10 +549,7 @@ module MilkTea
549
549
  end
550
550
 
551
551
  parser = self.class.new(tokens, path: @path)
552
- parser.instance_variable_set(:@known_type_names, @known_type_names.dup)
553
- parser.instance_variable_set(:@known_import_aliases, @known_import_aliases.dup)
554
- parser.instance_variable_set(:@known_generic_callable_names, @known_generic_callable_names.dup)
555
- parser.instance_variable_set(:@current_type_param_names, @current_type_param_names.dup)
552
+ parser.apply_parser_context(parser_context)
556
553
 
557
554
  expression = parser.parse_expression
558
555
  parser.skip_newlines
@@ -147,7 +147,7 @@ module MilkTea
147
147
 
148
148
  else_body = parse_block
149
149
  else
150
- consume_end_of_statement unless block_expression?(value)
150
+ finish_expression_statement(value)
151
151
  end
152
152
  else
153
153
  raise error(name_token, "local declaration without initializer requires a type") unless var_type
@@ -366,7 +366,7 @@ module MilkTea
366
366
  if match_arm_expr_form?
367
367
  consume(:colon, "expected ':' after match expression arm pattern")
368
368
  value = parse_expression
369
- consume_end_of_statement unless block_expression?(value)
369
+ finish_expression_statement(value)
370
370
  patterns.map do |pattern|
371
371
  AST::MatchExprArm.new(
372
372
  pattern:,
@@ -585,7 +585,7 @@ module MilkTea
585
585
  token = previous
586
586
  line = token.line
587
587
  value = check(:newline) ? nil : parse_expression
588
- consume_end_of_statement unless block_expression?(value)
588
+ finish_expression_statement(value)
589
589
  AST::ReturnStmt.new(value:, line:, column: token.column, length: token.lexeme.length)
590
590
  end
591
591
 
@@ -768,10 +768,10 @@ module MilkTea
768
768
  operator = previous.lexeme
769
769
  column = previous.column
770
770
  value = parse_expression
771
- consume_end_of_statement unless block_expression?(value)
771
+ finish_expression_statement(value)
772
772
  AST::Assignment.new(target: expression, operator:, value:, line:, column:)
773
773
  else
774
- consume_end_of_statement unless block_expression?(expression)
774
+ finish_expression_statement(expression)
775
775
  AST::ExpressionStmt.new(expression:, line:)
776
776
  end
777
777
  end
@@ -38,6 +38,8 @@ module MilkTea
38
38
  def initialize(ast:, errors: []) = super
39
39
  end
40
40
 
41
+ ParseContext = Data.define(:known_type_names, :known_import_aliases, :known_generic_callable_names, :current_type_param_names)
42
+
41
43
  include Parse::Blocks
42
44
  include Parse::Recovery
43
45
  include Parse::Types
@@ -250,6 +252,10 @@ module MilkTea
250
252
  consume(:newline, "expected end of statement")
251
253
  end
252
254
 
255
+ def finish_expression_statement(value)
256
+ consume_end_of_statement unless block_expression?(value)
257
+ end
258
+
253
259
  def block_expression?(expression)
254
260
  expression.is_a?(AST::ProcExpr) || expression.is_a?(AST::MatchExpr) || expression.is_a?(AST::IfExpr)
255
261
  end
@@ -304,6 +310,26 @@ module MilkTea
304
310
  @current_type_param_names = saved_names
305
311
  end
306
312
 
313
+ # Returns a ParseContext snapshot of the parser state that must propagate
314
+ # to nested parsers (e.g. format string interpolations). When adding
315
+ # parser state that must be cloned, update both this method,
316
+ # apply_parser_context, and the ParseContext Data class.
317
+ def parser_context
318
+ ParseContext.new(
319
+ known_type_names: @known_type_names.dup,
320
+ known_import_aliases: @known_import_aliases.dup,
321
+ known_generic_callable_names: @known_generic_callable_names.dup,
322
+ current_type_param_names: @current_type_param_names.dup,
323
+ )
324
+ end
325
+
326
+ def apply_parser_context(context)
327
+ @known_type_names = context.known_type_names
328
+ @known_import_aliases = context.known_import_aliases
329
+ @known_generic_callable_names = context.known_generic_callable_names
330
+ @current_type_param_names = context.current_type_param_names
331
+ end
332
+
307
333
  def seed_known_names
308
334
  MilkTea::BUILTIN_TYPE_NAMES.each { |name| @known_type_names[name] = true }
309
335