mt-lang 0.3.34 → 0.3.38

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/docs/index.html +5 -5
  4. data/docs/language-design.md +3 -3
  5. data/docs/language-manual.md +1 -1
  6. data/lib/milk_tea/base.rb +1 -1
  7. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +12 -2
  8. data/lib/milk_tea/bindings/bindgen.rb +5 -0
  9. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +4 -0
  10. data/lib/milk_tea/core/c_backend/expressions.rb +25 -3
  11. data/lib/milk_tea/core/c_backend/reinterpret.rb +2 -2
  12. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +15 -2
  13. data/lib/milk_tea/core/c_backend/type_collectors.rb +39 -2
  14. data/lib/milk_tea/core/c_backend.rb +12 -0
  15. data/lib/milk_tea/core/compile_time.rb +109 -74
  16. data/lib/milk_tea/core/lexer.rb +12 -0
  17. data/lib/milk_tea/core/lowering/block.rb +6 -7
  18. data/lib/milk_tea/core/lowering/declarations.rb +40 -1
  19. data/lib/milk_tea/core/lowering/loops.rb +25 -1
  20. data/lib/milk_tea/core/lowering/resolve.rb +13 -6
  21. data/lib/milk_tea/core/lowering/utils.rb +6 -2
  22. data/lib/milk_tea/core/lowering.rb +12 -0
  23. data/lib/milk_tea/core/module_loader.rb +59 -37
  24. data/lib/milk_tea/core/parser/statements.rb +35 -7
  25. data/lib/milk_tea/core/parser.rb +12 -0
  26. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +4 -1
  27. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +2 -1
  28. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +8 -20
  29. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +2 -1
  30. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +12 -2
  31. data/lib/milk_tea/core/semantic_analyzer/statements.rb +10 -10
  32. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +10 -12
  33. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +14 -5
  34. data/lib/milk_tea/core/semantic_analyzer.rb +6 -31
  35. data/lib/milk_tea/lsp/server/code_actions.rb +12 -2
  36. data/lib/milk_tea/lsp/server/formatting.rb +172 -58
  37. data/lib/milk_tea/lsp/server/inlay_hints.rb +33 -11
  38. data/lib/milk_tea/lsp/server/selection_range.rb +4 -4
  39. data/lib/milk_tea/lsp/server/semantic_tokens.rb +9 -5
  40. data/lib/milk_tea/lsp/server/type_hierarchy.rb +5 -5
  41. data/lib/milk_tea/packages/manifest.rb +9 -0
  42. data/lib/milk_tea/tooling/linter/visitors.rb +4 -1
  43. data/std/box2d.mt +14 -14
  44. data/std/c/box2d.mt +19 -19
  45. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d0786ef5ca9025e069a44b7ea3915199110e1f2397d294ed81122a308c79e2e
4
- data.tar.gz: 42e3fa0077192377fa7da61cea865fd04e8ef49d66343c71a1bd5e2e1caa8b59
3
+ metadata.gz: 952280dd2ad14bc3b5751fc7e8a3d29b39363293b1436cc1b3b546c31c938427
4
+ data.tar.gz: e0bdf3468681b07914143c32b0b9cc2470cdbeaf55cf81994186003dc406261b
5
5
  SHA512:
6
- metadata.gz: fd4a874279ff48b7d0f1de8baf092e92e01ede8a4c969133855c9dd9c9f061490c11ff29dfc24905466fe1df56d1d53a86a5c156ce1cbe5efeed60d78f5a21e8
7
- data.tar.gz: 30bb123ddb1939f7694c62dae186152b8f592d78826e7b4aa416c6a010202d07bda5474b72014114d8064b7168ff02b6b7dd252a8b7d007d9b9bcae8b31b4786
6
+ metadata.gz: 9dcf6f6c955f1428b5374315782fda121669ae3c12a8c84d683205cb44bb4ca7903465e70447860e89c0753695f01af513903e93b82987838fa724be19fc7b2f
7
+ data.tar.gz: 67c259de17bdd81a4f5785d7dbe05cf39ab959ea51c1f27ed2b05a516ed97ce91a4b2291aa415e0abaa695337f1fc1825ead4acdfceb480a9be4db3cd3a24dd4
data/README.md CHANGED
@@ -939,7 +939,7 @@ See module source for full method surface. Iterator forms:
939
939
 
940
940
  Text categories:
941
941
 
942
- - `str` -> string view. The `+` operator concatenates two `str` values, allocating a new heap-backed string. For loops or repeated concatenation, prefer `string.String` for amortized performance.
942
+ - `str` -> string view. The `+` operator concatenates two `str` values into a per-thread scratch buffer (no heap allocation), returning a borrowed `str`; the result stays valid while cumulative concatenations on that thread remain within the scratch buffer budget. For loops or repeated concatenation, prefer `string.String` for amortized building.
943
943
  - `cstr` -> C ABI string
944
944
  - `str_buffer[N]` -> fixed-capacity mutable UTF-8 text buffer
945
945
 
@@ -959,7 +959,7 @@ Format strings:
959
959
 
960
960
  - `f"count=#{count}"` has type `str`.
961
961
  - Allowed interpolations: `str`, `cstr`, `bool`, numeric primitives, integer-backed enums and flags, plus types implementing `format_len() -> ptr_uint` and `append_format(output: ref[std.string.String]) -> void`.
962
- - `f"..."` is a borrowed temporary on the stack it cannot be returned from a function as `str`. Use `std.fmt.format(f"...")` returning `string.String` when ownership must escape.
962
+ - Dynamic `f"..."` expressions build into a heap-backed temporary that the compiler releases after use. A dynamic `f"..."` may be returned from a function (or stored in a `str` local): ownership of that buffer transfers to the caller. `std.fmt.format(f"...")` returns an owned `string.String` when you want explicit owned-text lifetime management.
963
963
  - Float and double interpolations support `:.N` precision.
964
964
  - Integer primitive and integer-backed enum/flags interpolations support `:x` (lowercase hex) and `:X` (uppercase hex).
965
965
  - Integer primitive and integer-backed enum/flags interpolations support `:o` / `:O` (octal) and `:b` / `:B` (binary).
data/docs/index.html CHANGED
@@ -1262,7 +1262,7 @@ let d: dyn[Drawable] = adapt[Drawable](ref_of(entity))</code></pre>
1262
1262
  <tr><td><code>ptr_int</code> <code>ptr_uint</code></td><td>Pointer-sized integers</td></tr>
1263
1263
  <tr><td><code>float</code> <code>double</code></td><td>Floating-point</td></tr>
1264
1264
  <tr><td><code>void</code></td><td>No value</td></tr>
1265
- <tr><td><code>str</code></td><td>UTF-8 string view (borrowed). The <code>+</code> operator concatenates two <code>str</code> values, allocating a new heap-backed string.</td></tr>
1265
+ <tr><td><code>str</code></td><td>UTF-8 string view (borrowed). The <code>+</code> operator concatenates two <code>str</code> values into a per-thread scratch buffer (no allocation); the result is valid while the scratch budget lasts.</td></tr>
1266
1266
  <tr><td><code>cstr</code></td><td>NUL-terminated C string</td></tr>
1267
1267
  <tr><td><code>vec2</code> <code>vec3</code> <code>vec4</code></td><td>Float vectors with <code>.x .y .z .w</code></td></tr>
1268
1268
  <tr><td><code>ivec2</code> <code>ivec3</code> <code>ivec4</code></td><td>Integer vectors</td></tr>
@@ -1871,7 +1871,7 @@ function field_equal[T](a: const_ptr[T], b: const_ptr[T]) -&gt; bool:
1871
1871
  <div class="table-wrap">
1872
1872
  <table class="attr-table">
1873
1873
  <tr><th>Type</th><th>Ownership</th><th>Use Case</th></tr>
1874
- <tr><td><code>str</code></td><td>Borrowed</td><td>Read-only UTF-8 view (literals, format strings, slicing)</td></tr>
1874
+ <tr><td><code>str</code></td><td>Borrowed</td><td>Read-only UTF-8 view (literals, format strings, slicing); a stored or returned dynamic format string carries its own heap buffer</td></tr>
1875
1875
  <tr><td><code>cstr</code></td><td>Borrowed</td><td>NUL-terminated C ABI string (<code>c"hello"</code>)</td></tr>
1876
1876
  <tr><td><code>str_buffer[N]</code></td><td>Owned (stack)</td><td>Fixed-capacity mutable UTF-8 builder</td></tr>
1877
1877
  <tr><td><code>std.string.String</code></td><td>Owned (heap)</td><td>Growable owned text via <code>fmt.format(f"...")</code></td></tr>
@@ -1889,9 +1889,9 @@ let info = f"value=#{pi:.4}"
1889
1889
  ## Hex format for ints
1890
1890
  let hex = f"address=#{ptr_value:x}"
1891
1891
 
1892
- ## Owned text (escape stack lifetime)
1892
+ ## Owned text with explicit release
1893
1893
  import std.fmt
1894
- let owned = fmt.format(f"count=#{count}") ## -> string.String</code></pre>
1894
+ let owned = fmt.format(f"count=#{count}") ## -> string.String (call .release())</code></pre>
1895
1895
  </div>
1896
1896
  <p>Interpolated expressions must be <code>str</code>, <code>cstr</code>, <code>bool</code>, a numeric primitive, an integer-backed enum or flags type, or a type implementing <code>format_len()</code> and <code>append_format()</code> (custom formatting hooks). The compiler lowers <code>fmt.format(f"...")</code>, <code>str_buffer.append_format(f"...")</code>, and <code>string.String.append_format(f"...")</code> directly to the formatted output without an intermediate allocation.</p>
1897
1897
 
@@ -2242,7 +2242,7 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
2242
2242
  <tr><td><code>std.gzip</code> <code>std.tar</code></td><td>Compression</td></tr>
2243
2243
  <tr><td><code>std.sync</code> <code>std.thread</code> <code>std.jobs</code></td><td>Concurrency</td></tr>
2244
2244
  <tr><td><code>std.fsm</code> <code>std.goap</code> <code>std.behavior_tree</code></td><td>AI / State machines</td></tr>
2245
- <tr><td><code>std.cell</code></td><td>Shared mutable cell allocation</td></tr>
2245
+ <tr><td><code>std.box</code></td><td>Explicit single-value heap storage for shared mutable state</td></tr>
2246
2246
  </table>
2247
2247
  </div>
2248
2248
  </section>
@@ -28,7 +28,7 @@ The output target is beautiful C. The generated C should be readable enough that
28
28
  - No macro system that rewrites arbitrary ASTs
29
29
  - No garbage collector
30
30
  - No implicit conversions between unrelated primitive types
31
- - No user-invisible allocation for strings, collections, ordinary values, or method calls. Capturing a `proc` may allocate a ref-counted closure environment as part of proc value semantics; owned text and other storage use explicit allocating surfaces.
31
+ - No user-invisible allocation for collections, ordinary values, or method calls. Capturing a `proc` may allocate a ref-counted closure environment as part of proc value semantics. Dynamic format strings are the one text case that builds a heap-backed temporary, released after use (see the text-construction rule in §1); other owned text and storage use explicit allocating surfaces.
32
32
 
33
33
  ## Design rules
34
34
 
@@ -38,7 +38,7 @@ If code allocates, takes an address, dereferences a raw pointer, performs an FFI
38
38
 
39
39
  FFI visibility belongs at the declaration site. Raw `external` files expose exact ABI types. Imported foreign declarations may project those raw types into ordinary Milk Tea types, but the projection rule, temporary-storage rule, and ownership rule must be declared there instead of repeated at every call site.
40
40
 
41
- The same rule applies to text construction. Plain string literals and format string literals are borrowed `str` values. The `+` operator on `str` allocates a new heap-backed `str` — the one everyday convenience that does allocate. For loops or amortized building, `string.String` and `str_buffer[N]` remain the explicit surfaces with visible cost. Any other surface that builds owned text must say so explicitly, for example `std.fmt.format(f"...")` when ownership must escape.
41
+ The same rule applies to text construction. Plain string literals are borrowed `str` values. A dynamic format string `f"..."` builds into a heap-backed temporary that the compiler releases after the surrounding statement; returning it (or storing it in a `str` local) transfers ownership of that buffer to the caller. The `+` operator on `str` concatenates into a per-thread scratch buffer without allocating — the result is a borrowed `str` valid until that buffer's budget is exhausted. For loops or amortized building, and whenever ownership should stay explicit and controllable, `string.String` and `str_buffer[N]` remain the explicit surfaces with visible cost; `std.fmt.format(f"...")` is the explicit owned-text path returning a `string.String`.
42
42
 
43
43
  ### 2. C is the ABI ground truth
44
44
 
@@ -762,7 +762,7 @@ Built-in operators should match familiar C behavior where possible:
762
762
 
763
763
  No user-defined operator overloading.
764
764
 
765
- The `+` operator also concatenates two `str` values, allocating a new heap-backed string; `cstr` values are not concatenable. This is the one operator exception to the arithmetic rules, and it is described in the text-construction rule under Design rules §1.
765
+ The `+` operator also concatenates two `str` values into a per-thread scratch buffer, returning a borrowed `str`; `cstr` values are not concatenable. This is the one operator exception to the arithmetic rules, and it is described in the text-construction rule under Design rules §1.
766
766
 
767
767
  Built-in vector, matrix, and quaternion types support component-wise arithmetic with the standard operators:
768
768
 
@@ -1072,7 +1072,7 @@ Rules:
1072
1072
  11. `+`, `-`
1073
1073
  12. `*`, `/`, `%`
1074
1074
 
1075
- The `+` operator also concatenates two `str` operands, producing a new heap-backed `str` (§2.3). It does not concatenate `cstr` or mixed `str`/`cstr` operands.
1075
+ The `+` operator also concatenates two `str` operands into a per-thread scratch buffer, producing a borrowed `str` (no heap allocation) whose validity is bounded by the scratch buffer budget (§2.3). It does not concatenate `cstr` or mixed `str`/`cstr` operands.
1076
1076
 
1077
1077
  ### 5.4 Assignment operators
1078
1078
 
data/lib/milk_tea/base.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "pathname"
4
4
 
5
5
  module MilkTea
6
- VERSION = "0.3.34"
6
+ VERSION = "0.3.38"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -22,8 +22,14 @@ module MilkTea
22
22
  "cstr"
23
23
  else
24
24
  pointee = pointer_candidate.sub(/\s*\*\z/, "")
25
- pointer_name = top_level_const_qualified?(pointee) ? "const_ptr" : "ptr"
26
- "#{pointer_name}[#{map_c_type(pointee, context:)}]"
25
+ if function_type_typedef?(pointee)
26
+ # `Name *` where Name is a function-type typedef is a function
27
+ # pointer, which is exactly what the `fn` type already is.
28
+ map_c_type(pointee, context:)
29
+ else
30
+ pointer_name = top_level_const_qualified?(pointee) ? "const_ptr" : "ptr"
31
+ "#{pointer_name}[#{map_c_type(pointee, context:)}]"
32
+ end
27
33
  end
28
34
  else
29
35
  unqualified = strip_qualifiers(normalized)
@@ -515,6 +521,10 @@ module MilkTea
515
521
  qual_type.end_with?("*")
516
522
  end
517
523
 
524
+ def function_type_typedef?(qual_type)
525
+ @function_type_typedef_names.include?(strip_qualifiers(qual_type))
526
+ end
527
+
518
528
  def c_string_pointer?(qual_type)
519
529
  pointee = qual_type.sub(/\s*\*\z/, "")
520
530
  unqualified = strip_qualifiers(pointee)
@@ -147,6 +147,11 @@ module MilkTea
147
147
  @visible_typedef_names = top_level_nodes.filter_map do |node|
148
148
  node["name"] if node["kind"] == "TypedefDecl" && allowed_declaration_name?(node["name"])
149
149
  end
150
+ @function_type_typedef_names = top_level_nodes.filter_map do |node|
151
+ if node["kind"] == "TypedefDecl" && allowed_declaration_name?(node["name"]) && extract_function_proto(node) && !function_pointer_type?(type_qual_type(node))
152
+ node["name"]
153
+ end
154
+ end
150
155
  build_alias_maps(top_level_nodes)
151
156
 
152
157
  declarations = []
@@ -139,6 +139,8 @@ module MilkTea
139
139
  end
140
140
  when Types::Task
141
141
  [task_type_name(type)]
142
+ when Types::Dyn
143
+ [dyn_type_name(type)]
142
144
  when Types::Proc
143
145
  [proc_type_name(type)]
144
146
  when Types::GenericInstance
@@ -153,6 +155,8 @@ module MilkTea
153
155
  end
154
156
  when Types::Function
155
157
  []
158
+ when Types::Tuple
159
+ [tuple_type_name(type)]
156
160
  when Types::Struct, Types::StructInstance, Types::Union, Types::Variant, Types::VariantInstance, Types::Event, Types::Subscription
157
161
  [named_type_c_name(type)]
158
162
  when Types::VariantArmPayload
@@ -347,7 +347,22 @@ module MilkTea
347
347
  def emit_address_of_operand(expression)
348
348
  return emit_expression(expression.operand) if expression.is_a?(IR::Unary) && expression.operator == "*"
349
349
 
350
- "&#{wrap_expression(expression)}"
350
+ return "&#{wrap_expression(expression)}" if c_expression_lvalue?(expression)
351
+
352
+ "&(#{c_type(expression.type)}[1]){ #{emit_expression(expression)} }[0]"
353
+ end
354
+
355
+ def c_expression_lvalue?(expression)
356
+ case expression
357
+ when IR::Name, IR::CheckedIndex, IR::CheckedSpanIndex, IR::AggregateLiteral, IR::ArrayLiteral, IR::VariantLiteral, IR::ZeroInit
358
+ true
359
+ when IR::Member, IR::Index
360
+ c_expression_lvalue?(expression.receiver)
361
+ when IR::Unary
362
+ expression.operator == "*"
363
+ else
364
+ false
365
+ end
351
366
  end
352
367
 
353
368
  def emit_cast_operand(expression)
@@ -464,7 +479,12 @@ module MilkTea
464
479
  def emit_addressof_field_initializer(field_type, value)
465
480
  c_type_name = named_type_c_name(field_type)
466
481
  inner = value.expression
467
- "((#{c_type_name}*)memcpy(malloc(sizeof(#{c_type_name})), &(#{emit_expression(inner)}), sizeof(#{c_type_name})))"
482
+ source = if c_expression_lvalue?(inner)
483
+ "&(#{emit_expression(inner)})"
484
+ else
485
+ "&(#{c_type(field_type)}[1]){ #{emit_expression(inner)} }[0]"
486
+ end
487
+ "((#{c_type_name}*)memcpy(malloc(sizeof(#{c_type_name})), #{source}, sizeof(#{c_type_name})))"
468
488
  end
469
489
 
470
490
  def emit_cyclic_array_initializer(field_type, value)
@@ -479,8 +499,10 @@ module MilkTea
479
499
  init = emit_initializer(value)
480
500
  source_expr = if init.start_with?("{")
481
501
  "&(#{c_type(field_type)})#{init}"
482
- else
502
+ elsif c_expression_lvalue?(value)
483
503
  "&(#{init})"
504
+ else
505
+ "&(#{c_type(field_type)}[1]){ #{init} }[0]"
484
506
  end
485
507
  "((#{field_c_name}*)memcpy(malloc(sizeof(#{field_c_name})), #{source_expr}, sizeof(#{field_c_name})))"
486
508
  end
@@ -116,13 +116,13 @@ module MilkTea
116
116
  return false if expression.expression.type.is_a?(Types::Null)
117
117
 
118
118
  c_type(expression.target_type) == c_type(expression.expression.type)
119
- rescue StandardError
119
+ rescue CBackendError
120
120
  false
121
121
  end
122
122
 
123
123
  def identity_reinterpret?(target_type, source_type)
124
124
  c_type(target_type) == c_type(source_type)
125
- rescue StandardError
125
+ rescue CBackendError
126
126
  false
127
127
  end
128
128
 
@@ -104,7 +104,17 @@ module MilkTea
104
104
  def emit_struct_equality_helpers
105
105
  struct_decls_by_linkage = (emitted_aggregate_structs + collect_generic_struct_decls).each_with_object({}) { |decl, map| map[decl.linkage_name] = decl }
106
106
  struct_equality_types
107
- .filter_map { |type| type.is_a?(Types::VariantArmPayload) ? type : struct_decls_by_linkage[named_type_c_name(type)] }
107
+ .filter_map do |type|
108
+ if type.is_a?(Types::VariantArmPayload)
109
+ type
110
+ elsif struct_decls_by_linkage.key?(named_type_c_name(type))
111
+ struct_decls_by_linkage[named_type_c_name(type)]
112
+ elsif type.is_a?(Types::Struct)
113
+ # External structs (raw ABI bindings) are not lowered into
114
+ # @program.structs, but their field layout is still known.
115
+ type
116
+ end
117
+ end
108
118
  .flat_map { |decl_or_type| emit_struct_equality_helper(decl_or_type) }
109
119
  end
110
120
 
@@ -112,11 +122,14 @@ module MilkTea
112
122
  if struct_decl_or_type.is_a?(IR::StructDecl)
113
123
  outer_c = struct_decl_or_type.linkage_name
114
124
  fields = struct_decl_or_type.fields
115
- else
125
+ elsif struct_decl_or_type.is_a?(Types::VariantArmPayload)
116
126
  payload = struct_decl_or_type
117
127
  outer_c = named_type_c_name(payload)
118
128
  arm_fields = payload.variant_type.arm(payload.arm_name) || {}
119
129
  fields = arm_fields.map { |name, field_type| IR::Field.new(name:, type: field_type) }
130
+ else
131
+ outer_c = named_type_c_name(struct_decl_or_type)
132
+ fields = struct_decl_or_type.fields.map { |name, field_type| IR::Field.new(name:, type: field_type) }
120
133
  end
121
134
 
122
135
  lines = ["static bool mt_struct_eq_#{outer_c}(struct #{outer_c} left, struct #{outer_c} right) {"]
@@ -266,6 +266,10 @@ module MilkTea
266
266
  simd_types = []
267
267
  visited = {}
268
268
 
269
+ all_emitted_top_level_values.each do |value|
270
+ collect_simd_type(value.type, simd_types, visited)
271
+ end
272
+
269
273
  emitted_functions.each do |function|
270
274
  collect_simd_type(function.return_type, simd_types, visited)
271
275
  function.params.each do |param|
@@ -280,6 +284,16 @@ module MilkTea
280
284
  end
281
285
  end
282
286
 
287
+ @program.unions.each do |union_decl|
288
+ union_decl.fields.each do |field|
289
+ collect_simd_type(field.type, simd_types, visited)
290
+ end
291
+ end
292
+
293
+ each_variant_arm_field_type do |field_type|
294
+ collect_simd_type(field_type, simd_types, visited)
295
+ end
296
+
283
297
  simd_types.uniq
284
298
  end
285
299
 
@@ -299,9 +313,32 @@ module MilkTea
299
313
  return unless type
300
314
  return if visited[type]
301
315
 
302
- if type.is_a?(Types::Simd)
316
+ visited[type] = true
317
+
318
+ case type
319
+ when Types::Simd
303
320
  simd_types << type
304
- visited[type] = true
321
+ when Types::Nullable
322
+ collect_simd_type(type.base, simd_types, visited)
323
+ when Types::GenericInstance
324
+ type.arguments.each do |argument|
325
+ collect_simd_type(argument, simd_types, visited) unless argument.is_a?(Types::LiteralTypeArg)
326
+ end
327
+ when Types::Function
328
+ type.params.each do |param|
329
+ collect_simd_type(param.type, simd_types, visited)
330
+ end
331
+ collect_simd_type(type.return_type, simd_types, visited)
332
+ when Types::Struct, Types::Union
333
+ type.fields.each_value do |field_type|
334
+ collect_simd_type(field_type, simd_types, visited)
335
+ end
336
+ when Types::Variant
337
+ type.arm_names.each do |arm_name|
338
+ type.arm(arm_name).each_value do |field_type|
339
+ collect_simd_type(field_type, simd_types, visited)
340
+ end
341
+ end
305
342
  end
306
343
  end
307
344
 
@@ -27,6 +27,18 @@ module MilkTea
27
27
  def code
28
28
  "backend/internal"
29
29
  end
30
+
31
+ def to_diagnostic(path: nil)
32
+ Diagnostic.new(
33
+ path: @path || path,
34
+ line: @line,
35
+ column: @column,
36
+ length: nil,
37
+ code: code,
38
+ message: message,
39
+ severity: :error,
40
+ )
41
+ end
30
42
  end
31
43
 
32
44
  class CBackend
@@ -6,16 +6,27 @@ module MilkTea
6
6
  module CompileTime
7
7
  Layout = ::MilkTea::Types::Layout
8
8
 
9
- class ReturnValue < StandardError
10
- attr_reader :value
9
+ # Carries the value of a `return` statement out of the block evaluator as
10
+ # an ordinary value instead of an exception; callers unwrap it when present.
11
+ ReturnOutcome = Data.define(:value)
11
12
 
12
- def initialize(value)
13
- @value = value
14
- super("return #{value.inspect}")
13
+ class Error < StandardError
14
+ def code
15
+ "compile_time/error"
15
16
  end
16
- end
17
17
 
18
- class Error < StandardError; end
18
+ def to_diagnostic(path: nil)
19
+ Diagnostic.new(
20
+ path: path,
21
+ line: nil,
22
+ column: nil,
23
+ length: nil,
24
+ code: code,
25
+ message: message,
26
+ severity: :error,
27
+ )
28
+ end
29
+ end
19
30
 
20
31
  def self.evaluate(expression, resolve_identifier:, resolve_member_access:, resolve_type_ref: nil, resolve_call: nil)
21
32
  Evaluator.new(
@@ -78,6 +89,10 @@ module MilkTea
78
89
  nil
79
90
  when AST::ExpressionList
80
91
  expression.elements.filter_map { |element| evaluate(element) }
92
+ when AST::RangeExpr
93
+ start_val = evaluate(expression.start_expr)
94
+ end_val = evaluate(expression.end_expr)
95
+ start_val.is_a?(Integer) && end_val.is_a?(Integer) ? (start_val...end_val).to_a : nil
81
96
  when AST::IntegerLiteral, AST::FloatLiteral, AST::BooleanLiteral
82
97
  expression.value
83
98
  when AST::StringLiteral
@@ -184,6 +199,8 @@ module MilkTea
184
199
  right = evaluate(expression.right)
185
200
 
186
201
  case expression.operator
202
+ when ".."
203
+ left.is_a?(Integer) && right.is_a?(Integer) ? (left...right).to_a : nil
187
204
  when "=="
188
205
  CompileTime.equality_result(left, right)
189
206
  when "!="
@@ -243,35 +260,45 @@ module MilkTea
243
260
  result = nil
244
261
 
245
262
  statements.each do |statement|
246
- case statement
247
- when AST::LocalDecl
248
- result = evaluate_local_decl(statement, scopes:)
249
- when AST::ReturnStmt
250
- value = statement.value ? evaluate_expression(statement.value, scopes:) : nil
251
- raise ReturnValue.new(value)
252
- when AST::WhileStmt
253
- result = evaluate_while(statement, scopes:)
254
- when AST::ForStmt
255
- result = evaluate_for(statement, scopes:)
256
- when AST::Assignment
257
- result = evaluate_assignment(statement, scopes:)
258
- when AST::IfStmt
259
- result = evaluate_if(statement, scopes:)
260
- when AST::ExpressionStmt
261
- evaluate_expression(statement.expression, scopes:)
262
- when AST::PassStmt, AST::BreakStmt, AST::ContinueStmt
263
- # no-op at compile time
264
- when AST::EmitStmt
265
- # evaluated during lowering
266
- result = nil
267
- else
268
- result = nil
269
- end
263
+ outcome = evaluate_statement(statement, scopes:)
264
+ return outcome if outcome.is_a?(ReturnOutcome)
265
+
266
+ result = outcome
270
267
  end
271
268
 
272
269
  result
273
270
  end
274
271
 
272
+ def evaluate_statement(statement, scopes:)
273
+ case statement
274
+ when AST::LocalDecl
275
+ evaluate_local_decl(statement, scopes:)
276
+ when AST::ReturnStmt
277
+ value = statement.value ? evaluate_expression(statement.value, scopes:) : nil
278
+ ReturnOutcome.new(value)
279
+ when AST::WhileStmt
280
+ evaluate_while(statement, scopes:)
281
+ when AST::ForStmt
282
+ evaluate_for(statement, scopes:)
283
+ when AST::MatchStmt
284
+ evaluate_match(statement, scopes:)
285
+ when AST::Assignment
286
+ evaluate_assignment(statement, scopes:)
287
+ when AST::IfStmt
288
+ evaluate_if(statement, scopes:)
289
+ when AST::ExpressionStmt
290
+ evaluate_expression(statement.expression, scopes:)
291
+ when AST::PassStmt, AST::BreakStmt, AST::ContinueStmt
292
+ # no-op at compile time
293
+ nil
294
+ when AST::EmitStmt
295
+ # emitted declarations are collected during lowering
296
+ nil
297
+ else
298
+ nil
299
+ end
300
+ end
301
+
275
302
  def evaluate_expression(expression, scopes:)
276
303
  case expression
277
304
  when AST::Identifier
@@ -304,11 +331,34 @@ module MilkTea
304
331
  value = evaluate_expression(assignment.value, scopes:)
305
332
  case assignment.target
306
333
  when AST::Identifier
334
+ if assignment.operator != "="
335
+ current = @variables[assignment.target.name]
336
+ value = apply_compile_time_binary(assignment.operator.chomp("="), current, value)
337
+ end
307
338
  @variables[assignment.target.name] = value
308
339
  end
309
340
  value
310
341
  end
311
342
 
343
+ def apply_compile_time_binary(operator, left, right)
344
+ case operator
345
+ when "+" then left.is_a?(Numeric) && right.is_a?(Numeric) ? left + right : nil
346
+ when "-" then left.is_a?(Numeric) && right.is_a?(Numeric) ? left - right : nil
347
+ when "*" then left.is_a?(Numeric) && right.is_a?(Numeric) ? left * right : nil
348
+ when "/" then left.is_a?(Numeric) && right.is_a?(Numeric) && !zero_numeric?(right) ? left / right : nil
349
+ when "%" then left.is_a?(Integer) && right.is_a?(Integer) && !right.zero? ? left % right : nil
350
+ when "&" then left.is_a?(Integer) && right.is_a?(Integer) ? left & right : nil
351
+ when "|" then left.is_a?(Integer) && right.is_a?(Integer) ? left | right : nil
352
+ when "^" then left.is_a?(Integer) && right.is_a?(Integer) ? left ^ right : nil
353
+ when "<<" then left.is_a?(Integer) && right.is_a?(Integer) ? left << right : nil
354
+ when ">>" then left.is_a?(Integer) && right.is_a?(Integer) ? left >> right : nil
355
+ end
356
+ end
357
+
358
+ def zero_numeric?(value)
359
+ (value.is_a?(Integer) && value.zero?) || (value.is_a?(Float) && value.zero?)
360
+ end
361
+
312
362
  def evaluate_while(statement, scopes:)
313
363
  result = nil
314
364
  iterations = 0
@@ -320,15 +370,8 @@ module MilkTea
320
370
  break unless CompileTime.boolean_value?(condition)
321
371
 
322
372
  statement.body.each do |body_stmt|
323
- case body_stmt
324
- when AST::ReturnStmt
325
- value = body_stmt.value ? evaluate_expression(body_stmt.value, scopes:) : nil
326
- raise ReturnValue.new(value)
327
- when AST::Assignment
328
- evaluate_assignment(body_stmt, scopes:)
329
- when AST::ExpressionStmt
330
- evaluate_expression(body_stmt.expression, scopes:)
331
- end
373
+ outcome = evaluate_statement(body_stmt, scopes:)
374
+ return outcome if outcome.is_a?(ReturnOutcome)
332
375
  end
333
376
  iterations += 1
334
377
  end
@@ -348,19 +391,8 @@ module MilkTea
348
391
  iterable.each do |element|
349
392
  @variables[loop_var_name] = element
350
393
  statement.body.each do |body_stmt|
351
- case body_stmt
352
- when AST::ReturnStmt
353
- value = body_stmt.value ? evaluate_expression(body_stmt.value, scopes:) : nil
354
- raise ReturnValue.new(value)
355
- when AST::Assignment
356
- evaluate_assignment(body_stmt, scopes:)
357
- when AST::ExpressionStmt
358
- evaluate_expression(body_stmt.expression, scopes:)
359
- when AST::IfStmt
360
- result = evaluate_if(body_stmt, scopes:)
361
- when AST::WhileStmt
362
- result = evaluate_while(body_stmt, scopes:)
363
- end
394
+ outcome = evaluate_statement(body_stmt, scopes:)
395
+ return outcome if outcome.is_a?(ReturnOutcome)
364
396
  end
365
397
  end
366
398
 
@@ -372,15 +404,8 @@ module MilkTea
372
404
  condition = evaluate_expression(branch.condition, scopes:)
373
405
  if CompileTime.boolean_value?(condition) && condition
374
406
  branch.body.each do |body_stmt|
375
- case body_stmt
376
- when AST::ReturnStmt
377
- value = body_stmt.value ? evaluate_expression(body_stmt.value, scopes:) : nil
378
- raise ReturnValue.new(value)
379
- when AST::Assignment
380
- evaluate_assignment(body_stmt, scopes:)
381
- when AST::ExpressionStmt
382
- evaluate_expression(body_stmt.expression, scopes:)
383
- end
407
+ outcome = evaluate_statement(body_stmt, scopes:)
408
+ return outcome if outcome.is_a?(ReturnOutcome)
384
409
  end
385
410
  return condition
386
411
  end
@@ -388,15 +413,26 @@ module MilkTea
388
413
 
389
414
  if statement.else_body
390
415
  statement.else_body.each do |body_stmt|
391
- case body_stmt
392
- when AST::ReturnStmt
393
- value = body_stmt.value ? evaluate_expression(body_stmt.value, scopes:) : nil
394
- raise ReturnValue.new(value)
395
- when AST::Assignment
396
- evaluate_assignment(body_stmt, scopes:)
397
- when AST::ExpressionStmt
398
- evaluate_expression(body_stmt.expression, scopes:)
416
+ outcome = evaluate_statement(body_stmt, scopes:)
417
+ return outcome if outcome.is_a?(ReturnOutcome)
418
+ end
419
+ end
420
+
421
+ nil
422
+ end
423
+
424
+ def evaluate_match(statement, scopes:)
425
+ scrutinee = evaluate_expression(statement.expression, scopes:)
426
+ return nil unless scrutinee
427
+
428
+ statement.arms.each do |arm|
429
+ wildcard = arm.pattern.is_a?(AST::Identifier) && arm.pattern.name == "_"
430
+ if wildcard || CompileTime.equality_result(scrutinee, evaluate_expression(arm.pattern, scopes:)) == true
431
+ arm.body.each do |body_stmt|
432
+ outcome = evaluate_statement(body_stmt, scopes:)
433
+ return outcome if outcome.is_a?(ReturnOutcome)
399
434
  end
435
+ return scrutinee
400
436
  end
401
437
  end
402
438
 
@@ -445,9 +481,8 @@ module MilkTea
445
481
  initial_vars[param.name] = arg_value
446
482
  end
447
483
  ctx = BlockContext.new(@checker, initial_variables: initial_vars)
448
- ctx.evaluate_block(func.ast.body, scopes:)
449
- rescue ReturnValue => e
450
- e.value
484
+ result = ctx.evaluate_block(func.ast.body, scopes:)
485
+ result.is_a?(ReturnOutcome) ? result.value : result
451
486
  end
452
487
  end
453
488
 
@@ -506,7 +541,7 @@ module MilkTea
506
541
  end
507
542
 
508
543
  def self.core_member_handles(type)
509
- type.members.map { |name, value| Types::MemberHandle.new(nil, name, value) }
544
+ type.members.map { |name| Types::MemberHandle.new(nil, name, type.member_value(name)) }
510
545
  end
511
546
 
512
547
  def self.core_evaluate_type_returning(
@@ -26,6 +26,18 @@ module MilkTea
26
26
  def code
27
27
  "lex/error"
28
28
  end
29
+
30
+ def to_diagnostic(path: nil)
31
+ Diagnostic.new(
32
+ path: @path || path,
33
+ line: @line,
34
+ column: @column,
35
+ length: nil,
36
+ code: code,
37
+ message: message,
38
+ severity: :error,
39
+ )
40
+ end
29
41
  end
30
42
 
31
43
  class Lexer