mt-lang 0.3.13 → 0.3.16
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 -5
- data/docs/index.html +31 -23
- data/docs/language-design.md +13 -17
- data/docs/language-manual.md +19 -7
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/core/ast.rb +2 -2
- data/lib/milk_tea/core/control_flow/builder.rb +62 -2
- data/lib/milk_tea/core/lowering/async/analysis.rb +3 -9
- data/lib/milk_tea/core/lowering/async/lowering.rb +2 -10
- data/lib/milk_tea/core/lowering/async/normalization.rb +2 -7
- data/lib/milk_tea/core/lowering/block.rb +1 -5
- data/lib/milk_tea/core/lowering/expressions.rb +47 -3
- data/lib/milk_tea/core/lowering/proc.rb +1 -5
- data/lib/milk_tea/core/lowering/resolve.rb +26 -2
- data/lib/milk_tea/core/lowering/scans.rb +3 -1
- data/lib/milk_tea/core/lowering/utils.rb +7 -28
- data/lib/milk_tea/core/parser/statements.rb +18 -12
- data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +3 -11
- data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +2 -3
- data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +1 -1
- data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +1 -2
- data/lib/milk_tea/core/semantic_analyzer/nullability.rb +2 -4
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +2 -8
- data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +29 -2
- data/lib/milk_tea/core/types.rb +0 -6
- data/lib/milk_tea/tooling/linter/imports_platform.rb +2 -4
- data/lib/milk_tea/tooling/linter/release_rules.rb +1 -6
- data/lib/milk_tea/tooling/linter/source_helpers.rb +0 -6
- data/lib/milk_tea/tooling/linter/visitors.rb +4 -8
- data/lib/milk_tea/tooling/sexpr_dumper.rb +2 -0
- data/std/asset_pack.mt +1 -1
- data/std/async/libuv_runtime.mt +6 -6
- data/std/async/mailbox.mt +4 -4
- data/std/{cell.mt → box.mt} +9 -9
- data/std/cookie.mt +2 -2
- data/std/crypto.mt +1 -1
- data/std/curl/runtime.mt +10 -10
- data/std/fs.linux.mt +26 -26
- data/std/fs.windows.mt +22 -22
- data/std/goap.mt +2 -2
- data/std/http/server.mt +21 -21
- data/std/http.mt +14 -14
- data/std/jobs.mt +4 -4
- data/std/json.mt +5 -5
- data/std/log.mt +1 -1
- data/std/mem/tracking.mt +1 -1
- data/std/net/channel.mt +4 -4
- data/std/net/clock.mt +6 -6
- data/std/net/discovery.mt +7 -7
- data/std/net/lobby.mt +10 -10
- data/std/net/manager.mt +1 -1
- data/std/net/mux.mt +4 -4
- data/std/net/nat.mt +4 -4
- data/std/net/packet.mt +3 -3
- data/std/net/punch.mt +3 -3
- data/std/net/rpc.mt +3 -3
- data/std/net/session.mt +15 -15
- data/std/net/stun.mt +4 -4
- data/std/net/turn.mt +4 -4
- data/std/oauth2.mt +12 -12
- data/std/path.mt +8 -8
- data/std/process.mt +4 -4
- data/std/raylib/packed_assets.mt +5 -5
- data/std/serialize.mt +1 -1
- data/std/tar.mt +10 -10
- data/std/terminal.mt +18 -18
- data/std/tls.mt +8 -8
- data/std/uri.mt +1 -1
- data/std/utility.mt +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b5cd3c1ef5e380b8d0c5d4e20f829400d01c6fdfd3e495e8c98050f4af8b59ba
|
|
4
|
+
data.tar.gz: 0e083d1780c9d591b996604a8b489726fa0f0c6fb85b8dc03da87b681383d5ca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 44f866e7d6bf6fc4998e0a9054bf898b27ed47ef38149de1881baf1d810e845b94e3d1f3ff304486654ea339329d726a10d969bcff4021943cc98b03bfe3fe01
|
|
7
|
+
data.tar.gz: 0fa7237c14aa9e038b4d51ae08a5971430d0e47b97fb19caf619aeb80a5e562de99aae1cf342a11ffea41e3b2268a128454bbf022b3e9182ddd94ff2ff0aff45
|
data/README.md
CHANGED
|
@@ -236,7 +236,7 @@ Callable and `ref[...]` rules:
|
|
|
236
236
|
- External functions still cannot take `ref[...]` parameters, and ordinary functions still cannot return `ref[...]`.
|
|
237
237
|
- `proc` captures are value captures. A captured local is not a mutable alias back to the outer binding. Any storable type may be captured, including scalars, arrays, structs, and other `proc` values. Captured `proc` values participate in the ref-counted lifecycle: the capturing proc retains the captured proc on creation and releases it when the env is freed.
|
|
238
238
|
- `ref[T]` values are not capturable by design since they are non-owning.
|
|
239
|
-
- Shared mutable proc state should use explicit storage such as `std.
|
|
239
|
+
- Shared mutable proc state should use explicit storage such as `std.box.alloc[T](...)` or other explicit pointer-backed state, not implicit mutable capture.
|
|
240
240
|
|
|
241
241
|
## 5. Data Declarations
|
|
242
242
|
|
|
@@ -477,7 +477,7 @@ Rules:
|
|
|
477
477
|
- Conditions must be `bool`.
|
|
478
478
|
- There is no truthy or falsy coercion from integers or pointers.
|
|
479
479
|
- `pass` is an explicit no-op statement for intentionally empty block bodies.
|
|
480
|
-
-
|
|
480
|
+
- Every block-taking statement accepts a `:`-newline-indented block, or a single statement on the same line after `:`. This applies to `if`/`else` (e.g. `if cond: stmt else: stmt`), `while`, `for`, `defer` (e.g. `defer: cleanup()`), and `unsafe`. `match` arms are the exception: their inline form is a single value expression.
|
|
481
481
|
|
|
482
482
|
`match` supports the following scrutinee types:
|
|
483
483
|
|
|
@@ -615,7 +615,7 @@ Rules:
|
|
|
615
615
|
|
|
616
616
|
`defer`:
|
|
617
617
|
|
|
618
|
-
- `defer expr`
|
|
618
|
+
- `defer: expr` — single-statement inline form
|
|
619
619
|
- `defer:` block form
|
|
620
620
|
- `return` is not allowed inside defer blocks.
|
|
621
621
|
|
|
@@ -902,7 +902,7 @@ Core modules in `std/`:
|
|
|
902
902
|
|
|
903
903
|
**Compression**: `std.gzip`, `std.tar`
|
|
904
904
|
|
|
905
|
-
**Other**: `std.bytes`, `std.ctype`, `std.asset_pack`, `std.
|
|
905
|
+
**Other**: `std.bytes`, `std.ctype`, `std.asset_pack`, `std.box`
|
|
906
906
|
|
|
907
907
|
See module source for full method surface. Iterator forms:
|
|
908
908
|
- Pointer-returning (`next() -> nullable ptr[T]`): `Vec`, `Deque`, `BinaryHeap`/`PriorityQueue`/`OrderedSet` (read-only), `OrderedMap.keys`/`Map.keys`/`Set`/`LinkedMap.keys`/`LinkedSet`/`Counter.keys`/`MultiSet.values`, `Queue`/`Stack` (mutable), `RingBuffer`, `SparseSet`
|
|
@@ -955,7 +955,7 @@ Heredoc notes:
|
|
|
955
955
|
- Conditions must be `bool`.
|
|
956
956
|
- No truthy or falsy coercion.
|
|
957
957
|
- Outside external-call boundaries, enum and flags values do not implicitly coerce to their backing integer types.
|
|
958
|
-
- Mixed signed and unsigned integer arithmetic requires an explicit cast.
|
|
958
|
+
- Mixed signed and unsigned integer arithmetic promotes to the narrowest signed type that holds both ranges (e.g. `ubyte + int` is `int`, `int + uint` is `long`); mixing with a 64-bit unsigned type such as `ulong` or `ptr_uint` requires an explicit cast.
|
|
959
959
|
- Non-widening integer conversions (narrowing, signed → unsigned) require an explicit cast. Lossless widening conversions (same signedness to wider or equal width, unsigned → wider signed) are implicit.
|
|
960
960
|
- `%` requires integer-compatible operands.
|
|
961
961
|
- Bitwise operators require matching integer or flags types.
|
data/docs/index.html
CHANGED
|
@@ -311,7 +311,7 @@ function main() -> int:
|
|
|
311
311
|
<div class="table-wrap">
|
|
312
312
|
<table class="attr-table">
|
|
313
313
|
<tr><th>Principle</th><th>Description</th></tr>
|
|
314
|
-
<tr><td><strong>What you see is what runs</strong></td><td>
|
|
314
|
+
<tr><td><strong>What you see is what runs</strong></td><td>Ordinary values have no hidden allocation or dispatch. Runtime interface dispatch and closure environments are explicit language features with their own costs.</td></tr>
|
|
315
315
|
<tr><td><strong>C is the ABI ground truth</strong></td><td>Structs, unions, pointers, and calling conventions map directly without lossy translation.</td></tr>
|
|
316
316
|
<tr><td><strong>Safe by default, unsafe by choice</strong></td><td>Pointer operations require <code>unsafe</code>. Indexing is bounds-checked. Conversions are explicit.</td></tr>
|
|
317
317
|
<tr><td><strong>Data-oriented first</strong></td><td>Plain structs, arrays, spans, pools, and arenas matter more than elaborate object systems.</td></tr>
|
|
@@ -355,7 +355,7 @@ function main() -> int:
|
|
|
355
355
|
<!-- ────────────────────────────────────────────────────────────────────────── -->
|
|
356
356
|
<section id="generated-c">
|
|
357
357
|
<h2>Generated C</h2>
|
|
358
|
-
<p>Milk Tea compiles to readable C that mirrors the source structure.
|
|
358
|
+
<p>Milk Tea compiles to readable C that mirrors the source structure. Ordinary value operations do not introduce hidden heap traffic or dispatch; runtime interface values and closure environments remain explicit language features. The output is designed for a human to debug, diff, and ship.</p>
|
|
359
359
|
|
|
360
360
|
<div class="code-wrap">
|
|
361
361
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
@@ -466,7 +466,7 @@ variant Action:
|
|
|
466
466
|
heal(amount: int)
|
|
467
467
|
idle
|
|
468
468
|
|
|
469
|
-
## ── Interfaces (nominal contracts
|
|
469
|
+
## ── Interfaces (nominal contracts; dyn provides explicit dispatch) ───
|
|
470
470
|
public interface Damageable:
|
|
471
471
|
editable function take_damage(amount: int) -> void
|
|
472
472
|
function is_alive() -> bool
|
|
@@ -658,12 +658,12 @@ function main() -> int:
|
|
|
658
658
|
|
|
659
659
|
## Collections: Vec and Map (import std.hash for primitive keys)
|
|
660
660
|
var entities = vec.Vec[Entity].create()
|
|
661
|
-
defer entities.release()
|
|
661
|
+
defer: entities.release()
|
|
662
662
|
entities.push(Entity.create(1, EntityKind.player, "Hero"))
|
|
663
663
|
entities.push(Entity.create(2, EntityKind.enemy, "Goblin"))
|
|
664
664
|
|
|
665
665
|
var name_counts = map.Map[str, int].create()
|
|
666
|
-
defer name_counts.release()
|
|
666
|
+
defer: name_counts.release()
|
|
667
667
|
name_counts.set("player", 1)
|
|
668
668
|
name_counts.set("enemy", 5)
|
|
669
669
|
|
|
@@ -680,7 +680,7 @@ function main() -> int:
|
|
|
680
680
|
break
|
|
681
681
|
io.print_line(f"value #{v}")
|
|
682
682
|
|
|
683
|
-
##
|
|
683
|
+
## Lockstep for over arrays/spans (lengths must match)
|
|
684
684
|
let xs = array[float, 3](1.0, 2.0, 3.0)
|
|
685
685
|
let ys = array[float, 3](4.0, 5.0, 6.0)
|
|
686
686
|
for x, y in xs, ys:
|
|
@@ -742,7 +742,7 @@ function main() -> int:
|
|
|
742
742
|
io.print_line(f"Position: #{position.x:.2}, #{position.y:.2}")
|
|
743
743
|
io.print_line(f"ID hex: #{player.entity.id:x}")
|
|
744
744
|
var msg = fmt.format(f"Score: #{score}")
|
|
745
|
-
defer msg.release()
|
|
745
|
+
defer: msg.release()
|
|
746
746
|
|
|
747
747
|
## Heredoc string
|
|
748
748
|
let help = <<-HELP
|
|
@@ -753,7 +753,7 @@ function main() -> int:
|
|
|
753
753
|
|
|
754
754
|
## Defer: cleanup at scope exit (lowers to C cleanup labels)
|
|
755
755
|
let data: own[int] = heap.must_alloc[int](1)
|
|
756
|
-
defer heap.release[int](data)
|
|
756
|
+
defer: heap.release[int](data)
|
|
757
757
|
|
|
758
758
|
## Proc / closure
|
|
759
759
|
let scale = make_scaler(2.5)
|
|
@@ -840,7 +840,7 @@ function main() -> int:
|
|
|
840
840
|
<p>Milk Tea source files use the <code class="code-inline">.mt</code> extension. A file is either an <strong>ordinary</strong> source file or an <strong>external</strong> file for raw ABI bindings. There are no wildcard imports — every module must be imported by name.</p>
|
|
841
841
|
|
|
842
842
|
<h3>Ordinary Files</h3>
|
|
843
|
-
<p>The normal source form. No module header; module identity is inferred from the file path relative to <code class="code-inline">package.source_root</code>. <code class="code-inline">import</code> statements appear only at the top.</p>
|
|
843
|
+
<p>The normal source form. No module header; module identity is inferred from the file path relative to <code class="code-inline">package.source_root</code>. <code class="code-inline">import</code> statements appear only at the top. Circular module imports are supported through forward-declaration bindings and two-pass checking.</p>
|
|
844
844
|
<div class="code-wrap">
|
|
845
845
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
846
846
|
<pre><code>import std.math as math
|
|
@@ -1184,7 +1184,7 @@ static_assert(MAX_ENTITIES > 0, "need at least one entity")</code></pre>
|
|
|
1184
1184
|
<h2>Interfaces & Methods</h2>
|
|
1185
1185
|
|
|
1186
1186
|
<h3>Interfaces</h3>
|
|
1187
|
-
<p>Explicit nominal method-set contracts for static polymorphism. No inheritance
|
|
1187
|
+
<p>Explicit nominal method-set contracts for static polymorphism. No inheritance or hidden dispatch; runtime polymorphism is explicit through <code>dyn[I]</code>.</p>
|
|
1188
1188
|
<div class="code-wrap">
|
|
1189
1189
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
1190
1190
|
<pre><code>public interface Damageable:
|
|
@@ -1304,7 +1304,7 @@ let handle = null[ptr[char]]</code></pre>
|
|
|
1304
1304
|
</div>
|
|
1305
1305
|
|
|
1306
1306
|
<h3>Memory Model</h3>
|
|
1307
|
-
<p>Milk Tea follows <strong>value semantics by default</strong>. Scalars, structs, and fixed arrays copy by value.
|
|
1307
|
+
<p>Milk Tea follows <strong>value semantics by default</strong>. Scalars, structs, and fixed arrays copy by value. Ordinary values do not carry hidden heap boxes or reference counts; captured <code>proc</code> values may allocate ref-counted closure environments as part of their lifecycle. User-owned heap allocation remains explicit and allocator-driven:</p>
|
|
1308
1308
|
<div class="table-wrap">
|
|
1309
1309
|
<table class="attr-table">
|
|
1310
1310
|
<tr><th>Module</th><th>Use Case</th></tr>
|
|
@@ -1450,8 +1450,8 @@ for item in items:
|
|
|
1450
1450
|
process(item)</code></pre>
|
|
1451
1451
|
</div>
|
|
1452
1452
|
|
|
1453
|
-
<h3>for (
|
|
1454
|
-
<p>Arrays and spans only
|
|
1453
|
+
<h3>for (Lockstep)</h3>
|
|
1454
|
+
<p>This is the lockstep multi-iterable form, distinct from <code>parallel for</code>. Arrays and spans only; lengths must match.</p>
|
|
1455
1455
|
<div class="code-wrap">
|
|
1456
1456
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
1457
1457
|
<pre><code>for entity, position, velocity in entities, positions, velocities:
|
|
@@ -1598,11 +1598,11 @@ match record:
|
|
|
1598
1598
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
1599
1599
|
<pre><code>function main() -> int:
|
|
1600
1600
|
rl.init_window(800, 600, "Game")
|
|
1601
|
-
defer rl.close_window()
|
|
1601
|
+
defer: rl.close_window()
|
|
1602
1602
|
|
|
1603
1603
|
while not rl.window_should_close():
|
|
1604
1604
|
rl.begin_drawing()
|
|
1605
|
-
defer rl.end_drawing()
|
|
1605
|
+
defer: rl.end_drawing()
|
|
1606
1606
|
render_frame()
|
|
1607
1607
|
|
|
1608
1608
|
return 0</code></pre>
|
|
@@ -1628,7 +1628,7 @@ unsafe:
|
|
|
1628
1628
|
<h2>Compile-Time Control Flow</h2>
|
|
1629
1629
|
|
|
1630
1630
|
<h3>when</h3>
|
|
1631
|
-
<p>Evaluates discriminant at compile time; only the chosen branch is type-checked and emitted.</p>
|
|
1631
|
+
<p>Evaluates discriminant at compile time; only the chosen branch is type-checked and emitted. <code>when</code> may also appear at module level to conditionally include declarations, imports, or type definitions.</p>
|
|
1632
1632
|
<div class="code-wrap">
|
|
1633
1633
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
1634
1634
|
<pre><code>when TARGET_OS:
|
|
@@ -2012,7 +2012,7 @@ let r = aio.result(task)</code></pre>
|
|
|
2012
2012
|
<!-- ────────────────────────────────────────────────────────────────────────── -->
|
|
2013
2013
|
<section id="concurrency">
|
|
2014
2014
|
<h2>Concurrency</h2>
|
|
2015
|
-
<p>Milk Tea has first-class compiler support for multithreading using real OS threads (libuv).
|
|
2015
|
+
<p>Milk Tea has first-class compiler support for multithreading using real OS threads (libuv). <code>parallel for</code> and <code>parallel:</code> are structured barriers; <code>detach</code> continues asynchronously until an explicit <code>gather</code>. There is no GC interaction or implicit fire-and-forget cleanup.</p>
|
|
2016
2016
|
|
|
2017
2017
|
<h3>Parallel For (data-parallel)</h3>
|
|
2018
2018
|
<p>Dispatches loop iterations across CPU cores. Each iteration writes to its own index range — the bread and butter of data-oriented game programming.</p>
|
|
@@ -2149,8 +2149,8 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
|
|
|
2149
2149
|
let closed_sub = window.closed.subscribe(on_close)?
|
|
2150
2150
|
let resized_sub = window.resized.subscribe(on_resize)?
|
|
2151
2151
|
|
|
2152
|
-
defer window.closed.unsubscribe(closed_sub)
|
|
2153
|
-
defer window.resized.unsubscribe(resized_sub)
|
|
2152
|
+
defer: window.closed.unsubscribe(closed_sub)
|
|
2153
|
+
defer: window.resized.unsubscribe(resized_sub)
|
|
2154
2154
|
|
|
2155
2155
|
return Result[void, EventError].success()</code></pre>
|
|
2156
2156
|
</div>
|
|
@@ -2245,7 +2245,7 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
|
|
|
2245
2245
|
<tr><td><code>mtc lower <path></code></td><td>Print lowered IR</td></tr>
|
|
2246
2246
|
<tr><td><code>mtc debug <file.mt></code></td><td>Print debug info (tokens, AST, facts, bindings, diagnostics)</td></tr>
|
|
2247
2247
|
<tr><td><code>mtc emit-c <path></code></td><td>Emit generated C to stdout</td></tr>
|
|
2248
|
-
<tr><td><code>mtc format <path></code></td><td>Format
|
|
2248
|
+
<tr><td><code>mtc format <path></code></td><td>Format source to stdout (<code>--write</code> rewrites in place, <code>--check</code> verifies)</td></tr>
|
|
2249
2249
|
<tr><td><code>mtc lint <path></code></td><td>Run linter (<code>--fix</code>, <code>--select</code>, <code>--ignore</code>)</td></tr>
|
|
2250
2250
|
<tr><td><code>mtc test <path></code></td><td>Discover and run <code>@[test]</code> functions</td></tr>
|
|
2251
2251
|
<tr><td><code>mtc new <name></code></td><td>Scaffold a new package (<code>package.toml</code> + <code>src/main.mt</code>)</td></tr>
|
|
@@ -2416,9 +2416,17 @@ import std.math # intentionally available for downstream</code></pre>
|
|
|
2416
2416
|
|
|
2417
2417
|
<h3>Event Restrictions</h3>
|
|
2418
2418
|
<ul style="margin-left:1.25rem;margin-bottom:1rem">
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2419
|
+
<li>Event payload types must be storable and cannot be event storage types</li>
|
|
2420
|
+
<li>Event payload cannot be <code>ref[T]</code></li>
|
|
2421
|
+
<li>Event storage types cannot be returned, passed through ordinary value parameters, or copied into locals</li>
|
|
2422
|
+
<li><code>emit</code> is only callable from within the declaring module</li>
|
|
2423
|
+
<li>Events not allowed in external files</li>
|
|
2424
|
+
</ul>
|
|
2425
|
+
|
|
2426
|
+
<h3>Interface Restrictions</h3>
|
|
2427
|
+
<ul style="margin-left:1.25rem;margin-bottom:1rem">
|
|
2428
|
+
<li>Interface methods cannot be <code>async</code> or declare their own type parameters</li>
|
|
2429
|
+
<li>Interface conformance must be declared on the nominal type, not an <code>extending</code> block</li>
|
|
2422
2430
|
</ul>
|
|
2423
2431
|
</section>
|
|
2424
2432
|
|
data/docs/language-design.md
CHANGED
|
@@ -16,7 +16,7 @@ The output target is beautiful C. The generated C should be readable enough that
|
|
|
16
16
|
2. Stay statically typed. Public APIs must be explicit and unsurprising.
|
|
17
17
|
3. Feel good for game programming. Tight loops, structs, arrays, arenas, handles, and FFI should be first-class.
|
|
18
18
|
4. Interoperate with C as a native citizen. C libraries are not a side feature; they are part of the core story.
|
|
19
|
-
5. Generate C that mirrors the source closely.
|
|
19
|
+
5. Generate C that mirrors the source closely. Ordinary value operations do not add hidden heap traffic or dispatch; explicit runtime surfaces such as `dyn` and captured `proc` environments remain visible in the language model.
|
|
20
20
|
|
|
21
21
|
## Non-goals
|
|
22
22
|
|
|
@@ -28,7 +28,7 @@ The output target is beautiful C. The generated C should be readable enough that
|
|
|
28
28
|
- No macro system that rewrites arbitrary ASTs
|
|
29
29
|
- No garbage collector
|
|
30
30
|
- No implicit conversions between unrelated primitive types
|
|
31
|
-
- No user-invisible allocation for strings, collections,
|
|
31
|
+
- No user-invisible allocation for strings, collections, ordinary values, or method calls. Capturing a `proc` may allocate a ref-counted closure environment as part of proc value semantics; owned text and other storage use explicit allocating surfaces.
|
|
32
32
|
|
|
33
33
|
## Design rules
|
|
34
34
|
|
|
@@ -119,7 +119,7 @@ extending Player:
|
|
|
119
119
|
|
|
120
120
|
function main() -> int:
|
|
121
121
|
rl.init_window(screen_width, screen_height, "Milk Tea")
|
|
122
|
-
defer rl.close_window()
|
|
122
|
+
defer: rl.close_window()
|
|
123
123
|
|
|
124
124
|
var player = Player(
|
|
125
125
|
position = rl.Vector2(x = 400.0, y = 300.0),
|
|
@@ -132,7 +132,7 @@ function main() -> int:
|
|
|
132
132
|
player.update(dt)
|
|
133
133
|
|
|
134
134
|
rl.begin_drawing()
|
|
135
|
-
defer rl.end_drawing()
|
|
135
|
+
defer: rl.end_drawing()
|
|
136
136
|
|
|
137
137
|
rl.clear_background(rl.BLACK)
|
|
138
138
|
rl.draw_circle_v(player.position, player.radius, rl.GOLD)
|
|
@@ -291,7 +291,7 @@ The rule is fixed and inspectable. There is no method lookup at runtime.
|
|
|
291
291
|
### Interfaces
|
|
292
292
|
|
|
293
293
|
Interfaces are explicit nominal method-set contracts for static polymorphism.
|
|
294
|
-
They do not introduce inheritance, hidden dispatch, or a second object model.
|
|
294
|
+
They do not introduce inheritance, hidden dispatch, or a second object model. Runtime polymorphism is an explicit `dyn[Interface]` surface.
|
|
295
295
|
|
|
296
296
|
```mt
|
|
297
297
|
interface Damageable:
|
|
@@ -363,7 +363,7 @@ Because fixed arrays copy by value, mutating interface-constrained collection co
|
|
|
363
363
|
|
|
364
364
|
Iteration stays structural in v1 rather than going through a nominal `Iterator[T]` or `Iterable[T]` interface. Arrays, spans, and ranges keep their built-in behavior, and custom iterables participate by exposing the same method shape the compiler already recognizes: a non-editable `iter()` method on the iterable, then either `next() ->` nullable pointer-like item or `next() -> bool` together with `current()` on the iterator.
|
|
365
365
|
|
|
366
|
-
This is deliberate. Interfaces are
|
|
366
|
+
This is deliberate. Interfaces are nominal contracts, and generic interfaces are supported, but introducing a central iterator interface hierarchy would still either erase the item type or duplicate type-specific interfaces. The standard library should instead standardize on one iterator convention:
|
|
367
367
|
|
|
368
368
|
- `collection.iter()` is the canonical traversal surface.
|
|
369
369
|
- Alternate traversals use explicit view methods such as `map.keys()`, `map.values()`, and `map.entries()`.
|
|
@@ -423,16 +423,12 @@ Milk Tea should include a small number of control-flow features that materially
|
|
|
423
423
|
|
|
424
424
|
#### `defer`
|
|
425
425
|
|
|
426
|
-
`defer` registers cleanup code at scope exit and lowers to obvious cleanup labels in C.
|
|
426
|
+
`defer` registers cleanup code at scope exit and lowers to obvious cleanup labels in C. Both forms use the block-header `:`:
|
|
427
427
|
|
|
428
428
|
```mt
|
|
429
|
-
|
|
430
|
-
|
|
431
429
|
function load_texture(path: str) -> Result[Texture, LoadError]:
|
|
432
430
|
let texture = rl.load_texture(path)
|
|
433
|
-
|
|
434
|
-
if texture.id == 0:
|
|
435
|
-
return Result[Texture, LoadError].failure(error= LoadError.file_not_found)
|
|
431
|
+
defer: rl.unload_texture(texture)
|
|
436
432
|
|
|
437
433
|
return Result[Texture, LoadError].success(value= texture)
|
|
438
434
|
```
|
|
@@ -484,7 +480,7 @@ The default async model is a language-integrated entry boundary. `std.async` rem
|
|
|
484
480
|
|
|
485
481
|
#### Concurrency: `parallel for` and `parallel:` blocks
|
|
486
482
|
|
|
487
|
-
Milk Tea has first-class compiler support for multithreading. The
|
|
483
|
+
Milk Tea has first-class compiler support for multithreading. The compiler-integrated `parallel for` and `parallel:` forms use structured barriers; explicit `detach` work continues until a matching `gather`. There is no hidden thread pool, no garbage collector interaction, and no implicit fire-and-forget cleanup. Every thread boundary is visible in source.
|
|
488
484
|
|
|
489
485
|
```mt
|
|
490
486
|
# Data-parallel: each iteration runs on a separate CPU core
|
|
@@ -794,7 +790,7 @@ unsafe:
|
|
|
794
790
|
Binary arithmetic and numeric comparison operators may promote primitive operands to a common type locally.
|
|
795
791
|
This is limited to `+ - * / % == != < <= > >=` and does not change assignment, return, or aggregate-field typing rules.
|
|
796
792
|
Non-external call boundaries remain strict, but external calls may pass enum or flags values to same-width fixed-width integer parameters without an explicit cast for C ABI interop.
|
|
797
|
-
Mixed signed and unsigned
|
|
793
|
+
Mixed signed and unsigned integer operands use the same lossless rule as assignment: they promote to the narrowest signed type that holds both full ranges — a strictly-wider signed type covers an unsigned operand (`ubyte + int` is `int`, `uint + long` is `long`), and equal-width or narrower signed operands widen to the next signed width (`int + uint` is `long`, `byte + ubyte` is `short`). Mixing with a 64-bit unsigned type (`ulong`, `ptr_uint`) has no safe signed common type and still requires an explicit cast.
|
|
798
794
|
|
|
799
795
|
`char` stays outside the general numeric-promotion rules. If code wants arithmetic on a character value, cast it to an integer type first. If code wants to write bytes back into a `char` buffer, either use `char<-...` explicitly or rely on the expected `char` boundary where a known `char` target is being initialized or assigned.
|
|
800
796
|
|
|
@@ -876,7 +872,7 @@ The memory model must feel like C with better defaults and cleaner surfaces.
|
|
|
876
872
|
- Fixed arrays copy by value.
|
|
877
873
|
- Returning a struct is allowed and lowers to normal C return or out-parameter lowering as needed.
|
|
878
874
|
|
|
879
|
-
|
|
875
|
+
Ordinary values do not carry hidden reference counts or heap boxes. Captured `proc` values are the explicit callable-model exception: their environments retain and release nested captured procs so stored closures have a defined lifecycle.
|
|
880
876
|
|
|
881
877
|
### Explicit allocation
|
|
882
878
|
|
|
@@ -1100,7 +1096,7 @@ Rules:
|
|
|
1100
1096
|
- one module identity per file, derived from its path
|
|
1101
1097
|
- explicit imports only
|
|
1102
1098
|
- no wildcard imports in v1
|
|
1103
|
-
-
|
|
1099
|
+
- cyclic imports are supported through forward-declaration bindings and two-pass checking
|
|
1104
1100
|
- package naming stays filesystem-friendly
|
|
1105
1101
|
|
|
1106
1102
|
Recommended layout:
|
|
@@ -1428,7 +1424,7 @@ Callbacks must map directly to C function pointers.
|
|
|
1428
1424
|
type LogCallback = fn(level: int, message: cstr, user_data: ptr[void]) -> void
|
|
1429
1425
|
```
|
|
1430
1426
|
|
|
1431
|
-
Capturing closures should not be lowered to hidden heap objects. If user state is needed, pass it explicitly as `user_data` at ABI boundaries or allocate explicit local storage such as `std.
|
|
1427
|
+
Capturing closures should not be lowered to hidden heap objects. If user state is needed, pass it explicitly as `user_data` at ABI boundaries or allocate explicit local storage such as `std.box.alloc[T](...)` in ordinary Milk Tea code.
|
|
1432
1428
|
|
|
1433
1429
|
## Data layout and ABI controls
|
|
1434
1430
|
|
data/docs/language-manual.md
CHANGED
|
@@ -292,7 +292,7 @@ Callable and `ref[...]` rules:
|
|
|
292
292
|
- External functions still cannot take `ref[...]` parameters, and ordinary functions still cannot return `ref[...]`.
|
|
293
293
|
- `proc` captures are value captures. A captured local is not a mutable alias back to the outer binding. Any storable type may be captured, including scalars, arrays, structs, and other `proc` values. Captured `proc` values participate in the ref-counted lifecycle: the capturing proc retains the captured proc on creation and releases it when the env is freed.
|
|
294
294
|
- `ref[T]` values are not capturable by design since they are non-owning.
|
|
295
|
-
- Shared mutable proc state should use explicit storage such as `std.
|
|
295
|
+
- Shared mutable proc state should use explicit storage such as `std.box.alloc[T](...)` or other explicit pointer-backed state, not implicit mutable capture.
|
|
296
296
|
|
|
297
297
|
### 3.4 Struct, union, enum, flags, opaque
|
|
298
298
|
|
|
@@ -596,6 +596,8 @@ if x > 10: return "big" else if x > 5: return "med" else: return "small"
|
|
|
596
596
|
|
|
597
597
|
The inline form supports all statement kinds in the body (`return`, `let`, `var`, `defer`, assignment, expression statement, etc.). Blocks and inline form may be mixed: any branch may use either form independently.
|
|
598
598
|
|
|
599
|
+
Every block-taking statement follows the same rule: a `:` followed by a newline starts an indented block, while a `:` followed by anything else on the same line introduces exactly one inline statement. This applies uniformly to `if`/`else`, `while`, `for`, `defer`, and `unsafe` (§4.3, §4.4, §4.5). `match` arms are the exception: their inline form is a single value expression (§4.2).
|
|
600
|
+
|
|
599
601
|
### 4.2 Match
|
|
600
602
|
|
|
601
603
|
Scrutinee types supported:
|
|
@@ -717,6 +719,13 @@ Single-form `for` supports:
|
|
|
717
719
|
- `span[T]`
|
|
718
720
|
- custom structural iterables with a non-editable zero-argument `iter()` method
|
|
719
721
|
|
|
722
|
+
Every loop body accepts either a `:`-newline-indented block or a single statement on the same line as the header:
|
|
723
|
+
|
|
724
|
+
```mt
|
|
725
|
+
while ready: poll()
|
|
726
|
+
for value in values: accumulate(value)
|
|
727
|
+
```
|
|
728
|
+
|
|
720
729
|
Iterator protocol for custom structural iterables:
|
|
721
730
|
|
|
722
731
|
- the iterable value must expose `iter()` with no parameters
|
|
@@ -795,10 +804,10 @@ Rules:
|
|
|
795
804
|
|
|
796
805
|
### 4.4 Defer
|
|
797
806
|
|
|
798
|
-
|
|
807
|
+
Both forms use the block-header `:` introduced in §4:
|
|
799
808
|
|
|
800
809
|
```mt
|
|
801
|
-
defer cleanup()
|
|
810
|
+
defer: cleanup()
|
|
802
811
|
|
|
803
812
|
# or
|
|
804
813
|
|
|
@@ -807,7 +816,10 @@ defer:
|
|
|
807
816
|
release_b()
|
|
808
817
|
```
|
|
809
818
|
|
|
810
|
-
|
|
819
|
+
Rules:
|
|
820
|
+
|
|
821
|
+
- `defer` always requires `:`. A single statement may follow on the same line (`defer: release_a()`), or a `:`-newline-indented block may hold several statements.
|
|
822
|
+
- `return` is not allowed inside defer blocks.
|
|
811
823
|
|
|
812
824
|
### 4.5 Unsafe
|
|
813
825
|
|
|
@@ -1363,8 +1375,8 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
|
|
|
1363
1375
|
let closed_sub = window.closed.subscribe(on_close)?
|
|
1364
1376
|
let resized_sub = window.resized.subscribe(on_resize)?
|
|
1365
1377
|
|
|
1366
|
-
defer window.closed.unsubscribe(closed_sub)
|
|
1367
|
-
defer window.resized.unsubscribe(resized_sub)
|
|
1378
|
+
defer: window.closed.unsubscribe(closed_sub)
|
|
1379
|
+
defer: window.resized.unsubscribe(resized_sub)
|
|
1368
1380
|
|
|
1369
1381
|
return Result[void, EventError].success()
|
|
1370
1382
|
```
|
|
@@ -1508,7 +1520,7 @@ The compiler intentionally rejects the following patterns. These are design cons
|
|
|
1508
1520
|
### 13.6 Type system restrictions
|
|
1509
1521
|
|
|
1510
1522
|
- conditions must be `bool`; integers and pointers have no implicit truthy or falsy coercion
|
|
1511
|
-
- mixed signed and unsigned integer arithmetic requires an explicit cast
|
|
1523
|
+
- mixed signed and unsigned integer arithmetic promotes to the narrowest signed type that holds both ranges (`ubyte + int` is `int`, `int + uint` is `long`, `byte + ubyte` is `short`); mixing with a 64-bit unsigned type (`ulong`, `ptr_uint`) still requires an explicit cast
|
|
1512
1524
|
- enum and flags values do not implicitly coerce to their backing integer types outside external-call boundaries
|
|
1513
1525
|
- `enum` backing types must be integer primitives; flags members must be compile-time integer constants
|
|
1514
1526
|
- variant arm payloads cannot use `ref[T]` in v1
|
data/lib/milk_tea/base.rb
CHANGED
data/lib/milk_tea/core/ast.rb
CHANGED
|
@@ -212,8 +212,8 @@ module MilkTea
|
|
|
212
212
|
ReturnStmt = Data.define(:value, :line, :column, :length) do
|
|
213
213
|
def initialize(value:, line: nil, column: nil, length: nil) = super
|
|
214
214
|
end
|
|
215
|
-
DeferStmt = Data.define(:
|
|
216
|
-
def initialize(
|
|
215
|
+
DeferStmt = Data.define(:body, :line, :column, :length) do
|
|
216
|
+
def initialize(body:, line: nil, column: nil, length: nil) = super
|
|
217
217
|
end
|
|
218
218
|
ErrorBlockStmt = Data.define(:body, :line, :column, :length, :message, :header_type, :header_expression, :header_bindings, :header_iterables) do
|
|
219
219
|
def initialize(body:, line: nil, column: nil, length: nil, message: nil, header_type: nil, header_expression: nil, header_bindings: nil, header_iterables: nil) = super
|
|
@@ -212,8 +212,8 @@ module MilkTea
|
|
|
212
212
|
when AST::UnsafeStmt
|
|
213
213
|
build_block(stmt.body, next_id, break_target:, continue_target:)
|
|
214
214
|
when AST::DeferStmt
|
|
215
|
-
body_entry =
|
|
216
|
-
expression_reads, expression_reads_info =
|
|
215
|
+
body_entry = build_block(stmt.body, next_id, break_target:, continue_target:)
|
|
216
|
+
expression_reads, expression_reads_info = statement_list_reads_with_sites(stmt.body)
|
|
217
217
|
defer_id = @graph.add_node(
|
|
218
218
|
kind: :defer,
|
|
219
219
|
statement: stmt,
|
|
@@ -315,6 +315,66 @@ module MilkTea
|
|
|
315
315
|
[reads, reads_info]
|
|
316
316
|
end
|
|
317
317
|
|
|
318
|
+
def statement_list_reads_with_sites(statements)
|
|
319
|
+
reads = Set.new
|
|
320
|
+
reads_info = []
|
|
321
|
+
statement_list_read_identifiers(statements, reads, reads_info)
|
|
322
|
+
[reads, reads_info]
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def statement_list_read_identifiers(statements, reads, reads_info)
|
|
326
|
+
Array(statements).each do |statement|
|
|
327
|
+
statement_read_identifiers(statement, reads, reads_info)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def statement_read_identifiers(statement, reads, reads_info)
|
|
332
|
+
case statement
|
|
333
|
+
when AST::ExpressionStmt
|
|
334
|
+
read_identifiers(statement.expression, reads, reads_info)
|
|
335
|
+
when AST::DeferStmt
|
|
336
|
+
statement_list_read_identifiers(statement.body, reads, reads_info)
|
|
337
|
+
when AST::LocalDecl
|
|
338
|
+
read_identifiers(statement.value, reads, reads_info)
|
|
339
|
+
statement_list_read_identifiers(statement.else_body, reads, reads_info)
|
|
340
|
+
when AST::Assignment
|
|
341
|
+
read_identifiers(statement.target, reads, reads_info)
|
|
342
|
+
read_identifiers(statement.value, reads, reads_info)
|
|
343
|
+
when AST::ReturnStmt
|
|
344
|
+
read_identifiers(statement.value, reads, reads_info)
|
|
345
|
+
when AST::IfStmt
|
|
346
|
+
statement.branches.each do |branch|
|
|
347
|
+
read_identifiers(branch.condition, reads, reads_info)
|
|
348
|
+
statement_list_read_identifiers(branch.body, reads, reads_info)
|
|
349
|
+
end
|
|
350
|
+
statement_list_read_identifiers(statement.else_body, reads, reads_info)
|
|
351
|
+
when AST::WhileStmt
|
|
352
|
+
read_identifiers(statement.condition, reads, reads_info)
|
|
353
|
+
statement_list_read_identifiers(statement.body, reads, reads_info)
|
|
354
|
+
when AST::ForStmt
|
|
355
|
+
statement.iterables.each { |iterable| read_identifiers(iterable, reads, reads_info) }
|
|
356
|
+
statement_list_read_identifiers(statement.body, reads, reads_info)
|
|
357
|
+
when AST::UnsafeStmt
|
|
358
|
+
statement_list_read_identifiers(statement.body, reads, reads_info)
|
|
359
|
+
when AST::MatchStmt
|
|
360
|
+
read_identifiers(statement.expression, reads, reads_info)
|
|
361
|
+
statement.arms.each do |arm|
|
|
362
|
+
read_identifiers(arm.pattern, reads, reads_info)
|
|
363
|
+
statement_list_read_identifiers(arm.body, reads, reads_info)
|
|
364
|
+
end
|
|
365
|
+
when AST::StaticAssert
|
|
366
|
+
read_identifiers(statement.condition, reads, reads_info)
|
|
367
|
+
read_identifiers(statement.message, reads, reads_info)
|
|
368
|
+
when AST::WhenStmt
|
|
369
|
+
read_identifiers(statement.discriminant, reads, reads_info)
|
|
370
|
+
statement.branches.each do |branch|
|
|
371
|
+
read_identifiers(branch.pattern, reads, reads_info)
|
|
372
|
+
statement_list_read_identifiers(branch.body, reads, reads_info)
|
|
373
|
+
end
|
|
374
|
+
statement_list_read_identifiers(statement.else_body, reads, reads_info)
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
318
378
|
def read_identifiers(expression, names = Set.new, reads_info = [])
|
|
319
379
|
case expression
|
|
320
380
|
when nil
|
|
@@ -145,15 +145,9 @@ module MilkTea
|
|
|
145
145
|
when AST::UnsafeStmt
|
|
146
146
|
await_counter = analyze_async_statements!(statement.body, await_counter, env, param_fields, local_fields, await_fields)
|
|
147
147
|
when AST::DeferStmt
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
await_counter = analyze_async_statements!(statement.body, await_counter, cleanup_env, param_fields, local_fields, await_fields)
|
|
152
|
-
end
|
|
153
|
-
if statement.expression.is_a?(AST::AwaitExpr)
|
|
154
|
-
await_fields[statement.expression.object_id] = build_async_await_field_info(statement.expression, await_counter, env:, param_fields:, local_fields:)
|
|
155
|
-
await_counter += 1
|
|
156
|
-
end
|
|
148
|
+
cleanup_env = duplicate_env(env)
|
|
149
|
+
cleanup_env[:return_context] = cleanup_env[:return_context]&.merge(allow_return: false)
|
|
150
|
+
await_counter = analyze_async_statements!(statement.body, await_counter, cleanup_env, param_fields, local_fields, await_fields)
|
|
157
151
|
else
|
|
158
152
|
nil
|
|
159
153
|
end
|
|
@@ -194,7 +194,7 @@ module MilkTea
|
|
|
194
194
|
when AST::UnsafeStmt
|
|
195
195
|
statements_contain_await?(s.body, async_info)
|
|
196
196
|
when AST::DeferStmt
|
|
197
|
-
|
|
197
|
+
statements_contain_await?(s.body, async_info)
|
|
198
198
|
else
|
|
199
199
|
false
|
|
200
200
|
end
|
|
@@ -1237,15 +1237,7 @@ module MilkTea
|
|
|
1237
1237
|
end
|
|
1238
1238
|
|
|
1239
1239
|
def lower_async_defer_cleanup(statement, env:, async_info:)
|
|
1240
|
-
body
|
|
1241
|
-
statement.body
|
|
1242
|
-
elsif statement.expression
|
|
1243
|
-
[AST::ExpressionStmt.new(expression: statement.expression, line: statement.line)]
|
|
1244
|
-
else
|
|
1245
|
-
[]
|
|
1246
|
-
end
|
|
1247
|
-
|
|
1248
|
-
{ body:, env: snapshot_env(env) }
|
|
1240
|
+
{ body: statement.body, env: snapshot_env(env) }
|
|
1249
1241
|
end
|
|
1250
1242
|
|
|
1251
1243
|
def lower_async_cleanup_entries(local_defers, outer_defers, frame_expr:, raw_frame_expr:, async_info:)
|
|
@@ -154,13 +154,8 @@ module MilkTea
|
|
|
154
154
|
when AST::DeferStmt
|
|
155
155
|
cleanup_env = duplicate_env(env)
|
|
156
156
|
cleanup_env[:return_context] = cleanup_env[:return_context]&.merge(allow_return: false)
|
|
157
|
-
cleanup_body =
|
|
158
|
-
|
|
159
|
-
else
|
|
160
|
-
expression_setup, expression = normalize_async_expression(statement.expression, counter, env: cleanup_env)
|
|
161
|
-
expression_setup + [AST::ExpressionStmt.new(expression:, line: statement.line)]
|
|
162
|
-
end
|
|
163
|
-
[AST::DeferStmt.new(expression: nil, body: cleanup_body, line: statement.line, column: statement.column, length: statement.length)]
|
|
157
|
+
cleanup_body = normalize_async_statements(statement.body, counter, cleanup_env, return_type:)
|
|
158
|
+
[AST::DeferStmt.new(body: cleanup_body, line: statement.line, column: statement.column, length: statement.length)]
|
|
164
159
|
when AST::BreakStmt, AST::ContinueStmt, AST::StaticAssert, AST::PassStmt
|
|
165
160
|
[statement]
|
|
166
161
|
else
|
|
@@ -16,11 +16,7 @@ module MilkTea
|
|
|
16
16
|
statements.each do |statement|
|
|
17
17
|
case statement
|
|
18
18
|
when AST::DeferStmt
|
|
19
|
-
local_defers <<
|
|
20
|
-
lower_defer_cleanup_body(statement.body, env: local_env, return_type:)
|
|
21
|
-
else
|
|
22
|
-
lower_defer_cleanup_expression(statement.expression, env: local_env)
|
|
23
|
-
end
|
|
19
|
+
local_defers << lower_defer_cleanup_body(statement.body, env: local_env, return_type:)
|
|
24
20
|
when AST::UnsafeStmt
|
|
25
21
|
lower_block_unsafe_stmt(statement, lowered:, local_defers:, local_env:, active_defers:, return_type:, loop_flow:, allow_return:)
|
|
26
22
|
when AST::LocalDecl
|