@shd101wyy/yo 0.1.22 → 0.1.24
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.
- package/.github/skills/yo-async-effects/async-effects-recipes.md +74 -1
- package/.github/skills/yo-core-patterns/core-patterns-cheatsheet.md +85 -0
- package/.github/skills/yo-syntax/syntax-cheatsheet.md +193 -6
- package/out/cjs/index.cjs +528 -519
- package/out/cjs/yo-cli.cjs +656 -647
- package/out/cjs/yo-lsp.cjs +554 -545
- package/out/esm/index.mjs +467 -458
- package/out/types/src/evaluator/context.d.ts +6 -0
- package/out/types/src/parser.d.ts +1 -0
- package/out/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/std/build.yo +2 -2
- package/std/collections/array_list.yo +26 -0
- package/std/{env/index.yo → env.yo} +11 -11
- package/std/{glob/index.yo → glob.yo} +2 -2
- package/std/imm/map.yo +15 -15
- package/std/imm/sorted_map.yo +14 -14
- package/std/imm/string.yo +4 -4
- package/std/{log/index.yo → log.yo} +3 -3
- package/std/prelude.yo +18 -23
- package/std/process/command.yo +8 -8
- package/std/string/string.yo +76 -55
- package/std/string/string_builder.yo +35 -0
- package/std/string/unicode.yo +6 -6
- package/std/sys/signal.yo +6 -6
|
@@ -26,6 +26,17 @@ pause_then_answer :: (fn(using(io : IO)) -> Impl(Future(i32, IO)))(
|
|
|
26
26
|
|
|
27
27
|
- `io.async(...)` is lazy
|
|
28
28
|
- If a function uses `using(io : IO)` and returns a future, include `IO` in the `Future(...)` type
|
|
29
|
+
- **Type annotations can be omitted** in the lambda's `using` params — the compiler infers types from the `Future(T, IO, Raise, ...)` return type. You can even rename the params:
|
|
30
|
+
|
|
31
|
+
```rust
|
|
32
|
+
// Equivalent — types inferred from Future(i32, IO, Raise) in the return type
|
|
33
|
+
work :: (fn(using(io : IO, raise : Raise)) -> Impl(Future(i32, IO, Raise)))(
|
|
34
|
+
io.async((using(my_io, my_raise)) => { // ← any names, no type annotations needed
|
|
35
|
+
my_io.await(yield());
|
|
36
|
+
my_raise("error")
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
```
|
|
29
40
|
|
|
30
41
|
## Sequential await
|
|
31
42
|
|
|
@@ -128,12 +139,50 @@ work :: (fn(using(io : IO, raise : Raise)) -> Impl(Future(i32, IO, Raise)))(
|
|
|
128
139
|
- Every effect used by the future should appear in the `Future(...)` type
|
|
129
140
|
- Effects propagate through `using(...)` just like other contextual parameters
|
|
130
141
|
|
|
142
|
+
## Async recursion — use an iterative worklist instead
|
|
143
|
+
|
|
144
|
+
`recur` does **not** work inside an `io.async` lambda — it refers to the lambda's own signature, not the outer function, so the argument types will not match. Calling the outer function by name is also forbidden in Yo. Attempting either will produce a compile-time error.
|
|
145
|
+
|
|
146
|
+
**Solution**: replace async recursion with an iterative worklist using `ArrayList` as a stack:
|
|
147
|
+
|
|
148
|
+
```rust
|
|
149
|
+
{ read_dir, DirEntry } :: import "std/fs/dir";
|
|
150
|
+
|
|
151
|
+
process_dir :: (fn(root: Path, using(io: IO, exn: Exception)) -> Impl(Future(unit, IO, Exception)))(
|
|
152
|
+
io.async((using(io, exn)) => {
|
|
153
|
+
stack := ArrayList(Path).new();
|
|
154
|
+
{ stack.push(root); };
|
|
155
|
+
|
|
156
|
+
while runtime((stack.len() > usize(0))), {
|
|
157
|
+
cur := match(stack.pop(), .Some(p) => p, .None => return ());
|
|
158
|
+
entries := io.await(read_dir(cur));
|
|
159
|
+
// process `entries`, push subdirectories to `stack`
|
|
160
|
+
n := entries.len();
|
|
161
|
+
i := usize(0);
|
|
162
|
+
while runtime((i < n)), {
|
|
163
|
+
match(entries.get(i),
|
|
164
|
+
.None => (),
|
|
165
|
+
.Some(e) => {
|
|
166
|
+
match(e.file_type,
|
|
167
|
+
.Directory => { stack.push(cur.join(Path.new(e.name))); },
|
|
168
|
+
_ => () // handle files here
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
i = (i + usize(1));
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
```
|
|
178
|
+
|
|
131
179
|
## Common pitfalls
|
|
132
180
|
|
|
133
181
|
- `io.async(...)` does not run immediately
|
|
134
182
|
- `escape` inside async aborts the future instead of completing it normally
|
|
135
183
|
- `io.await(...)` on an aborted future can panic; `JoinHandle.await(...)` converts abort into `.None`
|
|
136
184
|
- Handler functions cannot capture outer variables like closures; pass required state explicitly
|
|
185
|
+
- **`recur` inside `io.async` calls the lambda, not the outer function** — use an iterative worklist for async recursion
|
|
137
186
|
|
|
138
187
|
## Exception (non-resumable)
|
|
139
188
|
|
|
@@ -176,7 +225,31 @@ export main;
|
|
|
176
225
|
- Handler uses `escape` to discard the continuation and exit the enclosing function
|
|
177
226
|
- Code after the escaped call is never reached
|
|
178
227
|
|
|
179
|
-
|
|
228
|
+
### Swallowing exceptions with a fallback value (return in Exception handler)
|
|
229
|
+
|
|
230
|
+
When an exception is thrown inside an async operation (e.g., `cmd.status()` or `cmd.output()`), you can **swallow the error and resume with a fallback value** by using `return` in the handler (not `escape`). The `ResumeType` is the return type of the operation that would have thrown.
|
|
231
|
+
|
|
232
|
+
```rust
|
|
233
|
+
{ Command, ExitStatus, Output } :: import "std/process/command";
|
|
234
|
+
|
|
235
|
+
// Check if a tool is available — returns false if it throws (e.g., not found)
|
|
236
|
+
given(try_exn) := Exception(throw: ((err) -> {
|
|
237
|
+
return ExitStatus(raw: i32(1)); // resume with "failed" exit status
|
|
238
|
+
}));
|
|
239
|
+
status := io.await(cmd.status(using(io, try_exn)));
|
|
240
|
+
available := status.success(); // false if exception was swallowed
|
|
241
|
+
|
|
242
|
+
// For cmd.output(), resume with a failed Output:
|
|
243
|
+
given(out_exn) := Exception(throw: ((err) -> {
|
|
244
|
+
return Output(status: ExitStatus(raw: i32(1)), stdout: ArrayList(u8).new(), stderr: ArrayList(u8).new());
|
|
245
|
+
}));
|
|
246
|
+
out := io.await(cmd.output(using(io, out_exn)));
|
|
247
|
+
if((!(out.status.success())), { return (); }); // handle failure
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Key: the `return` inside the handler resumes the _effect invocation site_ with the provided value. The calling code then sees the fallback as if the operation returned normally. Use `escape` only when the enclosing function returns `unit` (e.g., test bodies).
|
|
251
|
+
|
|
252
|
+
**`escape T_value` constraint**: `escape T_value` inside an `Exception` handler requires that the enclosing `io.async` closure's return type matches `T_value`. Due to forward type inference, the evaluator may not know the closure's return type at the point where `given` is declared. This causes a "Expected: unit" error when `escape non_unit` is used in a handler declared before the final return expression. Prefer `return fallback_value` (resume) when possible.
|
|
180
253
|
|
|
181
254
|
`ResumableException(ResumeType)` is a module effect for resumable error handling. The handler uses `return` to resume with a recovery value:
|
|
182
255
|
|
|
@@ -33,6 +33,7 @@ Key rules:
|
|
|
33
33
|
- In **runtime** code, `"hello"` is always `str`. Mixing literal and variable branches in `cond`/`match` works fine.
|
|
34
34
|
- In **comptime** functions (return type `comptime(...)`), `"hello"` is `comptime_string`. It does NOT auto-convert to `str`. Use `str.from_raw_parts(*(u8)("..."), usize(N))` if a comptime function needs to return `str`.
|
|
35
35
|
- For `String` constants, prefer `` `hello` `` over `String.from("hello")`.
|
|
36
|
+
- **PITFALL:** Never write `String.from(`hello`)` — backtick strings are already `String`, not `str`. `String.from` takes `str`, so wrapping a backtick in `String.from` causes a type error ("Cannot unify String and str"). Only use `String.from(str_expr)` for actual `str` values.
|
|
36
37
|
|
|
37
38
|
## Import patterns
|
|
38
39
|
|
|
@@ -88,6 +89,22 @@ numbers := ArrayList(i32).new();
|
|
|
88
89
|
numbers.push(i32(1));
|
|
89
90
|
numbers.push(i32(2));
|
|
90
91
|
|
|
92
|
+
// Index via call syntax (Index trait) — returns the value directly:
|
|
93
|
+
first := numbers(usize(0)); // → i32 (value)
|
|
94
|
+
|
|
95
|
+
// Mutate in place — direct assignment syntax:
|
|
96
|
+
numbers(usize(0)) = i32(99);
|
|
97
|
+
|
|
98
|
+
// When you need the pointer explicitly:
|
|
99
|
+
ptr := &(numbers(usize(0))); // → *(i32)
|
|
100
|
+
ptr.* = i32(100);
|
|
101
|
+
|
|
102
|
+
// Safe access:
|
|
103
|
+
match(numbers.get(usize(0)),
|
|
104
|
+
.Some(v) => println(`${v}`),
|
|
105
|
+
.None => ()
|
|
106
|
+
);
|
|
107
|
+
|
|
91
108
|
counts := HashMap(String, i32).new();
|
|
92
109
|
counts.set(`yo`, i32(1));
|
|
93
110
|
```
|
|
@@ -126,6 +143,20 @@ counter.* = (counter.* + i32(1));
|
|
|
126
143
|
- Use `Box(T)` or `box(value)` for owned heap allocation
|
|
127
144
|
- Use `*(T)` for raw pointers
|
|
128
145
|
- Model nullable pointers as `Option(*(T))` or `?*(T)`, not sentinel integers
|
|
146
|
+
- Constructor syntax: `Box(T)(value)` — NOT `Box(T).new(value)`
|
|
147
|
+
- For self-referential `object` types, use `Box(Self)` to break the recursive cycle:
|
|
148
|
+
|
|
149
|
+
```rust
|
|
150
|
+
Node :: object(
|
|
151
|
+
value : i32,
|
|
152
|
+
next : Option(Box(Self)) // Box(Self) breaks the recursive type cycle
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
n := Node(value: i32(1), next: Option(Box(Node)).None);
|
|
156
|
+
// Constructing a Box:
|
|
157
|
+
child := Box(Node)(Node(value: i32(2), next: Option(Box(Node)).None));
|
|
158
|
+
parent := Node(value: i32(1), next: Option(Box(Node)).Some(child));
|
|
159
|
+
```
|
|
129
160
|
|
|
130
161
|
## Unicode and platform checks
|
|
131
162
|
|
|
@@ -225,6 +256,60 @@ println(p1.to_string());
|
|
|
225
256
|
- Works for both structs and enums
|
|
226
257
|
- Custom derives can be registered with `derive_rule`; see [DERIVE_TRAITS.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/DERIVE_TRAITS.md)
|
|
227
258
|
|
|
259
|
+
### ⚠️ Circular derive trap: recursive enum with `ArrayList`
|
|
260
|
+
|
|
261
|
+
`derive(T, Eq)` and `derive(T, Clone)` fail when any field's type requires the derived trait to be already registered:
|
|
262
|
+
|
|
263
|
+
```rust
|
|
264
|
+
// PROBLEM: derive expansion generates `fields_l == fields_r` (ArrayList(Node) needs
|
|
265
|
+
// Eq(Node)), but Eq(Node) isn't registered yet — compile error.
|
|
266
|
+
Node :: enum(Leaf, Branch(children : ArrayList(Self)));
|
|
267
|
+
derive(Node, Eq); // ← ERROR: "No matching call found for __lhs_children == __rhs_children"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Fix**: skip `derive`, write a manual recursive equality function with `recur`:
|
|
271
|
+
|
|
272
|
+
```rust
|
|
273
|
+
node_eq :: (fn(a : Node, b : Node) -> bool)(
|
|
274
|
+
match(a,
|
|
275
|
+
.Leaf => match(b, .Leaf => true, _ => false),
|
|
276
|
+
.Branch(acs) =>
|
|
277
|
+
match(b,
|
|
278
|
+
.Branch(bcs) => {
|
|
279
|
+
cond(
|
|
280
|
+
(acs.len() != bcs.len()) => false,
|
|
281
|
+
true => {
|
|
282
|
+
(i : usize) = usize(0);
|
|
283
|
+
(ok : bool) = true;
|
|
284
|
+
while runtime(((i < acs.len()) && ok)), {
|
|
285
|
+
match(acs.get(i),
|
|
286
|
+
.Some(ac) => match(bcs.get(i),
|
|
287
|
+
.Some(bc) => { ok = recur(ac, bc); },
|
|
288
|
+
.None => { ok = false; }
|
|
289
|
+
),
|
|
290
|
+
.None => { ok = false; }
|
|
291
|
+
);
|
|
292
|
+
i = (i + usize(1));
|
|
293
|
+
};
|
|
294
|
+
ok
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
},
|
|
298
|
+
_ => false
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
impl(Node, Eq(Node)(
|
|
304
|
+
(==) : (fn(a : Self, b : Self) -> bool)(node_eq(a, b))
|
|
305
|
+
));
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Same issue applies to `Clone` when fields contain `ArrayList(Self)`.
|
|
309
|
+
Yo's reference counting handles shallow copies automatically (no `Clone` trait call needed);
|
|
310
|
+
the `Clone` trait is only for deep cloning and has the same circularity problem.
|
|
311
|
+
In practice, passing `EvalValue`-like types by value works fine without a `Clone` impl.
|
|
312
|
+
|
|
228
313
|
## Error handling
|
|
229
314
|
|
|
230
315
|
```rust
|
|
@@ -51,7 +51,7 @@ total := {
|
|
|
51
51
|
};
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
Remember: `{ expr }` without semicolons is a struct literal, not a block.
|
|
54
|
+
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
55
|
|
|
56
56
|
## Control flow
|
|
57
57
|
|
|
@@ -90,6 +90,7 @@ Key rules:
|
|
|
90
90
|
- In **runtime** code, `"hello"` is `str`. Mixing literals and variables in `cond`/`match` branches is fine.
|
|
91
91
|
- In **comptime** functions (return type `comptime(...)`), `"hello"` is `comptime_string` — it does NOT auto-convert to `str`.
|
|
92
92
|
- For `String` constants, prefer `` `hello` `` over `String.from("hello")`.
|
|
93
|
+
- **`assert` takes `str`, not `String`**: `assert(cond, "message")` — always use `""`. Passing a template string `` `...` `` causes a type mismatch. Use a custom `check_str` helper when you need `String` diagnostics.
|
|
93
94
|
|
|
94
95
|
## Calls, operators, and whitespace
|
|
95
96
|
|
|
@@ -102,7 +103,9 @@ masked := ((A | B) | C);
|
|
|
102
103
|
- Prefer parenthesized calls: `func(arg1, arg2)`
|
|
103
104
|
- `func (a, b)` is a different parse shape than `func(a, b)`
|
|
104
105
|
- Yo has no operator precedence; fully parenthesize binary expressions
|
|
105
|
-
-
|
|
106
|
+
- **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! Always wrap: `p := &s; func(p, a, b)` or use `func((&s), a, b)`.
|
|
107
|
+
- Parenthesize other unary operands too: `!(ready)`, `-(value)`
|
|
108
|
+
- **`!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.
|
|
106
109
|
|
|
107
110
|
## Functions and methods
|
|
108
111
|
|
|
@@ -125,6 +128,8 @@ impl(Counter,
|
|
|
125
128
|
- Use `Self` in method signatures and in type definitions for recursive references (the type name is not available during its own definition)
|
|
126
129
|
- `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.
|
|
127
130
|
- Wrap `fn` types in parentheses when they appear after `:`
|
|
131
|
+
- **Forward references between methods in the same `impl` block are supported.** A method defined later in the block can be called by a method defined earlier. Both `self.method()` and `Self.method(...)` dispatch work. Only the canonical `name : (fn(...) -> R)(body)` method shape participates; bare lambdas do not get forward-ref shells.
|
|
132
|
+
- **Module-level `::` function definitions are processed in order.** A function body that calls another function declared later in the same file will fail with "Variable not found". Always define leaf helpers first (bottom-up order): `eval_identifier` → `eval_atom` → `evaluate`.
|
|
128
133
|
|
|
129
134
|
### Named arguments and default values
|
|
130
135
|
|
|
@@ -222,6 +227,20 @@ text := match(value,
|
|
|
222
227
|
- Construction and match branches use the leading `.`
|
|
223
228
|
- Nested destructuring is not supported; match one layer at a time
|
|
224
229
|
|
|
230
|
+
Three destructuring shapes for arms (mix freely across arms):
|
|
231
|
+
|
|
232
|
+
```rust
|
|
233
|
+
Shape :: enum(Circle(radius : i32), Rectangle(width : i32, height : i32));
|
|
234
|
+
|
|
235
|
+
match(s,
|
|
236
|
+
.Circle(r) => (r * r), // positional
|
|
237
|
+
.Rectangle(width: w, height: h) => (w * h), // labeled
|
|
238
|
+
.Rectangle({width, height: h}) => (width * h) // curly shorthand
|
|
239
|
+
)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
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.
|
|
243
|
+
|
|
225
244
|
## Generics and compile-time
|
|
226
245
|
|
|
227
246
|
```rust
|
|
@@ -293,14 +312,21 @@ factorial :: (fn(n : i32) -> i32)(
|
|
|
293
312
|
)
|
|
294
313
|
);
|
|
295
314
|
|
|
296
|
-
while runtime
|
|
315
|
+
// Runtime infinite loop — `while cond` is ALWAYS runtime
|
|
316
|
+
while true, {
|
|
297
317
|
work();
|
|
298
318
|
};
|
|
319
|
+
|
|
320
|
+
// Compile-time loop unrolling — requires comptime() modifier
|
|
321
|
+
while comptime(i < 10), {
|
|
322
|
+
// body evaluated/unrolled at compile time
|
|
323
|
+
};
|
|
299
324
|
```
|
|
300
325
|
|
|
301
326
|
- Use `recur(...)` for self-recursion
|
|
302
|
-
- `while
|
|
303
|
-
-
|
|
327
|
+
- `while cond` is **always a runtime loop** — use this for open-ended loops (e.g., server accept loops, event loops)
|
|
328
|
+
- `while comptime(cond)` explicitly unrolls at compile time — `cond` must be a compile-time-known value
|
|
329
|
+
- Using a comptime-only (`::`) variable in a bare `while` condition without `comptime()` is a **compile error** (would be an infinite loop at runtime)
|
|
304
330
|
|
|
305
331
|
## Return and branch safety
|
|
306
332
|
|
|
@@ -332,9 +358,27 @@ get_value :: (fn(opt : Option(i32)) -> i32)(
|
|
|
332
358
|
|
|
333
359
|
- `return expr1, expr2` parses as a single function call: `return(expr1, expr2)`
|
|
334
360
|
- In `cond` or `match` branches, **always use begin blocks** when you need `return`
|
|
361
|
+
- `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.
|
|
335
362
|
- If the whole function is one expression, prefer expression-bodied style and skip `return` entirely
|
|
336
363
|
- The same trap applies to any function call without parens in match branches
|
|
337
364
|
|
|
365
|
+
## String concatenation pitfall
|
|
366
|
+
|
|
367
|
+
```rust
|
|
368
|
+
// WRONG — str + str causes "comptime_string vs str" type unification error:
|
|
369
|
+
content := String.from("line1\n" + "line2\n");
|
|
370
|
+
|
|
371
|
+
// CORRECT — use .concat() on String objects:
|
|
372
|
+
content := String.from("line1\n").concat(String.from("line2\n"));
|
|
373
|
+
|
|
374
|
+
// Also CORRECT — single long string literal:
|
|
375
|
+
content := String.from("line1\nline2\n");
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
- `"hello" + "world"` at runtime uses `+` on `str` values, which can cause type mismatches
|
|
379
|
+
- The `str + str` operator can produce a `comptime_string` in some contexts, which is not always compatible with `str`
|
|
380
|
+
- Prefer `.concat()` method on `String` objects when building multi-part strings at runtime
|
|
381
|
+
|
|
338
382
|
## Iterator and for loop
|
|
339
383
|
|
|
340
384
|
```rust
|
|
@@ -380,7 +424,150 @@ test "Async test", {
|
|
|
380
424
|
- `comptime_assert(condition)` — compile-time assertion
|
|
381
425
|
- `comptime_expect_error(expr)` — verify code produces a compile error
|
|
382
426
|
|
|
383
|
-
##
|
|
427
|
+
## Common pitfalls
|
|
428
|
+
|
|
429
|
+
### `impl(...)` requires a trailing semicolon
|
|
430
|
+
|
|
431
|
+
```rust
|
|
432
|
+
// WRONG — "Invalid function call on type" at runtime:
|
|
433
|
+
impl(MyType,
|
|
434
|
+
get : (fn(self : Self) -> i32)(self.x)
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
// CORRECT:
|
|
438
|
+
impl(MyType,
|
|
439
|
+
get : (fn(self : Self) -> i32)(self.x)
|
|
440
|
+
);
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### `___` discard variable cannot appear twice in the same scope
|
|
444
|
+
|
|
445
|
+
```rust
|
|
446
|
+
// WRONG — shadowing of ___ is not allowed:
|
|
447
|
+
___ := foo();
|
|
448
|
+
___ := bar();
|
|
449
|
+
|
|
450
|
+
// CORRECT — use unique names or bare calls:
|
|
451
|
+
_a := foo();
|
|
452
|
+
_b := bar();
|
|
453
|
+
// or simply:
|
|
454
|
+
foo();
|
|
455
|
+
bar();
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### `type` is a reserved keyword — avoid as field/param name
|
|
459
|
+
|
|
460
|
+
```rust
|
|
461
|
+
// WRONG:
|
|
462
|
+
Variable :: object(name : String, type : TypeValue);
|
|
463
|
+
|
|
464
|
+
// CORRECT:
|
|
465
|
+
Variable :: object(name : String, ty : TypeValue);
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### ArrayList indexing uses call syntax
|
|
469
|
+
|
|
470
|
+
```rust
|
|
471
|
+
list := ArrayList(i32).new();
|
|
472
|
+
list.push(i32(42));
|
|
473
|
+
|
|
474
|
+
val := list(usize(0)); // → i32 (value copy via Index trait)
|
|
475
|
+
list(usize(0)) = i32(99); // mutate in place directly
|
|
476
|
+
|
|
477
|
+
// When you need the pointer explicitly:
|
|
478
|
+
ptr := &(list(usize(0))); // → *(i32)
|
|
479
|
+
ptr.* = i32(99); // also works
|
|
480
|
+
|
|
481
|
+
// Safe access (returns Option(T)):
|
|
482
|
+
match(list.get(usize(0)),
|
|
483
|
+
.Some(v) => println(`${v}`),
|
|
484
|
+
.None => ()
|
|
485
|
+
);
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
- `list(i)` returns the value `T` (not a pointer)
|
|
489
|
+
- `list(i) = val` mutates in place directly (preferred)
|
|
490
|
+
- `&(list(i))` returns `*(T)` if you need the pointer explicitly
|
|
491
|
+
- `list.get(i)` returns `Option(T)` for safe bounds-checked access
|
|
492
|
+
|
|
493
|
+
### Named fields required for `struct`/`object` constructors
|
|
494
|
+
|
|
495
|
+
```rust
|
|
496
|
+
Point :: struct(x : i32, y : i32);
|
|
497
|
+
|
|
498
|
+
// CORRECT:
|
|
499
|
+
p := Point(x: i32(1), y: i32(2));
|
|
500
|
+
|
|
501
|
+
// WRONG — positional not supported for struct/object:
|
|
502
|
+
p := Point(i32(1), i32(2));
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Enum variant construction is positional (no field names needed).
|
|
506
|
+
|
|
507
|
+
### Object types (RC) are passed by value
|
|
508
|
+
|
|
509
|
+
`HashMap`, `ArrayList`, and other `object(...)` types are reference-counted. Passing them by value shares the underlying data — mutations are visible to all holders.
|
|
510
|
+
|
|
511
|
+
```rust
|
|
512
|
+
// DO NOT use pointer params for RC objects:
|
|
513
|
+
// WRONG: fn(m : *(HashMap(String, V))) — will cause greedy & issues at call site
|
|
514
|
+
// CORRECT: fn(m : HashMap(String, V)) — pass by value, mutations propagate via RC
|
|
515
|
+
|
|
516
|
+
process_map :: (fn(m : HashMap(String, i32)) -> unit)({
|
|
517
|
+
m.set(String.from("key"), i32(42)); // mutation visible to caller
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
counts := HashMap(String, i32).new();
|
|
521
|
+
process_map(counts);
|
|
522
|
+
// counts now has "key" => 42
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Forward references are NOT allowed
|
|
526
|
+
|
|
527
|
+
Top-level bindings are evaluated strictly in order. A function must be defined BEFORE it is called (even inside closures that are called later).
|
|
528
|
+
|
|
529
|
+
```rust
|
|
530
|
+
// WRONG — forward reference:
|
|
531
|
+
caller :: (fn() -> unit)({ helper(); });
|
|
532
|
+
helper :: (fn() -> unit)({ println("hi"); });
|
|
533
|
+
|
|
534
|
+
// CORRECT — helper before caller:
|
|
535
|
+
helper :: (fn() -> unit)({ println("hi"); });
|
|
536
|
+
caller :: (fn() -> unit)({ helper(); });
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
This applies to ALL callee-before-caller relationships:
|
|
540
|
+
|
|
541
|
+
- `_walk_dag` before `build_dag`
|
|
542
|
+
- `compile_artifact`, `run_executable`, `run_test_suite` before `execute_node`
|
|
543
|
+
- `execute_node` before `execute_dag`
|
|
544
|
+
- `_print_summary_node` before `print_build_summary`
|
|
545
|
+
- `print_build_summary` before `execute_step`
|
|
546
|
+
- Exports section must come AFTER all definitions
|
|
547
|
+
|
|
548
|
+
### `if(!cond, block)` — use parentheses
|
|
549
|
+
|
|
550
|
+
The `!` operator is greedy and consumes all following args including the block. Always parenthesize:
|
|
551
|
+
|
|
552
|
+
```rust
|
|
553
|
+
// WRONG — ! consumes "cond, block" as one arg:
|
|
554
|
+
if(!cond, { do_thing(); });
|
|
555
|
+
|
|
556
|
+
// CORRECT — ! only consumes (cond):
|
|
557
|
+
if((!cond), { do_thing(); });
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Template strings produce `String`, literals are `str`
|
|
561
|
+
|
|
562
|
+
```rust
|
|
563
|
+
// Template string `` `...` `` → String
|
|
564
|
+
// String literal "..." → str
|
|
565
|
+
|
|
566
|
+
// If a function takes `str`, call .as_str() on a template string:
|
|
567
|
+
fn_taking_str((`prefix_${value}`).as_str());
|
|
568
|
+
|
|
569
|
+
// Or change the function to take String
|
|
570
|
+
```
|
|
384
571
|
|
|
385
572
|
These features are powerful but less commonly used. Consult the linked docs for full details.
|
|
386
573
|
|