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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -2
  3. data/docs/index.html +20 -4
  4. data/docs/language-design.md +5 -2
  5. data/docs/language-manual.md +28 -5
  6. data/lib/milk_tea/base.rb +1 -1
  7. data/lib/milk_tea/bindings/bindgen/declaration.rb +17 -0
  8. data/lib/milk_tea/bindings/bindgen/emitter.rb +44 -5
  9. data/lib/milk_tea/bindings/imported_bindings/defaults.rb +7 -0
  10. data/lib/milk_tea/bindings/raw_bindings/defaults.rb +22 -0
  11. data/lib/milk_tea/bindings/upstream_sources.rb +9 -0
  12. data/lib/milk_tea/bindings/vendored_box3d.rb +77 -0
  13. data/lib/milk_tea/bindings.rb +1 -0
  14. data/lib/milk_tea/core/c_backend/expressions.rb +21 -5
  15. data/lib/milk_tea/core/c_backend/feature_detection.rb +6 -1
  16. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +38 -4
  17. data/lib/milk_tea/core/c_backend/statements.rb +1 -1
  18. data/lib/milk_tea/core/c_backend/type_collectors.rb +97 -4
  19. data/lib/milk_tea/core/c_backend.rb +9 -0
  20. data/lib/milk_tea/core/lowering/async/async_lowering.rb +2 -6
  21. data/lib/milk_tea/core/lowering/async/frame_builder.rb +12 -5
  22. data/lib/milk_tea/core/lowering/async/normalization.rb +28 -5
  23. data/lib/milk_tea/core/lowering/expressions.rb +31 -6
  24. data/lib/milk_tea/core/lowering/proc.rb +3 -0
  25. data/lib/milk_tea/core/lowering/resolve.rb +3 -0
  26. data/lib/milk_tea/core/lowering/utils.rb +26 -0
  27. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +21 -2
  28. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +49 -1
  29. data/lib/milk_tea/core/semantic_analyzer/statements.rb +1 -0
  30. data/lib/milk_tea/core/types/registry.rb +39 -16
  31. data/lib/milk_tea/lsp/server/hover.rb +0 -1
  32. data/lib/milk_tea/lsp/server/text_documents.rb +1 -1
  33. data/lib/milk_tea/tooling/docs_app.rb +1 -1
  34. data/std/box3d.mt +784 -0
  35. data/std/c/box3d.mt +1871 -0
  36. data/std/c/miniaudio.mt +19 -28
  37. data/std/c/steamworks.mt +2 -5
  38. data/std/miniaudio.mt +9 -12
  39. data/std/steamworks.mt +0 -1
  40. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91b5015b218da9925b56895b551063fca825ca776fc336fe898ef17e332aae4e
4
- data.tar.gz: 46e4a2da80edfb7c64fc847ee10844c9f5d36572df35105ab6ed51e2455d347a
3
+ metadata.gz: 6842b1cfb6eac5181fdbcd31cd90dbb9f436bb0f3c7b0cc25d49a8c1f8e13a8b
4
+ data.tar.gz: 4e97091f3da86a115656c6ec7d9bb10fe5cd65360bd59a203fc6595111285aff
5
5
  SHA512:
6
- metadata.gz: 0c1dcedaf98ef374c9dd21cded75f8512c755a63e4a3a155e494d9edf098b2d53da3b258edab5d63066a2a9eaa3c2a3883aa69241608509085caf398c1edecc1
7
- data.tar.gz: a427c7ad0f2475b7dc5cccadcb6cc143ab195f906b7b9b13407a7e754043fd8550495c7dc750f5f27c92e2eca13477d9f6697650bddd515403526812b1dac06b
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.41"
6
+ VERSION = "0.3.43"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -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
- next unless anonymous_record
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
- pending << [synthetic_aggregate_name(owner_name, field, aggregate_node), anonymous_record]
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
@@ -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)}(#{emit_address_of_operand(expression.receiver)}, #{emit_expression(expression.index)}))"
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)}(#{emit_address_of_operand(expression.receiver)}, #{emit_expression(expression.index)})"
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)}(#{emit_address_of_operand(expression.expression.receiver)}, #{emit_expression(expression.expression.index)})"
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
- elements = value.is_a?(IR::ArrayLiteral) ? value.elements.map { |e| emit_initializer(e) }.join(", ") : ""
494
- "((#{elem_c_name}*)memcpy(malloc(#{elem_count} * sizeof(#{elem_c_name})), &(#{elem_c_name}[#{elem_count}]){ #{elements} }, #{elem_count} * sizeof(#{elem_c_name})))"
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