mt-lang 0.3.42 → 0.3.43

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 927d840392dcd8afb43392ab7ef3f0b620ff267a13107c1f539ad17596364c6c
4
- data.tar.gz: f9f09a4989def57fbb058b3cc1ceeea62a23d1bfcec0c6bfe030c58e4ea26e8e
3
+ metadata.gz: 6842b1cfb6eac5181fdbcd31cd90dbb9f436bb0f3c7b0cc25d49a8c1f8e13a8b
4
+ data.tar.gz: 4e97091f3da86a115656c6ec7d9bb10fe5cd65360bd59a203fc6595111285aff
5
5
  SHA512:
6
- metadata.gz: a7d0a57ff106227cc3e59d3ac1b425ad8cc0deecf4f0f9016f09984074cd624f43ddc7e178935c4af31a472cdd79eff6275c18a410030d678c0e1a0567666343
7
- data.tar.gz: 53edd11a322c1145dbab6d63209dd321e720a22c0f269b08f84be31638fac77f7bb092aa5920611cb12603905eb2260612f49e325d0644ebc35bb95295b134c1
6
+ metadata.gz: 71630831d0eceda79fefdd93b07a9f83b48c3ef3b0cd879c5c7db8b064e459ef321d36b49a2e0d46e8ba20f134fffc65039cdf455d3cc47638ea031c3edddd83
7
+ data.tar.gz: b7fccbe3f6547fcb7e17c07b9002d278965934a0f0ec68d1b6c79a1ce5b48d807186633a9e57b75e34c8671413ed984a18823a575719ba1f8af4e3777c3309f3
data/README.md CHANGED
@@ -746,6 +746,7 @@ Postfix forms:
746
746
 
747
747
  - member access: `a.b`
748
748
  - indexing: `a[i]`
749
+ - range index: `a[start..stop]` — `str` yields a borrowed `str` view; `array[T, N]` and `span[T]` yield a borrowed `span[T]` view; half-open `[start, stop)`, no copy, no allocation
749
750
  - call: `f(x)`
750
751
  - partial field update: `v.with(x = 10.0)` — returns a copy with specified fields replaced
751
752
  - specialization: `name[T]`, `name[32]`, `mod.name[T]`
@@ -939,7 +940,7 @@ See module source for full method surface. Iterator forms:
939
940
 
940
941
  Text categories:
941
942
 
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
+ - `str` -> string view. `s[i]` reads the byte at `i` as `ubyte` (bounds-checked). `s[start..stop]` returns a borrowed `str` view using byte offsets; bounds must be UTF-8 code-unit boundaries or the slice traps. 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
944
  - `cstr` -> C ABI string
944
945
  - `str_buffer[N]` -> fixed-capacity mutable UTF-8 text buffer
945
946
 
@@ -989,6 +990,7 @@ Heredoc notes:
989
990
  - Shift operators require integer operands.
990
991
  - Safe array indexing requires an addressable array value.
991
992
  - Safe indexing (`arr[i]`) is bounds-checked and calls `fatal` on out-of-bounds access.
993
+ - Range indexing (`arr[start..stop]`, `span[start..stop]`, `str[start..stop]`) returns a borrowed view: `span[T]` for arrays and spans, `str` for strings. Bounds may be any integer types (converted to `ptr_uint`); `start > stop` or `stop > len` traps at runtime. Array range slices require an addressable array value; str slices must land on UTF-8 code-unit boundaries. Bounds-checked with no copy or allocation.
992
994
  - Use `get(arr, i)` for recoverable indexing that returns `ptr[T]?` (null on out-of-bounds) instead of aborting.
993
995
  - Pointer indexing requires `unsafe`.
994
996
  - `read(ptr)` requires `unsafe`.
@@ -1145,7 +1147,8 @@ Current compiler rejects:
1145
1147
 
1146
1148
  - `+` concatenates `str`; `cstr` and mixed `str`/`cstr` concatenation are not supported
1147
1149
  - `==`/`!=` on structs and variants requires all fields equality-comparable; use `equal[T]` otherwise
1148
- - range expressions are restricted to `for`-loop iterables and range-index assignment targets
1150
+ - range expressions are restricted to `for`-loop iterables, range index reads (`a[start..stop]`), and range-index assignment targets
1151
+ - `str` is an immutable borrowed view: index and range-index results cannot be assigned through
1149
1152
  - functions, methods, generic functions, and variant arms must be called — they are not usable as bare values
1150
1153
  - `read(...)` of a raw pointer requires `unsafe`
1151
1154
  - a statement cannot begin with a binary operator (including `+`, `-`, `and`, `or`, `is`, `..`); continuation requires ending the previous line with the operator or wrapping in `()`
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 into a per-thread scratch buffer (no allocation); the result is valid while the scratch budget lasts.</td></tr>
1265
+ <tr><td><code>str</code></td><td>UTF-8 string view (borrowed). <code>s[i]</code> reads the byte at <code>i</code> as <code>ubyte</code> (bounds-checked); <code>s[start..stop]</code> returns a borrowed view using byte offsets and traps off UTF-8 boundaries. 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>
@@ -1487,8 +1487,18 @@ else:
1487
1487
  do_work()</code></pre>
1488
1488
  </div>
1489
1489
 
1490
+ <h3>Range Index</h3>
1491
+ <p>Range-index reads borrow a sub-view with no copy and no allocation. The result is a borrowed <code class="code-inline">str</code> for string receivers and a borrowed <code class="code-inline">span[T]</code> for arrays and spans. Bounds may be any integer types. The range is start-inclusive and end-exclusive. <code>start &gt; stop</code> or <code>stop &gt; len</code> traps at runtime; string slices additionally require UTF-8 code-unit boundaries at both bounds.</p>
1492
+ <div class="code-wrap">
1493
+ <button class="copy-btn" onclick="copyCode(this)">Copy</button>
1494
+ <pre><code>let text: str = "hello world"
1495
+ let head = text[0..5] # str view: "hello"
1496
+ var values: array[int, 4] = (10, 20, 30, 40)
1497
+ let middle = values[1..3] # span[int] view over 20, 30</code></pre>
1498
+ </div>
1499
+
1490
1500
  <h3>Range Index Assignment</h3>
1491
- <p>Assign a tuple to a contiguous slice using an exclusive range with literal bounds. The tuple width must match the slice width exactly.</p>
1501
+ <p>Assign a tuple to a contiguous slice using an exclusive range with literal bounds. The tuple width must match the slice width exactly. String views are immutable and cannot be assigned through.</p>
1492
1502
  <div class="code-wrap">
1493
1503
  <button class="copy-btn" onclick="copyCode(this)">Copy</button>
1494
1504
  <pre><code>var buf: array[float, 4]
@@ -1749,6 +1759,10 @@ value.field
1749
1759
  arr[i]
1750
1760
  func(arg)
1751
1761
 
1762
+ ## Range index: str returns str view; arrays and spans return span[T] view
1763
+ let head = text[0..5]
1764
+ let middle = values[1..3]
1765
+
1752
1766
  ## Partial field update (returns copy)
1753
1767
  let moved = v.with(x = 10.0)
1754
1768
 
@@ -1871,7 +1885,7 @@ function field_equal[T](a: const_ptr[T], b: const_ptr[T]) -&gt; bool:
1871
1885
  <div class="table-wrap">
1872
1886
  <table class="attr-table">
1873
1887
  <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); a stored or returned dynamic format string carries its own heap buffer</td></tr>
1888
+ <tr><td><code>str</code></td><td>Borrowed</td><td>Read-only UTF-8 view (literals, format strings, slicing, <code>x[start..stop]</code>); a stored or returned dynamic format string carries its own heap buffer</td></tr>
1875
1889
  <tr><td><code>cstr</code></td><td>Borrowed</td><td>NUL-terminated C ABI string (<code>c"hello"</code>)</td></tr>
1876
1890
  <tr><td><code>str_buffer[N]</code></td><td>Owned (stack)</td><td>Fixed-capacity mutable UTF-8 builder</td></tr>
1877
1891
  <tr><td><code>std.string.String</code></td><td>Owned (heap)</td><td>Growable owned text via <code>fmt.format(f"...")</code></td></tr>
@@ -1980,7 +1994,7 @@ MSG</code></pre>
1980
1994
  <tr><td>Conditions must be <code>bool</code></td><td>No truthy/falsy coercion from integers or pointers</td></tr>
1981
1995
  <tr><td>Explicit casts for mixed signed/unsigned</td><td>Mixed signed/unsigned arithmetic requires <code>T&lt;-value</code></td></tr>
1982
1996
  <tr><td>Enum/flags don't auto-coerce</td><td>Outside external-call boundaries, no implicit conversion to backing integers</td></tr>
1983
- <tr><td>Safe indexing is bounds-checked</td><td><code>arr[i]</code> calls <code>fatal</code> on OOB; use <code>get(arr, i)</code> for recoverable access</td></tr>
1997
+ <tr><td>Safe indexing is bounds-checked</td><td><code>arr[i]</code> and <code>a[start..stop]</code> call <code>fatal</code> on OOB; string slices additionally require UTF-8 boundaries; use <code>get(arr, i)</code> for recoverable single-element access</td></tr>
1984
1998
  <tr><td>Pointer indexing requires <code>unsafe</code></td><td>Raw pointer dereference, arithmetic, casts, <code>reinterpret</code> all need <code>unsafe</code></td></tr>
1985
1999
  <tr><td><code>%</code> requires integer operands</td><td></td></tr>
1986
2000
  <tr><td>Bitwise ops require matching types</td><td>Integer or flags types</td></tr>
@@ -2429,6 +2443,8 @@ import std.math # intentionally available for downstream</code></pre>
2429
2443
  <li>Enum and flags values do not implicitly coerce to backing integers</li>
2430
2444
  <li><code>+</code> concatenates <code>str</code>; <code>cstr</code> and mixed <code>str</code>/<code>cstr</code> are not concatenable</li>
2431
2445
  <li><code>==</code>/<code>!=</code> on structs and variants requires all fields equality-comparable; use <code>equal[T]</code> for non-comparable types</li>
2446
+ <li>Range expressions are restricted to <code>for</code>-loop iterables, range-index reads (<code>a[start..stop]</code>), and range-index assignment targets</li>
2447
+ <li><code>str</code> views are immutable borrowed text; index and range-index results cannot be assigned through</li>
2432
2448
  <li>A statement cannot begin with a binary operator (including <code>+</code>, <code>-</code>, <code>and</code>, <code>or</code>, <code>is</code>, <code>..</code>); continuation requires ending the previous line with the operator or wrapping in <code>()</code></li>
2433
2449
  <li>Bare function and method names are not usable as values (cannot be assigned or passed without calling). Use <code>fn(...)</code> function pointer types or <code>proc(...)</code> closures for callable values.</li>
2434
2450
  </ul>
@@ -409,13 +409,15 @@ Rules:
409
409
  - Parallel `for` accepts multiple array/span iterables and binds them in lockstep.
410
410
  - Parallel `for` does not accept ranges, and iterable lengths must match.
411
411
 
412
+ Range-index reads borrow sub-views with the same syntax. `a[start..stop]` on `array[T, N]` or `span[T]` returns a borrowed `span[T]`; on `str` it returns a borrowed `str`. The range is end-exclusive, bounds may be any integer types, and out-of-bounds or inverted bounds trap at runtime like all safe indexing. These reads copy nothing and allocate nothing — they only form a pointer-plus-length view.
413
+
412
414
  Range-index assignment is also part of the control-flow-and-mutation surface when code wants an explicit fixed-width slice update:
413
415
 
414
416
  ```mt
415
417
  buf[0..3] = (1.0, 2.0, 3.0)
416
418
  ```
417
419
 
418
- The bounds are integer literals, the range is end-exclusive, and the right-hand tuple width must match the slice width exactly.
420
+ The bounds are integer literals, the range is end-exclusive, and the right-hand tuple width must match the slice width exactly. Range-index assignment writes element-wise into array or span storage; `str` views are immutable and cannot be assigned through.
419
421
 
420
422
  ### Useful structured features
421
423
 
@@ -580,6 +582,7 @@ Notes:
580
582
  - Fixed-array indexing is bounds-checked and safe by default.
581
583
  - Safe array indexing requires an addressable array value; bind temporaries before indexing them.
582
584
  - Safe indexing (`arr[i]`) aborts on out-of-bounds via `fatal`. Use `get(arr, i)` for recoverable bounds-checked access that returns `ptr[T]?`.
585
+ - Range indexing (`arr[start..stop]` on arrays and spans) returns a borrowed `span[T]` view; it copies nothing and requires an addressable array value.
583
586
  - When a `span[T]` boundary is expected, addressable `array[T, N]` values coerce directly. Arrays also expose `.as_span()` for explicit conversion when the target type is not a call boundary.
584
587
  - `array[char, N]` and `span[char]` are the ordinary source-level forms for raw writable character storage and byte-oriented foreign buffers. They are not alternate text objects and should not grow a parallel everyday text API.
585
588
  - `str_buffer[N]` is the one source-level mutable UTF-8 text type. It owns `N` writable text bytes plus an implementation-managed trailing NUL slot, tracks current text length, and refreshes that length when a writable buffer alias mutates the underlying storage.
@@ -592,7 +595,7 @@ Notes:
592
595
  - `.len()` returns the tracked text length, revalidating UTF-8 and rescanning for the trailing NUL if the builder was passed through a writable `span[char]` or `ptr[char]` alias.
593
596
  - `.capacity()` reports the maximum writable text bytes, not counting the reserved trailing NUL slot.
594
597
  - `.as_str()` and `.as_cstr()` borrow from the same builder storage and revalidate through that same dirty-refresh path before returning.
595
- - `str.slice(start, len)` uses byte offsets and byte lengths, but both the start and end position must be UTF-8 code-unit boundaries or the slice traps at runtime.
598
+ - `str[start..stop]` is the language-level slice surface: it returns a borrowed `str` view using byte offsets with an end-exclusive range, and both bounds must be UTF-8 code-unit boundaries or the slice traps at runtime. `s[i]` reads the byte at `i` as `ubyte`, bounds-checked. `str.slice(start, len)` remains the library spelling for start-plus-length slicing.
596
599
  - Ordinary string lists stay `array[str, N]` or `span[str]` in source. If an imported foreign declaration chooses that public surface for a raw `char **`, `span[cstr]`, or pointer-plus-length text-list API, the boundary owns the temporary marshalling.
597
600
  - Imported foreign declarations may map `str_buffer[N] as ptr[char]` directly when the public surface wants writable UTF-8 text with fixed caller capacity.
598
601
  - If the raw call also needs the caller buffer size, a `str_buffer[N]` public signature should pass `text_public.capacity() + 1` in the foreign mapping so the raw side sees the full writable byte count including the trailing NUL slot.
@@ -934,22 +934,41 @@ Unsafe context is required for raw-pointer-level operations such as:
934
934
  - pointer casts
935
935
  - `reinterpret[...]`
936
936
 
937
- ### 4.6 Range index assignment
937
+ ### 4.6 Range indexing
938
938
 
939
- Contiguous indexed slices may be assigned from an expression list:
939
+ Range index reads borrow a sub-view with no copy and no allocation:
940
+
941
+ ```mt
942
+ let text: str = "hello world"
943
+ let head = text[0..5] # str view: "hello"
944
+ var values: array[int, 4] = (10, 20, 30, 40)
945
+ let middle = values[1..3] # span[int] view over 20, 30
946
+ ```
947
+
948
+ Rules for range index reads:
949
+
950
+ - the receiver must be `str`, `array[T, N]`, or `span[T]`
951
+ - the result is a borrowed view: `str` for string receivers, `span[T]` for arrays and spans
952
+ - the range is start-inclusive and end-exclusive; bounds may be any integer types
953
+ - `start > stop` or `stop > len` traps at runtime
954
+ - array receivers require an addressable array value; literal bounds are checked at compile time
955
+ - `str` slices use byte offsets and both bounds must be UTF-8 code-unit boundaries or the slice traps
956
+
957
+ Range index assignment writes elements in place:
940
958
 
941
959
  ```mt
942
960
  var buf: array[float, 4]
943
961
  buf[0..3] = (1.0, 2.0, 3.0)
944
962
  ```
945
963
 
946
- Rules:
964
+ Rules for range index assignment:
947
965
 
948
966
  - the target must be an addressable array-, span-, or pointer-indexable lvalue
949
967
  - the index must be a range expression with integer literal bounds
950
968
  - the range is start-inclusive and end-exclusive
951
969
  - the right-hand side must be an expression list whose length exactly matches the range width
952
970
  - each element must be assignable to the indexed element type
971
+ - `str` receivers cannot be assigned through; str views are immutable
953
972
 
954
973
  ### 4.7 When (compile-time conditional)
955
974
 
@@ -1051,6 +1070,7 @@ Rules:
1051
1070
 
1052
1071
  - member access: `a.b`
1053
1072
  - indexing: `a[i]`
1073
+ - range index: `a[start..stop]` — `str` yields a borrowed `str` view; `array[T, N]` and `span[T]` yield a borrowed `span[T]` view; half-open `[start, stop)` with no copy (§4.6)
1054
1074
  - call: `f(x)`
1055
1075
  - partial field update: `v.with(x = 10.0)` — returns a copy with specified fields replaced; supported on structs and native types (vector, matrix, quaternion)
1056
1076
  - specialization: `name[T]`, `name[32]`, `mod.name[T]`
@@ -1306,7 +1326,7 @@ Iterator notes for those collection modules:
1306
1326
 
1307
1327
  String categories:
1308
1328
 
1309
- - `str` -> string view
1329
+ - `str` -> string view. `s[i]` reads the byte at `i` as `ubyte` (bounds-checked); `s[start..stop]` returns a borrowed `str` view using byte offsets (UTF-8 boundary-checked)
1310
1330
  - `cstr` -> C ABI string
1311
1331
  - `str_buffer[N]` -> fixed-capacity mutable UTF-8 text buffer
1312
1332
 
@@ -1373,6 +1393,8 @@ Custom formatting hook notes:
1373
1393
  - shift operators require integer operands
1374
1394
  - safe array indexing requires an addressable array value
1375
1395
  - safe indexing (`arr[i]`) is bounds-checked and calls `fatal` on out-of-bounds access
1396
+ - safe range indexing (`a[start..stop]`) is bounds-checked and calls `fatal` on out-of-bounds or out-of-order bounds
1397
+ - `str` range slices require UTF-8 code-unit boundaries at both bounds
1376
1398
  - use `get(arr, i)` for recoverable indexing that returns `ptr[T]?` (null on out-of-bounds) instead of aborting
1377
1399
  - pointer indexing requires `unsafe`
1378
1400
  - `read(...)` of raw pointer requires `unsafe`
@@ -1622,7 +1644,8 @@ The compiler intentionally rejects the following patterns. These are design cons
1622
1644
 
1623
1645
  - `+` concatenates `str`; `cstr` and mixed `str`/`cstr` are not concatenable
1624
1646
  - `==`/`!=` on structs and variants requires all fields equality-comparable; use `equal[T]` otherwise (§3.4b)
1625
- - range expressions are restricted to `for`-loop iterables and range-index assignment targets
1647
+ - range expressions are restricted to `for`-loop iterables, range-index reads (`a[start..stop]`), and range-index assignment targets
1648
+ - `str` views are immutable borrowed text; index and range-index results cannot be assigned through
1626
1649
  - functions, methods, generic functions, and variant arms must be called — they are not usable as bare values
1627
1650
  - `read(...)` of a raw pointer requires `unsafe`
1628
1651
 
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.42"
6
+ VERSION = "0.3.43"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -136,8 +136,9 @@ module MilkTea
136
136
  return true if @debug_guards
137
137
 
138
138
  collect_checked_array_index_types.any? || collect_checked_span_index_types.any? ||
139
+ !collect_span_slice_helper_names.empty? ||
139
140
  uses_format_helpers? ||
140
- emitted_functions.any? { |function| function_uses_named_call?(function, %w[mt_fatal mt_str_buffer_len mt_str_buffer_as_cstr mt_str_buffer_assign mt_str_buffer_append mt_foreign_str_to_cstr_temp mt_foreign_strs_to_cstrs_temp mt_str_concat]) }
141
+ emitted_functions.any? { |function| function_uses_named_call?(function, %w[mt_fatal mt_str_buffer_len mt_str_buffer_as_cstr mt_str_buffer_assign mt_str_buffer_append mt_foreign_str_to_cstr_temp mt_foreign_strs_to_cstrs_temp mt_str_concat mt_str_index mt_str_slice]) }
141
142
  end
142
143
 
143
144
  def uses_mt_fatal_str_helper?
@@ -297,6 +298,10 @@ module MilkTea
297
298
  end
298
299
  end
299
300
 
301
+ def uses_str_slice_helpers?
302
+ emitted_functions.any? { |function| function_uses_named_call?(function, %w[mt_str_index mt_str_slice]) }
303
+ end
304
+
300
305
  def uses_variant_equality_helper?
301
306
  !variant_equality_types.empty?
302
307
  end
@@ -64,6 +64,36 @@ module MilkTea
64
64
  ]
65
65
  end
66
66
 
67
+ def emit_str_slice_helpers
68
+ [
69
+ "static uint8_t mt_str_index(mt_str text, uintptr_t index) {",
70
+ "#{INDENT}if (index >= text.len) mt_fatal(\"str index out of bounds\");",
71
+ "#{INDENT}return (uint8_t)text.data[index];",
72
+ "}",
73
+ "",
74
+ "static bool mt_str_utf8_boundary(mt_str text, uintptr_t index) {",
75
+ "#{INDENT}if (index == 0 || index == text.len) return true;",
76
+ "#{INDENT}return ((uint8_t)text.data[index] & 0xC0) != 0x80;",
77
+ "}",
78
+ "",
79
+ "static mt_str mt_str_slice(mt_str text, uintptr_t start, uintptr_t stop) {",
80
+ "#{INDENT}if (start > stop || stop > text.len) mt_fatal(\"str slice out of bounds\");",
81
+ "#{INDENT}if (!mt_str_utf8_boundary(text, start) || !mt_str_utf8_boundary(text, stop)) mt_fatal(\"str slice bounds must be UTF-8 code-unit boundaries\");",
82
+ "#{INDENT}return (mt_str){ .data = text.data + start, .len = stop - start };",
83
+ "}",
84
+ ]
85
+ end
86
+
87
+ def emit_span_slice_helper(helper_name)
88
+ span_name = helper_name.sub("mt_span_slice_", "mt_span_")
89
+ [
90
+ "static inline #{span_name} #{helper_name}(#{span_name} span, uintptr_t start, uintptr_t stop) {",
91
+ "#{INDENT}if (start > stop || stop > span.len) mt_fatal(\"span slice out of bounds\");",
92
+ "#{INDENT}return (#{span_name}){ .data = span.data + start, .len = stop - start };",
93
+ "}",
94
+ ]
95
+ end
96
+
67
97
  def emit_variant_equality_helpers
68
98
  variant_decls_by_linkage = (emitted_aggregate_variants + collect_generic_variant_decls).each_with_object({}) { |decl, map| map[decl.linkage_name] = decl }
69
99
  variant_equality_types
@@ -19,6 +19,80 @@ module MilkTea
19
19
  span_types.uniq
20
20
  end
21
21
 
22
+ def collect_span_slice_helper_names
23
+ names = []
24
+ emitted_functions.each do |function|
25
+ collect_span_slice_helper_names_from_statements(function.body, names)
26
+ end
27
+ names.uniq
28
+ end
29
+
30
+ def collect_span_slice_helper_names_from_statements(statements, names)
31
+ statements.each do |statement|
32
+ case statement
33
+ when IR::LocalDecl
34
+ collect_span_slice_helper_names_from_expression(statement.value, names)
35
+ when IR::Assignment
36
+ collect_span_slice_helper_names_from_expression(statement.target, names)
37
+ collect_span_slice_helper_names_from_expression(statement.value, names)
38
+ when IR::BlockStmt
39
+ collect_span_slice_helper_names_from_statements(statement.body, names)
40
+ when IR::WhileStmt
41
+ collect_span_slice_helper_names_from_expression(statement.condition, names)
42
+ collect_span_slice_helper_names_from_statements(statement.body, names)
43
+ when IR::ForStmt
44
+ collect_span_slice_helper_names_from_statements([statement.init], names)
45
+ collect_span_slice_helper_names_from_expression(statement.condition, names)
46
+ collect_span_slice_helper_names_from_statements(statement.body, names)
47
+ collect_span_slice_helper_names_from_statements([statement.post], names)
48
+ when IR::IfStmt
49
+ collect_span_slice_helper_names_from_expression(statement.condition, names)
50
+ collect_span_slice_helper_names_from_statements(statement.then_body, names)
51
+ collect_span_slice_helper_names_from_statements(statement.else_body, names) if statement.else_body
52
+ when IR::SwitchStmt
53
+ collect_span_slice_helper_names_from_expression(statement.expression, names)
54
+ statement.cases.each { |switch_case| collect_span_slice_helper_names_from_statements(switch_case.body, names) }
55
+ when IR::ReturnStmt
56
+ collect_span_slice_helper_names_from_expression(statement.value, names) if statement.value
57
+ when IR::ExpressionStmt
58
+ collect_span_slice_helper_names_from_expression(statement.expression, names)
59
+ end
60
+ end
61
+ end
62
+
63
+ def collect_span_slice_helper_names_from_expression(expression, names)
64
+ return unless expression
65
+
66
+ case expression
67
+ when IR::Call
68
+ names << expression.callee if expression.callee.is_a?(String) && expression.callee.start_with?("mt_span_slice_")
69
+ collect_span_slice_helper_names_from_expression(expression.callee, names) unless expression.callee.is_a?(String)
70
+ expression.arguments.each { |argument| collect_span_slice_helper_names_from_expression(argument, names) }
71
+ when IR::Member
72
+ collect_span_slice_helper_names_from_expression(expression.receiver, names)
73
+ when IR::Index, IR::CheckedIndex, IR::CheckedSpanIndex, IR::NullableIndex, IR::NullableSpanIndex
74
+ collect_span_slice_helper_names_from_expression(expression.receiver, names)
75
+ collect_span_slice_helper_names_from_expression(expression.index, names)
76
+ when IR::Unary
77
+ collect_span_slice_helper_names_from_expression(expression.operand, names)
78
+ when IR::Binary
79
+ collect_span_slice_helper_names_from_expression(expression.left, names)
80
+ collect_span_slice_helper_names_from_expression(expression.right, names)
81
+ when IR::Conditional
82
+ collect_span_slice_helper_names_from_expression(expression.condition, names)
83
+ collect_span_slice_helper_names_from_expression(expression.then_expression, names)
84
+ collect_span_slice_helper_names_from_expression(expression.else_expression, names)
85
+ when IR::Cast, IR::AddressOf
86
+ collect_span_slice_helper_names_from_expression(expression.expression, names)
87
+ when IR::AggregateLiteral
88
+ expression.fields.each { |field| collect_span_slice_helper_names_from_expression(field.value, names) }
89
+ when IR::ArrayLiteral
90
+ expression.elements.each { |element| collect_span_slice_helper_names_from_expression(element, names) }
91
+ when IR::VariantLiteral
92
+ expression.fields.each { |field| collect_span_slice_helper_names_from_expression(field.value, names) }
93
+ end
94
+ end
95
+
22
96
  def collect_checked_array_index_types_from_statements(statements, array_types, nullable_only: false)
23
97
  statements.each do |statement|
24
98
  case statement
@@ -162,6 +162,10 @@ module MilkTea
162
162
  lines.concat(emit_str_concat_helper)
163
163
  lines << ""
164
164
  end
165
+ if uses_str_slice_helpers?
166
+ lines.concat(emit_str_slice_helpers)
167
+ lines << ""
168
+ end
165
169
  if uses_str_buffer_helpers?
166
170
  lines.concat(emit_utf8_validation_helpers)
167
171
  lines << ""
@@ -335,6 +339,11 @@ module MilkTea
335
339
  lines << ""
336
340
  end
337
341
 
342
+ collect_span_slice_helper_names.each do |helper_name|
343
+ lines.concat(emit_span_slice_helper(helper_name))
344
+ lines << ""
345
+ end
346
+
338
347
  collect_checked_array_index_types(nullable_only: true).each do |type|
339
348
  lines.concat(emit_nullable_array_index_helper(type))
340
349
  lines << ""
@@ -400,6 +400,8 @@ module MilkTea
400
400
  AST::TypeRef.new(name: AST::QualifiedName.new(parts: ["span"]), arguments: [AST::TypeArgument.new(value: ast_type_ref_for(type.element_type))], nullable: false)
401
401
  when Types::Task
402
402
  AST::TypeRef.new(name: AST::QualifiedName.new(parts: ["Task"]), arguments: [AST::TypeArgument.new(value: ast_type_ref_for(type.result_type))], nullable: false)
403
+ when Types::StringView
404
+ AST::TypeRef.new(name: AST::QualifiedName.new(parts: ["str"]), arguments: [], nullable: false)
403
405
  when Types::TypeVar
404
406
  AST::TypeRef.new(name: AST::QualifiedName.new(parts: [type.name]), arguments: [], nullable: false)
405
407
  when Types::StructInstance
@@ -1364,13 +1364,19 @@ module MilkTea
1364
1364
  when AST::IndexAccess
1365
1365
  receiver_type = infer_expression_type(expression.receiver, env:)
1366
1366
  receiver = lower_expression(expression.receiver, env:)
1367
- index = lower_expression(expression.index, env:)
1368
- if array_type?(receiver_type) && addressable_storage_expression?(expression.receiver)
1369
- IR::CheckedIndex.new(receiver:, index:, receiver_type:, type:)
1370
- elsif receiver_type.is_a?(Types::Span)
1371
- IR::CheckedSpanIndex.new(receiver:, index:, receiver_type:, type:)
1367
+ if expression.index.is_a?(AST::RangeExpr)
1368
+ lower_range_index_access(receiver, receiver_type, expression.index, env:, type:)
1372
1369
  else
1373
- IR::Index.new(receiver:, index:, type:)
1370
+ index = lower_expression(expression.index, env:)
1371
+ if array_type?(receiver_type) && addressable_storage_expression?(expression.receiver)
1372
+ IR::CheckedIndex.new(receiver:, index:, receiver_type:, type:)
1373
+ elsif receiver_type.is_a?(Types::Span)
1374
+ IR::CheckedSpanIndex.new(receiver:, index:, receiver_type:, type:)
1375
+ elsif receiver_type.is_a?(Types::StringView)
1376
+ IR::Call.new(callee: "mt_str_index", arguments: [receiver, index], type:)
1377
+ else
1378
+ IR::Index.new(receiver:, index:, type:)
1379
+ end
1374
1380
  end
1375
1381
  when AST::UnaryOp
1376
1382
  raise LoweringError.new("propagation expressions must be prepared before direct lowering", line: 0, column: 0, path: @ctx.current_analysis_path) if expression.operator == "?"
@@ -1452,6 +1458,25 @@ module MilkTea
1452
1458
  end
1453
1459
  end
1454
1460
 
1461
+ def lower_range_index_access(receiver, receiver_type, range, env:, type:)
1462
+ start = lower_range_index_bound(range.start_expr, env:)
1463
+ stop = lower_range_index_bound(range.end_expr, env:)
1464
+
1465
+ if receiver_type.is_a?(Types::StringView)
1466
+ return IR::Call.new(callee: "mt_str_slice", arguments: [receiver, start, stop], type:)
1467
+ end
1468
+
1469
+ span_type = range_index_result_type(receiver_type)
1470
+ span_argument = receiver_type.is_a?(Types::Span) ? receiver : lower_array_to_span_expression(receiver, span_type)
1471
+ IR::Call.new(callee: span_slice_callee(span_type), arguments: [span_argument, start, stop], type: span_type)
1472
+ end
1473
+
1474
+ def lower_range_index_bound(bound, env:)
1475
+ ptr_uint = @ctx.types.fetch("ptr_uint")
1476
+ lowered = lower_expression(bound, env:, expected_type: ptr_uint)
1477
+ lowered.type == ptr_uint ? lowered : IR::Cast.new(target_type: ptr_uint, expression: lowered, type: ptr_uint)
1478
+ end
1479
+
1455
1480
  def lower_member_access(expression, env:, type:)
1456
1481
  if (type_expr = resolve_type_expression(expression.receiver))
1457
1482
  if type_expr.is_a?(Types::Variant)
@@ -206,6 +206,9 @@ module MilkTea
206
206
  when AST::IndexAccess
207
207
  collect_proc_captures_from_expression(expression.receiver, env, local_scopes, captures)
208
208
  collect_proc_captures_from_expression(expression.index, env, local_scopes, captures)
209
+ when AST::RangeExpr
210
+ collect_proc_captures_from_expression(expression.start_expr, env, local_scopes, captures)
211
+ collect_proc_captures_from_expression(expression.end_expr, env, local_scopes, captures)
209
212
  when AST::Specialization
210
213
  collect_proc_captures_from_expression(expression.callee, env, local_scopes, captures)
211
214
  expression.arguments.each { |argument| collect_proc_captures_from_expression(argument.value, env, local_scopes, captures) }
@@ -786,6 +786,9 @@ module MilkTea
786
786
  raise LoweringError.new("unknown member #{expression.member}", line: expression.line, column: expression.column, path: @ctx.current_analysis_path)
787
787
  when AST::IndexAccess
788
788
  receiver_type = infer_expression_type(expression.receiver, env:)
789
+ if expression.index.is_a?(AST::RangeExpr)
790
+ return range_index_result_type(receiver_type)
791
+ end
789
792
  index_type = infer_expression_type(expression.index, env:)
790
793
  infer_index_result_type(receiver_type, index_type)
791
794
  when AST::UnaryOp
@@ -213,6 +213,10 @@ module MilkTea
213
213
 
214
214
  receiver_type = referenced_type(receiver_type) if ref_type?(receiver_type)
215
215
 
216
+ if receiver_type.is_a?(Types::StringView)
217
+ return @ctx.types.fetch("ubyte")
218
+ end
219
+
216
220
  if array_type?(receiver_type)
217
221
  return array_element_type(receiver_type)
218
222
  end
@@ -236,6 +240,28 @@ module MilkTea
236
240
  raise LoweringError.new("cannot index #{receiver_type}", line: 0, column: 0, path: @ctx.current_analysis_path)
237
241
  end
238
242
 
243
+ def range_index_result_type(receiver_type)
244
+ receiver_type = referenced_type(receiver_type) if ref_type?(receiver_type)
245
+
246
+ if receiver_type.is_a?(Types::StringView)
247
+ return @ctx.types.fetch("str")
248
+ end
249
+
250
+ if array_type?(receiver_type)
251
+ return Types::Span.new(array_element_type(receiver_type))
252
+ end
253
+
254
+ if receiver_type.is_a?(Types::Span)
255
+ return receiver_type
256
+ end
257
+
258
+ raise LoweringError.new("cannot range-index #{receiver_type}; expected str, array[T, N], or span[T]", line: 0, column: 0, path: @ctx.current_analysis_path)
259
+ end
260
+
261
+ def span_slice_callee(span_type)
262
+ "mt_span_slice_#{sanitize_identifier(span_type.element_type.to_s)}"
263
+ end
264
+
239
265
  def stored_ref_supported_type?(type, visited = {})
240
266
  return true unless type
241
267
 
@@ -37,6 +37,8 @@ module MilkTea
37
37
  allow_span_param_identifier: true,
38
38
  )
39
39
 
40
+ raise_sema_error("cannot assign through str index; str is an immutable borrowed view") if receiver_type.is_a?(Types::StringView)
41
+
40
42
  index_type = infer_expression(expression.index, scopes:)
41
43
  infer_index_result_type(receiver_type, index_type)
42
44
  when AST::Call
@@ -101,6 +103,8 @@ module MilkTea
101
103
  require_mutable_pointer:,
102
104
  allow_span_param_identifier:,
103
105
  )
106
+ raise_sema_error("cannot index through str as an assignment receiver; str is an immutable borrowed view") if receiver_type.is_a?(Types::StringView)
107
+
104
108
  index_type = infer_expression(expression.index, scopes:)
105
109
  infer_index_result_type(receiver_type, index_type)
106
110
  when AST::Call
@@ -470,8 +474,17 @@ module MilkTea
470
474
 
471
475
  def infer_index_access(expression, scopes:)
472
476
  receiver_type = infer_expression(expression.receiver, scopes:)
477
+
478
+ if expression.index.is_a?(AST::RangeExpr)
479
+ return infer_range_index_access(expression, receiver_type, scopes:)
480
+ end
481
+
473
482
  index_type = infer_expression(expression.index, scopes:)
474
483
 
484
+ if receiver_type.is_a?(Types::StringView) && (literal = integer_literal_bound_value(expression.index)) && literal < 0
485
+ raise_sema_error("str index #{literal} is negative; str indices must be non-negative", expression)
486
+ end
487
+
475
488
  if soa_type?(receiver_type)
476
489
  return receiver_type.element_type
477
490
  end
@@ -1022,6 +1022,10 @@ module MilkTea
1022
1022
  def infer_index_result_type(receiver_type, index_type)
1023
1023
  raise_sema_error("index must be an integer type, got #{index_type}") unless integer_type?(index_type)
1024
1024
 
1025
+ if receiver_type.is_a?(Types::StringView)
1026
+ return @ctx.types.fetch("ubyte")
1027
+ end
1028
+
1025
1029
  if array_type?(receiver_type)
1026
1030
  return array_element_type(receiver_type)
1027
1031
  end
@@ -1047,6 +1051,48 @@ module MilkTea
1047
1051
  raise_sema_error("cannot index #{receiver_type}")
1048
1052
  end
1049
1053
 
1054
+ def infer_range_index_access(expression, receiver_type, scopes:)
1055
+ start_type = infer_expression(expression.index.start_expr, scopes:)
1056
+ stop_type = infer_expression(expression.index.end_expr, scopes:)
1057
+ raise_sema_error("range index bounds must be integer types, got #{start_type} and #{stop_type}") unless start_type.integer? && stop_type.integer?
1058
+
1059
+ if array_type?(receiver_type) && !addressable_storage_expression?(expression.receiver, scopes:)
1060
+ raise_sema_error("array range slice requires an addressable array value; bind it to a local first", expression)
1061
+ end
1062
+
1063
+ if array_type?(receiver_type) && (start_val = integer_literal_bound_value(expression.index.start_expr)) && (stop_val = integer_literal_bound_value(expression.index.end_expr))
1064
+ length = array_length(receiver_type)
1065
+ unless start_val >= 0 && start_val <= stop_val && stop_val <= length
1066
+ raise_sema_error("range index [#{start_val}..#{stop_val}] is out of bounds for array[T, #{length}]", expression)
1067
+ end
1068
+ end
1069
+
1070
+ range_index_result_type(receiver_type)
1071
+ end
1072
+
1073
+ def range_index_result_type(receiver_type)
1074
+ if receiver_type.is_a?(Types::StringView)
1075
+ return @ctx.types.fetch("str")
1076
+ end
1077
+
1078
+ if array_type?(receiver_type)
1079
+ return Types::Span.new(array_element_type(receiver_type))
1080
+ end
1081
+
1082
+ if span_type?(receiver_type)
1083
+ return receiver_type
1084
+ end
1085
+
1086
+ raise_sema_error("cannot range-index #{receiver_type}; expected str, array[T, N], or span[T]")
1087
+ end
1088
+
1089
+ def integer_literal_bound_value(expression)
1090
+ return expression.value if expression.is_a?(AST::IntegerLiteral)
1091
+ return -expression.operand.value if expression.is_a?(AST::UnaryOp) && expression.operator == "-" && expression.operand.is_a?(AST::IntegerLiteral)
1092
+
1093
+ nil
1094
+ end
1095
+
1050
1096
  def addressable_storage_expression?(expression, scopes:)
1051
1097
  case expression
1052
1098
  when AST::Identifier
@@ -538,6 +538,7 @@ module MilkTea
538
538
  require_mutable_pointer: true,
539
539
  allow_span_param_identifier: true,
540
540
  )
541
+ raise_sema_error("cannot assign through str index; str is an immutable borrowed view") if receiver_type.is_a?(Types::StringView)
541
542
  element_type = infer_index_result_type(receiver_type, @ctx.types.fetch("ptr_uint"))
542
543
 
543
544
  statement.value.elements.each_with_index do |elem, i|
@@ -21,40 +21,63 @@ module MilkTea
21
21
  end
22
22
 
23
23
  def self.nullable(base)
24
- _intern([:nullable, base]) { Nullable.new(base) }
24
+ _intern([:nullable, type_signature(base)]) { Nullable.new(base) }
25
25
  end
26
26
 
27
27
  def self.generic_instance(name, arguments)
28
28
  args = arguments.freeze
29
- _intern([:generic, name, args]) { GenericInstance.new(name, args) }
29
+ _intern([:generic, name, args.map { |a| type_signature(a) }.freeze]) { GenericInstance.new(name, args) }
30
30
  end
31
31
 
32
32
  def self.span(element_type)
33
- _intern([:span, element_type]) { Span.new(element_type) }
33
+ _intern([:span, type_signature(element_type)]) { Span.new(element_type) }
34
34
  end
35
35
 
36
36
  def self.task(result_type)
37
- _intern([:task, result_type]) { Task.new(result_type) }
37
+ _intern([:task, type_signature(result_type)]) { Task.new(result_type) }
38
38
  end
39
39
 
40
40
  def self.string_view
41
41
  _intern([:string_view]) { StringView.new }
42
42
  end
43
43
 
44
- # Param signature for intern keys. Parameter#eql? is name-insensitive
45
- # (assignability must not depend on parameter names), so arrays of
46
- # Parameter objects would conflate fn(value: int) with fn(arg0: int) in
47
- # the intern pool. Embedding names here keeps distinct signatures
48
- # distinct across independent programs sharing a long-lived pool (the LSP
49
- # never resets the registry between checks).
44
+ # Name-sensitive signature for intern keys. Parameter#eql? is
45
+ # name-insensitive (assignability must not depend on parameter names), so
46
+ # Function/Proc objects (and any wrapper containing them) that differ only
47
+ # in parameter names collide under Hash/==. Pool keys that embed such
48
+ # types raw therefore conflate fn(value: int) with fn(arg0: int) in a
49
+ # long-lived pool (the LSP never resets the registry between checks),
50
+ # leaking one program's parameter names into another's generated C.
51
+ # Embedding names here keeps distinct signatures distinct.
52
+ def self.type_signature(type)
53
+ case type
54
+ when Function
55
+ [:function, type.name, param_signature(type.params), type_signature(type.return_type), type_signature(type.receiver_type), type.receiver_editable, type.variadic, type.external]
56
+ when Proc
57
+ [:proc, param_signature(type.params), type_signature(type.return_type)]
58
+ when Nullable
59
+ [:nullable, type_signature(type.base)]
60
+ when GenericInstance
61
+ [:generic, type.name, type.arguments.map { |a| type_signature(a) }]
62
+ when Tuple
63
+ [:tuple, type.element_types.map { |t| type_signature(t) }, type.field_names]
64
+ when Span
65
+ [:span, type_signature(type.element_type)]
66
+ when Task
67
+ [:task, type_signature(type.result_type)]
68
+ else
69
+ type
70
+ end
71
+ end
72
+
50
73
  def self.param_signature(params)
51
- params.map { |p| [p.name, p.type, p.mutable, p.passing_mode, p.boundary_type] }
74
+ params.map { |p| [p.name, type_signature(p.type), p.mutable, p.passing_mode, p.boundary_type] }
52
75
  end
53
76
 
54
77
  def self.function(name, params:, return_type:, receiver_type: nil, receiver_editable: false, variadic: false, external: false)
55
78
  params_frozen = params.freeze
56
79
  param_key = param_signature(params_frozen)
57
- _intern([:function, name, param_key, return_type, receiver_type, receiver_editable, variadic, external]) {
80
+ _intern([:function, name, param_key, type_signature(return_type), type_signature(receiver_type), receiver_editable, variadic, external]) {
58
81
  Function.new(name, params: params_frozen, return_type: return_type, receiver_type: receiver_type, receiver_editable: receiver_editable, variadic: variadic, external: external)
59
82
  }
60
83
  end
@@ -62,7 +85,7 @@ module MilkTea
62
85
  def self.proc(params:, return_type:)
63
86
  params_frozen = params.freeze
64
87
  param_key = param_signature(params_frozen)
65
- _intern([:proc, param_key, return_type]) { Proc.new(params: params_frozen, return_type: return_type) }
88
+ _intern([:proc, param_key, type_signature(return_type)]) { Proc.new(params: params_frozen, return_type: return_type) }
66
89
  end
67
90
 
68
91
  def self.parameter(name, type, mutable: false, passing_mode: :plain, boundary_type: nil)
@@ -74,15 +97,15 @@ module MilkTea
74
97
  def self.tuple(element_types, field_names: nil)
75
98
  et_frozen = element_types.freeze
76
99
  fn_frozen = field_names&.freeze
77
- _intern([:tuple, et_frozen, fn_frozen]) { Tuple.new(et_frozen, field_names: fn_frozen) }
100
+ _intern([:tuple, et_frozen.map { |t| type_signature(t) }.freeze, fn_frozen]) { Tuple.new(et_frozen, field_names: fn_frozen) }
78
101
  end
79
102
 
80
103
  def self.soa(element_type, count:)
81
- _intern([:soa, element_type, count]) { SoA.new(element_type, count: count) }
104
+ _intern([:soa, type_signature(element_type), count]) { SoA.new(element_type, count: count) }
82
105
  end
83
106
 
84
107
  def self.simd(element_type, lane_count:)
85
- _intern([:simd, element_type, lane_count]) { Simd.new(element_type, lane_count: lane_count) }
108
+ _intern([:simd, type_signature(element_type), lane_count]) { Simd.new(element_type, lane_count: lane_count) }
86
109
  end
87
110
 
88
111
  def self.lifetime_ref(name)
@@ -1330,7 +1330,6 @@ module MilkTea
1330
1330
  end
1331
1331
  return nil unless idx && tokens[idx].type == :identifier
1332
1332
 
1333
- type_name = tokens[idx].lexeme
1334
1333
  type = resolve_arm_receiver_type(facts, tokens, idx)
1335
1334
 
1336
1335
  arm = if type.respond_to?(:arm)
@@ -86,7 +86,7 @@ module MilkTea
86
86
  rescue StandardError
87
87
  nil
88
88
  end
89
- clear_shared_module_cache if previous_content != disk_content
89
+ @workspace.clear_shared_module_cache if previous_content != disk_content
90
90
  unless defined?(@pull_diagnostics_active) && @pull_diagnostics_active
91
91
  @protocol.write_notification('textDocument/publishDiagnostics', {
92
92
  uri: uri,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mt-lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.42
4
+ version: 0.3.43
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -628,7 +628,7 @@ metadata:
628
628
  homepage_uri: https://teefan.github.io/mt-lang/
629
629
  source_code_uri: https://github.com/teefan/mt-lang
630
630
  post_install_message: |
631
- Milk Tea 0.3.42 installed!
631
+ Milk Tea 0.3.43 installed!
632
632
 
633
633
  System requirements:
634
634
  - A C compiler (gcc or clang) must be available on PATH