mt-lang 0.3.10 → 0.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0eb8fe30acd589069404d9a88cba70713a36937acf80299123b7a9b5a909b5da
4
- data.tar.gz: 3f7a0240fcfc10f0617e94fdb6d28abe40c1ffbc6bfaa12e87235f4440ffaef6
3
+ metadata.gz: bc80c3a3240cbcf4770dd067aa33559e74e3f139322b8fdb757316f3c7faf8af
4
+ data.tar.gz: a623e5ecaa9617aa161a83e96c4cb0e7a75b08855edae27fb56c8ddc47c7cd75
5
5
  SHA512:
6
- metadata.gz: f9623980449010f1a2895f715153948845edb191cb0164b9e48b615527d6d2aa0809abae016c07fa85197628e2baf190ca08adc2c14b31934b22465b9ca01f45
7
- data.tar.gz: 543375cd1021947da13d214f8aacf379d565a6e840564dc9e9233bd7673200d967318c1ef7ca209000182c78fa98c141c2ceddcd9ca1c91d5b221d0caac9d03b
6
+ metadata.gz: c90aa51a21411fdcfe612f2ded69afdde6e055a012a4e886f5aa9ac69110d39a8bf93a36dde0543d1375a8bfa75ffd2f287eb082833a28b42efe19ab45c62ae6
7
+ data.tar.gz: a87ac20f1228f059e3508818be58e11e5491a90299e56d5ab569ab3ebf1ae3dc1f7ddff8761321754bd2bfc8c520dc9ac3876360714ced2baf6e06a82bbf635d
data/README.md CHANGED
@@ -30,6 +30,7 @@ Package manifests, build workflow, and run workflow are documented separately in
30
30
  - External files are the dedicated raw ABI surface, usually for generated or low-level `std.c.*` bindings.
31
31
  - Module lookup resolves `a.b.c` to `a/b/c.mt`.
32
32
  - Inside a package, the file path relative to `package.source_root` defines the module name; platform-specific files such as `name.linux.mt` still map to module `name`.
33
+ - Circular module imports are supported via forward-declaration bindings and two-pass checking. Cycle members see each other's type declarations in the first pass and full type information in the second pass.
33
34
  - In ordinary files, `import` statements appear only at the top.
34
35
  - In external files, leading `import` statements are allowed after `external`.
35
36
  - Only external files accept `include`, `link`, and `compiler_flag` directives.
@@ -42,7 +43,7 @@ Blocks are indentation-based:
42
43
  - Tabs are rejected.
43
44
  - Indentation must be a multiple of 4 spaces.
44
45
  - Indentation can increase by only one level at a time.
45
- - Newlines end statements except inside `()` and `[]`, or when the previous physical line ends with a binary operator such as `+`, `and`, or `==`.
46
+ - Newlines end statements except inside `()` and `[]`, or when the previous physical line ends with a binary operator such as `+`, `and`, `==`, or `is`.
46
47
  - Comma-separated lists inside `()` and `[]` accept trailing commas. Prefer them for multiline parameters, arguments, and type lists.
47
48
 
48
49
  Long expressions should usually be wrapped with delimiters, following the same broad shape as Python's implicit line joining:
@@ -70,7 +71,15 @@ let values = 1 ..
70
71
  4
71
72
  ```
72
73
 
73
- Do not rely on starting the next physical line with the operator; wrap the expression in `()` instead if that layout reads better.
74
+ Starting a physical line with an operator is a hard parse error (`operator '<op>' cannot start a statement`). Continue by ending the previous line with the operator, or wrap the expression in `()` if that layout reads better. The continuation operator set is closed: `+` `-` `*` `/` `%`, `|` `&` `^`, `<<` `>>`, `==` `!=` `<` `<=` `>` `>=`, `and` `or` `is`, and `..`. Member-access chains that exceed one line wrap in `()` with leading dots:
75
+
76
+ ```mt
77
+ let total = (
78
+ values.iter()
79
+ .filter(pred)
80
+ .fold(0, add)
81
+ )
82
+ ```
74
83
 
75
84
  Comments:
76
85
 
@@ -314,6 +323,7 @@ Rules:
314
323
  - Compile-time reflection over validated attributes uses `has_attribute`, `attribute_of`, `attribute_arg[T]`, `field_of`, and `callable_of`.
315
324
  - `field_of(...)`, `callable_of(...)`, and `attribute_of(...)` produce compile-time handle values with source-visible handle types `field_handle`, `callable_handle`, and `attribute_handle`.
316
325
  - The current C backend lowers `packed` / `align(...)` attributes with GNU-style `__attribute__((...))`, so these layout controls currently require a Clang/GCC-family compiler. On Windows that means Clang or GCC-family toolchains such as MinGW; `cl.exe` is not a supported backend for these attributes today. On wasm/browser targets the same feature works through Emscripten `emcc`, which is Clang-based.
326
+ - Keywords and reserved words are accepted as variant arm names, struct/union field names, enum/flags member names, lifetime parameters (`@return`), type parameters (`Box[return]`), named constructor arguments (`Meta(return = 1)`), and named tuple fields (`(return = 1, let = 2)`). Primitive type names (`int`, `str`, `ref`, etc.) remain reserved and cannot be used in these positions.
317
327
 
318
328
  Enum and flags values support the full set of comparison operators (`==`, `!=`, `<`, `<=`, `>`, `>=`) against values of the same enum type and against their backing integer type. Comparisons use the underlying integer backing values. Flags also support bitwise operators (`|`, `&`, `^`, `~`).
319
329
 
@@ -867,9 +877,9 @@ Core modules in `std/`:
867
877
 
868
878
  - `std.linear_algebra` — extends native vector/matrix/quaternion types with `dot`, `cross`, `length`, `normalized`, `lerp`, `identity`, `transpose`, `conjugate` (pure Mt, no C dependency beyond `std.math` for `sqrt`)
869
879
  - `std.graph.Graph[T]` — adjacency-list graph with `add_node`, `add_edge`, `has_edge`, `remove_edge`, `neighbors`, `bfs`, `dfs`, `toposort`; directed or undirected; `compile()` converts to CSR-based `DenseGraph[T]` for O(degree) neighbor iteration
870
- - `std.str` — extends `str` with `byte_at`, `equal`, `starts_with`, `ends_with`, `find_substring`, `is_valid_utf8`, `slice`, `to_cstr`, `hash`, `order`
880
+ - `std.str` — extends `str` with `byte_at`, `equal`, `starts_with`, `ends_with`, `find_substring`, `is_valid_utf8`, `slice`, `to_cstr`, `hash`, `order`; also provides C-string conversion helpers (`cstr_len`, `cstr_as_str`, `chars_as_str`, `nullable_cstr_as_str`)
871
881
  - `std.hash` — extends the primitive integer types (`byte`/`ubyte`/`short`/`ushort`/`int`/`uint`/`long`/`ulong`/`ptr_int`/`ptr_uint`), `bool`, `float`, `double`, and `char` with canonical `hash`/`equal`/`order` hooks; import once to use primitives as Map/Set/BinaryHeap/OrderedMap keys (`str` keys come from `std.str`). Also provides generic `hash_struct[T]`, `equal_struct[T]`, `order_struct[T]` that dispatch each field through its own canonical hook via `field.type` (content-correct, including `str` and nested-struct fields).
872
- - `std.cstring` — C string helpers (`cstr_len`, `cstr_as_str`)
882
+ - `std.cstring` — low-level byte and C-string helpers (`copy_bytes`, `move_bytes`, `set_bytes`, `compare_bytes`, `find_byte`, `length`, `compare`, `compare_prefix`, `find_char`, `find_last_char`, `find_substring`)
873
883
  - `std.math` — `sqrt`, `sin`, `cos`, `abs`, `pow`, etc. via C math
874
884
  - `std.encoding` — UTF-8 validation (`is_valid_utf8`, `utf8_codepoint_count`, `decode_utf8_codepoint`, `utf8_overlong_check`)
875
885
  - `std.string.String` — growable owned UTF-8 text
@@ -1111,6 +1121,7 @@ Current compiler rejects:
1111
1121
  - range expressions are restricted to `for`-loop iterables and range-index assignment targets
1112
1122
  - functions, methods, generic functions, and variant arms must be called — they are not usable as bare values
1113
1123
  - `read(...)` of a raw pointer requires `unsafe`
1124
+ - a statement cannot begin with a binary operator (including `+`, `-`, `and`, `or`, `is`, `..`); continuation requires ending the previous line with the operator or wrapping in `()`
1114
1125
 
1115
1126
  ### Control flow restrictions
1116
1127
 
@@ -1161,11 +1172,12 @@ mtc build <path> # Build only (emit C, compile, link, --no-cache to
1161
1172
  mtc run <path> # Build and execute (--no-cache to build and run without cache)
1162
1173
  mtc debug <file.mt> # Print debug info (tokens, AST, facts, bindings, diagnostics)
1163
1174
  mtc emit-c <path> # Emit generated C to stdout
1164
- mtc format <path> # Format sources in place (--check for dry-run)
1175
+ mtc format <path> # Format source to stdout (--write rewrites in place, --check verifies)
1165
1176
  mtc lint <path> # Run linter (--fix to apply fixes, --select/--ignore to filter)
1166
1177
  mtc test <path> # Discover and run @[test] functions (--timeout, --mem, --jobs)
1167
1178
  mtc new <name> # Scaffold a new package (package.toml + src/main.mt)
1168
1179
  mtc cache status # Show build cache stats
1180
+ mtc cache purge # Remove the entire build cache
1169
1181
  mtc lex <file.mt> # Print lexer token stream
1170
1182
  mtc parse <path> # Print parsed AST
1171
1183
  mtc lower <path> # Print lowered IR
@@ -1183,10 +1195,10 @@ mtc deps publish <path> # Publish a package to the local registry
1183
1195
  mtc deps fetch <path> # Materialize cache-backed sources
1184
1196
  ```
1185
1197
 
1186
- Run a pre-built module (no compilation):
1198
+ Resolve a module by name, build it, and run it:
1187
1199
 
1188
1200
  ```
1189
- mtc run-module <module> # Run compiled module by name (e.g. std.fmt.bench)
1201
+ mtc run-module <module> # Resolve, build, and run a module by name (e.g. std.http.server)
1190
1202
  ```
1191
1203
 
1192
1204
  Toolchain maintenance:
@@ -1197,9 +1209,19 @@ mtc toolchain doctor # Diagnose toolchain setup
1197
1209
  mtc toolchain tools # List available native tools
1198
1210
  ```
1199
1211
 
1212
+ Tooling and editors:
1213
+
1214
+ ```
1215
+ mtc bindgen <header.h> # Generate an external binding module from a C header
1216
+ mtc snapshot <path> # Render a highlighted HTML snapshot of a source file
1217
+ mtc lsp # Start the Language Server Protocol server
1218
+ mtc dap # Start the Debug Adapter Protocol server
1219
+ mtc docs [--open] [--port P] # Serve the local documentation site
1220
+ ```
1221
+
1200
1222
  Build and run commands support `--profile`, `--platform`, `--cc`, `--keep-c`, `--locked`, `--frozen`, and `-I` include paths. Dependency-locked flows support `--locked` (use package.lock) and `--frozen` (require current package.lock).
1201
1223
 
1202
- Global options work with any command (before or after the subcommand, up to a `--` separator): `-h`/`--help` (also `mtc help <command>` for command-specific help), `-V`/`--version`, `-q`/`--quiet` (suppress informational output), `-v`/`--verbose` (per-file progress), and `--color auto|always|never`. Generate shell completions with `mtc completions bash|zsh|fish`. Timing breakdowns use `--timings` (on `format`, `lint`, `build`, `run`, `test`); `lint` reports warnings with per-file progress.
1224
+ Global options work with any command (before or after the subcommand, up to a `--` separator): `-h`/`--help` (also `mtc help <command>` for command-specific help), `-V`/`--version`, `-q`/`--quiet` (suppress informational output), `-v`/`--verbose` (per-file progress), and `--color auto|always|never`. Generate shell completions with `mtc completions bash|zsh|fish`. Timing breakdowns use `--timings` (on `format` and `lint`); `lint` reports warnings with per-file progress.
1203
1225
 
1204
1226
  Diagnostic output uses standard compiler format (file:line:column with source context, error codes, and caret highlighting):
1205
1227
 
data/docs/index.html CHANGED
@@ -343,7 +343,7 @@ function main() -> int:
343
343
  <tr><td><code>mtc check &lt;path&gt;</code></td><td>Type-check + lint; reports all diagnostics sorted by line</td></tr>
344
344
  <tr><td><code>mtc run &lt;path&gt;</code></td><td>Build and execute</td></tr>
345
345
  <tr><td><code>mtc build &lt;path&gt;</code></td><td>Build only (emit C, compile, link)</td></tr>
346
- <tr><td><code>mtc format &lt;path&gt;</code></td><td>Format sources in place (<code>--check</code> for dry-run)</td></tr>
346
+ <tr><td><code>mtc format &lt;path&gt;</code></td><td>Format source to stdout (<code>--write</code> rewrites in place, <code>--check</code> verifies)</td></tr>
347
347
  <tr><td><code>mtc lint &lt;path&gt;</code></td><td>Run linter (<code>--fix</code> to apply fixes)</td></tr>
348
348
  <tr><td><code>mtc test &lt;path&gt;</code></td><td>Discover and run <code>@[test]</code> functions</td></tr>
349
349
  <tr><td><code>mtc new &lt;name&gt;</code></td><td>Scaffold a new package</td></tr>
@@ -403,6 +403,7 @@ void main_Vec2_scale(main_Vec2* this, float factor) {
403
403
  <pre><code>## ── Imports ──────────────────────────────────────────────────────────
404
404
  import std.stdio as io
405
405
  import std.hash
406
+ import std.str
406
407
  import std.vec
407
408
  import std.map
408
409
  import std.math
@@ -888,6 +889,7 @@ import mylib.utils</code></pre>
888
889
  <li>Indentation must be a multiple of <strong>4 spaces</strong></li>
889
890
  <li>Indentation increases by <strong>one level at a time</strong></li>
890
891
  <li>Newlines end statements except inside <code>()</code> and <code>[]</code>, or when the previous line ends with a binary operator</li>
892
+ <li>Starting a line with an operator is a hard parse error (<code>operator '&lt;op&gt;' cannot start a statement</code>); continuation is signaled by ending the previous line with the operator, never by starting the next one with it</li>
891
893
  </ul>
892
894
 
893
895
  <div class="code-wrap">
@@ -902,7 +904,16 @@ let total = (
902
904
  ## Also accepted: operator-led continuation.
903
905
  let total = subtotal +
904
906
  tax -
905
- discount</code></pre>
907
+ discount
908
+
909
+ ## The continuation operator set is closed:
910
+ ## + - * / % | &amp; ^ &lt;&lt; &gt;&gt; == != &lt; &lt;= &gt; &gt;= and or is ..
911
+ ## Member-access chains exceeding one line wrap in () with leading dots:
912
+ let total = (
913
+ values.iter()
914
+ .filter(pred)
915
+ .fold(0, add)
916
+ )</code></pre>
906
917
  </div>
907
918
 
908
919
  <h3>Naming Conventions</h3>
@@ -1733,14 +1744,16 @@ let sized = name[32]</code></pre>
1733
1744
  <tr><th>Precedence</th><th>Operators</th></tr>
1734
1745
  <tr><td>1 (lowest)</td><td><code>or</code></td></tr>
1735
1746
  <tr><td>2</td><td><code>and</code></td></tr>
1736
- <tr><td>3</td><td><code>|</code></td></tr>
1737
- <tr><td>4</td><td><code>^</code></td></tr>
1738
- <tr><td>5</td><td><code>&amp;</code></td></tr>
1739
- <tr><td>6</td><td><code>==</code> <code>!=</code></td></tr>
1740
- <tr><td>7</td><td><code>&lt;</code> <code>&lt;=</code> <code>&gt;</code> <code>&gt;=</code></td></tr>
1741
- <tr><td>8</td><td><code>&lt;&lt;</code> <code>&gt;&gt;</code></td></tr>
1742
- <tr><td>9</td><td><code>+</code> <code>-</code></td></tr>
1743
- <tr><td>10 (highest)</td><td><code>*</code> <code>/</code> <code>%</code></td></tr>
1747
+ <tr><td>3</td><td><code>not</code> (unary prefix)</td></tr>
1748
+ <tr><td>4</td><td><code>is</code></td></tr>
1749
+ <tr><td>5</td><td><code>|</code></td></tr>
1750
+ <tr><td>6</td><td><code>^</code></td></tr>
1751
+ <tr><td>7</td><td><code>&amp;</code></td></tr>
1752
+ <tr><td>8</td><td><code>==</code> <code>!=</code></td></tr>
1753
+ <tr><td>9</td><td><code>&lt;</code> <code>&lt;=</code> <code>&gt;</code> <code>&gt;=</code></td></tr>
1754
+ <tr><td>10</td><td><code>&lt;&lt;</code> <code>&gt;&gt;</code></td></tr>
1755
+ <tr><td>11</td><td><code>+</code> <code>-</code></td></tr>
1756
+ <tr><td>12 (highest)</td><td><code>*</code> <code>/</code> <code>%</code></td></tr>
1744
1757
  </table>
1745
1758
  </div>
1746
1759
 
@@ -2169,7 +2182,7 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
2169
2182
  <tr><td><code>std.spatial</code></td><td><code>SpatialGrid[T]</code></td><td>&mdash;</td><td>Uniform spatial hash grid (returns Vec)</td></tr>
2170
2183
  <tr><td><code>std.ring_buffer</code></td><td><code>RingBuffer[T]</code></td><td>Mutable <code>ptr[T]?</code></td><td>Fixed-capacity circular buffer</td></tr>
2171
2184
  <tr><td><code>std.sparse_set</code></td><td><code>SparseSet[T]</code></td><td>Mutable <code>ptr[T]?</code></td><td>O(1) insert/remove; dense iteration</td></tr>
2172
- <tr><td><code>std.lru_cache</code></td><td><code>LruCache[K,V]</code></td><td><code>entries()</code></td><td>Capacity-bounded LRU eviction map</td></tr>
2185
+ <tr><td><code>std.lru_cache</code></td><td><code>LruCache[K,V]</code></td><td><code>iter()</code> → <code>linked_map.Entries[K, V]</code></td><td>Capacity-bounded LRU eviction map</td></tr>
2173
2186
  </table>
2174
2187
  </div>
2175
2188
 
@@ -2236,7 +2249,7 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
2236
2249
  <tr><td><code>mtc lint &lt;path&gt;</code></td><td>Run linter (<code>--fix</code>, <code>--select</code>, <code>--ignore</code>)</td></tr>
2237
2250
  <tr><td><code>mtc test &lt;path&gt;</code></td><td>Discover and run <code>@[test]</code> functions</td></tr>
2238
2251
  <tr><td><code>mtc new &lt;name&gt;</code></td><td>Scaffold a new package (<code>package.toml</code> + <code>src/main.mt</code>)</td></tr>
2239
- <tr><td><code>mtc run-module &lt;module&gt;</code></td><td>Run a pre-built module by name</td></tr>
2252
+ <tr><td><code>mtc run-module &lt;module&gt;</code></td><td>Resolve a module by name, build it, and run it</td></tr>
2240
2253
  <tr><td><code>mtc completions &lt;shell&gt;</code></td><td>Print a bash/zsh/fish completion script</td></tr>
2241
2254
  </table>
2242
2255
  </div>
@@ -2263,10 +2276,22 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
2263
2276
  <tr><td><code>mtc toolchain doctor</code></td><td>Diagnose toolchain setup</td></tr>
2264
2277
  <tr><td><code>mtc toolchain tools</code></td><td>List available native tools</td></tr>
2265
2278
  <tr><td><code>mtc cache status</code></td><td>Show build cache stats</td></tr>
2279
+ <tr><td><code>mtc cache purge</code></td><td>Remove the entire build cache</td></tr>
2266
2280
  <tr><td><code>mtc docs [--open] [--port PORT]</code></td><td>Start a local HTTP server serving the language reference</td></tr>
2267
2281
  </table>
2268
2282
  </div>
2269
2283
 
2284
+ <h3>Tooling</h3>
2285
+ <div class="table-wrap">
2286
+ <table class="attr-table">
2287
+ <tr><th>Command</th><th>Description</th></tr>
2288
+ <tr><td><code>mtc bindgen &lt;header.h&gt;</code></td><td>Generate an external binding module from a C header</td></tr>
2289
+ <tr><td><code>mtc snapshot &lt;path&gt;</code></td><td>Render a highlighted HTML snapshot of a source file</td></tr>
2290
+ <tr><td><code>mtc lsp</code></td><td>Start the Language Server Protocol server (editor IntelliSense)</td></tr>
2291
+ <tr><td><code>mtc dap</code></td><td>Start the Debug Adapter Protocol server (debugging)</td></tr>
2292
+ </table>
2293
+ </div>
2294
+
2270
2295
  <h3>Build Flags</h3>
2271
2296
  <p>Build and run commands support: <code>--profile</code>, <code>--platform</code>, <code>--cc</code>, <code>--keep-c</code>, <code>--locked</code>, <code>--frozen</code>, and <code>-I</code> include paths.</p>
2272
2297
 
@@ -2385,6 +2410,7 @@ import std.math # intentionally available for downstream</code></pre>
2385
2410
  <li>Enum and flags values do not implicitly coerce to backing integers</li>
2386
2411
  <li><code>+</code> does not support <code>str</code>/<code>cstr</code> concatenation</li>
2387
2412
  <li><code>==</code> and <code>!=</code> not supported on struct types; use <code>equal[T]</code></li>
2413
+ <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>
2388
2414
  <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>
2389
2415
  </ul>
2390
2416
 
@@ -88,6 +88,7 @@ The compile-time evaluation surface is described in [Compile-Time Evaluation](co
88
88
  - Newlines terminate statements.
89
89
  - Newlines inside `()` and `[]` do not terminate statements.
90
90
  - A physical line that ends with a binary operator also continues onto the next line.
91
+ - A physical line that begins with an operator is a hard parse error; continuation is signaled by ending the previous line with the operator, never by starting the next one with it.
91
92
  - Tabs are illegal in source files; indentation is 4 spaces.
92
93
  - Trailing commas are allowed in multiline call arguments and aggregate literals. The linter additionally flags a redundant trailing comma in a call-argument list (`trailing-list-comma` hint); aggregate and array literals keep them for diff-friendliness.
93
94
  - Comments use `#`.
@@ -490,7 +491,7 @@ Milk Tea has first-class compiler support for multithreading. The threading mode
490
491
  parallel for i in 0..entity_count:
491
492
  positions[i] += velocities[i] * dt
492
493
 
493
- # Structured fork-join: each do block runs on a separate thread
494
+ # Structured fork-join: each statement runs on a separate thread
494
495
  parallel:
495
496
  textures = load_textures(path)
496
497
  sounds = load_sounds(path)
@@ -501,7 +502,6 @@ Design choices:
501
502
  - **Structured, not fire-and-forget.** All `parallel for` and `parallel:` blocks are synchronous barriers — the calling thread blocks until all work completes. This guarantees that captured local variables remain alive for the duration and eliminates lifetime concerns without ownership annotations.
502
503
  - **Compile-time safety.** `ref[T]` captures are rejected because mutable aliases across thread boundaries create data races. The `parallel:` block enforces single-writer-or-multiple-readers: if one statement writes a variable, no other block may read or write it.
503
504
  - **Value capture, pointer-based arrays.** Scalars and spans are captured by value (the span's data pointer still references the original storage). Arrays are captured as pointers to their first element, so writes in the worker affect the original array.
504
- - **`do` is a keyword.** It is recognized inside `parallel:` blocks to introduce each concurrent unit of work.
505
505
  - **Real OS threads via libuv.** Thread dispatch uses `uv_thread_create` / `uv_thread_join`, consistent with `std.thread` and `std.sync`. CPU count is detected at runtime via `uv_cpu_info`. The first chunk runs on the calling thread to avoid unnecessary dispatch when the workload is small.
506
506
  - **No forced dependency.** The build system automatically detects `parallel for` and `parallel:` usage via a sema-level flag and links libuv only when needed.
507
507
 
@@ -51,6 +51,7 @@ Rules:
51
51
  - Valid platform filename suffixes are `linux`, `windows`, and `wasm`.
52
52
  - The platform suffix is not part of the module name. `import a.b.c` stays the same on every target.
53
53
  - Milk Tea does not have a source-level conditional compilation syntax such as `#if`, `#ifdef`, or per-declaration platform attributes.
54
+ - Circular module imports are supported. When two modules import each other, the compiler uses forward-declaration bindings for the first pass and full type-checked bindings for the second pass, enabling cross-module type references and constructors in cycle groups.
54
55
 
55
56
  ## 2. Lexical Rules
56
57
 
@@ -82,7 +83,17 @@ let total = subtotal +
82
83
  discount
83
84
  ```
84
85
 
85
- Starting a new physical line with the operator is not part of the supported source contract; wrap in `()` instead if that layout is clearer.
86
+ The continuation operator set is closed: `+` `-` `*` `/` `%`, `|` `&` `^`, `<<` `>>`, `==` `!=` `<` `<=` `>` `>=`, `and` `or` `is`, and `..`. Assignment operators and unary `not` are excluded — wrap in `()` when a break is needed there.
87
+
88
+ Starting a new physical line with an operator is a hard error (`operator '<op>' cannot start a statement`); end the previous line with the operator, or wrap the expression in `()` if that layout is clearer. Member-access chains that exceed one line wrap in `()` with leading dots:
89
+
90
+ ```mt
91
+ let total = (
92
+ values.iter()
93
+ .filter(pred)
94
+ .fold(0, add)
95
+ )
96
+ ```
86
97
 
87
98
  ### 2.2 Comments
88
99
 
@@ -360,6 +371,18 @@ Compile-time reflection over validated attributes uses `has_attribute`, `attribu
360
371
 
361
372
  The current C backend lowers `packed` / `align(...)` attributes with GNU-style `__attribute__((...))`, so these layout controls currently require a Clang/GCC-family compiler. On Windows that means Clang or GCC-family toolchains such as MinGW; `cl.exe` is not a supported backend for these attributes today. On wasm/browser targets the same feature works through Emscripten `emcc`, which is Clang-based.
362
373
 
374
+ Language-level keywords and reserved words are accepted as:
375
+ - variant arm names (`return(val: int)`, `if(arg: bool)`)
376
+ - struct and union field names (`return: int`, `let: str`)
377
+ - enum and flags member names (`return = 1`, `if = 6`)
378
+ - variant arm payload field names (`execute(return: int)`)
379
+ - lifetime parameter names (`struct Buf[@return]`)
380
+ - type parameter names (`struct Box[return]`, `identity[type]`)
381
+ - named arguments in struct/variant literals (`Meta(return = 1)`)
382
+ - named tuple fields (`(return = 1, let = 2)`)
383
+
384
+ Primitive type names (`int`, `str`, `bool`, `ref`, `ptr`, `span`, `type`, etc.) remain reserved and cannot be used as names.
385
+
363
386
  ### 3.4a Enum and flags operators
364
387
 
365
388
  Enum and flags values support the full set of comparison operators (`==`, `!=`, `<`, `<=`, `>`, `>=`) against values of the same enum type and against their backing integer type. Comparisons use the underlying integer backing values.
@@ -746,7 +769,6 @@ parallel:
746
769
  Rules:
747
770
 
748
771
  - A `parallel:` block must contain at least two statements.
749
- - `do` is a contextual keyword — only recognized inside `parallel:` blocks, not reserved globally.
750
772
  - Each statement must not contain `break`, `continue`, `return`, or `defer`.
751
773
  - The compiler enforces single-writer-or-multiple-readers: if a variable is written in one statement, no other block may access it.
752
774
  - Captured `ref[T]` values are rejected at compile time.
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.10"
6
+ VERSION = "0.3.12"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -153,17 +153,17 @@ module MilkTea
153
153
  VariantArm = Data.define(:name, :fields, :line, :column) do
154
154
  def initialize(name:, fields:, line: nil, column: nil) = super
155
155
  end
156
- MatchArm = Data.define(:pattern, :binding_name, :binding_line, :binding_column, :body) do
157
- def initialize(pattern:, binding_name:, body:, binding_line: nil, binding_column: nil) = super
156
+ MatchArm = Data.define(:pattern, :binding_name, :binding_line, :binding_column, :body, :line, :column) do
157
+ def initialize(pattern:, binding_name:, body:, binding_line: nil, binding_column: nil, line: nil, column: nil) = super
158
158
  end
159
159
  MatchStmt = Data.define(:expression, :arms, :inline, :line, :column, :length) do
160
160
  def initialize(expression:, arms:, inline: false, line: nil, column: nil, length: nil) = super
161
161
  end
162
- MatchExprArm = Data.define(:pattern, :binding_name, :binding_line, :binding_column, :value) do
163
- def initialize(pattern:, binding_name:, value:, binding_line: nil, binding_column: nil) = super
162
+ MatchExprArm = Data.define(:pattern, :binding_name, :binding_line, :binding_column, :value, :line, :column) do
163
+ def initialize(pattern:, binding_name:, value:, binding_line: nil, binding_column: nil, line: nil, column: nil) = super
164
164
  end
165
- WhenBranch = Data.define(:pattern, :binding_name, :binding_line, :binding_column, :body) do
166
- def initialize(pattern:, binding_name:, body:, binding_line: nil, binding_column: nil) = super
165
+ WhenBranch = Data.define(:pattern, :binding_name, :binding_line, :binding_column, :body, :line, :column) do
166
+ def initialize(pattern:, binding_name:, body:, binding_line: nil, binding_column: nil, line: nil, column: nil) = super
167
167
  end
168
168
  WhenStmt = Data.define(:discriminant, :branches, :else_body, :line, :column, :length) do
169
169
  def initialize(discriminant:, branches:, else_body:, line: nil, column: nil, length: nil) = super
@@ -42,15 +42,7 @@ module MilkTea
42
42
  link opaque public static_assert struct type union var variant extending event
43
43
  ].freeze
44
44
 
45
- LINE_CONTINUATION_OPERATORS = %i[
46
- dot_dot
47
- plus minus star slash percent
48
- pipe amp caret
49
- or and
50
- equal_equal bang_equal
51
- less less_equal greater greater_equal
52
- shift_left shift_right
53
- ].freeze
45
+ LINE_CONTINUATION_OPERATORS = Token::LINE_CONTINUATION_OPERATORS
54
46
 
55
47
  THREE_CHAR_TOKENS = {
56
48
  "..." => :ellipsis,
@@ -358,7 +350,7 @@ module MilkTea
358
350
  newline_start = line_offset + line.length
359
351
  newline_end = has_newline ? (newline_start + 1) : newline_start
360
352
  if @grouping_depth.zero?
361
- if LINE_CONTINUATION_OPERATORS.include?(@tokens.last&.type)
353
+ if Token::LINE_CONTINUATION_OPERATORS.include?(@tokens.last&.type)
362
354
  @continuation_pending = true
363
355
  else
364
356
  @tokens << token(:newline, "\n", nil, line_number, line.length + 1, start_offset: newline_start, end_offset: newline_end)
@@ -93,7 +93,7 @@ module MilkTea
93
93
  arms = statement.arms.map do |arm|
94
94
  arm_env = duplicate_env(env)
95
95
  bind_async_variant_match_arm_env!(arm_env, scrutinee_type, arm)
96
- AST::MatchArm.new(pattern: arm.pattern, binding_name: arm.binding_name, body: normalize_async_statements(arm.body, counter, arm_env, return_type:))
96
+ AST::MatchArm.new(pattern: arm.pattern, binding_name: arm.binding_name, body: normalize_async_statements(arm.body, counter, arm_env, return_type:), line: arm.line, column: arm.column)
97
97
  end
98
98
  expr_setup + [AST::MatchStmt.new(expression:, arms:)]
99
99
  when AST::WhileStmt
@@ -307,6 +307,8 @@ module MilkTea
307
307
  binding_line: arm.binding_line,
308
308
  binding_column: arm.binding_column,
309
309
  value: normalized_value,
310
+ line: arm.line,
311
+ column: arm.column,
310
312
  )]
311
313
  end
312
314
 
@@ -326,6 +328,8 @@ module MilkTea
326
328
  binding_line: arm.binding_line,
327
329
  binding_column: arm.binding_column,
328
330
  body: pattern_setup + value_setup + [AST::Assignment.new(target: AST::Identifier.new(name: temp_name), operator: "=", value: arm.value)],
331
+ line: arm.line,
332
+ column: arm.column,
329
333
  )
330
334
  end,
331
335
  line: expression.line,
@@ -372,10 +372,10 @@ module MilkTea
372
372
  break if check(:rbracket)
373
373
 
374
374
  if match(:at)
375
- name_token = consume_name("expected lifetime name after @")
375
+ name_token = consume_name_allowing_keywords("expected lifetime name after @")
376
376
  lifetime_params << "@#{name_token.lexeme}"
377
377
  else
378
- name_token = consume_name("expected type parameter name")
378
+ name_token = consume_name_allowing_keywords("expected type parameter name")
379
379
  if match(:colon)
380
380
  value_type = parse_type_ref
381
381
  type_params << AST::ValueTypeParam.new(
@@ -419,7 +419,7 @@ module MilkTea
419
419
 
420
420
  raise error(visibility_token, "public is only allowed on struct events") if visibility == :public
421
421
 
422
- field_token = consume_name("expected field name")
422
+ field_token = consume_name_allowing_keywords("expected field name")
423
423
  field_name = field_token.lexeme
424
424
  consume(:colon, "expected ':' after field name")
425
425
  field_type = parse_type_ref
@@ -440,7 +440,7 @@ module MilkTea
440
440
  name = name_token.lexeme
441
441
  c_name = parse_optional_explicit_c_name
442
442
  fields = parse_named_block do
443
- field_name = consume_name("expected field name").lexeme
443
+ field_name = consume_name_allowing_keywords("expected field name").lexeme
444
444
  consume(:colon, "expected ':' after field name")
445
445
  field_type = parse_type_ref
446
446
  consume_end_of_statement
@@ -471,7 +471,7 @@ module MilkTea
471
471
  members = []
472
472
  skip_newlines
473
473
  until check(:dedent) || eof?
474
- member_token = consume_name("expected member name")
474
+ member_token = consume_name_allowing_keywords("expected member name")
475
475
  member_name = member_token.lexeme
476
476
  if match(:equal)
477
477
  value = parse_expression
@@ -493,10 +493,10 @@ module MilkTea
493
493
  name = name_token.lexeme
494
494
  type_params = parse_declaration_type_params
495
495
  arms = parse_named_block do
496
- arm_name = consume_name("expected variant arm name").lexeme
496
+ arm_name = consume_name_allowing_keywords("expected variant arm name").lexeme
497
497
  fields = if match(:lparen)
498
498
  parsed = parse_comma_separated_until(:rparen) do
499
- field_name = consume_name("expected field name").lexeme
499
+ field_name = consume_name_allowing_keywords("expected field name").lexeme
500
500
  consume(:colon, "expected ':' after field name")
501
501
  field_type = parse_type_ref
502
502
  AST::Field.new(name: field_name, type: field_type)
@@ -105,6 +105,8 @@ module MilkTea
105
105
  binding_line: binding_token&.line,
106
106
  binding_column: binding_token&.column,
107
107
  value:,
108
+ line: pattern.line,
109
+ column: pattern.column,
108
110
  )
109
111
  end
110
112
  end
@@ -153,6 +155,8 @@ module MilkTea
153
155
  binding_line: nil,
154
156
  binding_column: nil,
155
157
  value: AST::BooleanLiteral.new(value: true),
158
+ line: arm_pattern.line,
159
+ column: arm_pattern.column,
156
160
  ),
157
161
  AST::MatchExprArm.new(
158
162
  pattern: AST::Identifier.new(name: "_", line:, column:),
@@ -160,6 +164,8 @@ module MilkTea
160
164
  binding_line: nil,
161
165
  binding_column: nil,
162
166
  value: AST::BooleanLiteral.new(value: false),
167
+ line:,
168
+ column:,
163
169
  ),
164
170
  ],
165
171
  line:,
@@ -338,7 +344,7 @@ module MilkTea
338
344
  end
339
345
 
340
346
  def parse_call_argument
341
- if check_name && check_next(:equal)
347
+ if (check_name || keyword_token?(peek)) && check_next(:equal)
342
348
  name = advance.lexeme
343
349
  consume(:equal, "expected '=' after named argument name")
344
350
  AST::Argument.new(name:, value: parse_expression)
@@ -388,7 +394,7 @@ module MilkTea
388
394
  elsif match(:lparen)
389
395
  line = previous.line
390
396
  column = previous.column
391
- first = if check_name && check_next(:equal)
397
+ first = if (check_name || keyword_token?(peek)) && check_next(:equal)
392
398
  name_token = advance
393
399
  consume(:equal, "expected '=' after named tuple field")
394
400
  AST::Argument.new(name: name_token.lexeme, value: parse_expression)
@@ -398,7 +404,7 @@ module MilkTea
398
404
  if match(:comma)
399
405
  elements = [first]
400
406
  loop do
401
- if check_name && check_next(:equal)
407
+ if (check_name || keyword_token?(peek)) && check_next(:equal)
402
408
  name_token = advance
403
409
  consume(:equal, "expected '=' after named tuple field")
404
410
  value = parse_expression
@@ -35,7 +35,18 @@ module MilkTea
35
35
  check(:when)
36
36
  end
37
37
 
38
+ def reject_operator_statement_start!
39
+ return unless Token::LINE_START_OPERATOR_TYPES.include?(peek.type)
40
+
41
+ operator = peek.lexeme
42
+ raise error(
43
+ peek,
44
+ "operator '#{operator}' cannot start a statement; end the previous line with it or wrap the expression in ( )"
45
+ )
46
+ end
47
+
38
48
  def parse_statement
49
+ reject_operator_statement_start!
39
50
  if match(:let)
40
51
  parse_local_decl(:let)
41
52
  elsif match(:var)
@@ -351,6 +362,8 @@ module MilkTea
351
362
  binding_line: binding_token&.line,
352
363
  binding_column: binding_token&.column,
353
364
  value:,
365
+ line: pattern.line,
366
+ column: pattern.column,
354
367
  )
355
368
  end
356
369
  else
@@ -362,6 +375,8 @@ module MilkTea
362
375
  binding_line: binding_token&.line,
363
376
  binding_column: binding_token&.column,
364
377
  body:,
378
+ line: pattern.line,
379
+ column: pattern.column,
365
380
  )
366
381
  end
367
382
  end
@@ -376,6 +391,8 @@ module MilkTea
376
391
  binding_line: binding_token&.line,
377
392
  binding_column: binding_token&.column,
378
393
  body: recovered_body,
394
+ line: patterns.first&.line,
395
+ column: patterns.first&.column,
379
396
  )] if recovered_body
380
397
 
381
398
  raise
@@ -615,6 +632,8 @@ module MilkTea
615
632
  binding_line: binding_token.line,
616
633
  binding_column: binding_token.column,
617
634
  body:,
635
+ line: pattern.line,
636
+ column: pattern.column,
618
637
  )
619
638
  skip_newlines
620
639
  end
@@ -92,7 +92,7 @@ module MilkTea
92
92
 
93
93
  first_token = peek
94
94
  if match(:at)
95
- lt_token = consume_name("expected lifetime name after @")
95
+ lt_token = consume_name_allowing_keywords("expected lifetime name after @")
96
96
  return AST::TypeRef.new(name: AST::QualifiedName.new(parts: ["@#{lt_token.lexeme}"]), arguments: [], nullable: false, lifetime: nil, line: first_token.line, column: first_token.column, length: lt_token.lexeme.length + 1)
97
97
  end
98
98
  name = parse_qualified_name
@@ -101,7 +101,7 @@ module MilkTea
101
101
  if match(:lbracket)
102
102
  if check(:at) && name.to_s == "ref"
103
103
  match(:at)
104
- lt_token = consume_name("expected lifetime name after @")
104
+ lt_token = consume_name_allowing_keywords("expected lifetime name after @")
105
105
  lifetime = "@#{lt_token.lexeme}"
106
106
  consume(:comma, "expected ',' after lifetime in type arguments")
107
107
  end
@@ -167,7 +167,7 @@ module MilkTea
167
167
  end
168
168
 
169
169
  def parse_function_type_param
170
- name_token = consume_name("expected function type parameter name")
170
+ name_token = consume_name_allowing_keywords("expected function type parameter name")
171
171
  name = name_token.lexeme
172
172
  consume(:colon, "expected ':' after function type parameter name")
173
173
  type = parse_type_ref
@@ -190,7 +190,7 @@ module MilkTea
190
190
  return [] unless match(:lbracket)
191
191
 
192
192
  params = parse_comma_separated_until(:rbracket) do
193
- name_token = consume_name("expected type parameter name")
193
+ name_token = consume_name_allowing_keywords("expected type parameter name")
194
194
  name = name_token.lexeme
195
195
  if match(:colon)
196
196
  value_type = parse_type_ref
@@ -208,7 +208,7 @@ module MilkTea
208
208
  def resolve_type_param_constraints(type_params)
209
209
  type_params.each_with_object({}) do |type_param, constraints|
210
210
  if type_param.is_a?(AST::ValueTypeParam)
211
- ensure_non_reserved_type_binding_name!(
211
+ ensure_non_reserved_value_type_name!(
212
212
  type_param.name,
213
213
  kind_label: "type parameter",
214
214
  line: type_param.line,
@@ -218,7 +218,7 @@ module MilkTea
218
218
  next
219
219
  end
220
220
 
221
- ensure_non_reserved_type_binding_name!(
221
+ ensure_non_reserved_value_type_name!(
222
222
  type_param.name,
223
223
  kind_label: "type parameter",
224
224
  line: type_param.line,
@@ -314,6 +314,12 @@ module MilkTea
314
314
  expanded_declarations.grep(AST::AttributeDecl).each do |decl|
315
315
  with_error_node(decl) do
316
316
  raise_sema_error("duplicate attribute #{decl.name}") if @ctx.attributes.key?(decl.name)
317
+ ensure_non_reserved_value_type_name!(
318
+ decl.name,
319
+ kind_label: "attribute",
320
+ line: decl.line,
321
+ column: decl.column,
322
+ )
317
323
 
318
324
  params = []
319
325
  seen = {}
@@ -463,6 +469,15 @@ module MilkTea
463
469
  {}
464
470
  end
465
471
  decl.lifetime_params.each do |lt|
472
+ raw_name = lt.delete_prefix("@")
473
+ unless raw_module?
474
+ ensure_non_reserved_value_type_name!(
475
+ raw_name,
476
+ kind_label: "lifetime parameter #{decl.name}",
477
+ line: decl.line,
478
+ column: decl.column,
479
+ )
480
+ end
466
481
  type_params[lt] = Types::LifetimeRef.new(lt)
467
482
  end if decl.respond_to?(:lifetime_params)
468
483
  type_param_constraints = struct_type.is_a?(Types::GenericStructDefinition) ? struct_type.type_param_constraints : {}
@@ -482,7 +497,7 @@ module MilkTea
482
497
  raise_sema_error("duplicate field #{decl.name}.#{field.name}") if fields.key?(field.name)
483
498
  raise_sema_error("duplicate member #{decl.name}.#{field.name}") if events.key?(field.name)
484
499
  unless raw_module?
485
- ensure_non_reserved_type_binding_name!(
500
+ ensure_non_reserved_value_type_name!(
486
501
  field.name,
487
502
  kind_label: "field #{decl.name}",
488
503
  line: field.line || decl.line,
@@ -528,7 +543,7 @@ module MilkTea
528
543
  raise_sema_error("duplicate event #{decl.name}.#{event_decl.name}") if events.key?(event_decl.name)
529
544
  raise_sema_error("duplicate member #{decl.name}.#{event_decl.name}") if fields.key?(event_decl.name)
530
545
  unless raw_module?
531
- ensure_non_reserved_type_binding_name!(
546
+ ensure_non_reserved_value_type_name!(
532
547
  event_decl.name,
533
548
  kind_label: "event #{decl.name}",
534
549
  line: event_decl.line,
@@ -576,7 +591,7 @@ module MilkTea
576
591
  decl.members.each do |member|
577
592
  raise_sema_error("duplicate member #{decl.name}.#{member.name}") if member_names.include?(member.name)
578
593
  unless raw_module?
579
- ensure_non_reserved_type_binding_name!(
594
+ ensure_non_reserved_value_type_name!(
580
595
  member.name,
581
596
  kind_label: "member #{decl.name}",
582
597
  line: member.line || decl.line,
@@ -645,7 +660,7 @@ module MilkTea
645
660
  begin
646
661
  raise_sema_error("duplicate arm #{decl.name}.#{arm.name}") if seen_arms.include?(arm.name)
647
662
  unless raw_module?
648
- ensure_non_reserved_type_binding_name!(
663
+ ensure_non_reserved_value_type_name!(
649
664
  arm.name,
650
665
  kind_label: "arm #{decl.name}",
651
666
  line: arm.line || decl.line,
@@ -661,7 +676,7 @@ module MilkTea
661
676
  begin
662
677
  raise_sema_error("duplicate field #{arm.name}.#{field.name}") if seen_fields.include?(field.name)
663
678
  unless raw_module?
664
- ensure_non_reserved_type_binding_name!(
679
+ ensure_non_reserved_value_type_name!(
665
680
  field.name,
666
681
  kind_label: "field #{decl.name}.#{arm.name}",
667
682
  line: field.line || decl.line,
@@ -811,6 +826,14 @@ module MilkTea
811
826
  fields = {}
812
827
  nested.fields.each do |field|
813
828
  raise_sema_error("duplicate field #{qualified_name}.#{field.name}") if fields.key?(field.name)
829
+ unless raw_module?
830
+ ensure_non_reserved_value_type_name!(
831
+ field.name,
832
+ kind_label: "field #{qualified_name}",
833
+ line: field.line || nested.line,
834
+ column: field.column,
835
+ )
836
+ end
814
837
  begin
815
838
  field_type = resolve_type_ref(field.type, type_params:, type_param_constraints:, nested_types: nested_scope)
816
839
  validate_stored_ref_type!(field_type, "field #{qualified_name}.#{field.name}")
@@ -824,6 +847,14 @@ module MilkTea
824
847
  nested_events = {}
825
848
  nested.events.each do |event_decl|
826
849
  raise_sema_error("duplicate event #{qualified_name}.#{event_decl.name}") if nested_events.key?(event_decl.name)
850
+ unless raw_module?
851
+ ensure_non_reserved_value_type_name!(
852
+ event_decl.name,
853
+ kind_label: "event #{qualified_name}",
854
+ line: event_decl.line,
855
+ column: event_decl.column,
856
+ )
857
+ end
827
858
  begin
828
859
  nested_events[event_decl.name] = resolve_event_decl_type(event_decl, type_params:, type_param_constraints:, owner_type_name: qualified_name, nested_types: nested_scope)
829
860
  rescue SemanticError => e
@@ -11,6 +11,23 @@ module MilkTea
11
11
  amp_equal pipe_equal caret_equal shift_left_equal shift_right_equal
12
12
  ].freeze
13
13
 
14
+ # Operator token types that may never begin a statement. A statement
15
+ # that spans multiple lines must end the previous line with one of
16
+ # LINE_CONTINUATION_OPERATORS or wrap the expression in ( ) — it must
17
+ # never start the next line with an operator.
18
+ LINE_START_OPERATOR_TYPES = %i[
19
+ dot_dot plus minus star slash percent
20
+ pipe amp caret tilde
21
+ or and is
22
+ equal_equal bang_equal
23
+ less less_equal greater greater_equal
24
+ shift_left shift_right
25
+ ].freeze
26
+
27
+ # Binary operators that continue a statement when they end a physical
28
+ # line. Unary-only `~` is excluded because it has no left operand.
29
+ LINE_CONTINUATION_OPERATORS = (LINE_START_OPERATOR_TYPES - [:tilde]).freeze
30
+
14
31
  def assignment?
15
32
  ASSIGNMENT_TYPES.include?(type)
16
33
  end
@@ -275,6 +275,12 @@ module MilkTea
275
275
  end
276
276
 
277
277
  if KEYWORD_TOKEN_TYPES.include?(tok.type)
278
+ return [:typeParameter, []] if keyword_lifetime_or_type_param?(tokens, index)
279
+ return [:type, []] if keyword_type_annotation?(tokens, index)
280
+ return [:enumMember, [:declaration]] if variant_enum_member_declaration?(tokens, index)
281
+ return [:property, [:declaration]] if keyword_field_declaration_token?(tokens, index)
282
+ return [:property, []] if keyword_member_access?(tokens, index)
283
+ return [:parameter, []] if keyword_named_argument_token?(tokens, index)
278
284
  return [:keyword, []]
279
285
  end
280
286
 
@@ -550,6 +556,64 @@ module MilkTea
550
556
  next_tok&.type == :colon
551
557
  end
552
558
 
559
+ def keyword_field_declaration_token?(tokens, index)
560
+ return false if match_arm_binding_token?(tokens, index)
561
+ return false if destructure_let_binding?(tokens, index)
562
+
563
+ next_tok = next_non_trivia_token(tokens, index + 1)
564
+ next_tok&.type == :colon
565
+ end
566
+
567
+ def keyword_member_access?(tokens, index)
568
+ prev_index = previous_non_trivia_token_index(tokens, index)
569
+ return false unless prev_index
570
+
571
+ tokens[prev_index].type == :dot
572
+ end
573
+
574
+ def keyword_lifetime_or_type_param?(tokens, index)
575
+ prev_index = previous_non_trivia_token_index(tokens, index)
576
+ return false unless prev_index
577
+
578
+ prev_type = tokens[prev_index].type
579
+ return true if prev_type == :at
580
+ return true if prev_type == :lbracket
581
+ return true if prev_type == :comma && inside_type_params?(tokens, prev_index)
582
+ false
583
+ end
584
+
585
+ def inside_type_params?(tokens, comma_index)
586
+ i = comma_index - 1
587
+ while i >= 0
588
+ t = tokens[i]
589
+ break if t.type == :lbracket
590
+ return false if t.type == :newline || t.type == :rbracket || t.type == :lparen
591
+ i -= 1
592
+ end
593
+ return false if i < 0
594
+
595
+ prev = previous_non_trivia_token_index(tokens, i)
596
+ return true if prev && %i[struct variant enum flags function fn proc].include?(tokens[prev].type)
597
+ return true if prev && tokens[prev].type == :rbracket
598
+ return true if prev && tokens[prev].type == :identifier
599
+
600
+ false
601
+ end
602
+
603
+ def keyword_named_argument_token?(tokens, index)
604
+ next_tok = next_non_trivia_token(tokens, index + 1)
605
+ next_tok&.type == :equal
606
+ end
607
+
608
+ def keyword_type_annotation?(tokens, index)
609
+ tok = tokens[index]
610
+ prev_index = previous_non_trivia_token_index(tokens, index)
611
+ return false unless prev_index
612
+ return false unless %i[colon arrow].include?(tokens[prev_index].type)
613
+
614
+ tokens[prev_index].line == tok.line
615
+ end
616
+
553
617
  def destructure_let_binding?(tokens, index)
554
618
  tok = tokens[index]
555
619
  return false unless tok&.type == :identifier
@@ -1611,6 +1611,7 @@ module MilkTea
1611
1611
  input_path = nil
1612
1612
  theme_path = nil
1613
1613
  output_path = nil
1614
+ textmate_only = false
1614
1615
 
1615
1616
  until @argv.empty?
1616
1617
  arg = @argv.first
@@ -1629,6 +1630,9 @@ module MilkTea
1629
1630
  @err.puts("snapshot: missing value for --output")
1630
1631
  return 1
1631
1632
  end
1633
+ when "--textmate-only"
1634
+ @argv.shift
1635
+ textmate_only = true
1632
1636
  else
1633
1637
  if arg.start_with?("-")
1634
1638
  @err.puts("snapshot: unknown option #{arg}")
@@ -1674,10 +1678,12 @@ module MilkTea
1674
1678
  args.push("-o", output_path) if output_path
1675
1679
 
1676
1680
  semantic_result = nil
1677
- begin
1678
- semantic_result = MilkTea::LSP::Server.semantic_tokens_for_path(input_path)
1679
- rescue => e
1680
- @err.puts("snapshot: semantic analysis skipped: #{e.message}")
1681
+ unless textmate_only
1682
+ begin
1683
+ semantic_result = MilkTea::LSP::Server.semantic_tokens_for_path(input_path)
1684
+ rescue => e
1685
+ @err.puts("snapshot: semantic analysis skipped: #{e.message}")
1686
+ end
1681
1687
  end
1682
1688
 
1683
1689
  if semantic_result && semantic_result[:entries] && !semantic_result[:entries].empty?
@@ -111,7 +111,7 @@ module MilkTea
111
111
  line: type_param.line,
112
112
  column: type_param.column,
113
113
  kind_label:,
114
- reserved_names: RESERVED_TYPE_BINDING_NAMES,
114
+ reserved_names: RESERVED_VALUE_TYPE_NAMES,
115
115
  )
116
116
  end
117
117
  end
@@ -150,7 +150,19 @@ module MilkTea
150
150
  column: statement.column,
151
151
  var: statement.kind == :var
152
152
  )
153
- with_scope { visit_statement_list(statement.else_body) } if statement.else_body
153
+ if statement.else_body || statement.else_binding
154
+ with_scope do
155
+ if statement.else_binding
156
+ declare_local(
157
+ statement.else_binding.name,
158
+ statement.else_binding.line,
159
+ column: statement.else_binding.column,
160
+ var: false
161
+ )
162
+ end
163
+ visit_statement_list(statement.else_body) if statement.else_body
164
+ end
165
+ end
154
166
  check_redundant_type_annotation(statement)
155
167
  flag_redundant_widening_cast(statement.value) if statement.type && statement.value
156
168
  record_ptr_candidate(statement)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mt-lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.10
4
+ version: 0.3.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -603,7 +603,7 @@ metadata:
603
603
  homepage_uri: https://teefan.github.io/mt-lang/
604
604
  source_code_uri: https://github.com/teefan/mt-lang
605
605
  post_install_message: |
606
- Milk Tea 0.3.10 installed!
606
+ Milk Tea 0.3.12 installed!
607
607
 
608
608
  System requirements:
609
609
  - A C compiler (gcc or clang) must be available on PATH