@shd101wyy/yo 0.1.26 → 0.1.28

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 (174) hide show
  1. package/.github/skills/yo-async-effects/SKILL.md +4 -4
  2. package/.github/skills/yo-async-effects/async-effects-recipes.md +34 -34
  3. package/.github/skills/yo-core-patterns/SKILL.md +1 -1
  4. package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +29 -26
  5. package/.github/skills/yo-project-workflow/SKILL.md +6 -3
  6. package/.github/skills/yo-project-workflow/workflow-cheatsheet.md +36 -11
  7. package/.github/skills/yo-syntax/SKILL.md +7 -6
  8. package/.github/skills/yo-syntax/syntax-cheatsheet.md +225 -64
  9. package/.github/skills/yo-wasm-integration/wasm-integration-cheatsheet.md +3 -3
  10. package/README.md +10 -8
  11. package/out/cjs/index.cjs +553 -535
  12. package/out/cjs/yo-cli.cjs +685 -651
  13. package/out/cjs/yo-lsp.cjs +596 -569
  14. package/out/esm/index.mjs +396 -378
  15. package/out/types/src/env.d.ts +1 -0
  16. package/out/types/src/evaluator/calls/helper.d.ts +4 -2
  17. package/out/types/src/evaluator/types/synthesizer.d.ts +1 -0
  18. package/out/types/src/formatter.d.ts +11 -0
  19. package/out/types/src/lsp/formatting.d.ts +2 -0
  20. package/out/types/src/test-runner.d.ts +2 -0
  21. package/out/types/src/tests/formatter.test.d.ts +1 -0
  22. package/out/types/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +1 -1
  24. package/scripts/probe-parser-parity.ts +61 -0
  25. package/scripts/probe-yo-self-parser.sh +33 -0
  26. package/scripts/validate-yo-self-fmt.ts +184 -0
  27. package/std/alg/hash.yo +13 -21
  28. package/std/allocator.yo +25 -40
  29. package/std/async.yo +3 -7
  30. package/std/build.yo +105 -151
  31. package/std/cli/arg_parser.yo +184 -169
  32. package/std/collections/array_list.yo +350 -314
  33. package/std/collections/btree_map.yo +142 -131
  34. package/std/collections/deque.yo +132 -128
  35. package/std/collections/hash_map.yo +542 -566
  36. package/std/collections/hash_set.yo +623 -687
  37. package/std/collections/linked_list.yo +275 -293
  38. package/std/collections/ordered_map.yo +113 -85
  39. package/std/collections/priority_queue.yo +73 -73
  40. package/std/crypto/md5.yo +191 -95
  41. package/std/crypto/random.yo +56 -64
  42. package/std/crypto/sha256.yo +151 -107
  43. package/std/encoding/base64.yo +87 -81
  44. package/std/encoding/hex.yo +43 -50
  45. package/std/encoding/html.yo +56 -81
  46. package/std/encoding/html_char_utils.yo +7 -13
  47. package/std/encoding/html_entities.yo +2248 -2253
  48. package/std/encoding/json.yo +316 -224
  49. package/std/encoding/punycode.yo +86 -116
  50. package/std/encoding/toml.yo +67 -66
  51. package/std/encoding/utf16.yo +37 -44
  52. package/std/env.yo +62 -91
  53. package/std/error.yo +7 -15
  54. package/std/fmt/display.yo +5 -9
  55. package/std/fmt/index.yo +8 -14
  56. package/std/fmt/to_string.yo +330 -315
  57. package/std/fmt/writer.yo +58 -87
  58. package/std/fs/dir.yo +83 -102
  59. package/std/fs/file.yo +147 -180
  60. package/std/fs/metadata.yo +45 -78
  61. package/std/fs/temp.yo +55 -65
  62. package/std/fs/types.yo +27 -40
  63. package/std/fs/walker.yo +53 -68
  64. package/std/gc.yo +5 -8
  65. package/std/glob.yo +30 -43
  66. package/std/http/client.yo +107 -120
  67. package/std/http/http.yo +106 -96
  68. package/std/http/index.yo +4 -6
  69. package/std/imm/list.yo +88 -93
  70. package/std/imm/map.yo +528 -464
  71. package/std/imm/set.yo +52 -57
  72. package/std/imm/sorted_map.yo +340 -286
  73. package/std/imm/sorted_set.yo +57 -63
  74. package/std/imm/string.yo +404 -345
  75. package/std/imm/vec.yo +173 -181
  76. package/std/io/reader.yo +3 -6
  77. package/std/io/writer.yo +4 -8
  78. package/std/libc/assert.yo +5 -9
  79. package/std/libc/ctype.yo +32 -22
  80. package/std/libc/dirent.yo +26 -25
  81. package/std/libc/errno.yo +164 -90
  82. package/std/libc/fcntl.yo +52 -45
  83. package/std/libc/float.yo +66 -44
  84. package/std/libc/limits.yo +42 -33
  85. package/std/libc/math.yo +53 -82
  86. package/std/libc/signal.yo +72 -47
  87. package/std/libc/stdatomic.yo +217 -188
  88. package/std/libc/stdint.yo +5 -29
  89. package/std/libc/stdio.yo +5 -29
  90. package/std/libc/stdlib.yo +32 -39
  91. package/std/libc/string.yo +5 -23
  92. package/std/libc/sys/stat.yo +58 -56
  93. package/std/libc/time.yo +5 -19
  94. package/std/libc/unistd.yo +5 -20
  95. package/std/libc/wctype.yo +6 -9
  96. package/std/libc/windows.yo +26 -30
  97. package/std/log.yo +41 -55
  98. package/std/net/addr.yo +102 -97
  99. package/std/net/dns.yo +27 -28
  100. package/std/net/errors.yo +50 -49
  101. package/std/net/tcp.yo +113 -124
  102. package/std/net/udp.yo +55 -66
  103. package/std/os/env.yo +35 -33
  104. package/std/os/signal.yo +15 -25
  105. package/std/path.yo +276 -311
  106. package/std/prelude.yo +6304 -4315
  107. package/std/process/command.yo +87 -103
  108. package/std/process/index.yo +12 -31
  109. package/std/regex/compiler.yo +196 -95
  110. package/std/regex/flags.yo +58 -39
  111. package/std/regex/index.yo +157 -173
  112. package/std/regex/match.yo +20 -31
  113. package/std/regex/node.yo +134 -152
  114. package/std/regex/parser.yo +283 -259
  115. package/std/regex/unicode.yo +172 -202
  116. package/std/regex/vm.yo +155 -171
  117. package/std/string/index.yo +5 -7
  118. package/std/string/rune.yo +45 -55
  119. package/std/string/string.yo +937 -964
  120. package/std/string/string_builder.yo +94 -104
  121. package/std/string/unicode.yo +46 -64
  122. package/std/sync/channel.yo +72 -73
  123. package/std/sync/cond.yo +31 -36
  124. package/std/sync/mutex.yo +30 -32
  125. package/std/sync/once.yo +13 -16
  126. package/std/sync/rwlock.yo +26 -31
  127. package/std/sync/waitgroup.yo +20 -25
  128. package/std/sys/advise.yo +16 -24
  129. package/std/sys/bufio/buf_reader.yo +77 -93
  130. package/std/sys/bufio/buf_writer.yo +52 -65
  131. package/std/sys/clock.yo +4 -9
  132. package/std/sys/constants.yo +77 -61
  133. package/std/sys/copy.yo +4 -10
  134. package/std/sys/dir.yo +26 -43
  135. package/std/sys/dns.yo +41 -61
  136. package/std/sys/errors.yo +95 -103
  137. package/std/sys/events.yo +45 -57
  138. package/std/sys/externs.yo +319 -267
  139. package/std/sys/fallocate.yo +7 -11
  140. package/std/sys/fcntl.yo +14 -22
  141. package/std/sys/file.yo +26 -40
  142. package/std/sys/future.yo +5 -8
  143. package/std/sys/iov.yo +12 -25
  144. package/std/sys/lock.yo +12 -13
  145. package/std/sys/mmap.yo +38 -43
  146. package/std/sys/path.yo +3 -8
  147. package/std/sys/perm.yo +7 -21
  148. package/std/sys/pipe.yo +5 -12
  149. package/std/sys/process.yo +23 -29
  150. package/std/sys/seek.yo +10 -12
  151. package/std/sys/signal.yo +7 -13
  152. package/std/sys/signals.yo +52 -35
  153. package/std/sys/socket.yo +63 -58
  154. package/std/sys/socketpair.yo +3 -6
  155. package/std/sys/sockinfo.yo +11 -20
  156. package/std/sys/statfs.yo +11 -34
  157. package/std/sys/statx.yo +25 -52
  158. package/std/sys/sysinfo.yo +15 -20
  159. package/std/sys/tcp.yo +62 -92
  160. package/std/sys/temp.yo +5 -9
  161. package/std/sys/time.yo +5 -15
  162. package/std/sys/timer.yo +6 -11
  163. package/std/sys/tty.yo +10 -18
  164. package/std/sys/udp.yo +22 -39
  165. package/std/sys/umask.yo +3 -6
  166. package/std/sys/unix.yo +33 -52
  167. package/std/testing/bench.yo +49 -52
  168. package/std/thread.yo +10 -15
  169. package/std/time/datetime.yo +105 -89
  170. package/std/time/duration.yo +43 -56
  171. package/std/time/instant.yo +13 -18
  172. package/std/time/sleep.yo +5 -9
  173. package/std/url/index.yo +184 -209
  174. package/std/worker.yo +6 -10
@@ -13,7 +13,7 @@ These are baseline syntax rules for portable Yo code.
13
13
  ## Common declaration forms
14
14
 
15
15
  ```rust
16
- { println } :: import "std/fmt";
16
+ { println } :: import("std/fmt");
17
17
 
18
18
  app_name :: "yo-demo";
19
19
 
@@ -23,21 +23,22 @@ main :: (fn() -> unit)({
23
23
  println(message);
24
24
  });
25
25
 
26
- export main;
26
+ export(main);
27
27
  ```
28
28
 
29
29
  - Top-level binding: `name :: expr;`
30
30
  - Local binding: `name := expr;`
31
31
  - Typed binding: `(name : Type) = expr;`
32
32
  - Function definition: `name :: (fn(args...) -> ReturnType)(body);`
33
+ - Export: `export(name);`
33
34
 
34
35
  ## Blocks and expressions
35
36
 
36
- | Goal | Write | Avoid |
37
- | ----------------- | -------------------------- | -------------------------- |
38
- | Single expression | `cond(...)` | `{ cond(...) }` |
39
- | Begin block | `{ x := i32(1); x }` | `{ x := i32(1), x }` |
40
- | Struct literal | `{ name: "yo", ok: true }` | `{ name: "yo"; ok: true }` |
37
+ | Goal | Write | Avoid |
38
+ | ----------------- | ---------------------------- | ---------------------------- |
39
+ | Single expression | `cond(...)` | `{ cond(...) }` |
40
+ | Begin block | `{ x := i32(1); x }` | `{ x := i32(1), x }` |
41
+ | Struct literal | `{ name : "yo", ok : true }` | `{ name : "yo"; ok : true }` |
41
42
 
42
43
  ```rust
43
44
  result := cond(
@@ -53,6 +54,8 @@ total := {
53
54
 
54
55
  Remember: `{ expr }` without semicolons is a struct literal, not a block. The parser now detects this mistake and emits a clear error if the single expression is not a valid struct field.
55
56
 
57
+ In struct literals, keep spaces around `:` and parenthesize infix field values: `{ x : (1 + 2), y : 3 }`, not `{ x: 1 + 2, y: 3 }`.
58
+
56
59
  ## Control flow
57
60
 
58
61
  ```rust
@@ -74,6 +77,18 @@ if(done, println("done"), println("pending"));
74
77
  - Always write `cond(...)`, never bare `cond ...`
75
78
  - Always write `match(...)`, never bare `match ...`
76
79
  - `if(a, b)` and `if(a, b, c)` are macro forms over `cond`
80
+ - Write `return(value)` or `return()`; `return value` is invalid.
81
+ - Write `escape(value)` or `escape()`; `escape value` is invalid.
82
+ - If a `match`/`cond` branch returns an enum variant and inference fails, qualify
83
+ the variant with its enum type: `TypeValue.Unit` instead of `.Unit`.
84
+ - Do not match enum payload literals directly, e.g. avoid `.Some(false)` and
85
+ `.Some(true)` as sibling branches. Match `.Some(value)` once, then branch with
86
+ `if(value, ...)` or `cond(...)` inside the arm; otherwise generated C can
87
+ contain duplicate enum `case` labels.
88
+ - In large enum matches, avoid binding a pattern variable with the same name as a
89
+ variant field (for example, prefer `struct_field_types` over `field_types`).
90
+ This can currently produce invalid generated C in some self-hosted codegen
91
+ paths.
77
92
 
78
93
  ## String types
79
94
 
@@ -101,10 +116,18 @@ flag := ((a > b) && (b > c));
101
116
  masked := ((A | B) | C);
102
117
  ```
103
118
 
104
- - Prefer parenthesized calls: `func(arg1, arg2)`
105
- - `func (a, b)` is a different parse shape than `func(a, b)`
119
+ - Calls require immediate parentheses: `func(arg1, arg2)`
120
+ - `func arg1, arg2` and `func (arg1, arg2)` are invalid
106
121
  - Yo has no operator precedence; fully parenthesize binary expressions
107
- - **All unary operators (`!`, `&`, `-`, `~`) greedily consume everything that follows, including comma-separated args.** `func(&s, a, b)` is parsed as `func(&(s, a, b))` — ONE tuple argument! Preferred fix: parenthesize the operand: `func(&(s), a, b)`. The outer-parens form `func((&s), a, b)` also works. Either is fine; the operand-parens form `&(x)` matches how the parser thinks about it.
122
+ - Preserve grouping around infix expressions on operator RHS positions: `true => (x / y)`, `value := (x + y)`, `(ptr &+ 1).*`
123
+ - Line breaks can disambiguate operator chains; keep line-leading operators like `(4\n| 5\n| 6)` and newlines after `:` before a lambda unless you add equivalent grouping
124
+ - When an operator ends a line, indent its RHS one level as a continuation: `(given(x) : T) =\n (v) -> { ... }`
125
+ - Prefix operators (`!`, `&`, `-`, `~`) require parenthesized operands: `func(&(s), a, b)`, `!(ready)`, `-(value)`.
126
+ - Tight special forms also require immediate parentheses: `#(expr)`, `?*(u8)`, `T <: !(Runtime)`
127
+ - Dynamic field access with unquote must keep grouping after the dot: `value.(#(field_expr))`, not `value.#(field_expr)`.
128
+ - Unquote splicing is the tight operator `...#(exprs)`; do not insert a space between `...` and `#`.
129
+ - Canonical pointer dereference is `ptr.*`; formatter should canonicalize legacy `ptr.(*)` to `ptr.*`.
130
+ - Keep single-line array and tuple literals compact during formatting: `[1, 2, 3]`, `(1, 2, 3)`.
108
131
  - Parenthesize other unary operands too: `!(ready)`, `-(value)`
109
132
  - **`!x && y` is parsed as `!(x && y)`**, not `(!x) && y`. Prefix `!` greedily consumes the full right-hand expression. To get `(!x) && y`, write `((!x) && y)` with explicit inner parens.
110
133
  - **Nested `&&` / `||` in a single compound condition causes "Ambiguous operator precedence"** even with explicit parentheses: `((A && B) && (C && D))` on one line triggers the error. Fix: extract sub-conditions into named booleans first: `_c1 := (A && B); _c2 := (C && D); if((_c1 && _c2), ...)`.
@@ -127,6 +150,8 @@ impl(Counter,
127
150
  ```
128
151
 
129
152
  - No space between a function type and its body: `(fn(...) -> T)(...)`
153
+ - Top-level aliases for function types need parentheses too:
154
+ `Callback :: (fn(x : i32) -> i32);`, not `Callback :: fn(x : i32) -> i32;`
130
155
  - Use `Self` in method signatures and in type definitions for recursive references (the type name is not available during its own definition)
131
156
  - `Self` also works inside generic type constructors — it refers to the current instantiation (e.g., `Tree(T)` inside `Tree`). Use `recur(args)` only when type arguments differ from the current instantiation.
132
157
  - Use `struct(...)` for record and effect-record types. The legacy `module(...)`,
@@ -169,7 +194,7 @@ safe_divide :: (fn(x : i32, y : i32, using(raise : Raise)) -> i32)(
169
194
 
170
195
  caller :: (fn() -> i32)({
171
196
  (given(raise) : Raise) = (fn(msg : String) -> i32)({
172
- return i32(0);
197
+ return(i32(0));
173
198
  });
174
199
 
175
200
  safe_divide(i32(10), i32(0))
@@ -189,9 +214,9 @@ caller :: (fn() -> i32)({
189
214
  result := closure(i32(5));
190
215
 
191
216
  transform :: (fn(list : ArrayList(i32), f : Impl(Fn(x : i32) -> i32)) -> unit)({
192
- for list.iter(), (ptr) => {
217
+ for(list.iter(), (ptr) => {
193
218
  ptr.* = f(ptr.*);
194
- };
219
+ });
195
220
  });
196
221
  ```
197
222
 
@@ -203,15 +228,15 @@ transform :: (fn(list : ArrayList(i32), f : Impl(Fn(x : i32) -> i32)) -> unit)({
203
228
  ## Imports and modules
204
229
 
205
230
  ```rust
206
- { Parser } :: import "./parser.yo";
207
- parser_module :: import "./parser.yo";
231
+ { Parser } :: import("./parser.yo");
232
+ parser_module :: import("./parser.yo");
208
233
 
209
- open import "std/string";
210
- { ArrayList } :: import "std/collections/array_list";
234
+ open(import("std/string"));
235
+ { ArrayList } :: import("std/collections/array_list");
211
236
  ```
212
237
 
213
238
  - Use relative imports for nearby `.yo` files
214
- - Use `open import "std/module"` for standard-library modules you want fully in scope
239
+ - Use `open(import("std/module"))` for standard-library modules you want fully in scope
215
240
  - Do not write `import "./file.yo" as name`
216
241
  - Do not import `std/prelude`
217
242
 
@@ -237,15 +262,28 @@ text := match(value,
237
262
  Three destructuring shapes for arms (mix freely across arms):
238
263
 
239
264
  ```rust
240
- Shape :: enum(Circle(radius : i32), Rectangle(width : i32, height : i32));
265
+ Shape :: enum(
266
+ Circle(radius : i32),
267
+ Rectangle(width : i32, height : i32),
268
+ Triangle(base : i32, height : i32, label : str)
269
+ );
241
270
 
242
271
  match(s,
243
- .Circle(r) => (r * r), // positional
244
- .Rectangle(width: w, height: h) => (w * h), // labeled
245
- .Rectangle({width, height: h}) => (width * h) // curly shorthand
272
+ // Preferred curly shorthand names only the fields you use.
273
+ .Triangle({base, height: h}) => (base * h),
274
+
275
+ // Also OK — labeled (label : var) pairs; order-free, partial matches OK.
276
+ .Circle(radius: r) => (r * r),
277
+
278
+ // ⚠️ Avoid for 2+ field variants — positional with `_` is brittle when
279
+ // a field is added and harder to read (each `_` requires counting).
280
+ // OK when the variant has one field, or when every field is named.
281
+ .Rectangle(w, h) => (w * h)
246
282
  )
247
283
  ```
248
284
 
285
+ **Preferred form**: `.Variant({label, label: alias})`. Names only the fields the arm binds, so adding a field to the variant later doesn't silently break every arm. `tests/match_curly.test.yo` is the spec.
286
+
249
287
  Curly `{a, b: c}` is sugar for `(a: a, b: c)` — order-free, supports partial matches (omit fields). Use `{label: _}` to ignore a specific field. Bare `{_}` and empty `{}` are rejected.
250
288
 
251
289
  > **Critical**: Within a single match arm, you must use **either all positional or all named** field patterns. Mixing positional and named fields in the same arm (e.g., `.Foo(x, y: z, w)`) causes C codegen to emit undeclared identifiers for the named fields. This is a parser/codegen limitation — do not mix.
@@ -273,17 +311,17 @@ show :: (fn(forall(T : Type), value : T, where(T <: ToString)) -> unit)(
273
311
 
274
312
  ```rust
275
313
  main :: (fn() -> unit)(());
276
- export main;
314
+ export(main);
277
315
 
278
- export
316
+ export(
279
317
  helper,
280
318
  Config
281
- ;
319
+ );
282
320
  ```
283
321
 
284
- - `export name;` exports a single binding
322
+ - `export(name);` exports a single binding
285
323
  - Block form exports multiple bindings separated by commas
286
- - Every executable needs `export main;`
324
+ - Every executable needs `export(main);`
287
325
 
288
326
  ## Static and dynamic dispatch types
289
327
 
@@ -321,15 +359,15 @@ factorial :: (fn(n : i32) -> i32)(
321
359
  )
322
360
  );
323
361
 
324
- // Runtime infinite loop — `while cond` is ALWAYS runtime
325
- while true, {
362
+ // Runtime infinite loop — `while(cond, body)` is ALWAYS runtime
363
+ while(true, {
326
364
  work();
327
- };
365
+ });
328
366
 
329
367
  // Compile-time loop unrolling — requires comptime() modifier
330
- while comptime(i < 10), {
368
+ while(comptime((i < 10)), {
331
369
  // body evaluated/unrolled at compile time
332
- };
370
+ });
333
371
 
334
372
  // for loop — 2-arg prelude macro; first arg MUST be an iterator (has .next()):
335
373
  for(list.iter(), x => { // ArrayList, array → call .iter() first
@@ -346,8 +384,8 @@ for(list.iter(), (ptr) => { // ptr is a mutable reference to each element
346
384
  ```
347
385
 
348
386
  - Use `recur(...)` for self-recursion
349
- - `while cond` is **always a runtime loop** — use this for open-ended loops (e.g., server accept loops, event loops)
350
- - `while comptime(cond)` explicitly unrolls at compile time — `cond` must be a compile-time-known value
387
+ - `while(cond, body)` is **always a runtime loop** — use this for open-ended loops (e.g., server accept loops, event loops)
388
+ - `while(comptime(cond), body)` explicitly unrolls at compile time — `cond` must be a compile-time-known value
351
389
  - Using a comptime-only (`::`) variable in a bare `while` condition without `comptime()` is a **compile error** (would be an infinite loop at runtime)
352
390
  - **`for(arr, item => { body })`** — correct 2-arg prelude macro form. The `item => { body }` is an anonymous closure.
353
391
  - **Do NOT use `for(x, arr, { body })`** — this older 3-arg form is an evaluator-internal representation, not valid top-level Yo syntax. (The self-hosted evaluator currently only understands the 3-arg form in its internal for-loop handler; track issue: `issues/eval-for-loop-3arg-vs-2arg.md`)
@@ -355,19 +393,19 @@ for(list.iter(), (ptr) => { // ptr is a mutable reference to each element
355
393
  ## Return and branch safety
356
394
 
357
395
  ```rust
358
- // WRONG — return consumes the comma, capturing the next match branch:
396
+ // WRONG — paren-less return is invalid:
359
397
  match(opt,
360
- .Some(v) => return v, // parsed as return(v, .None => ...)
398
+ .Some(v) => return v,
361
399
  .None => default_value()
362
400
  );
363
401
 
364
- // CORRECT — begin blocks isolate return from the comma:
402
+ // CORRECT — explicit return calls in begin blocks:
365
403
  match(opt,
366
404
  .Some(v) => {
367
- return v;
405
+ return(v);
368
406
  },
369
407
  .None => {
370
- return default_value();
408
+ return(default_value());
371
409
  }
372
410
  );
373
411
 
@@ -380,11 +418,11 @@ get_value :: (fn(opt : Option(i32)) -> i32)(
380
418
  );
381
419
  ```
382
420
 
383
- - `return expr1, expr2` parses as a single function call: `return(expr1, expr2)`
421
+ - `return expr` is invalid; write `return(expr)` or `return()` for unit
384
422
  - In `cond` or `match` branches, **always use begin blocks** when you need `return`
385
- - `return` must be the **last expression** in a begin block — dead code after `return` is rejected. Do NOT write `{ return x; fallback_val }`. Write `{ return x; }` only.
423
+ - `return(...)` must be the **last expression** in a begin block — dead code after `return(...)` is rejected. Do NOT write `{ return(x); fallback_val }`. Write `{ return(x); }` only.
386
424
  - If the whole function is one expression, prefer expression-bodied style and skip `return` entirely
387
- - The same trap applies to any function call without parens in match branches
425
+ - The same rule applies to all calls in match branches: use immediate `(...)`
388
426
 
389
427
  ## String concatenation pitfall
390
428
 
@@ -406,43 +444,43 @@ content := String.from("line1\nline2\n");
406
444
  ## Iterator and for loop
407
445
 
408
446
  ```rust
409
- { ArrayList } :: import "std/collections/array_list";
447
+ { ArrayList } :: import("std/collections/array_list");
410
448
 
411
449
  list := ArrayList(i32).new();
412
450
  list.push(i32(10));
413
451
  list.push(i32(20));
414
452
 
415
- for list.iter(), (ptr) => {
453
+ for(list.iter(), (ptr) => {
416
454
  println(ptr.*);
417
- };
455
+ });
418
456
 
419
- for list.into_iter(), (value) => {
457
+ for(list.into_iter(), (value) => {
420
458
  println(value);
421
- };
459
+ });
422
460
  ```
423
461
 
424
- - `for collection, (variable) => { body }` iterates via the `Iterator` trait
462
+ - `for(collection, (variable) => { body })` iterates via the `Iterator` trait
425
463
  - `.iter()` borrows the collection and yields pointers
426
464
  - `.into_iter()` takes ownership and yields values
427
465
 
428
466
  ## Testing
429
467
 
430
468
  ```rust
431
- test "Addition works", {
469
+ test("Addition works", {
432
470
  assert(((i32(1) + i32(1)) == i32(2)), "1+1 should be 2");
433
- };
471
+ });
434
472
 
435
- test "Compile-time check", {
473
+ test("Compile-time check", {
436
474
  comptime_assert((2 + 2) == 4);
437
475
  comptime_expect_error({ x :: (1 / 0); });
438
- };
476
+ });
439
477
 
440
- test "Async test", {
478
+ test("Async test", {
441
479
  io.await(yield());
442
- };
480
+ });
443
481
  ```
444
482
 
445
- - `test "description", { body }` defines a test — `io : IO` is automatically available
483
+ - `test("description", { body })` defines a test — `io : IO` is automatically available
446
484
  - All tests can use `io.async(...)`, `io.await(...)`, etc. without a `using` clause
447
485
  - `assert(condition, "message")` — runtime assertion (always include a message)
448
486
  - `comptime_assert(condition)` — compile-time assertion
@@ -540,6 +578,23 @@ Variable :: object(name : String, type : TypeValue);
540
578
  Variable :: object(name : String, ty : TypeValue);
541
579
  ```
542
580
 
581
+ ### 1-element array literals require a trailing comma
582
+
583
+ `[expr]` without a trailing comma is **parsed as a Slice type** `Slice(expr)`, not an array literal. To create a 1-element array value, add a trailing comma:
584
+
585
+ ```rust
586
+ // WRONG — parsed as Slice type, not array literal:
587
+ arr := [i32(42)];
588
+
589
+ // CORRECT — trailing comma makes it an array literal:
590
+ arr := [i32(42),];
591
+
592
+ // Multi-element arrays work fine (comma separator detected):
593
+ arr2 := [i32(1), i32(2), i32(3)]; // ✓
594
+ ```
595
+
596
+ This also applies inside source strings in proto-evaluator tests.
597
+
543
598
  ### ArrayList indexing uses call syntax
544
599
 
545
600
  ```rust
@@ -671,7 +726,7 @@ if((!cond), { do_thing(); });
671
726
 
672
727
  ### `escape` requires a nested-function context
673
728
 
674
- `escape value` exits the **enclosing function** — the nearest `fn(...)` that
729
+ `escape(value)` exits the **enclosing function** — the nearest `fn(...)` that
675
730
  wraps the current code. It requires that the code is inside a nested function
676
731
  (e.g., a closure or `given` handler lambda), NOT at the top level of a
677
732
  standalone function definition.
@@ -679,20 +734,27 @@ standalone function definition.
679
734
  ```rust
680
735
  // CORRECT — escape inside a given handler lambda (lambda has enclosing fn):
681
736
  given(exn) := Exception(throw: ((err) -> {
682
- escape result // exits the outer function that contains this given()
737
+ escape(result); // exits the outer function that contains this given()
683
738
  }));
684
739
  do_something(using(exn));
685
740
 
741
+ // CORRECT — even after process-exit helpers, satisfy the handler's resume type:
742
+ given(exn2) := Exception(throw: ((err) -> {
743
+ eprintln(err.to_string());
744
+ exit(int(1));
745
+ escape(); // required because exit() returns unit, not the handler ResumeType
746
+ }));
747
+
686
748
  // CORRECT — escape inside a closure passed as argument:
687
749
  result := match(opt, .Some(x) => x, .None => {
688
750
  // This is NOT a nested function — use return:
689
- // WRONG: escape default_val // ERROR: no enclosing fn
690
- return default_val // CORRECT: return exits the enclosing fn directly
751
+ // WRONG: escape(default_val) // ERROR: no enclosing fn
752
+ return(default_val); // CORRECT: return exits the enclosing fn directly
691
753
  });
692
754
 
693
755
  // WRONG — escape inside a match arm (not a nested fn):
694
756
  match(opt,
695
- .Some(x) => { escape x } // ERROR: "can only be used inside a function that has an enclosing function"
757
+ .Some(x) => { escape(x); } // ERROR: "can only be used inside a function that has an enclosing function"
696
758
  );
697
759
  ```
698
760
 
@@ -923,16 +985,16 @@ if (((is_tuple_type(ty) || is_struct_type(ty)) || is_union_type(ty)), ...)
923
985
 
924
986
  ### Duplicate imports from the same path must be merged
925
987
 
926
- Having two `:: import "path"` lines importing from the same file causes a compile error.
988
+ Having two `:: import("path")` lines importing from the same file causes a compile error.
927
989
  Always merge them into a single destructuring import:
928
990
 
929
991
  ```rust
930
992
  // ❌ Two imports from the same path
931
- { Foo } :: import "../../mod.yo";
932
- { Bar } :: import "../../mod.yo";
993
+ { Foo } :: import("../../mod.yo");
994
+ { Bar } :: import("../../mod.yo");
933
995
 
934
996
  // ✅ Merged
935
- { Foo, Bar } :: import "../../mod.yo";
997
+ { Foo, Bar } :: import("../../mod.yo");
936
998
  ```
937
999
 
938
1000
  ### Implicit (`using`) parameters cannot be used with `:=` assignment
@@ -1023,3 +1085,102 @@ match(outer_val,
1023
1085
  .None => { fallback() } // ← outer .None arm
1024
1086
  ) // ← closes outer match
1025
1087
  ```
1088
+
1089
+ ### Nested enum patterns in match are NOT supported
1090
+
1091
+ Yo does **not** support nested enum patterns inside a single match arm.
1092
+ You cannot write `.Some(.IntLit(n))` — this is a parser error.
1093
+
1094
+ ```rust
1095
+ // ❌ WRONG — nested enum pattern, parser error:
1096
+ match(v.get(usize(0)),
1097
+ .Some(.IntLit(n)) => assert(n.as_str() == "3", "ok"),
1098
+ _ => assert(false, "err")
1099
+ )
1100
+
1101
+ // ✅ CORRECT — two-level match:
1102
+ match(v.get(usize(0)),
1103
+ .Some(x) => match(x, .IntLit(n) => assert(n.as_str() == "3", "ok"), _ => assert(false, "err")),
1104
+ .None => assert(false, "err")
1105
+ )
1106
+ ```
1107
+
1108
+ This applies to ALL nested enum patterns: `.Some(.BoolVal(b))`, `.Some(.ArrayVal(arr))`, etc. — always use a two-level match.
1109
+
1110
+ ### `get_callee()` returns ExprVal directly, not an Option-wrapped EnumVal
1111
+
1112
+ In the proto-evaluator source strings (`evaluate_module_body`), `ExprVal.get_callee()` on a FnCall returns the callee `ExprVal` directly — NOT wrapped in an `Option` EnumVal. Chaining `.is_some()` fails with SIGABRT because `is_some()` requires an `EnumVal` receiver.
1113
+
1114
+ ```rust
1115
+ // ❌ SIGABRT — get_callee() returns ExprVal, not Option(EnumVal)
1116
+ result := quote(foo(i64(1))).get_callee().is_some();
1117
+
1118
+ // ✅ Chain .is_atom() or .is_fn_call() on the returned ExprVal
1119
+ result := quote(foo(i64(1))).get_callee().is_atom(); // true: callee "foo" is an atom
1120
+ result := quote(foo(i64(1))).get_callee().is_fn_call(); // false: callee "foo" is not a fn call
1121
+ ```
1122
+
1123
+ Similarly, calling `get_callee()` on an Atom causes the overall evaluation to fail — do not test the Atom case via `get_callee()` in source strings.
1124
+
1125
+ ### Source-string evaluation pitfalls (proto-evaluator tests)
1126
+
1127
+ When writing source strings passed to `evaluate_module_body` in proto-evaluator tests:
1128
+
1129
+ **`cond` form**: Always use the `cond(condition => value, true => fallback)` form, NOT `cond(condition, value, fallback)`. The 3-arg form does NOT work inside lambdas or recursive functions in source strings.
1130
+
1131
+ ```
1132
+ // ❌ WRONG — crashes inside lambdas and recursive functions
1133
+ cond((n <= i32(1)), i32(1), (n * recur((n - i32(1)))))
1134
+
1135
+ // ✅ CORRECT
1136
+ cond((n <= i32(1)) => i32(1), true => (n * recur((n - i32(1)))))
1137
+ ```
1138
+
1139
+ **Recursive functions**: Use `recur(...)` for self-recursion inside named `::` functions. Never call the function by name from inside its own body.
1140
+
1141
+ **Chaining function calls with operators**: `f(a) + f(b) + f(c)` throws an exception. Use fold over an array instead:
1142
+
1143
+ ```
1144
+ // ❌ WRONG — exception in source strings
1145
+ result := abs_val(i32(3)) + abs_val(i32(1)) + abs_val(i32(4));
1146
+
1147
+ // ✅ CORRECT
1148
+ arr := [i32(3), i32(1), i32(4)];
1149
+ result := arr.fold(i32(0), (fn(acc : i32, x : i32) -> i32)((acc + abs_val(x))));
1150
+ ```
1151
+
1152
+ **Empty array `[]` in cond branches**: `cond(condition => [x], true => [])` crashes because the empty array type is unknown. Avoid empty array literals in conditional branches inside `flat_map` lambdas.
1153
+
1154
+ **Option types**: Must use `Option(T).Some(val)` not `Option.Some(val)`. `Option(T).None` with a type annotation crashes — use `r := Option(i32).None` without annotation. `.is_none()` is not supported; use `!(r.is_some())`. `and_then(f)` returns the raw value (not wrapped in Option), so calling `.unwrap_or()` on the result crashes.
1155
+
1156
+ **Number literals**: `i32(-3)` crashes — use `(i32(0) - i32(3))`. `i32.as_usize()` / `usize.as_i32()` not supported.
1157
+
1158
+ **Fibonacci without tmp variable**: `b = (a + b); a = (b - a)` computes fib correctly without a temp variable. After N iterations, `a` holds fib(N) and `b` holds fib(N+1).
1159
+
1160
+ **3-term multiplication in source strings**: `(x * x * x)` causes an exception in evaluated source strings. Break it into a block:
1161
+
1162
+ ```
1163
+ // ❌ WRONG — causes exception
1164
+ cubes := arr.map((fn(x : i32) -> i32)((x * x * x)));
1165
+
1166
+ // ✅ CORRECT — use a block with a local binding
1167
+ cubes := arr.map((fn(x : i32) -> i32)({
1168
+ sq := (x * x);
1169
+ (sq * x)
1170
+ }));
1171
+ ```
1172
+
1173
+ **3-term sum in fold on tuples**: `(acc + p.0 + p.1)` inside a fold lambda on tuple pairs crashes. Always map pairs to scalars first, then fold:
1174
+
1175
+ ```
1176
+ // ❌ WRONG — crashes in fold on (i32, i32) tuples
1177
+ total := pairs.fold(i32(0), (fn(acc : i32, p : (i32, i32)) -> i32)((acc + p.0 + p.1)));
1178
+
1179
+ // ✅ CORRECT — map to scalars first, then fold
1180
+ sums := pairs.map((fn(p : (i32, i32)) -> i32)((p.0 + p.1)));
1181
+ total := sums.fold(i32(0), (fn(acc : i32, x : i32) -> i32)((acc + x)));
1182
+ ```
1183
+
1184
+ **`&&` in `cond` conditions inside `while` body**: Crashes. Avoid by restructuring (e.g., start loop at 1 instead of 0 to eliminate the `&& (i > 0)` guard).
1185
+
1186
+ **Test API format**: Use `evaluate_module_body(exprs, &(env))` (reference syntax, returns `Option`). Match with function-style `match(result, .None => ..., .Some(m) => ...)`. Do NOT use block-style `match(result) { ... }` — it causes a parse error ("Paren-less function and operator calls are not supported").
@@ -16,7 +16,7 @@ Export C-compatible functions that operate on linear memory:
16
16
 
17
17
  ```rust
18
18
  // src/wasm_api.yo
19
- open import "std/string";
19
+ open(import("std/string"));
20
20
 
21
21
  // Allocate WASM memory for the caller
22
22
  wasm_alloc :: (fn(size : usize) -> *(u8))(
@@ -89,7 +89,7 @@ The `Executable` struct accepts: `name`, `root`, `target`, `optimize`, `allocato
89
89
  Emscripten-specific flags go in `add_c_flags(...)` after creating the step.
90
90
 
91
91
  ```rust
92
- build :: import "std/build";
92
+ build :: import("std/build");
93
93
 
94
94
  wasm_api :: build.executable({
95
95
  name: "my_lib_wasm_api",
@@ -242,7 +242,7 @@ cd npm && node -e "const m = require('.'); m.createRenderer().render('hello').th
242
242
  - **Memory leaks**: Always `_wasm_free` every `_wasm_alloc` on the JavaScript side.
243
243
  - **String encoding**: WASM only sees bytes — use `TextEncoder`/`TextDecoder` for UTF-8.
244
244
  - **Errno differences**: WASI errno values differ from POSIX. Use `std/libc/errno` constants.
245
- - **No `main` needed**: Library WASM modules don't need `main` or `export main;` — just export the API functions.
245
+ - **No `main` needed**: Library WASM modules don't need `main` or `export(main);` — just export the API functions.
246
246
  - **Emscripten environment**: Set `-sENVIRONMENT='web,node'` to support both contexts, or `'node'` for Node.js only.
247
247
  - **Module initialization is async**: Emscripten's `createModule()` returns a Promise. Initialize once and reuse.
248
248
  - **WASM memory growth**: Always use `-sALLOW_MEMORY_GROWTH=1` for dynamic allocations.
package/README.md CHANGED
@@ -202,14 +202,14 @@ my-project/
202
202
 
203
203
  `src/main.yo`:
204
204
 
205
- ```typescript
206
- { println } :: import "std/fmt";
205
+ ```rust
206
+ { println } :: import("std/fmt");
207
207
 
208
208
  main :: (fn() -> unit)({
209
209
  println("Hello, world!");
210
210
  });
211
211
 
212
- export main;
212
+ export(main);
213
213
  ```
214
214
 
215
215
  Common build commands:
@@ -220,6 +220,8 @@ $ yo build run # Build and run the executable
220
220
  $ yo build test # Run tests
221
221
  $ yo build --list-steps # List available build steps
222
222
  $ yo build doc # Generate HTML documentation
223
+ $ yo fmt # Format Yo source files
224
+ $ yo fmt --check # Check formatting without writing changes
223
225
  ```
224
226
 
225
227
  ## Prelude
@@ -254,15 +256,15 @@ Check the [./tests](./tests/) and [./std](./std/) folders for more code examples
254
256
 
255
257
  ### Hello World
256
258
 
257
- ```typescript
259
+ ```rust
258
260
  // main.yo
259
- { println } :: import "std/fmt";
261
+ { println } :: import("std/fmt");
260
262
 
261
- main :: (fn() -> unit) {
263
+ main :: (fn() -> unit)({
262
264
  println("Hello, world!");
263
- };
265
+ });
264
266
 
265
- export main;
267
+ export(main);
266
268
 
267
269
  // $ yo compile main.yo --release -o main
268
270
  // $ ./main