mt-lang 0.3.41 → 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 +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/bindings/bindgen/declaration.rb +17 -0
- data/lib/milk_tea/bindings/bindgen/emitter.rb +44 -5
- data/lib/milk_tea/bindings/imported_bindings/defaults.rb +7 -0
- data/lib/milk_tea/bindings/raw_bindings/defaults.rb +22 -0
- data/lib/milk_tea/bindings/upstream_sources.rb +9 -0
- data/lib/milk_tea/bindings/vendored_box3d.rb +77 -0
- data/lib/milk_tea/bindings.rb +1 -0
- data/lib/milk_tea/core/c_backend/expressions.rb +21 -5
- data/lib/milk_tea/core/c_backend/feature_detection.rb +6 -1
- data/lib/milk_tea/core/c_backend/runtime_helpers.rb +38 -4
- data/lib/milk_tea/core/c_backend/statements.rb +1 -1
- data/lib/milk_tea/core/c_backend/type_collectors.rb +97 -4
- data/lib/milk_tea/core/c_backend.rb +9 -0
- data/lib/milk_tea/core/lowering/async/async_lowering.rb +2 -6
- data/lib/milk_tea/core/lowering/async/frame_builder.rb +12 -5
- data/lib/milk_tea/core/lowering/async/normalization.rb +28 -5
- 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 +3 -0
- data/lib/milk_tea/core/lowering/utils.rb +26 -0
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +21 -2
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +49 -1
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +1 -0
- 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
- data/lib/milk_tea/tooling/docs_app.rb +1 -1
- data/std/box3d.mt +784 -0
- data/std/c/box3d.mt +1871 -0
- data/std/c/miniaudio.mt +19 -28
- data/std/c/steamworks.mt +2 -5
- data/std/miniaudio.mt +9 -12
- data/std/steamworks.mt +0 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6842b1cfb6eac5181fdbcd31cd90dbb9f436bb0f3c7b0cc25d49a8c1f8e13a8b
|
|
4
|
+
data.tar.gz: 4e97091f3da86a115656c6ec7d9bb10fe5cd65360bd59a203fc6595111285aff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 > 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
|
@@ -280,11 +280,28 @@ module MilkTea
|
|
|
280
280
|
qual_type = node.dig("type", "qualType")
|
|
281
281
|
if macro_probe_declaration?(node) && qual_type.to_s.include?("typeof")
|
|
282
282
|
node.dig("type", "desugaredQualType") || qual_type
|
|
283
|
+
elsif constant_typedef_alias_aggregate?(node)
|
|
284
|
+
node.dig("type", "desugaredQualType") || qual_type
|
|
283
285
|
else
|
|
284
286
|
qual_type
|
|
285
287
|
end
|
|
286
288
|
end
|
|
287
289
|
|
|
290
|
+
# A constant whose spelled type is a typedef alias that resolves to an
|
|
291
|
+
# aggregate (e.g. `typedef b3Vec3 b3Pos` followed by
|
|
292
|
+
# `static const b3Pos b3Pos_zero`) must lower against the underlying
|
|
293
|
+
# record, not the alias name, so init-list emission can find the
|
|
294
|
+
# aggregate declaration.
|
|
295
|
+
def constant_typedef_alias_aggregate?(node)
|
|
296
|
+
qual_type = node.dig("type", "qualType").to_s
|
|
297
|
+
spelled = strip_qualifiers(qual_type)
|
|
298
|
+
return false unless spelled.match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/)
|
|
299
|
+
return false if @aggregate_declarations.key?(spelled)
|
|
300
|
+
|
|
301
|
+
desugared = strip_qualifiers(node.dig("type", "desugaredQualType").to_s)
|
|
302
|
+
!desugared.empty? && desugared != spelled && desugared.match?(/\A(?:struct|union)\b/)
|
|
303
|
+
end
|
|
304
|
+
|
|
288
305
|
def function_return_type(node)
|
|
289
306
|
qual_type = type_qual_type(node)
|
|
290
307
|
match = qual_type&.match(/\A(.+?)\s*\((?:.*)\)\z/)
|
|
@@ -75,6 +75,11 @@ module MilkTea
|
|
|
75
75
|
lines = [header]
|
|
76
76
|
fields = Array(node["inner"]).select { |child| child["kind"] == "FieldDecl" }
|
|
77
77
|
fields.each do |field|
|
|
78
|
+
anonymous_record = anonymous_record_decl_for_field(field, node)
|
|
79
|
+
if anonymous_record && field_unnamed?(field)
|
|
80
|
+
lines.concat(emit_flattened_anonymous_record(anonymous_record, owner_name: name))
|
|
81
|
+
next
|
|
82
|
+
end
|
|
78
83
|
field_type = aggregate_field_type(field, owner_name: name, aggregate_node: node)
|
|
79
84
|
mt_name = emitted_name(aggregate_field_name(field, aggregate_node: node))
|
|
80
85
|
lines << " #{mt_name}: #{field_type}"
|
|
@@ -82,6 +87,26 @@ module MilkTea
|
|
|
82
87
|
lines
|
|
83
88
|
end
|
|
84
89
|
|
|
90
|
+
# Anonymous union/struct members are flattened into the owning aggregate,
|
|
91
|
+
# matching C semantics where their fields are accessible directly on the
|
|
92
|
+
# enclosing type (e.g. `b3ChildShape.hull`, `b3TreeNode.children`).
|
|
93
|
+
def emit_flattened_anonymous_record(record, owner_name:)
|
|
94
|
+
Array(record["inner"]).select { |child| child["kind"] == "FieldDecl" }.flat_map do |field|
|
|
95
|
+
anonymous_record = anonymous_record_decl_for_field(field, record)
|
|
96
|
+
if anonymous_record && field_unnamed?(field)
|
|
97
|
+
emit_flattened_anonymous_record(anonymous_record, owner_name:)
|
|
98
|
+
else
|
|
99
|
+
field_type = aggregate_field_type(field, owner_name:, aggregate_node: record)
|
|
100
|
+
[" #{emitted_name(field["name"])}: #{field_type}"]
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def field_unnamed?(field)
|
|
106
|
+
name = field["name"]
|
|
107
|
+
name.nil? || name.empty?
|
|
108
|
+
end
|
|
109
|
+
|
|
85
110
|
def bindgen_param_name(name)
|
|
86
111
|
name = emitted_name(name)
|
|
87
112
|
return [name, nil] unless generated_binding_name_conflict?(name)
|
|
@@ -110,6 +135,13 @@ module MilkTea
|
|
|
110
135
|
anonymous_record = anonymous_record_decl_for_field(field, aggregate_node)
|
|
111
136
|
next unless anonymous_record
|
|
112
137
|
|
|
138
|
+
if field_unnamed?(field)
|
|
139
|
+
# Anonymous member: flattened into the parent. Keep walking so
|
|
140
|
+
# named nested records still get synthesized.
|
|
141
|
+
pending << [owner_name, anonymous_record]
|
|
142
|
+
next
|
|
143
|
+
end
|
|
144
|
+
|
|
113
145
|
synthetic_name = synthetic_aggregate_name(owner_name, field, aggregate_node)
|
|
114
146
|
unless @synthetic_declarations.any? { |declaration| declaration[:name] == synthetic_name }
|
|
115
147
|
@synthetic_declarations << { kind: anonymous_record.fetch("tagUsed"), name: synthetic_name, node: anonymous_record }
|
|
@@ -140,12 +172,19 @@ module MilkTea
|
|
|
140
172
|
seen[key] = true
|
|
141
173
|
|
|
142
174
|
Array(aggregate_node["inner"]).select { |child| child["kind"] == "FieldDecl" }.each do |field|
|
|
143
|
-
aggregate_field_type(field, owner_name:, aggregate_node:)
|
|
144
|
-
|
|
145
175
|
anonymous_record = anonymous_record_decl_for_field(field, aggregate_node)
|
|
146
|
-
|
|
176
|
+
if anonymous_record
|
|
177
|
+
if field_unnamed?(field)
|
|
178
|
+
# Flattened anonymous member: walk its fields under the owner.
|
|
179
|
+
pending << [owner_name, anonymous_record]
|
|
180
|
+
next
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
pending << [synthetic_aggregate_name(owner_name, field, aggregate_node), anonymous_record]
|
|
184
|
+
next
|
|
185
|
+
end
|
|
147
186
|
|
|
148
|
-
|
|
187
|
+
aggregate_field_type(field, owner_name:, aggregate_node:)
|
|
149
188
|
end
|
|
150
189
|
end
|
|
151
190
|
end
|
|
@@ -155,7 +194,7 @@ module MilkTea
|
|
|
155
194
|
return override if override
|
|
156
195
|
|
|
157
196
|
anonymous_record = anonymous_record_decl_for_field(field, aggregate_node)
|
|
158
|
-
return synthetic_aggregate_name(owner_name, field, aggregate_node) if anonymous_record
|
|
197
|
+
return synthetic_aggregate_name(owner_name, field, aggregate_node) if anonymous_record && !field_unnamed?(field)
|
|
159
198
|
|
|
160
199
|
map_type_node(field, context: "field #{owner_name}.#{field["name"]}")
|
|
161
200
|
end
|
|
@@ -60,6 +60,13 @@ module MilkTea
|
|
|
60
60
|
raw_module_name: "std.c.box2d",
|
|
61
61
|
policy_path: root.join("bindings/imported/box2d.binding.json"),
|
|
62
62
|
),
|
|
63
|
+
Binding.new(
|
|
64
|
+
name: "box3d",
|
|
65
|
+
module_name: "std.box3d",
|
|
66
|
+
binding_path: root.join("std/box3d.mt"),
|
|
67
|
+
raw_module_name: "std.c.box3d",
|
|
68
|
+
policy_path: root.join("bindings/imported/box3d.binding.json"),
|
|
69
|
+
),
|
|
63
70
|
Binding.new(
|
|
64
71
|
name: "cjson",
|
|
65
72
|
module_name: "std.cjson",
|
|
@@ -11,6 +11,8 @@ module MilkTea
|
|
|
11
11
|
vendored_glfw_library = vendored_glfw.library(root:)
|
|
12
12
|
vendored_box2d = MilkTea::VendoredBox2D
|
|
13
13
|
vendored_box2d_library = vendored_box2d.library(root:)
|
|
14
|
+
vendored_box3d = MilkTea::VendoredBox3D
|
|
15
|
+
vendored_box3d_library = vendored_box3d.library(root:)
|
|
14
16
|
vendored_cjson = MilkTea::VendoredCJSON
|
|
15
17
|
vendored_cjson_library = vendored_cjson.library(root:)
|
|
16
18
|
vendored_flecs = MilkTea::VendoredFlecs
|
|
@@ -960,6 +962,26 @@ module MilkTea
|
|
|
960
962
|
vendored_box2d.header_root(root:).join("box2d.h").to_s,
|
|
961
963
|
],
|
|
962
964
|
),
|
|
965
|
+
Binding.new(
|
|
966
|
+
name: "box3d",
|
|
967
|
+
module_name: "std.c.box3d",
|
|
968
|
+
binding_path: root.join("std/c/box3d.mt"),
|
|
969
|
+
include_directives: ["box3d/box3d.h"],
|
|
970
|
+
link_libraries: ["box3d"],
|
|
971
|
+
vendored_library: vendored_box3d_library,
|
|
972
|
+
clang_args: vendored_box3d.include_flags(root:),
|
|
973
|
+
compiler_flags: vendored_box3d.include_flags(root:),
|
|
974
|
+
tracked_header_paths: [
|
|
975
|
+
vendored_box3d.header_root(root:).join("box3d.h").to_s,
|
|
976
|
+
],
|
|
977
|
+
tracked_header_prefixes: [
|
|
978
|
+
vendored_box3d.header_root(root:).to_s,
|
|
979
|
+
],
|
|
980
|
+
declaration_name_prefixes: ["b3", "B3_"],
|
|
981
|
+
header_candidates: [
|
|
982
|
+
vendored_box3d.header_root(root:).join("box3d.h").to_s,
|
|
983
|
+
],
|
|
984
|
+
),
|
|
963
985
|
Binding.new(
|
|
964
986
|
name: "cjson",
|
|
965
987
|
module_name: "std.c.cjson",
|
|
@@ -195,6 +195,15 @@ module MilkTea
|
|
|
195
195
|
include/box2d/box2d.h
|
|
196
196
|
],
|
|
197
197
|
),
|
|
198
|
+
Source.new(
|
|
199
|
+
name: "box3d",
|
|
200
|
+
checkout_root: data.join("third_party/box3d-upstream"),
|
|
201
|
+
repository_url: "https://github.com/erincatto/box3d.git",
|
|
202
|
+
revision: "30c67b5e6d0a3a66f0f506c69ce9e9e0587e3b7c",
|
|
203
|
+
sentinel_paths: %w[
|
|
204
|
+
include/box3d/box3d.h
|
|
205
|
+
],
|
|
206
|
+
),
|
|
198
207
|
Source.new(
|
|
199
208
|
name: "cjson",
|
|
200
209
|
checkout_root: data.join("third_party/cjson-upstream"),
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "vendored_c_library"
|
|
4
|
+
|
|
5
|
+
module MilkTea
|
|
6
|
+
module VendoredBox3D
|
|
7
|
+
Error = VendoredCLibrary::Error
|
|
8
|
+
|
|
9
|
+
CONFIGURE_ARGS = %w[
|
|
10
|
+
-DCMAKE_BUILD_TYPE=Release
|
|
11
|
+
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
|
12
|
+
-DBUILD_SHARED_LIBS=OFF
|
|
13
|
+
-DBOX3D_SAMPLES=OFF
|
|
14
|
+
-DBOX3D_BENCHMARKS=OFF
|
|
15
|
+
-DBOX3D_DOCS=OFF
|
|
16
|
+
-DBOX3D_PROFILE=OFF
|
|
17
|
+
-DBOX3D_VALIDATE=OFF
|
|
18
|
+
-DBOX3D_UNIT_TESTS=OFF
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
SYSTEM_LINK_FLAGS = %w[
|
|
22
|
+
-lm
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
25
|
+
def self.library(root: MilkTea.root)
|
|
26
|
+
resolved_root = Pathname.new(File.expand_path(root.to_s))
|
|
27
|
+
@libraries ||= {}
|
|
28
|
+
@libraries[resolved_root.to_s] ||= VendoredCLibrary::CMake.new(
|
|
29
|
+
name: "box3d",
|
|
30
|
+
source_root: source_root(root: resolved_root),
|
|
31
|
+
build_root: build_root(root: resolved_root),
|
|
32
|
+
install_root: install_root(root: resolved_root),
|
|
33
|
+
archive_path: archive_path(root: resolved_root),
|
|
34
|
+
include_roots: [include_root(root: resolved_root)],
|
|
35
|
+
configure_args: CONFIGURE_ARGS,
|
|
36
|
+
system_link_flags: SYSTEM_LINK_FLAGS,
|
|
37
|
+
cc_env_var: "BOX3D_CC",
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.source_root(root: MilkTea.root)
|
|
42
|
+
MilkTea.writable_root_for(root).join("third_party/box3d-upstream")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.include_root(root: MilkTea.root)
|
|
46
|
+
source_root(root:).join("include")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.header_root(root: MilkTea.root)
|
|
50
|
+
include_root(root:).join("box3d")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.build_root(root: MilkTea.root)
|
|
54
|
+
MilkTea.writable_root_for(root).join("tmp/vendored-box3d")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.install_root(root: MilkTea.root)
|
|
58
|
+
MilkTea.writable_root_for(root).join("tmp/vendored-box3d-prefix")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.archive_path(root: MilkTea.root)
|
|
62
|
+
install_root(root:).join("lib/libbox3d.a")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.include_flags(root: MilkTea.root)
|
|
66
|
+
library(root:).include_flags
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.link_flags(root: MilkTea.root)
|
|
70
|
+
library(root:).link_flags
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.prepare!(root: MilkTea.root, **kwargs)
|
|
74
|
+
library(root:).prepare!(**kwargs)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/milk_tea/bindings.rb
CHANGED
|
@@ -11,6 +11,7 @@ require_relative "bindings/vendored_raylib"
|
|
|
11
11
|
require_relative "bindings/vendored_sdl3"
|
|
12
12
|
require_relative "bindings/vendored_glfw"
|
|
13
13
|
require_relative "bindings/vendored_box2d"
|
|
14
|
+
require_relative "bindings/vendored_box3d"
|
|
14
15
|
require_relative "bindings/vendored_cjson"
|
|
15
16
|
require_relative "bindings/vendored_flecs"
|
|
16
17
|
require_relative "bindings/vendored_libuv"
|
|
@@ -17,7 +17,7 @@ module MilkTea
|
|
|
17
17
|
if (alias_name = checked_index_alias(expression))
|
|
18
18
|
"(*#{alias_name})"
|
|
19
19
|
else
|
|
20
|
-
"(*#{checked_array_index_helper_name(expression.receiver_type)}(#{
|
|
20
|
+
"(*#{checked_array_index_helper_name(expression.receiver_type)}(#{emit_checked_array_index_argument(expression.receiver)}, #{emit_expression(expression.index)}))"
|
|
21
21
|
end
|
|
22
22
|
when IR::CheckedSpanIndex
|
|
23
23
|
if (alias_name = checked_index_alias(expression))
|
|
@@ -26,7 +26,7 @@ module MilkTea
|
|
|
26
26
|
"(*#{checked_span_index_helper_name(expression.receiver_type)}(#{emit_expression(expression.receiver)}, #{emit_expression(expression.index)}))"
|
|
27
27
|
end
|
|
28
28
|
when IR::NullableIndex
|
|
29
|
-
"#{nullable_array_index_helper_name(expression.receiver_type)}(#{
|
|
29
|
+
"#{nullable_array_index_helper_name(expression.receiver_type)}(#{emit_checked_array_index_argument(expression.receiver)}, #{emit_expression(expression.index)})"
|
|
30
30
|
when IR::NullableSpanIndex
|
|
31
31
|
"#{nullable_span_index_helper_name(expression.receiver_type)}(#{emit_expression(expression.receiver)}, #{emit_expression(expression.index)})"
|
|
32
32
|
when IR::Call
|
|
@@ -81,7 +81,7 @@ module MilkTea
|
|
|
81
81
|
case expression.expression
|
|
82
82
|
when IR::CheckedIndex
|
|
83
83
|
alias_name = checked_index_alias(expression.expression)
|
|
84
|
-
alias_name || "#{checked_array_index_helper_name(expression.expression.receiver_type)}(#{
|
|
84
|
+
alias_name || "#{checked_array_index_helper_name(expression.expression.receiver_type)}(#{emit_checked_array_index_argument(expression.expression.receiver)}, #{emit_expression(expression.expression.index)})"
|
|
85
85
|
when IR::CheckedSpanIndex
|
|
86
86
|
alias_name = checked_index_alias(expression.expression)
|
|
87
87
|
alias_name || "#{checked_span_index_helper_name(expression.expression.receiver_type)}(#{emit_expression(expression.expression.receiver)}, #{emit_expression(expression.expression.index)})"
|
|
@@ -344,6 +344,16 @@ module MilkTea
|
|
|
344
344
|
"(void)#{wrap_expression(expression)}"
|
|
345
345
|
end
|
|
346
346
|
|
|
347
|
+
def emit_checked_array_index_argument(receiver)
|
|
348
|
+
if receiver.is_a?(IR::Unary) && receiver.operator == "*"
|
|
349
|
+
emit_expression(receiver.operand)
|
|
350
|
+
elsif c_expression_lvalue?(receiver)
|
|
351
|
+
emit_expression(receiver)
|
|
352
|
+
else
|
|
353
|
+
"&(#{c_type(receiver.type)}[1]){ #{emit_expression(receiver)} }[0]"
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
347
357
|
def emit_address_of_operand(expression)
|
|
348
358
|
return emit_expression(expression.operand) if expression.is_a?(IR::Unary) && expression.operator == "*"
|
|
349
359
|
|
|
@@ -490,8 +500,14 @@ module MilkTea
|
|
|
490
500
|
def emit_cyclic_array_initializer(field_type, value)
|
|
491
501
|
elem_c_name = c_type(array_element_type(field_type))
|
|
492
502
|
elem_count = array_length(field_type)
|
|
493
|
-
|
|
494
|
-
|
|
503
|
+
source_expr = if value.is_a?(IR::ArrayLiteral)
|
|
504
|
+
"&(#{elem_c_name}[#{elem_count}]){ #{value.elements.map { |e| emit_initializer(e) }.join(", ")} }"
|
|
505
|
+
elsif c_expression_lvalue?(value)
|
|
506
|
+
"&(#{emit_expression(value)})"
|
|
507
|
+
else
|
|
508
|
+
"&(#{elem_c_name}[#{elem_count}]){ #{emit_expression(value)} }"
|
|
509
|
+
end
|
|
510
|
+
"((#{elem_c_name}*)memcpy(malloc(#{elem_count} * sizeof(#{elem_c_name})), #{source_expr}, #{elem_count} * sizeof(#{elem_c_name})))"
|
|
495
511
|
end
|
|
496
512
|
|
|
497
513
|
def emit_cyclic_struct_initializer(field_type, value)
|
|
@@ -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]) }
|
|
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
|