mt-lang 0.3.42 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -2
- data/docs/index.html +20 -4
- data/docs/language-design.md +5 -2
- data/docs/language-manual.md +28 -5
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/core/ast.rb +2 -2
- data/lib/milk_tea/core/c_backend/feature_detection.rb +6 -1
- data/lib/milk_tea/core/c_backend/runtime_helpers.rb +30 -0
- data/lib/milk_tea/core/c_backend/type_collectors.rb +74 -0
- data/lib/milk_tea/core/c_backend.rb +9 -0
- data/lib/milk_tea/core/compile_time/method_folding.rb +157 -0
- data/lib/milk_tea/core/compile_time.rb +303 -26
- data/lib/milk_tea/core/lowering/async/normalization.rb +2 -0
- data/lib/milk_tea/core/lowering/expressions.rb +31 -6
- data/lib/milk_tea/core/lowering/proc.rb +3 -0
- data/lib/milk_tea/core/lowering/resolve.rb +79 -2
- data/lib/milk_tea/core/lowering/utils.rb +26 -0
- data/lib/milk_tea/core/parser/declarations.rb +10 -5
- data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +10 -8
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +13 -0
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +46 -0
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +1 -0
- data/lib/milk_tea/core/semantic_analyzer/top_level.rb +70 -1
- data/lib/milk_tea/core/semantic_analyzer.rb +9 -2
- data/lib/milk_tea/core/types/registry.rb +39 -16
- data/lib/milk_tea/lsp/server/hover.rb +0 -1
- data/lib/milk_tea/lsp/server/text_documents.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 990a1e0ede945713a31152caccc4a7de798dc6ead6078d46d5a376f19054a52a
|
|
4
|
+
data.tar.gz: 97d7877587ca50ab673212d1d0d80d6d60ad8f97a74c439965feb32c2f5a4e37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c04b4d611724c8b4f42ae14929b3d8d98b4aa4da5157f7c3ee765afb6356be643b6ada8c96ed45a8e61103e71792c7855c25d87f65865b9609cdb0cc85544507
|
|
7
|
+
data.tar.gz: 82416c2665d49928f0d6dd5e9b8f225177f9df0f782e973339545a6375cc673e082bccc3bbb6126cbdc50875dc6fa29880b5e23b8e3ccd458934b32f5d2f6718
|
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 > stop</code> or <code>stop > 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]) -> 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<-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>
|
|
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>
|
data/docs/language-design.md
CHANGED
|
@@ -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
|
|
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.
|
data/docs/language-manual.md
CHANGED
|
@@ -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
|
|
937
|
+
### 4.6 Range indexing
|
|
938
938
|
|
|
939
|
-
|
|
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
data/lib/milk_tea/core/ast.rb
CHANGED
|
@@ -120,8 +120,8 @@ module MilkTea
|
|
|
120
120
|
FunctionDef = Data.define(:name, :type_params, :params, :return_type, :body, :visibility, :async, :const, :attributes, :line, :column) do
|
|
121
121
|
def initialize(name:, type_params:, params:, return_type:, body:, visibility:, async:, const: false, attributes: [], line: nil, column: nil) = super
|
|
122
122
|
end
|
|
123
|
-
MethodDef = Data.define(:name, :type_params, :params, :return_type, :body, :kind, :visibility, :async, :attributes, :line, :column) do
|
|
124
|
-
def initialize(name:, type_params:, params:, return_type:, body:, kind:, visibility:, async:, attributes: [], line: nil, column: nil) = super
|
|
123
|
+
MethodDef = Data.define(:name, :type_params, :params, :return_type, :body, :kind, :visibility, :async, :const, :attributes, :line, :column) do
|
|
124
|
+
def initialize(name:, type_params:, params:, return_type:, body:, kind:, visibility:, async:, const: false, attributes: [], line: nil, column: nil) = super
|
|
125
125
|
end
|
|
126
126
|
ExternFunctionDecl = Data.define(:name, :type_params, :params, :return_type, :variadic, :attributes, :line, :column, :mapping) do
|
|
127
127
|
def initialize(name:, type_params:, params:, return_type:, variadic:, attributes: [], line: nil, column: nil, mapping: nil) = super
|
|
@@ -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 << ""
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
module CompileTime
|
|
5
|
+
# Compile-time folding for const-method calls and the resolution helpers
|
|
6
|
+
# behind them. Included by both the semantic checker and the lowering
|
|
7
|
+
# engine so the folding behavior lives in one place instead of two
|
|
8
|
+
# near-identical copies. Each includer supplies a small set of `comptime_*`
|
|
9
|
+
# adapters for the phase-specific lookups (ctx maps, scope/env evaluation,
|
|
10
|
+
# and the BlockContext factory).
|
|
11
|
+
module MethodFolding
|
|
12
|
+
def comptime_methods_map_for_binding(type, keys)
|
|
13
|
+
dispatch_type = type.respond_to?(:definition) ? type.definition : type
|
|
14
|
+
method_map = comptime_methods_map.fetch(dispatch_type, nil)
|
|
15
|
+
method_map ||= comptime_methods_map_by_to_s(dispatch_type)
|
|
16
|
+
return nil unless method_map
|
|
17
|
+
|
|
18
|
+
keys.each do |key|
|
|
19
|
+
binding = method_map[key]
|
|
20
|
+
next unless binding
|
|
21
|
+
|
|
22
|
+
ast = binding.ast
|
|
23
|
+
next unless ast.respond_to?(:const) && ast.const && ast.body
|
|
24
|
+
next unless binding.type_params.empty?
|
|
25
|
+
|
|
26
|
+
return binding
|
|
27
|
+
end
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def comptime_method_binding(member_access, ctx)
|
|
32
|
+
member = member_access.member
|
|
33
|
+
keys = ["static:#{member}", member]
|
|
34
|
+
|
|
35
|
+
if (type = resolve_type_expression(member_access.receiver)) &&
|
|
36
|
+
(binding = comptime_methods_map_for_binding(type, keys))
|
|
37
|
+
return binding
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
receiver_type = comptime_receiver_type(member_access.receiver, ctx)
|
|
41
|
+
return nil unless receiver_type
|
|
42
|
+
|
|
43
|
+
comptime_methods_map_for_binding(receiver_type, keys)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def comptime_method_binding_for_receiver(receiver_type, member)
|
|
47
|
+
comptime_methods_map_for_binding(receiver_type, ["static:#{member}", member])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def comptime_receiver_type(receiver, ctx)
|
|
51
|
+
case receiver
|
|
52
|
+
when AST::Identifier
|
|
53
|
+
type = comptime_scoped_value_type(receiver.name, ctx)
|
|
54
|
+
return type if type
|
|
55
|
+
|
|
56
|
+
comptime_value_type(receiver.name)
|
|
57
|
+
when AST::MemberAccess
|
|
58
|
+
return nil unless receiver.receiver.is_a?(AST::Identifier)
|
|
59
|
+
|
|
60
|
+
comptime_imported_value_type(receiver.receiver.name, receiver.member)
|
|
61
|
+
when AST::Call, AST::Specialization
|
|
62
|
+
comptime_call_return_type(receiver, ctx)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def comptime_member_receiver_value(receiver, ctx)
|
|
67
|
+
return nil if resolve_type_expression(receiver)
|
|
68
|
+
|
|
69
|
+
comptime_eval(receiver, ctx)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def comptime_call_return_type(call_expr, ctx)
|
|
73
|
+
case call_expr.callee
|
|
74
|
+
when AST::MemberAccess
|
|
75
|
+
binding = comptime_method_binding(call_expr.callee, ctx)
|
|
76
|
+
return binding.body_return_type if binding
|
|
77
|
+
when AST::Identifier
|
|
78
|
+
func = comptime_const_function(call_expr.callee.name)
|
|
79
|
+
return func.body_return_type if comptime_const_function_return?(func)
|
|
80
|
+
when AST::Specialization
|
|
81
|
+
callee_name = call_expr.callee.callee.is_a?(AST::Identifier) ? call_expr.callee.callee.name : nil
|
|
82
|
+
if callee_name
|
|
83
|
+
func = comptime_const_function(callee_name)
|
|
84
|
+
return func.body_return_type if comptime_const_function_return?(func)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def comptime_expression_type(expression, scopes:)
|
|
91
|
+
case expression
|
|
92
|
+
when AST::Call, AST::Specialization
|
|
93
|
+
comptime_call_return_type(expression, scopes)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def comptime_struct_field_names(type_name)
|
|
98
|
+
parts = type_name.is_a?(Array) ? type_name.map(&:to_s) : [type_name.to_s]
|
|
99
|
+
return nil if parts.empty?
|
|
100
|
+
|
|
101
|
+
type = resolve_type_expression(::MilkTea::AST.build_chain_from_parts(parts))
|
|
102
|
+
return nil unless type
|
|
103
|
+
return nil unless type.respond_to?(:fields) && type.fields
|
|
104
|
+
|
|
105
|
+
type.fields.keys
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def comptime_folded_value?(value)
|
|
109
|
+
return false if value.nil?
|
|
110
|
+
return false if value.is_a?(Types::Base)
|
|
111
|
+
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def comptime_const_function_return?(func)
|
|
116
|
+
func && func.respond_to?(:ast) && func.ast.respond_to?(:const) && func.ast.const
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def comptime_const_method_body(binding, arguments, scopes:, receiver_value:)
|
|
120
|
+
method = binding.ast
|
|
121
|
+
return nil unless method.respond_to?(:body) && method.body
|
|
122
|
+
return nil if binding.type_params.any?
|
|
123
|
+
return nil unless method.params.length == arguments.length
|
|
124
|
+
|
|
125
|
+
initial_vars = {}
|
|
126
|
+
if binding.type.receiver_type
|
|
127
|
+
return nil unless comptime_folded_value?(receiver_value)
|
|
128
|
+
|
|
129
|
+
initial_vars["this"] = receiver_value
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
method.params.each_with_index do |param, idx|
|
|
133
|
+
arg_value = comptime_eval(arguments[idx].value, scopes)
|
|
134
|
+
return nil unless arg_value
|
|
135
|
+
|
|
136
|
+
initial_vars[param.name] = arg_value
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
variable_types = binding.body_params.each_with_object({}) do |param, acc|
|
|
140
|
+
acc[param.name] = param.type
|
|
141
|
+
end
|
|
142
|
+
block_context = comptime_block_context(initial_vars, variable_types)
|
|
143
|
+
result = block_context.evaluate_block(method.body, scopes: nil)
|
|
144
|
+
result.is_a?(CompileTime::ReturnOutcome) ? result.value : result
|
|
145
|
+
rescue CompileTime::Error => e
|
|
146
|
+
comptime_raise_error(e)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def comptime_imported_value_type(import_name, member)
|
|
150
|
+
imported_module = @ctx.imports.fetch(import_name, nil)
|
|
151
|
+
return nil unless imported_module
|
|
152
|
+
|
|
153
|
+
imported_module.values[member]&.type
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|