Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/alexjokela/projects/lattice/src/eval.c
Line
Count
Source
1
#include "eval.h"
2
#include "lattice.h"
3
#include "string_ops.h"
4
#include "format_ops.h"
5
#include "builtins.h"
6
#include "array_ops.h"
7
#include "net.h"
8
#include "tls.h"
9
#include "json.h"
10
#include "math_ops.h"
11
#include "env_ops.h"
12
#include "time_ops.h"
13
#include "datetime_ops.h"
14
#include "type_ops.h"
15
#include "fs_ops.h"
16
#include "path_ops.h"
17
#include "regex_ops.h"
18
#include "crypto_ops.h"
19
#include "process_ops.h"
20
#include "http.h"
21
#include "toml_ops.h"
22
#include "yaml_ops.h"
23
#include "lexer.h"
24
#include "parser.h"
25
#include "channel.h"
26
#include "ext.h"
27
#include "runtime.h"
28
#include <stdlib.h>
29
#include <stdint.h>
30
#include <string.h>
31
#include <stdio.h>
32
#include <time.h>
33
#include <limits.h>
34
#include <ctype.h>
35
#include <libgen.h>
36
#include <sys/resource.h>
37
#ifndef __EMSCRIPTEN__
38
#include <pthread.h>
39
#endif
40
41
/* Monotonic clock in nanoseconds */
42
15.8k
static uint64_t now_ns(void) {
43
15.8k
    struct timespec ts;
44
15.8k
    clock_gettime(CLOCK_MONOTONIC, &ts);
45
15.8k
    return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
46
15.8k
}
47
48
/* ── Memory stats helpers ── */
49
50
868
static void stats_init(MemoryStats *s) { memset(s, 0, sizeof(*s)); }
51
349
static void stats_freeze(MemoryStats *s) { s->freezes++; }
52
196
static void stats_thaw(MemoryStats *s) { s->thaws++; }
53
3
static void stats_deep_clone(MemoryStats *s) { s->deep_clones++; }
54
3.67k
static void stats_array(MemoryStats *s) { s->array_allocs++; }
55
196
static void stats_struct(MemoryStats *s) { s->struct_allocs++; }
56
148
static void stats_closure(MemoryStats *s) { s->closure_allocs++; }
57
4.69k
static void stats_binding(MemoryStats *s) { s->bindings_created++; }
58
1.25k
static void stats_fn_call(MemoryStats *s) { s->fn_calls++; }
59
821
static void stats_closure_call(MemoryStats *s) { s->closure_calls++; }
60
1
static void stats_forge(MemoryStats *s) { s->forge_blocks++; }
61
62
7.35k
static void stats_scope_push(MemoryStats *s) {
63
7.35k
    s->scope_pushes++;
64
7.35k
    s->current_scope_depth++;
65
7.35k
    if (s->current_scope_depth > s->peak_scope_depth)
66
1.53k
        s->peak_scope_depth = s->current_scope_depth;
67
7.35k
}
68
69
7.35k
static void stats_scope_pop(MemoryStats *s) {
70
7.35k
    s->scope_pops++;
71
7.35k
    if (s->current_scope_depth > 0) s->current_scope_depth--;
72
7.35k
}
73
74
/* ── Call stack helpers (stack traces) ── */
75
76
1.67k
static void ev_push_frame(Evaluator *ev, const char *name) {
77
1.67k
    if (ev->call_depth >= ev->call_stack_cap) {
78
839
        ev->call_stack_cap = ev->call_stack_cap ? ev->call_stack_cap * 2 : 16;
79
839
        ev->call_stack = realloc(ev->call_stack, ev->call_stack_cap * sizeof(const char *));
80
839
    }
81
1.67k
    ev->call_stack[ev->call_depth++] = name;
82
1.67k
}
83
84
1.57k
static void ev_pop_frame(Evaluator *ev) {
85
1.57k
    if (ev->call_depth > 0) ev->call_depth--;
86
1.57k
}
87
88
/* Format a stack trace and append it to an error message */
89
96
static char *ev_attach_trace(Evaluator *ev, char *msg) {
90
96
    if (ev->call_depth == 0) return msg;
91
92
    size_t msg_len = strlen(msg);
92
    /* Estimate space: header + per-frame lines */
93
92
    size_t extra = 32;
94
192
    for (size_t i = 0; i < ev->call_depth; i++)
95
100
        extra += strlen(ev->call_stack[i]) + 16;
96
92
    char *out = realloc(msg, msg_len + extra);
97
92
    if (!out) return msg;
98
92
    size_t pos = msg_len;
99
92
    size_t rem = extra;
100
92
    int n = snprintf(out + pos, rem, "\nstack trace:");
101
92
    pos += (size_t)n; rem -= (size_t)n;
102
192
    for (size_t i = ev->call_depth; i > 0; i--) {
103
100
        n = snprintf(out + pos, rem, "\n  in %s()", ev->call_stack[i - 1]);
104
100
        pos += (size_t)n; rem -= (size_t)n;
105
100
    }
106
92
    return out;
107
92
}
108
109
/* ── EvalResult helpers ── */
110
111
82.6k
static EvalResult eval_ok(LatValue v) {
112
82.6k
    EvalResult r;
113
82.6k
    r.ok = true;
114
82.6k
    r.value = v;
115
82.6k
    r.error = NULL;
116
82.6k
    r.cf.tag = CF_NONE;
117
82.6k
    return r;
118
82.6k
}
119
120
123
static EvalResult eval_err(char *msg) {
121
123
    EvalResult r;
122
123
    r.ok = false;
123
123
    r.value = value_unit();
124
123
    r.error = msg;
125
123
    r.cf.tag = CF_NONE;
126
123
    return r;
127
123
}
128
129
810
static EvalResult eval_signal(ControlFlowTag tag, LatValue v) {
130
810
    EvalResult r;
131
810
    r.ok = false;
132
810
    r.value = value_unit();
133
810
    r.error = NULL;
134
810
    r.cf.tag = tag;
135
810
    r.cf.value = v;
136
810
    return r;
137
810
}
138
139
84.3k
#define IS_OK(r) ((r).ok)
140
2.77k
#define IS_ERR(r) (!(r).ok && (r).error != NULL)
141
21.0k
#define IS_SIGNAL(r) (!(r).ok && (r).error == NULL)
142
143
/* ── Shadow stack macros ── */
144
39.2k
#define GC_PUSH(ev, vptr) lat_vec_push(&(ev)->gc_roots, &(LatValue*){(vptr)})
145
38.4k
#define GC_POP(ev)        lat_vec_pop(&(ev)->gc_roots, NULL)
146
23.2k
#define GC_POP_N(ev, n)   do { for (size_t _i = 0; _i < (n); _i++) GC_POP(ev); } while(0)
147
148
/* Forward declarations */
149
static EvalResult eval_expr_inner(Evaluator *ev, const Expr *expr);
150
static EvalResult eval_stmt(Evaluator *ev, const Stmt *stmt);
151
static EvalResult eval_block_stmts(Evaluator *ev, Stmt **stmts, size_t count);
152
153
/* eval_expr now directly calls eval_expr_inner; temporaries are protected
154
 * by GC_PUSH/GC_POP at individual expression sites instead of the blunt
155
 * gc_inhibit hammer. */
156
68.3k
static inline EvalResult eval_expr(Evaluator *ev, const Expr *expr) {
157
68.3k
    return eval_expr_inner(ev, expr);
158
68.3k
}
159
160
/* ── Garbage Collector ── */
161
162
/* Forward declaration for mutual recursion with gc_mark_env_value */
163
static void gc_mark_value(FluidHeap *fh, LatValue *v, LatVec *reachable_regions);
164
165
/* Callback for env_iter_values to mark each value */
166
typedef struct {
167
    FluidHeap *fh;
168
    LatVec    *reachable_regions;
169
} GcMarkCtx;
170
171
30.7k
static void gc_mark_env_value(LatValue *v, void *ctx) {
172
30.7k
    GcMarkCtx *mc = (GcMarkCtx *)ctx;
173
30.7k
    gc_mark_value(mc->fh, v, mc->reachable_regions);
174
30.7k
}
175
176
/*
177
 * Mark a single LatValue as reachable, recursively marking contained
178
 * heap pointers in the fluid heap.  Collects reachable crystal region
179
 * IDs into the supplied vector.
180
 */
181
144k
static void gc_mark_value(FluidHeap *fh, LatValue *v, LatVec *reachable_regions) {
182
    /* Arena-backed values: record the region as reachable and skip traversal.
183
     * All child pointers reside in the same region, so no fluid marking needed.
184
     * Note: compiled bytecode closures repurpose region_id as upvalue count,
185
     * so exclude them from this check. */
186
144k
    if (v->region_id != REGION_NONE && v->region_id != REGION_EPHEMERAL) {
187
20.0k
        bool is_compiled_closure = (v->type == VAL_CLOSURE &&
188
20.0k
                                    v->as.closure.body == NULL &&
189
20.0k
                                    v->as.closure.native_fn != NULL);
190
20.0k
        if (!is_compiled_closure) {
191
20.0k
            lat_vec_push(reachable_regions, &v->region_id);
192
20.0k
            return;
193
20.0k
        }
194
20.0k
    }
195
124k
    switch (v->type) {
196
415
        case VAL_STR:
197
415
            if (v->as.str_val)
198
415
                fluid_mark(fh, v->as.str_val);
199
415
            break;
200
12.3k
        case VAL_ARRAY:
201
12.3k
            if (v->as.array.elems) {
202
12.3k
                fluid_mark(fh, v->as.array.elems);
203
101k
                for (size_t i = 0; i < v->as.array.len; i++)
204
88.9k
                    gc_mark_value(fh, &v->as.array.elems[i], reachable_regions);
205
12.3k
            }
206
12.3k
            break;
207
624
        case VAL_STRUCT:
208
624
            if (v->as.strct.name) fluid_mark(fh, v->as.strct.name);
209
624
            if (v->as.strct.field_names) {
210
624
                fluid_mark(fh, v->as.strct.field_names);
211
1.88k
                for (size_t i = 0; i < v->as.strct.field_count; i++) {
212
1.25k
                    if (v->as.strct.field_names[i])
213
1.25k
                        fluid_mark(fh, v->as.strct.field_names[i]);
214
1.25k
                }
215
624
            }
216
624
            if (v->as.strct.field_values) {
217
624
                fluid_mark(fh, v->as.strct.field_values);
218
1.88k
                for (size_t i = 0; i < v->as.strct.field_count; i++)
219
1.25k
                    gc_mark_value(fh, &v->as.strct.field_values[i], reachable_regions);
220
624
            }
221
624
            break;
222
109
        case VAL_CLOSURE:
223
109
            if (v->as.closure.param_names) {
224
109
                fluid_mark(fh, v->as.closure.param_names);
225
218
                for (size_t i = 0; i < v->as.closure.param_count; i++) {
226
109
                    if (v->as.closure.param_names[i])
227
109
                        fluid_mark(fh, v->as.closure.param_names[i]);
228
109
                }
229
109
            }
230
            /* Mark captured env's values recursively */
231
109
            if (v->as.closure.captured_env) {
232
109
                Env *cenv = v->as.closure.captured_env;
233
109
                GcMarkCtx cctx = { fh, reachable_regions };
234
109
                env_iter_values(cenv, gc_mark_env_value, &cctx);
235
109
            }
236
109
            break;
237
0
        case VAL_MAP:
238
0
            if (v->as.map.map) {
239
0
                fluid_mark(fh, v->as.map.map);
240
0
                for (size_t i = 0; i < v->as.map.map->cap; i++) {
241
0
                    if (v->as.map.map->entries[i].state == MAP_OCCUPIED) {
242
0
                        gc_mark_value(fh, (LatValue *)v->as.map.map->entries[i].value, reachable_regions);
243
0
                    }
244
0
                }
245
0
            }
246
0
            break;
247
0
        case VAL_ENUM:
248
0
            if (v->as.enm.enum_name) fluid_mark(fh, v->as.enm.enum_name);
249
0
            if (v->as.enm.variant_name) fluid_mark(fh, v->as.enm.variant_name);
250
0
            if (v->as.enm.payload) {
251
0
                fluid_mark(fh, v->as.enm.payload);
252
0
                for (size_t i = 0; i < v->as.enm.payload_count; i++)
253
0
                    gc_mark_value(fh, &v->as.enm.payload[i], reachable_regions);
254
0
            }
255
0
            break;
256
0
        case VAL_SET:
257
0
            if (v->as.set.map) {
258
0
                fluid_mark(fh, v->as.set.map);
259
0
                if (v->as.set.map->entries) {
260
0
                    fluid_mark(fh, v->as.set.map->entries);
261
0
                    for (size_t i = 0; i < v->as.set.map->cap; i++) {
262
0
                        if (v->as.set.map->entries[i].state == MAP_OCCUPIED) {
263
0
                            fluid_mark(fh, v->as.set.map->entries[i].key);
264
0
                            fluid_mark(fh, v->as.set.map->entries[i].value);
265
0
                            gc_mark_value(fh, (LatValue *)v->as.set.map->entries[i].value, reachable_regions);
266
0
                        }
267
0
                    }
268
0
                }
269
0
            }
270
0
            break;
271
110k
        default:
272
110k
            break;
273
124k
    }
274
124k
}
275
276
#ifndef NDEBUG
277
#include <assert.h>
278
/*
279
 * Debug assertion: verify that no crystal value's heap pointers appear
280
 * in the fluid alloc list.  Violation would mean freeze didn't properly
281
 * untrack the pointer, risking a double-free during sweep.
282
 */
283
13.8k
static bool ptr_in_fluid(FluidHeap *fh, void *ptr) {
284
13.8k
    if (!ptr) return false;
285
47.7k
    for (FluidAlloc *a = fh->allocs; a; a = a->next) {
286
33.8k
        if (a->ptr == ptr) return true;
287
33.8k
    }
288
13.8k
    return false;
289
13.8k
}
290
291
29.9k
static void assert_crystal_not_fluid(LatValue *v, void *ctx) {
292
29.9k
    FluidHeap *fh = (FluidHeap *)ctx;
293
29.9k
    if (v->phase != VTAG_CRYSTAL || v->region_id == (size_t)-1) return;
294
6.67k
    switch (v->type) {
295
0
        case VAL_STR:
296
0
            assert(!ptr_in_fluid(fh, v->as.str_val) &&
297
0
                   "crystal string in fluid heap");
298
0
            break;
299
357
        case VAL_ARRAY:
300
357
            assert(!ptr_in_fluid(fh, v->as.array.elems) &&
301
357
                   "crystal array elems in fluid heap");
302
357
            break;
303
357
        case VAL_STRUCT:
304
301
            assert(!ptr_in_fluid(fh, v->as.strct.name) &&
305
301
                   "crystal struct name in fluid heap");
306
301
            assert(!ptr_in_fluid(fh, v->as.strct.field_names) &&
307
301
                   "crystal struct field_names in fluid heap");
308
301
            assert(!ptr_in_fluid(fh, v->as.strct.field_values) &&
309
301
                   "crystal struct field_values in fluid heap");
310
903
            for (size_t i = 0; i < v->as.strct.field_count; i++) {
311
602
                assert(!ptr_in_fluid(fh, v->as.strct.field_names[i]) &&
312
602
                       "crystal struct field_name string in fluid heap");
313
602
            }
314
301
            break;
315
6.01k
        case VAL_CLOSURE:
316
6.01k
            assert(!ptr_in_fluid(fh, v->as.closure.param_names) &&
317
6.01k
                   "crystal closure param_names in fluid heap");
318
12.0k
            for (size_t i = 0; i < v->as.closure.param_count; i++) {
319
6.01k
                assert(!ptr_in_fluid(fh, v->as.closure.param_names[i]) &&
320
6.01k
                       "crystal closure param_name string in fluid heap");
321
6.01k
            }
322
6.01k
            break;
323
6.01k
        case VAL_MAP:
324
0
            if (v->as.map.map) {
325
0
                assert(!ptr_in_fluid(fh, v->as.map.map) &&
326
0
                       "crystal map struct in fluid heap");
327
0
                assert(!ptr_in_fluid(fh, v->as.map.map->entries) &&
328
0
                       "crystal map entries in fluid heap");
329
0
            }
330
0
            break;
331
0
        case VAL_SET:
332
0
            if (v->as.set.map) {
333
0
                assert(!ptr_in_fluid(fh, v->as.set.map) &&
334
0
                       "crystal set struct in fluid heap");
335
0
                assert(!ptr_in_fluid(fh, v->as.set.map->entries) &&
336
0
                       "crystal set entries in fluid heap");
337
0
            }
338
0
            break;
339
1
        default:
340
1
            break;
341
6.67k
    }
342
6.67k
}
343
344
7.37k
static void assert_dual_heap_invariant(Evaluator *ev) {
345
7.37k
    env_iter_values(ev->env, assert_crystal_not_fluid, ev->heap->fluid);
346
7.47k
    for (size_t i = 0; i < ev->saved_envs.len; i++) {
347
100
        Env **ep = lat_vec_get(&ev->saved_envs, i);
348
100
        env_iter_values(*ep, assert_crystal_not_fluid, ev->heap->fluid);
349
100
    }
350
7.37k
}
351
#endif /* NDEBUG */
352
353
/*
354
 * Run a full GC cycle: mark all roots, sweep unreachable.
355
 */
356
7.37k
static void gc_cycle(Evaluator *ev) {
357
7.37k
    FluidHeap *fh = ev->heap->fluid;
358
7.37k
    LatVec reachable_regions = lat_vec_new(sizeof(RegionId));
359
360
    /* 0. Advance epoch — groups frozen values by GC generation */
361
7.37k
    if (!ev->no_regions)
362
7.37k
        region_advance_epoch(ev->heap->regions);
363
364
    /* 1. Clear all marks */
365
7.37k
    fluid_unmark_all(fh);
366
367
    /* 2. Mark roots from environment */
368
7.37k
    GcMarkCtx ctx = { fh, &reachable_regions };
369
7.37k
    env_iter_values(ev->env, gc_mark_env_value, &ctx);
370
371
    /* 3. Mark roots from shadow stack */
372
30.7k
    for (size_t i = 0; i < ev->gc_roots.len; i++) {
373
23.3k
        LatValue **vp = lat_vec_get(&ev->gc_roots, i);
374
23.3k
        if (*vp) gc_mark_value(fh, *vp, &reachable_regions);
375
23.3k
    }
376
377
    /* 4. Mark values from saved caller environments (closure env swap) */
378
7.47k
    for (size_t i = 0; i < ev->saved_envs.len; i++) {
379
100
        Env **ep = lat_vec_get(&ev->saved_envs, i);
380
100
        env_iter_values(*ep, gc_mark_env_value, &ctx);
381
100
    }
382
383
    /* 5. Sweep unmarked fluid allocations */
384
7.37k
    size_t fluid_before = fh->total_bytes;
385
7.37k
    size_t swept_fluid = fluid_sweep(fh);
386
7.37k
    ev->stats.gc_bytes_swept += fluid_before - fh->total_bytes;
387
388
    /* 6. Collect unreachable crystal regions */
389
7.37k
    size_t swept_regions = 0;
390
7.37k
    if (!ev->no_regions) {
391
7.37k
        swept_regions = region_collect(
392
7.37k
            ev->heap->regions,
393
7.37k
            (RegionId *)reachable_regions.data,
394
7.37k
            reachable_regions.len);
395
7.37k
    }
396
397
    /* 7. Update stats */
398
7.37k
    ev->stats.gc_cycles++;
399
7.37k
    ev->stats.gc_swept_fluid += swept_fluid;
400
7.37k
    ev->stats.gc_swept_regions += swept_regions;
401
402
7.37k
    lat_vec_free(&reachable_regions);
403
404
7.37k
#ifndef NDEBUG
405
    /* 8. Verify dual-heap invariant: no crystal pointers in fluid heap */
406
7.37k
    if (!ev->no_regions)
407
7.37k
        assert_dual_heap_invariant(ev);
408
7.37k
#endif
409
7.37k
}
410
411
/*
412
 * Maybe trigger GC if heap exceeds threshold.
413
 * Called after allocations.
414
 */
415
14.9k
static void gc_maybe_collect(Evaluator *ev) {
416
14.9k
    if (ev->gc_stress ||
417
14.9k
        ev->heap->fluid->total_bytes >= ev->heap->fluid->gc_threshold) {
418
7.37k
        uint64_t t0 = now_ns();
419
7.37k
        gc_cycle(ev);
420
7.37k
        ev->stats.gc_total_ns += now_ns() - t0;
421
7.37k
    }
422
14.9k
}
423
424
/*
425
 * Recursively set region_id on a value and all nested values.
426
 * Must walk into closure captured environments so that GC knows
427
 * every arena pointer belongs to this region.
428
 */
429
static void set_region_id_env(Env *env, RegionId rid);
430
431
1.01k
static void set_region_id_recursive(LatValue *v, RegionId rid) {
432
1.01k
    v->region_id = rid;
433
1.01k
    switch (v->type) {
434
103
        case VAL_ARRAY:
435
419
            for (size_t i = 0; i < v->as.array.len; i++)
436
316
                set_region_id_recursive(&v->as.array.elems[i], rid);
437
103
            break;
438
159
        case VAL_STRUCT:
439
479
            for (size_t i = 0; i < v->as.strct.field_count; i++)
440
320
                set_region_id_recursive(&v->as.strct.field_values[i], rid);
441
159
            break;
442
13
        case VAL_CLOSURE:
443
13
            if (v->as.closure.captured_env)
444
13
                set_region_id_env(v->as.closure.captured_env, rid);
445
13
            break;
446
22
        case VAL_MAP:
447
22
            if (v->as.map.map) {
448
374
                for (size_t i = 0; i < v->as.map.map->cap; i++) {
449
352
                    if (v->as.map.map->entries[i].state == MAP_OCCUPIED) {
450
15
                        LatValue *mv = (LatValue *)v->as.map.map->entries[i].value;
451
15
                        set_region_id_recursive(mv, rid);
452
15
                    }
453
352
                }
454
22
            }
455
22
            break;
456
0
        case VAL_ENUM:
457
0
            for (size_t i = 0; i < v->as.enm.payload_count; i++)
458
0
                set_region_id_recursive(&v->as.enm.payload[i], rid);
459
0
            break;
460
0
        case VAL_SET:
461
0
            if (v->as.set.map) {
462
0
                for (size_t i = 0; i < v->as.set.map->cap; i++) {
463
0
                    if (v->as.set.map->entries[i].state == MAP_OCCUPIED) {
464
0
                        LatValue *sv = (LatValue *)v->as.set.map->entries[i].value;
465
0
                        set_region_id_recursive(sv, rid);
466
0
                    }
467
0
                }
468
0
            }
469
0
            break;
470
718
        default:
471
718
            break;
472
1.01k
    }
473
1.01k
}
474
475
19
static void set_region_id_env_value(LatValue *v, void *ctx) {
476
19
    RegionId rid = *(RegionId *)ctx;
477
19
    set_region_id_recursive(v, rid);
478
19
}
479
480
13
static void set_region_id_env(Env *env, RegionId rid) {
481
13
    env_iter_values(env, set_region_id_env_value, &rid);
482
13
}
483
484
/*
485
 * Freeze support: deep-clone value into a new arena-backed region,
486
 * set region_id recursively, free the original fluid-heap value,
487
 * and replace it with the arena clone.
488
 *
489
 * In no-regions baseline mode, crystal values stay in the fluid heap.
490
 */
491
345
static void freeze_to_region(Evaluator *ev, LatValue *v) {
492
345
    if (ev->no_regions) return;
493
494
345
    CrystalRegion *region = region_create(ev->heap->regions);
495
496
345
    value_set_arena(region);
497
345
    LatValue clone = value_deep_clone(v);
498
345
    value_set_arena(NULL);
499
500
345
    ev->heap->regions->cumulative_data_bytes += region->total_bytes;
501
502
345
    set_region_id_recursive(&clone, region->id);
503
504
345
    value_free(v);
505
345
    *v = clone;
506
345
}
507
508
/* Record a history snapshot for a tracked variable */
509
5.63k
static void record_history(Evaluator *ev, const char *name) {
510
5.63k
    for (size_t i = 0; i < ev->tracked_count; i++) {
511
17
        if (strcmp(ev->tracked_vars[i].name, name) != 0) continue;
512
17
        VariableHistory *vh = &ev->tracked_vars[i].history;
513
17
        LatValue cur;
514
17
        if (!env_get(ev->env, name, &cur)) return;
515
17
        if (vh->count >= vh->cap) {
516
0
            vh->cap = vh->cap ? vh->cap * 2 : 8;
517
0
            vh->snapshots = realloc(vh->snapshots, vh->cap * sizeof(HistorySnapshot));
518
0
        }
519
17
        const char *phase = builtin_phase_of_str(&cur);
520
17
        vh->snapshots[vh->count].phase_name = strdup(phase);
521
17
        vh->snapshots[vh->count].value = value_deep_clone(&cur);
522
17
        vh->snapshots[vh->count].line = 0;
523
17
        vh->snapshots[vh->count].fn_name = NULL;
524
17
        vh->count++;
525
17
        value_free(&cur);
526
17
        return;
527
17
    }
528
5.63k
}
529
530
/* Forward declaration for fire_reactions */
531
static EvalResult call_closure(Evaluator *ev, char **params, size_t param_count,
532
                               const Expr *body, Env *closure_env, LatValue *args, size_t arg_count,
533
                               Expr **default_values, bool has_variadic);
534
535
/// @builtin react(var: Ident, callback: Closure) -> Unit
536
/// @category Phase Reactions
537
/// Register a callback that fires when a variable's phase changes.
538
/// @example react(data, |phase, val| { print(phase) })
539
540
/// @builtin unreact(var: Ident) -> Unit
541
/// @category Phase Reactions
542
/// Remove all phase reaction callbacks from a variable.
543
/// @example unreact(data)
544
545
478
static EvalResult fire_reactions(Evaluator *ev, const char *var_name, const char *phase_name) {
546
479
    for (size_t i = 0; i < ev->reaction_count; i++) {
547
9
        if (strcmp(ev->reactions[i].var_name, var_name) != 0) continue;
548
8
        LatValue cur;
549
8
        if (!env_get(ev->env, var_name, &cur)) return eval_ok(value_unit());
550
16
        for (size_t j = 0; j < ev->reactions[i].cb_count; j++) {
551
9
            LatValue *cb = &ev->reactions[i].callbacks[j];
552
9
            LatValue args[2];
553
9
            args[0] = value_string(phase_name);
554
9
            args[1] = value_deep_clone(&cur);
555
9
            EvalResult r = call_closure(ev, cb->as.closure.param_names,
556
9
                cb->as.closure.param_count, cb->as.closure.body,
557
9
                cb->as.closure.captured_env, args, 2,
558
9
                cb->as.closure.default_values, cb->as.closure.has_variadic);
559
9
            if (!IS_OK(r)) {
560
1
                value_free(&cur);
561
1
                char *err = NULL;
562
1
                (void)asprintf(&err, "reaction error: %s", r.error);
563
1
                free(r.error);
564
1
                return eval_err(err);
565
1
            }
566
8
            value_free(&r.value);
567
8
        }
568
7
        value_free(&cur);
569
7
        return eval_ok(value_unit());
570
8
    }
571
470
    return eval_ok(value_unit());
572
478
}
573
574
/* Cascade freeze through bonded variables */
575
/* Returns NULL on success, heap-allocated error string on failure */
576
284
static char *freeze_cascade(Evaluator *ev, const char *target_name) {
577
297
    for (size_t bi = 0; bi < ev->bond_count; bi++) {
578
24
        if (strcmp(ev->bonds[bi].target, target_name) != 0) continue;
579
        /* Found bond entry for target — process all deps by strategy */
580
23
        for (size_t di = 0; di < ev->bonds[bi].dep_count; di++) {
581
13
            const char *dep = ev->bonds[bi].deps[di];
582
13
            const char *strategy = ev->bonds[bi].dep_strategies ? ev->bonds[bi].dep_strategies[di] : "mirror";
583
13
            LatValue dval;
584
13
            if (!env_get(ev->env, dep, &dval)) continue;  /* variable gone */
585
13
            if (dval.type == VAL_CHANNEL) { value_free(&dval); continue; }
586
587
13
            if (strcmp(strategy, "mirror") == 0) {
588
10
                if (dval.phase == VTAG_CRYSTAL) { value_free(&dval); continue; }
589
10
                dval = value_freeze(dval);
590
10
                freeze_to_region(ev, &dval);
591
10
                env_set(ev->env, dep, dval);
592
10
                EvalResult fr = fire_reactions(ev, dep, "crystal");
593
10
                if (!IS_OK(fr)) free(fr.error);
594
10
                char *err = freeze_cascade(ev, dep);
595
10
                if (err) return err;
596
10
            } else if (strcmp(strategy, "inverse") == 0) {
597
                /* Inverse: thaw the dep when target freezes */
598
1
                if (dval.phase != VTAG_CRYSTAL && dval.phase != VTAG_SUBLIMATED) { value_free(&dval); continue; }
599
1
                LatValue thawed = value_thaw(&dval);
600
1
                value_free(&dval);
601
1
                env_set(ev->env, dep, thawed);
602
1
                EvalResult fr = fire_reactions(ev, dep, "fluid");
603
1
                if (!IS_OK(fr)) free(fr.error);
604
2
            } else if (strcmp(strategy, "gate") == 0) {
605
                /* Gate: dep must already be crystal for target to freeze */
606
2
                if (dval.phase != VTAG_CRYSTAL) {
607
1
                    value_free(&dval);
608
1
                    char *err = NULL;
609
1
                    (void)asprintf(&err, "gate bond: '%s' must be crystal before '%s' can freeze", dep, target_name);
610
1
                    return err;
611
1
                }
612
1
                value_free(&dval);
613
1
            } else {
614
0
                value_free(&dval);
615
0
            }
616
13
        }
617
        /* Consume the bond entry */
618
22
        for (size_t di = 0; di < ev->bonds[bi].dep_count; di++) {
619
12
            free(ev->bonds[bi].deps[di]);
620
12
            if (ev->bonds[bi].dep_strategies) free(ev->bonds[bi].dep_strategies[di]);
621
12
        }
622
10
        free(ev->bonds[bi].deps);
623
10
        free(ev->bonds[bi].dep_strategies);
624
10
        free(ev->bonds[bi].target);
625
10
        ev->bonds[bi] = ev->bonds[--ev->bond_count];
626
10
        break;
627
11
    }
628
283
    return NULL;
629
284
}
630
631
/*
632
 * Resolve a mutable pointer to a LatValue from an lvalue expression.
633
 * Walks chains of field_access and index expressions to find the
634
 * actual storage location in the environment. Returns NULL + sets *err on failure.
635
 */
636
1.22k
static LatValue *resolve_lvalue(Evaluator *ev, const Expr *expr, char **err) {
637
1.22k
    if (expr->tag == EXPR_IDENT) {
638
1.23k
        for (size_t s = ev->env->count; s > 0; s--) {
639
1.23k
            LatValue *v = lat_map_get(&ev->env->scopes[s-1], expr->as.str_val);
640
1.23k
            if (v) return v;
641
1.23k
        }
642
0
        (void)asprintf(err, "undefined variable '%s'", expr->as.str_val);
643
0
        return NULL;
644
1.16k
    }
645
59
    if (expr->tag == EXPR_FIELD_ACCESS) {
646
20
        LatValue *parent = resolve_lvalue(ev, expr->as.field_access.object, err);
647
20
        if (!parent) return NULL;
648
20
        if (parent->type != VAL_STRUCT) {
649
0
            (void)asprintf(err, "cannot access field '%s' on %s",
650
0
                           expr->as.field_access.field, value_type_name(parent));
651
0
            return NULL;
652
0
        }
653
29
        for (size_t i = 0; i < parent->as.strct.field_count; i++) {
654
29
            if (strcmp(parent->as.strct.field_names[i], expr->as.field_access.field) == 0) {
655
20
                return &parent->as.strct.field_values[i];
656
20
            }
657
29
        }
658
0
        (void)asprintf(err, "struct has no field '%s'", expr->as.field_access.field);
659
0
        return NULL;
660
20
    }
661
39
    if (expr->tag == EXPR_INDEX) {
662
        /* Evaluate the index expression BEFORE resolving the parent lvalue.
663
         * eval_expr may trigger GC or scope-map mutations (lat_map_set →
664
         * rehash), which would invalidate any raw pointer returned by
665
         * resolve_lvalue.  By evaluating first we avoid stale pointers. */
666
39
        EvalResult idxr = eval_expr(ev, expr->as.index.index);
667
39
        if (!IS_OK(idxr)) { *err = idxr.error; return NULL; }
668
669
39
        LatValue *parent = resolve_lvalue(ev, expr->as.index.object, err);
670
39
        if (!parent) { value_free(&idxr.value); return NULL; }
671
672
        /* Ref unwrap: delegate indexing to the inner value */
673
39
        if (parent->type == VAL_REF) parent = &parent->as.ref.ref->value;
674
675
39
        if (parent->type == VAL_MAP) {
676
30
            if (idxr.value.type != VAL_STR) {
677
0
                value_free(&idxr.value);
678
0
                *err = strdup("map key must be a string");
679
0
                return NULL;
680
0
            }
681
30
            const char *key = idxr.value.as.str_val;
682
            /* Auto-vivify: if key doesn't exist, create it with unit */
683
30
            if (!lat_map_contains(parent->as.map.map, key)) {
684
25
                LatValue unit = value_unit();
685
25
                lat_map_set(parent->as.map.map, key, &unit);
686
25
            }
687
30
            LatValue *target = (LatValue *)lat_map_get(parent->as.map.map, key);
688
30
            value_free(&idxr.value);
689
30
            return target;
690
30
        }
691
9
        if (parent->type == VAL_ARRAY) {
692
9
            if (idxr.value.type != VAL_INT) {
693
0
                value_free(&idxr.value);
694
0
                *err = strdup("array index must be an integer");
695
0
                return NULL;
696
0
            }
697
9
            size_t idx = (size_t)idxr.value.as.int_val;
698
9
            value_free(&idxr.value);
699
9
            if (idx >= parent->as.array.len) {
700
0
                (void)asprintf(err, "index %zu out of bounds (length %zu)",
701
0
                               idx, parent->as.array.len);
702
0
                return NULL;
703
0
            }
704
9
            return &parent->as.array.elems[idx];
705
9
        }
706
0
        value_free(&idxr.value);
707
0
        (void)asprintf(err, "cannot index into %s", value_type_name(parent));
708
0
        return NULL;
709
9
    }
710
0
    *err = strdup("invalid lvalue expression");
711
0
    return NULL;
712
39
}
713
static EvalResult eval_method_call(Evaluator *ev, LatValue obj, const char *method,
714
                                   LatValue *args, size_t arg_count);
715
716
/* ── FnDecl lookup helpers ── */
717
/* We store FnDecl* pointers in the map */
718
719
1.70k
static FnDecl *find_fn(Evaluator *ev, const char *name) {
720
1.70k
    FnDecl **ptr = lat_map_get(&ev->fn_defs, name);
721
1.70k
    return ptr ? *ptr : NULL;
722
1.70k
}
723
724
/* ── Phase helpers for constraints & dispatch ── */
725
726
3
static const char *phase_tag_name(PhaseTag p) {
727
3
    switch (p) {
728
1
        case VTAG_FLUID:      return "fluid (flux)";
729
2
        case VTAG_CRYSTAL:    return "crystal (fix)";
730
0
        case VTAG_UNPHASED:   return "unphased";
731
0
        case VTAG_SUBLIMATED: return "sublimated";
732
3
    }
733
0
    return "unknown";
734
3
}
735
736
3
static const char *ast_phase_name(AstPhase p) {
737
3
    switch (p) {
738
2
        case PHASE_FLUID:       return "flux";
739
1
        case PHASE_CRYSTAL:     return "fix";
740
0
        case PHASE_UNSPECIFIED: return "unspecified";
741
3
    }
742
0
    return "unknown";
743
3
}
744
745
16
static bool phase_compatible(PhaseTag value_phase, AstPhase param_phase) {
746
16
    switch (param_phase) {
747
9
        case PHASE_FLUID:       return value_phase != VTAG_CRYSTAL;
748
6
        case PHASE_CRYSTAL:     return value_phase != VTAG_FLUID;
749
1
        case PHASE_UNSPECIFIED: return true;
750
16
    }
751
0
    return true;
752
16
}
753
754
1.11k
static bool type_matches_value(const LatValue *val, const TypeExpr *te) {
755
1.11k
    if (!te || !te->name) return true;  /* no annotation = Any */
756
1.11k
    if (strcmp(te->name, "Any") == 0 || strcmp(te->name, "any") == 0) return true;
757
585
    if (te->kind == TYPE_ARRAY) {
758
0
        if (val->type != VAL_ARRAY) return false;
759
0
        if (!te->inner) return true;  /* [Any] or unspecified inner */
760
0
        for (size_t i = 0; i < val->as.array.len; i++) {
761
0
            if (!type_matches_value(&val->as.array.elems[i], te->inner))
762
0
                return false;
763
0
        }
764
0
        return true;
765
0
    }
766
    /* Named types */
767
585
    const char *n = te->name;
768
585
    if (strcmp(n, "Int") == 0)    return val->type == VAL_INT;
769
511
    if (strcmp(n, "Float") == 0)  return val->type == VAL_FLOAT;
770
511
    if (strcmp(n, "String") == 0) return val->type == VAL_STR;
771
458
    if (strcmp(n, "Bool") == 0)   return val->type == VAL_BOOL;
772
457
    if (strcmp(n, "Nil") == 0)    return val->type == VAL_NIL;
773
457
    if (strcmp(n, "Map") == 0)    return val->type == VAL_MAP;
774
75
    if (strcmp(n, "Array") == 0)  return val->type == VAL_ARRAY;
775
40
    if (strcmp(n, "Fn") == 0 || strcmp(n, "Closure") == 0) return val->type == VAL_CLOSURE;
776
11
    if (strcmp(n, "Channel") == 0) return val->type == VAL_CHANNEL;
777
11
    if (strcmp(n, "Range") == 0)  return val->type == VAL_RANGE;
778
11
    if (strcmp(n, "Set") == 0)    return val->type == VAL_SET;
779
11
    if (strcmp(n, "Tuple") == 0)  return val->type == VAL_TUPLE;
780
11
    if (strcmp(n, "Buffer") == 0) return val->type == VAL_BUFFER;
781
11
    if (strcmp(n, "Ref") == 0) return val->type == VAL_REF;
782
11
    if (strcmp(n, "Number") == 0) return val->type == VAL_INT || val->type == VAL_FLOAT;
783
    /* Struct name check */
784
6
    if (val->type == VAL_STRUCT && val->as.strct.name)
785
5
        return strcmp(val->as.strct.name, n) == 0;
786
    /* Enum name check */
787
1
    if (val->type == VAL_ENUM && val->as.enm.enum_name)
788
1
        return strcmp(val->as.enm.enum_name, n) == 0;
789
0
    return false;
790
1
}
791
792
4
static const char *value_type_display(const LatValue *val) {
793
4
    switch (val->type) {
794
0
        case VAL_INT:     return "Int";
795
0
        case VAL_FLOAT:   return "Float";
796
0
        case VAL_BOOL:    return "Bool";
797
3
        case VAL_STR:     return "String";
798
0
        case VAL_ARRAY:   return "Array";
799
1
        case VAL_STRUCT:  return val->as.strct.name ? val->as.strct.name : "Struct";
800
0
        case VAL_CLOSURE: return "Fn";
801
0
        case VAL_UNIT:    return "Unit";
802
0
        case VAL_NIL:     return "Nil";
803
0
        case VAL_RANGE:   return "Range";
804
0
        case VAL_MAP:     return "Map";
805
0
        case VAL_CHANNEL: return "Channel";
806
0
        case VAL_ENUM:    return val->as.enm.enum_name ? val->as.enm.enum_name : "Enum";
807
0
        case VAL_SET:     return "Set";
808
0
        case VAL_TUPLE:   return "Tuple";
809
0
        case VAL_BUFFER:  return "Buffer";
810
0
        case VAL_REF:     return "Ref";
811
4
    }
812
0
    return "Unknown";
813
4
}
814
815
4
static bool phase_signatures_match(const FnDecl *a, const FnDecl *b) {
816
4
    if (a->param_count != b->param_count) return false;
817
4
    for (size_t i = 0; i < a->param_count; i++) {
818
3
        if (a->params[i].ty.phase != b->params[i].ty.phase) return false;
819
3
    }
820
1
    return true;
821
4
}
822
823
3
static FnDecl *resolve_overload(FnDecl *head, LatValue *args, size_t argc) {
824
3
    FnDecl *best = NULL;
825
3
    int best_score = -1;
826
827
9
    for (FnDecl *cand = head; cand; cand = cand->next_overload) {
828
        /* Check arity */
829
6
        size_t required = 0;
830
6
        bool has_variadic = false;
831
12
        for (size_t i = 0; i < cand->param_count; i++) {
832
6
            if (cand->params[i].is_variadic) has_variadic = true;
833
6
            else if (!cand->params[i].default_value) required++;
834
6
        }
835
6
        size_t max_pos = has_variadic ? cand->param_count - 1 : cand->param_count;
836
6
        if (argc < required || (!has_variadic && argc > max_pos)) continue;
837
838
        /* Check phase compatibility and compute score */
839
6
        bool compatible = true;
840
6
        int score = 0;
841
6
        size_t check_count = argc < cand->param_count ? argc : cand->param_count;
842
10
        for (size_t i = 0; i < check_count; i++) {
843
6
            if (cand->params[i].is_variadic) break;
844
6
            AstPhase pp = cand->params[i].ty.phase;
845
6
            PhaseTag vp = args[i].phase;
846
6
            if (!phase_compatible(vp, pp)) { compatible = false; break; }
847
4
            if (pp == PHASE_FLUID && vp == VTAG_FLUID)
848
1
                score += 3;  /* exact: fluid→flux */
849
3
            else if (pp == PHASE_CRYSTAL && vp == VTAG_CRYSTAL)
850
1
                score += 3;  /* exact: crystal→fix */
851
2
            else if (pp == PHASE_UNSPECIFIED && vp == VTAG_UNPHASED)
852
1
                score += 2;  /* exact: unphased→unspecified */
853
1
            else if (pp == PHASE_UNSPECIFIED)
854
0
                score += 1;  /* compatible: specific→unspecified */
855
            /* else: unphased→specific = 0 (compatible but weakest) */
856
4
        }
857
6
        if (!compatible) continue;
858
859
4
        if (score >= best_score) {
860
3
            best_score = score;
861
3
            best = cand;
862
3
        }
863
4
    }
864
3
    return best;
865
3
}
866
867
2.66k
static void register_fn_overload(LatMap *fn_defs, FnDecl *new_fn) {
868
2.66k
    FnDecl **existing = lat_map_get(fn_defs, new_fn->name);
869
2.66k
    if (!existing) {
870
        /* No existing fn with this name — simple insert */
871
2.66k
        lat_map_set(fn_defs, new_fn->name, &new_fn);
872
2.66k
        return;
873
2.66k
    }
874
    /* Check if same phase signature — replace */
875
4
    FnDecl *head = *existing;
876
4
    if (phase_signatures_match(head, new_fn)) {
877
        /* Replace head: keep the chain */
878
1
        new_fn->next_overload = head->next_overload;
879
1
        lat_map_set(fn_defs, new_fn->name, &new_fn);
880
1
        return;
881
1
    }
882
    /* Check rest of chain */
883
3
    for (FnDecl *prev = head; prev->next_overload; prev = prev->next_overload) {
884
0
        if (phase_signatures_match(prev->next_overload, new_fn)) {
885
            /* Replace this link */
886
0
            new_fn->next_overload = prev->next_overload->next_overload;
887
0
            prev->next_overload = new_fn;
888
0
            return;
889
0
        }
890
0
    }
891
    /* Different phase signature — chain it */
892
3
    new_fn->next_overload = head;
893
3
    lat_map_set(fn_defs, new_fn->name, &new_fn);
894
3
}
895
896
197
static StructDecl *find_struct(Evaluator *ev, const char *name) {
897
197
    StructDecl **ptr = lat_map_get(&ev->struct_defs, name);
898
197
    return ptr ? *ptr : NULL;
899
197
}
900
901
488
static EnumDecl *find_enum(Evaluator *ev, const char *name) {
902
488
    EnumDecl **ptr = lat_map_get(&ev->enum_defs, name);
903
488
    return ptr ? *ptr : NULL;
904
488
}
905
906
14
static VariantDecl *find_variant(EnumDecl *ed, const char *variant_name) {
907
17
    for (size_t i = 0; i < ed->variant_count; i++) {
908
17
        if (strcmp(ed->variants[i].name, variant_name) == 0)
909
14
            return &ed->variants[i];
910
17
    }
911
0
    return NULL;
912
14
}
913
914
/* Find a pressure constraint for a variable (returns mode string or NULL) */
915
137
static const char *find_pressure(Evaluator *ev, const char *var_name) {
916
137
    for (size_t i = 0; i < ev->pressure_count; i++) {
917
3
        if (strcmp(ev->pressures[i].var_name, var_name) == 0)
918
3
            return ev->pressures[i].mode;
919
3
    }
920
134
    return NULL;
921
137
}
922
923
/* Check if a pressure mode blocks a structural growth operation (push, insert, merge) */
924
132
static bool pressure_blocks_grow(const char *mode) {
925
132
    return mode && (strcmp(mode, "no_grow") == 0 || strcmp(mode, "no_resize") == 0);
926
132
}
927
928
/* Check if a pressure mode blocks a structural shrink operation (pop, remove, remove_at) */
929
5
static bool pressure_blocks_shrink(const char *mode) {
930
5
    return mode && (strcmp(mode, "no_shrink") == 0 || strcmp(mode, "no_resize") == 0);
931
5
}
932
933
/* Get the root variable name from a method call object expression */
934
7
static const char *get_method_obj_varname(const Expr *obj) {
935
7
    if (obj->tag == EXPR_IDENT) return obj->as.str_val;
936
0
    if (obj->tag == EXPR_FIELD_ACCESS) return get_method_obj_varname(obj->as.field_access.object);
937
0
    if (obj->tag == EXPR_INDEX) return get_method_obj_varname(obj->as.index.object);
938
0
    return NULL;
939
0
}
940
941
/* ── Function calling ── */
942
943
static EvalResult call_fn(Evaluator *ev, const FnDecl *decl, LatValue *args, size_t arg_count,
944
1.26k
                          LatValue **writeback_out) {
945
    /* Determine required count (params without defaults and not variadic) */
946
1.26k
    size_t required = 0;
947
1.26k
    bool has_variadic = false;
948
1.99k
    for (size_t i = 0; i < decl->param_count; i++) {
949
735
        if (decl->params[i].is_variadic) { has_variadic = true; }
950
731
        else if (!decl->params[i].default_value) required++;
951
735
    }
952
1.26k
    size_t max_positional = has_variadic ? decl->param_count - 1 : decl->param_count;
953
1.26k
    if (arg_count < required || (!has_variadic && arg_count > max_positional)) {
954
0
        char *err = NULL;
955
0
        if (has_variadic)
956
0
            (void)asprintf(&err, "function '%s' expects at least %zu arguments, got %zu",
957
0
                           decl->name, required, arg_count);
958
0
        else if (required < max_positional)
959
0
            (void)asprintf(&err, "function '%s' expects %zu to %zu arguments, got %zu",
960
0
                           decl->name, required, max_positional, arg_count);
961
0
        else
962
0
            (void)asprintf(&err, "function '%s' expects %zu arguments, got %zu",
963
0
                           decl->name, required, arg_count);
964
0
        return eval_err(err);
965
0
    }
966
    /* Phase constraint enforcement */
967
1.98k
    for (size_t i = 0; i < decl->param_count && i < arg_count; i++) {
968
731
        if (decl->params[i].is_variadic) break;
969
729
        if (decl->params[i].ty.phase != PHASE_UNSPECIFIED &&
970
729
            !phase_compatible(args[i].phase, decl->params[i].ty.phase)) {
971
3
            char *err = NULL;
972
3
            (void)asprintf(&err, "function '%s' parameter '%s' requires %s argument, got %s",
973
3
                           decl->name, decl->params[i].name,
974
3
                           ast_phase_name(decl->params[i].ty.phase),
975
3
                           phase_tag_name(args[i].phase));
976
3
            return eval_err(err);
977
3
        }
978
729
    }
979
    /* Runtime type checking */
980
1.98k
    for (size_t i = 0; i < decl->param_count && i < arg_count; i++) {
981
728
        if (decl->params[i].is_variadic) break;
982
726
        if (decl->params[i].ty.name && !type_matches_value(&args[i], &decl->params[i].ty)) {
983
2
            char *err = NULL;
984
2
            (void)asprintf(&err, "function '%s' parameter '%s' expects type %s, got %s",
985
2
                           decl->name, decl->params[i].name,
986
2
                           decl->params[i].ty.name,
987
2
                           value_type_display(&args[i]));
988
2
            return eval_err(err);
989
2
        }
990
726
    }
991
1.25k
    stats_fn_call(&ev->stats);
992
1.25k
    ev_push_frame(ev, decl->name);
993
1.25k
    stats_scope_push(&ev->stats);
994
1.25k
    env_push_scope(ev->env);
995
1.98k
    for (size_t i = 0; i < decl->param_count; i++) {
996
729
        if (decl->params[i].is_variadic) {
997
            /* Collect remaining args into an array */
998
4
            size_t rest_count = (arg_count > i) ? arg_count - i : 0;
999
4
            LatValue *rest_elems = malloc(rest_count * sizeof(LatValue));
1000
10
            for (size_t j = 0; j < rest_count; j++)
1001
6
                rest_elems[j] = args[i + j];
1002
4
            LatValue arr = value_array(rest_elems, rest_count);
1003
4
            free(rest_elems);
1004
4
            env_define(ev->env, decl->params[i].name, arr);
1005
725
        } else if (i < arg_count) {
1006
723
            env_define(ev->env, decl->params[i].name, args[i]);
1007
723
        } else {
1008
            /* Use default value */
1009
2
            EvalResult def = eval_expr(ev, decl->params[i].default_value);
1010
2
            if (!IS_OK(def)) {
1011
0
                env_pop_scope(ev->env);
1012
0
                stats_scope_pop(&ev->stats);
1013
0
                return def;
1014
0
            }
1015
2
            env_define(ev->env, decl->params[i].name, def.value);
1016
2
        }
1017
729
    }
1018
    /* Evaluate require contracts */
1019
1.25k
    if (ev->assertions_enabled && decl->contracts) {
1020
11
        for (size_t i = 0; i < decl->contract_count; i++) {
1021
7
            if (decl->contracts[i].is_ensure) continue;
1022
5
            EvalResult cr = eval_expr(ev, decl->contracts[i].condition);
1023
5
            if (!IS_OK(cr)) { env_pop_scope(ev->env); stats_scope_pop(&ev->stats); return cr; }
1024
5
            bool truthy = (cr.value.type == VAL_BOOL && cr.value.as.bool_val);
1025
5
            value_free(&cr.value);
1026
5
            if (!truthy) {
1027
2
                char *err = NULL;
1028
2
                if (decl->contracts[i].message)
1029
2
                    (void)asprintf(&err, "require failed in '%s': %s", decl->name, decl->contracts[i].message);
1030
0
                else
1031
0
                    (void)asprintf(&err, "require contract failed in '%s'", decl->name);
1032
2
                env_pop_scope(ev->env); stats_scope_pop(&ev->stats);
1033
2
                return eval_err(err);
1034
2
            }
1035
5
        }
1036
6
    }
1037
1038
1.25k
    EvalResult result = eval_block_stmts(ev, decl->body, decl->body_count);
1039
1040
    /* Evaluate ensure contracts on the return value */
1041
1.25k
    if (ev->assertions_enabled && decl->contracts && (IS_OK(result) || (IS_SIGNAL(result) && result.cf.tag == CF_RETURN))) {
1042
4
        LatValue ret_val = IS_OK(result) ? result.value : result.cf.value;
1043
8
        for (size_t i = 0; i < decl->contract_count; i++) {
1044
5
            if (!decl->contracts[i].is_ensure) continue;
1045
            /* Ensure condition is a closure — call it with the return value */
1046
2
            EvalResult cc = eval_expr(ev, decl->contracts[i].condition);
1047
2
            if (!IS_OK(cc)) {
1048
0
                if (IS_OK(result)) value_free(&result.value);
1049
0
                else value_free(&result.cf.value);
1050
0
                env_pop_scope(ev->env); stats_scope_pop(&ev->stats);
1051
0
                return cc;
1052
0
            }
1053
2
            if (cc.value.type == VAL_CLOSURE) {
1054
2
                LatValue arg = value_deep_clone(&ret_val);
1055
2
                LatValue call_args[1] = { arg };
1056
2
                EvalResult er = call_closure(ev, cc.value.as.closure.param_names,
1057
2
                    cc.value.as.closure.param_count, cc.value.as.closure.body,
1058
2
                    cc.value.as.closure.captured_env, call_args, 1,
1059
2
                    cc.value.as.closure.default_values, cc.value.as.closure.has_variadic);
1060
2
                value_free(&arg);
1061
2
                value_free(&cc.value);
1062
2
                if (!IS_OK(er)) {
1063
0
                    if (IS_OK(result)) value_free(&result.value);
1064
0
                    else value_free(&result.cf.value);
1065
0
                    env_pop_scope(ev->env); stats_scope_pop(&ev->stats);
1066
0
                    return er;
1067
0
                }
1068
2
                bool truthy = (er.value.type == VAL_BOOL && er.value.as.bool_val);
1069
2
                value_free(&er.value);
1070
2
                if (!truthy) {
1071
1
                    if (IS_OK(result)) value_free(&result.value);
1072
1
                    else value_free(&result.cf.value);
1073
1
                    char *err = NULL;
1074
1
                    if (decl->contracts[i].message)
1075
1
                        (void)asprintf(&err, "ensure failed in '%s': %s", decl->name, decl->contracts[i].message);
1076
0
                    else
1077
0
                        (void)asprintf(&err, "ensure contract failed in '%s'", decl->name);
1078
1
                    env_pop_scope(ev->env); stats_scope_pop(&ev->stats);
1079
1
                    return eval_err(err);
1080
1
                }
1081
2
            } else {
1082
                /* ensure with a non-closure: evaluate as boolean directly */
1083
0
                bool truthy = (cc.value.type == VAL_BOOL && cc.value.as.bool_val);
1084
0
                value_free(&cc.value);
1085
0
                if (!truthy) {
1086
0
                    if (IS_OK(result)) value_free(&result.value);
1087
0
                    else value_free(&result.cf.value);
1088
0
                    char *err = NULL;
1089
0
                    if (decl->contracts[i].message)
1090
0
                        (void)asprintf(&err, "ensure failed in '%s': %s", decl->name, decl->contracts[i].message);
1091
0
                    else
1092
0
                        (void)asprintf(&err, "ensure contract failed in '%s'", decl->name);
1093
0
                    env_pop_scope(ev->env); stats_scope_pop(&ev->stats);
1094
0
                    return eval_err(err);
1095
0
                }
1096
0
            }
1097
2
        }
1098
4
    }
1099
1100
    /* Return type checking */
1101
1.25k
    if (decl->return_type && (IS_OK(result) || (IS_SIGNAL(result) && result.cf.tag == CF_RETURN))) {
1102
391
        LatValue *ret_val = IS_OK(result) ? &result.value : &result.cf.value;
1103
391
        if (!type_matches_value(ret_val, decl->return_type)) {
1104
2
            char *err = NULL;
1105
2
            (void)asprintf(&err, "function '%s' return type expects %s, got %s",
1106
2
                           decl->name, decl->return_type->name, value_type_display(ret_val));
1107
2
            if (IS_OK(result)) value_free(&result.value);
1108
2
            else value_free(&result.cf.value);
1109
2
            env_pop_scope(ev->env); stats_scope_pop(&ev->stats);
1110
2
            return eval_err(err);
1111
2
        }
1112
391
    }
1113
1114
    /* Before popping the scope, capture fluid parameter values for write-back */
1115
1.25k
    if (writeback_out) {
1116
412
        size_t wb_count = has_variadic ? decl->param_count - 1 : decl->param_count;
1117
412
        if (wb_count > arg_count) wb_count = arg_count;
1118
1.12k
        for (size_t i = 0; i < wb_count; i++) {
1119
713
            if (decl->params[i].ty.phase == PHASE_FLUID) {
1120
4
                LatValue val;
1121
4
                if (env_get(ev->env, decl->params[i].name, &val)) {
1122
4
                    writeback_out[i] = malloc(sizeof(LatValue));
1123
4
                    *writeback_out[i] = val;
1124
4
                }
1125
4
            }
1126
713
        }
1127
412
    }
1128
1129
1.25k
    env_pop_scope(ev->env);
1130
1.25k
    stats_scope_pop(&ev->stats);
1131
1132
1.25k
    if (IS_ERR(result)) return result; /* leave frame on stack for trace */
1133
1.15k
    ev_pop_frame(ev);
1134
1135
1.15k
    if (IS_SIGNAL(result) && result.cf.tag == CF_RETURN) {
1136
396
        return eval_ok(result.cf.value);
1137
396
    }
1138
763
    return result;
1139
1.15k
}
1140
1141
static EvalResult call_native_closure(Evaluator *ev, void *native_fn,
1142
64
                                      LatValue *args, size_t arg_count) {
1143
64
    (void)ev;
1144
64
    LatValue result = ext_call_native(native_fn, args, arg_count);
1145
    /* Check if the result is an error string */
1146
64
    if (result.type == VAL_STR && strncmp(result.as.str_val, "EVAL_ERROR:", 11) == 0) {
1147
3
        char *msg = strdup(result.as.str_val + 11);
1148
3
        value_free(&result);
1149
3
        return eval_err(msg);
1150
3
    }
1151
61
    return eval_ok(result);
1152
64
}
1153
1154
static EvalResult call_closure(Evaluator *ev, char **params, size_t param_count,
1155
                               const Expr *body, Env *closure_env, LatValue *args, size_t arg_count,
1156
821
                               Expr **default_values, bool has_variadic) {
1157
    /* Determine required count */
1158
821
    size_t required = 0;
1159
1.78k
    for (size_t i = 0; i < param_count; i++) {
1160
971
        if (has_variadic && i == param_count - 1) break;
1161
963
        if (!default_values || !default_values[i]) required++;
1162
963
    }
1163
821
    size_t max_positional = has_variadic ? param_count - 1 : param_count;
1164
821
    if (arg_count < required || (!has_variadic && arg_count > max_positional)) {
1165
0
        char *err = NULL;
1166
0
        if (has_variadic)
1167
0
            (void)asprintf(&err, "closure expects at least %zu arguments, got %zu", required, arg_count);
1168
0
        else if (required < max_positional)
1169
0
            (void)asprintf(&err, "closure expects %zu to %zu arguments, got %zu", required, max_positional, arg_count);
1170
0
        else
1171
0
            (void)asprintf(&err, "closure expects %zu arguments, got %zu", param_count, arg_count);
1172
0
        return eval_err(err);
1173
0
    }
1174
821
    stats_closure_call(&ev->stats);
1175
1176
    /* Swap environments — save caller env so GC can still mark it */
1177
821
    Env *saved = ev->env;
1178
821
    lat_vec_push(&ev->saved_envs, &saved);
1179
821
    ev->env = closure_env;
1180
821
    stats_scope_push(&ev->stats);
1181
821
    env_push_scope(ev->env);
1182
1.79k
    for (size_t i = 0; i < param_count; i++) {
1183
971
        if (has_variadic && i == param_count - 1) {
1184
            /* Collect remaining args into an array */
1185
8
            size_t rest_count = (arg_count > i) ? arg_count - i : 0;
1186
8
            LatValue *rest_elems = malloc(rest_count * sizeof(LatValue));
1187
19
            for (size_t j = 0; j < rest_count; j++)
1188
11
                rest_elems[j] = args[i + j];
1189
8
            LatValue arr = value_array(rest_elems, rest_count);
1190
8
            free(rest_elems);
1191
8
            env_define(ev->env, params[i], arr);
1192
963
        } else if (i < arg_count) {
1193
947
            env_define(ev->env, params[i], args[i]);
1194
947
        } else if (default_values && default_values[i]) {
1195
            /* Evaluate default in the closure environment */
1196
16
            EvalResult def = eval_expr(ev, default_values[i]);
1197
16
            if (!IS_OK(def)) {
1198
0
                env_pop_scope(ev->env);
1199
0
                stats_scope_pop(&ev->stats);
1200
0
                ev->env = saved;
1201
0
                lat_vec_pop(&ev->saved_envs, NULL);
1202
0
                return def;
1203
0
            }
1204
16
            env_define(ev->env, params[i], def.value);
1205
16
        }
1206
971
    }
1207
821
    EvalResult result = eval_expr(ev, body);
1208
821
    env_pop_scope(ev->env);
1209
821
    stats_scope_pop(&ev->stats);
1210
821
    ev->env = saved;
1211
821
    lat_vec_pop(&ev->saved_envs, NULL);
1212
1213
821
    if (IS_SIGNAL(result) && result.cf.tag == CF_RETURN) {
1214
385
        return eval_ok(result.cf.value);
1215
385
    }
1216
436
    return result;
1217
821
}
1218
1219
/* ── Value equality (for pattern matching) ── */
1220
1221
8
static bool value_equal(const LatValue *a, const LatValue *b) {
1222
8
    if (a->type != b->type) return false;
1223
8
    switch (a->type) {
1224
5
        case VAL_INT:   return a->as.int_val == b->as.int_val;
1225
0
        case VAL_FLOAT: return a->as.float_val == b->as.float_val;
1226
0
        case VAL_BOOL:  return a->as.bool_val == b->as.bool_val;
1227
2
        case VAL_STR:   return strcmp(a->as.str_val, b->as.str_val) == 0;
1228
0
        case VAL_UNIT:  return true;
1229
1
        case VAL_NIL:   return true;
1230
0
        default:        return false;
1231
8
    }
1232
8
}
1233
1234
/* ── Binary operations ── */
1235
1236
13.8k
static EvalResult eval_binop(BinOpKind op, LatValue *lv, LatValue *rv) {
1237
    /* Integer arithmetic */
1238
13.8k
    if (lv->type == VAL_INT && rv->type == VAL_INT) {
1239
13.1k
        int64_t a = lv->as.int_val, b = rv->as.int_val;
1240
13.1k
        switch (op) {
1241
6.86k
            case BINOP_ADD: return eval_ok(value_int(a + b));
1242
43
            case BINOP_SUB: return eval_ok(value_int(a - b));
1243
2.13k
            case BINOP_MUL: return eval_ok(value_int(a * b));
1244
11
            case BINOP_DIV:
1245
11
                if (b == 0) return eval_err(strdup("division by zero"));
1246
3
                return eval_ok(value_int(a / b));
1247
107
            case BINOP_MOD:
1248
107
                if (b == 0) return eval_err(strdup("modulo by zero"));
1249
107
                return eval_ok(value_int(a % b));
1250
214
            case BINOP_EQ:   return eval_ok(value_bool(a == b));
1251
6
            case BINOP_NEQ:  return eval_ok(value_bool(a != b));
1252
3.21k
            case BINOP_LT:   return eval_ok(value_bool(a < b));
1253
265
            case BINOP_GT:   return eval_ok(value_bool(a > b));
1254
23
            case BINOP_LTEQ: return eval_ok(value_bool(a <= b));
1255
210
            case BINOP_GTEQ: return eval_ok(value_bool(a >= b));
1256
5
            case BINOP_BIT_AND: return eval_ok(value_int(a & b));
1257
4
            case BINOP_BIT_OR:  return eval_ok(value_int(a | b));
1258
4
            case BINOP_BIT_XOR: return eval_ok(value_int(a ^ b));
1259
3
            case BINOP_LSHIFT:
1260
3
                if (b < 0 || b > 63) return eval_err(strdup("shift amount out of range (0..63)"));
1261
2
                return eval_ok(value_int(a << b));
1262
2
            case BINOP_RSHIFT:
1263
2
                if (b < 0 || b > 63) return eval_err(strdup("shift amount out of range (0..63)"));
1264
2
                return eval_ok(value_int(a >> b));
1265
0
            default: break;
1266
13.1k
        }
1267
13.1k
    }
1268
    /* Float arithmetic */
1269
786
    if (lv->type == VAL_FLOAT && rv->type == VAL_FLOAT) {
1270
12
        double a = lv->as.float_val, b = rv->as.float_val;
1271
12
        switch (op) {
1272
2
            case BINOP_ADD: return eval_ok(value_float(a + b));
1273
1
            case BINOP_SUB: return eval_ok(value_float(a - b));
1274
1
            case BINOP_MUL: return eval_ok(value_float(a * b));
1275
2
            case BINOP_DIV: return eval_ok(value_float(a / b));
1276
0
            case BINOP_MOD: {
1277
0
                double r = a - (int64_t)(a / b) * b;
1278
0
                return eval_ok(value_float(r));
1279
0
            }
1280
0
            case BINOP_EQ:   return eval_ok(value_bool(a == b));
1281
0
            case BINOP_NEQ:  return eval_ok(value_bool(a != b));
1282
2
            case BINOP_LT:   return eval_ok(value_bool(a < b));
1283
3
            case BINOP_GT:   return eval_ok(value_bool(a > b));
1284
0
            case BINOP_LTEQ: return eval_ok(value_bool(a <= b));
1285
1
            case BINOP_GTEQ: return eval_ok(value_bool(a >= b));
1286
0
            default: break;
1287
12
        }
1288
12
    }
1289
    /* Mixed int/float */
1290
774
    if ((lv->type == VAL_INT && rv->type == VAL_FLOAT) ||
1291
774
        (lv->type == VAL_FLOAT && rv->type == VAL_INT)) {
1292
1
        double a = lv->type == VAL_FLOAT ? lv->as.float_val : (double)lv->as.int_val;
1293
1
        double b = rv->type == VAL_FLOAT ? rv->as.float_val : (double)rv->as.int_val;
1294
1
        switch (op) {
1295
0
            case BINOP_ADD: return eval_ok(value_float(a + b));
1296
0
            case BINOP_SUB: return eval_ok(value_float(a - b));
1297
1
            case BINOP_MUL: return eval_ok(value_float(a * b));
1298
0
            case BINOP_DIV: return eval_ok(value_float(a / b));
1299
0
            default: break;
1300
1
        }
1301
1
    }
1302
    /* String concatenation */
1303
773
    if (lv->type == VAL_STR && rv->type == VAL_STR && op == BINOP_ADD) {
1304
274
        size_t al = strlen(lv->as.str_val), bl = strlen(rv->as.str_val);
1305
274
        char *buf = malloc(al + bl + 1);
1306
274
        memcpy(buf, lv->as.str_val, al);
1307
274
        memcpy(buf + al, rv->as.str_val, bl);
1308
274
        buf[al + bl] = '\0';
1309
274
        return eval_ok(value_string_owned(buf));
1310
274
    }
1311
    /* String comparison */
1312
499
    if (lv->type == VAL_STR && rv->type == VAL_STR) {
1313
370
        if (op == BINOP_EQ) return eval_ok(value_bool(strcmp(lv->as.str_val, rv->as.str_val) == 0));
1314
40
        if (op == BINOP_NEQ) return eval_ok(value_bool(strcmp(lv->as.str_val, rv->as.str_val) != 0));
1315
40
    }
1316
    /* Bool comparison */
1317
129
    if (lv->type == VAL_BOOL && rv->type == VAL_BOOL) {
1318
71
        if (op == BINOP_EQ) return eval_ok(value_bool(lv->as.bool_val == rv->as.bool_val));
1319
67
        if (op == BINOP_NEQ) return eval_ok(value_bool(lv->as.bool_val != rv->as.bool_val));
1320
65
        if (op == BINOP_AND) return eval_ok(value_bool(lv->as.bool_val && rv->as.bool_val));
1321
2
        if (op == BINOP_OR) return eval_ok(value_bool(lv->as.bool_val || rv->as.bool_val));
1322
2
    }
1323
    /* Nil equality: nil == nil is true, nil == anything_else is false */
1324
58
    if ((op == BINOP_EQ || op == BINOP_NEQ) &&
1325
58
        (lv->type == VAL_NIL || rv->type == VAL_NIL)) {
1326
48
        bool eq = (lv->type == VAL_NIL && rv->type == VAL_NIL);
1327
48
        return eval_ok(value_bool(op == BINOP_EQ ? eq : !eq));
1328
48
    }
1329
    /* General equality using value_eq (enums, arrays, structs, etc.) */
1330
10
    if (lv->type == rv->type && (op == BINOP_EQ || op == BINOP_NEQ)) {
1331
10
        bool eq = value_eq(lv, rv);
1332
10
        return eval_ok(value_bool(op == BINOP_EQ ? eq : !eq));
1333
10
    }
1334
1335
0
    char *err = NULL;
1336
0
    (void)asprintf(&err, "unsupported binary operation on %s and %s",
1337
0
                   value_type_name(lv), value_type_name(rv));
1338
0
    return eval_err(err);
1339
10
}
1340
1341
49
static EvalResult eval_unaryop(UnaryOpKind op, LatValue *val) {
1342
49
    if (op == UNOP_NEG && val->type == VAL_INT) return eval_ok(value_int(-val->as.int_val));
1343
34
    if (op == UNOP_NEG && val->type == VAL_FLOAT) return eval_ok(value_float(-val->as.float_val));
1344
33
    if (op == UNOP_NOT && val->type == VAL_BOOL) return eval_ok(value_bool(!val->as.bool_val));
1345
3
    if (op == UNOP_BIT_NOT && val->type == VAL_INT) return eval_ok(value_int(~val->as.int_val));
1346
0
    char *err = NULL;
1347
0
    (void)asprintf(&err, "unsupported unary operation on %s", value_type_name(val));
1348
0
    return eval_err(err);
1349
3
}
1350
1351
/* ── Concurrency infrastructure ── */
1352
1353
#ifndef __EMSCRIPTEN__
1354
typedef struct {
1355
    Stmt      **stmts;
1356
    size_t      stmt_count;
1357
    Evaluator  *child_ev;
1358
    char       *error;       /* NULL on success */
1359
    pthread_t   thread;
1360
} SpawnTask;
1361
1362
3
static Evaluator *create_child_evaluator(Evaluator *parent) {
1363
3
    Evaluator *child = calloc(1, sizeof(Evaluator));
1364
3
    child->env = env_clone(parent->env);
1365
3
    child->mode = parent->mode;
1366
    /* Share AST pointers (borrowed, immutable after parse) */
1367
3
    child->struct_defs = lat_map_new(sizeof(StructDecl *));
1368
51
    for (size_t i = 0; i < parent->struct_defs.cap; i++) {
1369
48
        if (parent->struct_defs.entries[i].state == MAP_OCCUPIED) {
1370
0
            lat_map_set(&child->struct_defs,
1371
0
                        parent->struct_defs.entries[i].key,
1372
0
                        parent->struct_defs.entries[i].value);
1373
0
        }
1374
48
    }
1375
3
    child->fn_defs = lat_map_new(sizeof(FnDecl *));
1376
51
    for (size_t i = 0; i < parent->fn_defs.cap; i++) {
1377
48
        if (parent->fn_defs.entries[i].state == MAP_OCCUPIED) {
1378
8
            lat_map_set(&child->fn_defs,
1379
8
                        parent->fn_defs.entries[i].key,
1380
8
                        parent->fn_defs.entries[i].value);
1381
8
        }
1382
48
    }
1383
3
    stats_init(&child->stats);
1384
3
    child->heap = dual_heap_new();
1385
3
    child->gc_roots = lat_vec_new(sizeof(LatValue *));
1386
3
    child->saved_envs = lat_vec_new(sizeof(Env *));
1387
3
    child->gc_stress = parent->gc_stress;
1388
3
    child->no_regions = parent->no_regions;
1389
3
    child->required_files = lat_map_new(sizeof(bool));
1390
3
    child->script_dir = parent->script_dir ? strdup(parent->script_dir) : NULL;
1391
3
    return child;
1392
3
}
1393
1394
3
static void free_child_evaluator(Evaluator *child) {
1395
3
    if (!child) return;
1396
3
    env_free(child->env);
1397
3
    lat_map_free(&child->struct_defs);
1398
3
    lat_map_free(&child->fn_defs);
1399
3
    lat_map_free(&child->required_files);
1400
3
    free(child->script_dir);
1401
3
    value_set_heap(NULL);
1402
3
    dual_heap_free(child->heap);
1403
3
    lat_vec_free(&child->gc_roots);
1404
3
    lat_vec_free(&child->saved_envs);
1405
3
    free(child);
1406
3
}
1407
1408
3
static void *spawn_thread_fn(void *arg) {
1409
3
    SpawnTask *task = (SpawnTask *)arg;
1410
3
    Evaluator *child = task->child_ev;
1411
1412
    /* Set thread-local heap for this child evaluator */
1413
3
    value_set_heap(child->heap);
1414
3
    value_set_arena(NULL);
1415
1416
3
    EvalResult result = eval_block_stmts(child, task->stmts, task->stmt_count);
1417
1418
3
    if (IS_ERR(result)) {
1419
1
        task->error = result.error;  /* transfer ownership */
1420
2
    } else if (IS_SIGNAL(result)) {
1421
0
        switch (result.cf.tag) {
1422
0
            case CF_RETURN:
1423
0
                task->error = strdup("cannot use 'return' inside spawn");
1424
0
                value_free(&result.cf.value);
1425
0
                break;
1426
0
            case CF_BREAK:
1427
0
                task->error = strdup("cannot use 'break' inside spawn");
1428
0
                break;
1429
0
            case CF_CONTINUE:
1430
0
                task->error = strdup("cannot use 'continue' inside spawn");
1431
0
                break;
1432
0
            default:
1433
0
                break;
1434
0
        }
1435
2
    } else {
1436
2
        value_free(&result.value);
1437
2
    }
1438
1439
3
    return NULL;
1440
3
}
1441
#endif /* __EMSCRIPTEN__ */
1442
1443
/* ── Expression evaluation ── */
1444
1445
68.3k
static EvalResult eval_expr_inner(Evaluator *ev, const Expr *expr) {
1446
68.3k
    switch (expr->tag) {
1447
13.6k
        case EXPR_INT_LIT:    return eval_ok(value_int(expr->as.int_val));
1448
83
        case EXPR_FLOAT_LIT:  return eval_ok(value_float(expr->as.float_val));
1449
3.41k
        case EXPR_STRING_LIT: return eval_ok(value_string(expr->as.str_val));
1450
327
        case EXPR_BOOL_LIT:   return eval_ok(value_bool(expr->as.bool_val));
1451
85
        case EXPR_NIL_LIT:    return eval_ok(value_nil());
1452
1453
22.8k
        case EXPR_IDENT: {
1454
22.8k
            LatValue val;
1455
22.8k
            if (!env_get(ev->env, expr->as.str_val, &val)) {
1456
4
                char *err = NULL;
1457
4
                (void)asprintf(&err, "undefined variable '%s'", expr->as.str_val);
1458
4
                return eval_err(err);
1459
4
            }
1460
22.8k
            return eval_ok(val);
1461
22.8k
        }
1462
1463
13.8k
        case EXPR_BINOP: {
1464
            /* Short-circuit: ?? (nil coalescing) */
1465
13.8k
            if (expr->as.binop.op == BINOP_NIL_COALESCE) {
1466
6
                EvalResult lr = eval_expr(ev, expr->as.binop.left);
1467
6
                if (!IS_OK(lr)) return lr;
1468
6
                if (lr.value.type != VAL_NIL) return lr;
1469
5
                value_free(&lr.value);
1470
5
                return eval_expr(ev, expr->as.binop.right);
1471
6
            }
1472
13.8k
            EvalResult lr = eval_expr(ev, expr->as.binop.left);
1473
13.8k
            if (!IS_OK(lr)) return lr;
1474
13.8k
            GC_PUSH(ev, &lr.value);
1475
13.8k
            EvalResult rr = eval_expr(ev, expr->as.binop.right);
1476
13.8k
            GC_POP(ev);
1477
13.8k
            if (!IS_OK(rr)) { value_free(&lr.value); return rr; }
1478
13.8k
            EvalResult res = eval_binop(expr->as.binop.op, &lr.value, &rr.value);
1479
13.8k
            value_free(&lr.value);
1480
13.8k
            value_free(&rr.value);
1481
13.8k
            return res;
1482
13.8k
        }
1483
1484
49
        case EXPR_UNARYOP: {
1485
49
            EvalResult vr = eval_expr(ev, expr->as.unaryop.operand);
1486
49
            if (!IS_OK(vr)) return vr;
1487
49
            EvalResult res = eval_unaryop(expr->as.unaryop.op, &vr.value);
1488
49
            value_free(&vr.value);
1489
49
            return res;
1490
49
        }
1491
1492
2.42k
        case EXPR_CALL: {
1493
            /* ── track(x) / history(x) / phases(x) / rewind(x, n): convert ident to string ── */
1494
2.42k
            if (expr->as.call.func->tag == EXPR_IDENT) {
1495
2.42k
                const char *cfn = expr->as.call.func->as.str_val;
1496
2.42k
                bool is_1arg = (strcmp(cfn, "track") == 0 || strcmp(cfn, "history") == 0 ||
1497
2.42k
                                strcmp(cfn, "phases") == 0);
1498
2.42k
                bool is_rewind = strcmp(cfn, "rewind") == 0;
1499
2.42k
                if ((is_1arg && expr->as.call.arg_count == 1 &&
1500
2.42k
                     expr->as.call.args[0]->tag == EXPR_IDENT) ||
1501
2.42k
                    (is_rewind && expr->as.call.arg_count == 2 &&
1502
2.41k
                     expr->as.call.args[0]->tag == EXPR_IDENT)) {
1503
                    /* Temporarily swap first arg to a string literal with the variable name */
1504
10
                    Expr *orig = expr->as.call.args[0];
1505
10
                    Expr tmp_str;
1506
10
                    tmp_str.tag = EXPR_STRING_LIT;
1507
10
                    tmp_str.as.str_val = orig->as.str_val;
1508
10
                    tmp_str.line = orig->line;
1509
10
                    expr->as.call.args[0] = &tmp_str;
1510
10
                    EvalResult r = eval_expr(ev, expr);
1511
10
                    expr->as.call.args[0] = orig; /* restore */
1512
10
                    return r;
1513
10
                }
1514
2.42k
            }
1515
            /* ── react() / unreact(): special handling before arg evaluation ── */
1516
2.41k
            if (expr->as.call.func->tag == EXPR_IDENT &&
1517
2.41k
                strcmp(expr->as.call.func->as.str_val, "react") == 0) {
1518
9
                size_t argc = expr->as.call.arg_count;
1519
9
                if (argc != 2) {
1520
0
                    return eval_err(strdup("react() requires exactly 2 arguments (variable, callback)"));
1521
0
                }
1522
9
                if (expr->as.call.args[0]->tag != EXPR_IDENT) {
1523
0
                    return eval_err(strdup("react() first argument must be a variable name"));
1524
0
                }
1525
9
                const char *var_name = expr->as.call.args[0]->as.str_val;
1526
                /* Verify variable exists */
1527
9
                LatValue tmp;
1528
9
                if (!env_get(ev->env, var_name, &tmp)) {
1529
0
                    char *err = NULL;
1530
0
                    (void)asprintf(&err, "cannot react to undefined variable '%s'", var_name);
1531
0
                    return eval_err(err);
1532
0
                }
1533
9
                value_free(&tmp);
1534
                /* Evaluate the callback */
1535
9
                EvalResult cbr = eval_expr(ev, expr->as.call.args[1]);
1536
9
                if (!IS_OK(cbr)) return cbr;
1537
9
                if (cbr.value.type != VAL_CLOSURE) {
1538
0
                    value_free(&cbr.value);
1539
0
                    return eval_err(strdup("react() second argument must be a closure"));
1540
0
                }
1541
                /* Find or create ReactionEntry for var_name */
1542
9
                ReactionEntry *re = NULL;
1543
9
                for (size_t i = 0; i < ev->reaction_count; i++) {
1544
1
                    if (strcmp(ev->reactions[i].var_name, var_name) == 0) {
1545
1
                        re = &ev->reactions[i];
1546
1
                        break;
1547
1
                    }
1548
1
                }
1549
9
                if (!re) {
1550
8
                    if (ev->reaction_count >= ev->reaction_cap) {
1551
8
                        ev->reaction_cap = ev->reaction_cap ? ev->reaction_cap * 2 : 4;
1552
8
                        ev->reactions = realloc(ev->reactions, ev->reaction_cap * sizeof(ReactionEntry));
1553
8
                    }
1554
8
                    re = &ev->reactions[ev->reaction_count++];
1555
8
                    re->var_name = strdup(var_name);
1556
8
                    re->callbacks = NULL;
1557
8
                    re->cb_count = 0;
1558
8
                    re->cb_cap = 0;
1559
8
                }
1560
                /* Add the callback */
1561
9
                if (re->cb_count >= re->cb_cap) {
1562
8
                    re->cb_cap = re->cb_cap ? re->cb_cap * 2 : 4;
1563
8
                    re->callbacks = realloc(re->callbacks, re->cb_cap * sizeof(LatValue));
1564
8
                }
1565
9
                re->callbacks[re->cb_count++] = value_deep_clone(&cbr.value);
1566
9
                value_free(&cbr.value);
1567
9
                return eval_ok(value_unit());
1568
9
            }
1569
2.40k
            if (expr->as.call.func->tag == EXPR_IDENT &&
1570
2.40k
                strcmp(expr->as.call.func->as.str_val, "unreact") == 0) {
1571
1
                size_t argc = expr->as.call.arg_count;
1572
1
                if (argc != 1) {
1573
0
                    return eval_err(strdup("unreact() requires exactly 1 argument (variable)"));
1574
0
                }
1575
1
                if (expr->as.call.args[0]->tag != EXPR_IDENT) {
1576
0
                    return eval_err(strdup("unreact() argument must be a variable name"));
1577
0
                }
1578
1
                const char *var_name = expr->as.call.args[0]->as.str_val;
1579
                /* Find and remove ReactionEntry */
1580
1
                for (size_t i = 0; i < ev->reaction_count; i++) {
1581
1
                    if (strcmp(ev->reactions[i].var_name, var_name) == 0) {
1582
1
                        free(ev->reactions[i].var_name);
1583
2
                        for (size_t j = 0; j < ev->reactions[i].cb_count; j++)
1584
1
                            value_free(&ev->reactions[i].callbacks[j]);
1585
1
                        free(ev->reactions[i].callbacks);
1586
1
                        ev->reactions[i] = ev->reactions[--ev->reaction_count];
1587
1
                        break;
1588
1
                    }
1589
1
                }
1590
1
                return eval_ok(value_unit());
1591
1
            }
1592
            /* ── bond() / unbond(): special handling before arg evaluation ── */
1593
2.40k
            if (expr->as.call.func->tag == EXPR_IDENT &&
1594
2.40k
                (strcmp(expr->as.call.func->as.str_val, "bond") == 0 ||
1595
2.40k
                 strcmp(expr->as.call.func->as.str_val, "unbond") == 0)) {
1596
16
                bool is_bond = strcmp(expr->as.call.func->as.str_val, "bond") == 0;
1597
16
                size_t argc = expr->as.call.arg_count;
1598
16
                if (argc < 2) {
1599
0
                    return eval_err(strdup(is_bond
1600
0
                        ? "bond() requires at least 2 arguments (target, dep[, strategy])"
1601
0
                        : "unbond() requires at least 2 arguments (target, ...deps)"));
1602
0
                }
1603
                /* First 2 arguments must be identifiers; optional 3rd can be string (strategy) */
1604
16
                if (expr->as.call.args[0]->tag != EXPR_IDENT ||
1605
16
                    expr->as.call.args[1]->tag != EXPR_IDENT) {
1606
1
                    return eval_err(strdup(is_bond
1607
1
                        ? "bond() requires variable names for first two arguments"
1608
1
                        : "unbond() requires variable names, not expressions"));
1609
1
                }
1610
15
                if (!is_bond) {
1611
1
                    for (size_t i = 2; i < argc; i++) {
1612
0
                        if (expr->as.call.args[i]->tag != EXPR_IDENT) {
1613
0
                            return eval_err(strdup("unbond() requires variable names, not expressions"));
1614
0
                        }
1615
0
                    }
1616
1
                }
1617
15
                const char *target = expr->as.call.args[0]->as.str_val;
1618
15
                if (is_bond) {
1619
                    /* Determine strategy: if last arg is a string literal, use as strategy.
1620
                     * Otherwise all args after target are deps with default "mirror" strategy.
1621
                     * bond(target, dep) — mirror
1622
                     * bond(target, dep, "inverse") — explicit strategy
1623
                     * bond(target, dep1, dep2, ...) — multiple deps, all mirror */
1624
14
                    char *strategy = NULL;
1625
14
                    size_t dep_end = argc;  /* how many args are deps (excluding target at 0) */
1626
14
                    if (argc >= 3 && expr->as.call.args[argc - 1]->tag == EXPR_STRING_LIT) {
1627
                        /* Last arg is a string literal — treat as strategy */
1628
3
                        const char *sval = expr->as.call.args[argc - 1]->as.str_val;
1629
3
                        if (strcmp(sval, "mirror") == 0 || strcmp(sval, "inverse") == 0 ||
1630
3
                            strcmp(sval, "gate") == 0) {
1631
3
                            strategy = strdup(sval);
1632
3
                            dep_end = argc - 1;
1633
3
                        }
1634
3
                    }
1635
14
                    if (!strategy) strategy = strdup("mirror");
1636
                    /* All args from 1..dep_end-1 must be idents (dep vars) */
1637
30
                    for (size_t i = 1; i < dep_end; i++) {
1638
16
                        if (expr->as.call.args[i]->tag != EXPR_IDENT) {
1639
0
                            free(strategy);
1640
0
                            return eval_err(strdup("bond() dependency arguments must be variable names"));
1641
0
                        }
1642
16
                    }
1643
                    /* Verify all variables exist and are not frozen */
1644
41
                    for (size_t i = 0; i < dep_end; i++) {
1645
29
                        const char *vname = expr->as.call.args[i]->as.str_val;
1646
29
                        LatValue tmp;
1647
29
                        if (!env_get(ev->env, vname, &tmp)) {
1648
1
                            char *err = NULL;
1649
1
                            (void)asprintf(&err, "cannot bond undefined variable '%s'", vname);
1650
1
                            free(strategy);
1651
1
                            return eval_err(err);
1652
1
                        }
1653
28
                        if (tmp.phase == VTAG_CRYSTAL) {
1654
1
                            value_free(&tmp);
1655
1
                            char *err = NULL;
1656
1
                            (void)asprintf(&err, "cannot bond already-frozen variable '%s'", vname);
1657
1
                            free(strategy);
1658
1
                            return eval_err(err);
1659
1
                        }
1660
27
                        value_free(&tmp);
1661
27
                    }
1662
                    /* Register bonds for each dep */
1663
12
                    BondEntry *be = NULL;
1664
13
                    for (size_t i = 0; i < ev->bond_count; i++) {
1665
1
                        if (strcmp(ev->bonds[i].target, target) == 0) { be = &ev->bonds[i]; break; }
1666
1
                    }
1667
12
                    if (!be) {
1668
12
                        if (ev->bond_count >= ev->bond_cap) {
1669
11
                            ev->bond_cap = ev->bond_cap ? ev->bond_cap * 2 : 4;
1670
11
                            ev->bonds = realloc(ev->bonds, ev->bond_cap * sizeof(BondEntry));
1671
11
                        }
1672
12
                        be = &ev->bonds[ev->bond_count++];
1673
12
                        be->target = strdup(target);
1674
12
                        be->deps = NULL;
1675
12
                        be->dep_strategies = NULL;
1676
12
                        be->dep_count = 0;
1677
12
                        be->dep_cap = 0;
1678
12
                    }
1679
26
                    for (size_t di = 1; di < dep_end; di++) {
1680
14
                        const char *dep = expr->as.call.args[di]->as.str_val;
1681
14
                        if (be->dep_count >= be->dep_cap) {
1682
12
                            be->dep_cap = be->dep_cap ? be->dep_cap * 2 : 4;
1683
12
                            be->deps = realloc(be->deps, be->dep_cap * sizeof(char *));
1684
12
                            be->dep_strategies = realloc(be->dep_strategies, be->dep_cap * sizeof(char *));
1685
12
                        }
1686
14
                        be->deps[be->dep_count] = strdup(dep);
1687
14
                        be->dep_strategies[be->dep_count] = strdup(strategy);
1688
14
                        be->dep_count++;
1689
14
                    }
1690
12
                    free(strategy);
1691
12
                } else {
1692
                    /* unbond: remove deps from target's bond set */
1693
1
                    for (size_t i = 0; i < ev->bond_count; i++) {
1694
1
                        if (strcmp(ev->bonds[i].target, target) == 0) {
1695
2
                            for (size_t j = 1; j < argc; j++) {
1696
1
                                const char *dep = expr->as.call.args[j]->as.str_val;
1697
1
                                for (size_t k = 0; k < ev->bonds[i].dep_count; k++) {
1698
1
                                    if (strcmp(ev->bonds[i].deps[k], dep) == 0) {
1699
1
                                        free(ev->bonds[i].deps[k]);
1700
1
                                        if (ev->bonds[i].dep_strategies) free(ev->bonds[i].dep_strategies[k]);
1701
1
                                        ev->bonds[i].deps[k] = ev->bonds[i].deps[ev->bonds[i].dep_count - 1];
1702
1
                                        if (ev->bonds[i].dep_strategies)
1703
1
                                            ev->bonds[i].dep_strategies[k] = ev->bonds[i].dep_strategies[ev->bonds[i].dep_count - 1];
1704
1
                                        ev->bonds[i].dep_count--;
1705
1
                                        break;
1706
1
                                    }
1707
1
                                }
1708
1
                            }
1709
                            /* Remove entry if empty */
1710
1
                            if (ev->bonds[i].dep_count == 0) {
1711
1
                                free(ev->bonds[i].target);
1712
1
                                free(ev->bonds[i].deps);
1713
1
                                free(ev->bonds[i].dep_strategies);
1714
1
                                ev->bonds[i] = ev->bonds[--ev->bond_count];
1715
1
                            }
1716
1
                            break;
1717
1
                        }
1718
1
                    }
1719
1
                }
1720
13
                return eval_ok(value_unit());
1721
15
            }
1722
1723
            /* ── seed() / unseed(): special handling before arg evaluation ── */
1724
2.39k
            if (expr->as.call.func->tag == EXPR_IDENT &&
1725
2.39k
                strcmp(expr->as.call.func->as.str_val, "seed") == 0) {
1726
4
                size_t argc = expr->as.call.arg_count;
1727
4
                if (argc != 2) return eval_err(strdup("seed() requires exactly 2 arguments (variable, contract)"));
1728
4
                if (expr->as.call.args[0]->tag != EXPR_IDENT)
1729
0
                    return eval_err(strdup("seed() first argument must be a variable name"));
1730
4
                const char *var_name = expr->as.call.args[0]->as.str_val;
1731
4
                LatValue tmp;
1732
4
                if (!env_get(ev->env, var_name, &tmp)) {
1733
0
                    char *err = NULL;
1734
0
                    (void)asprintf(&err, "seed(): undefined variable '%s'", var_name);
1735
0
                    return eval_err(err);
1736
0
                }
1737
4
                value_free(&tmp);
1738
4
                EvalResult cbr = eval_expr(ev, expr->as.call.args[1]);
1739
4
                if (!IS_OK(cbr)) return cbr;
1740
4
                if (cbr.value.type != VAL_CLOSURE) {
1741
0
                    value_free(&cbr.value);
1742
0
                    return eval_err(strdup("seed() second argument must be a closure"));
1743
0
                }
1744
                /* Store seed entry */
1745
4
                if (ev->seed_count >= ev->seed_cap) {
1746
4
                    ev->seed_cap = ev->seed_cap ? ev->seed_cap * 2 : 4;
1747
4
                    ev->seeds = realloc(ev->seeds, ev->seed_cap * sizeof(SeedEntry));
1748
4
                }
1749
4
                ev->seeds[ev->seed_count].var_name = strdup(var_name);
1750
4
                ev->seeds[ev->seed_count].contract = value_deep_clone(&cbr.value);
1751
4
                ev->seed_count++;
1752
4
                value_free(&cbr.value);
1753
4
                return eval_ok(value_unit());
1754
4
            }
1755
2.38k
            if (expr->as.call.func->tag == EXPR_IDENT &&
1756
2.38k
                strcmp(expr->as.call.func->as.str_val, "unseed") == 0) {
1757
1
                size_t argc = expr->as.call.arg_count;
1758
1
                if (argc != 1) return eval_err(strdup("unseed() requires exactly 1 argument (variable)"));
1759
1
                if (expr->as.call.args[0]->tag != EXPR_IDENT)
1760
0
                    return eval_err(strdup("unseed() argument must be a variable name"));
1761
1
                const char *var_name = expr->as.call.args[0]->as.str_val;
1762
1
                for (size_t i = 0; i < ev->seed_count; i++) {
1763
1
                    if (strcmp(ev->seeds[i].var_name, var_name) == 0) {
1764
1
                        free(ev->seeds[i].var_name);
1765
1
                        value_free(&ev->seeds[i].contract);
1766
1
                        ev->seeds[i] = ev->seeds[--ev->seed_count];
1767
1
                        break;
1768
1
                    }
1769
1
                }
1770
1
                return eval_ok(value_unit());
1771
1
            }
1772
            /* ── pressurize() / depressurize(): special handling before arg evaluation ── */
1773
2.38k
            if (expr->as.call.func->tag == EXPR_IDENT &&
1774
2.38k
                strcmp(expr->as.call.func->as.str_val, "pressurize") == 0) {
1775
6
                size_t argc = expr->as.call.arg_count;
1776
6
                if (argc != 2) return eval_err(strdup("pressurize() requires 2 arguments (variable, mode)"));
1777
6
                if (expr->as.call.args[0]->tag != EXPR_IDENT)
1778
0
                    return eval_err(strdup("pressurize() first argument must be a variable name"));
1779
6
                const char *var_name = expr->as.call.args[0]->as.str_val;
1780
6
                LatValue tmp;
1781
6
                if (!env_get(ev->env, var_name, &tmp)) {
1782
0
                    char *err = NULL;
1783
0
                    (void)asprintf(&err, "pressurize(): undefined variable '%s'", var_name);
1784
0
                    return eval_err(err);
1785
0
                }
1786
6
                value_free(&tmp);
1787
6
                EvalResult mr = eval_expr(ev, expr->as.call.args[1]);
1788
6
                if (!IS_OK(mr)) return mr;
1789
6
                if (mr.value.type != VAL_STR) {
1790
0
                    value_free(&mr.value);
1791
0
                    return eval_err(strdup("pressurize() mode must be a string"));
1792
0
                }
1793
6
                const char *mode = mr.value.as.str_val;
1794
6
                if (strcmp(mode, "no_grow") != 0 && strcmp(mode, "no_shrink") != 0 &&
1795
6
                    strcmp(mode, "no_resize") != 0 && strcmp(mode, "read_heavy") != 0) {
1796
0
                    char *err = NULL;
1797
0
                    (void)asprintf(&err, "pressurize() unknown mode '%s'", mode);
1798
0
                    value_free(&mr.value);
1799
0
                    return eval_err(err);
1800
0
                }
1801
                /* Find or create PressureEntry */
1802
6
                PressureEntry *pe = NULL;
1803
6
                for (size_t i = 0; i < ev->pressure_count; i++) {
1804
0
                    if (strcmp(ev->pressures[i].var_name, var_name) == 0) { pe = &ev->pressures[i]; break; }
1805
0
                }
1806
6
                if (pe) {
1807
0
                    free(pe->mode);
1808
0
                    pe->mode = strdup(mode);
1809
6
                } else {
1810
6
                    if (ev->pressure_count >= ev->pressure_cap) {
1811
6
                        ev->pressure_cap = ev->pressure_cap ? ev->pressure_cap * 2 : 4;
1812
6
                        ev->pressures = realloc(ev->pressures, ev->pressure_cap * sizeof(PressureEntry));
1813
6
                    }
1814
6
                    pe = &ev->pressures[ev->pressure_count++];
1815
6
                    pe->var_name = strdup(var_name);
1816
6
                    pe->mode = strdup(mode);
1817
6
                }
1818
6
                value_free(&mr.value);
1819
6
                return eval_ok(value_unit());
1820
6
            }
1821
2.37k
            if (expr->as.call.func->tag == EXPR_IDENT &&
1822
2.37k
                strcmp(expr->as.call.func->as.str_val, "depressurize") == 0) {
1823
1
                size_t argc = expr->as.call.arg_count;
1824
1
                if (argc != 1) return eval_err(strdup("depressurize() requires 1 argument (variable)"));
1825
1
                if (expr->as.call.args[0]->tag != EXPR_IDENT)
1826
0
                    return eval_err(strdup("depressurize() argument must be a variable name"));
1827
1
                const char *var_name = expr->as.call.args[0]->as.str_val;
1828
1
                for (size_t i = 0; i < ev->pressure_count; i++) {
1829
1
                    if (strcmp(ev->pressures[i].var_name, var_name) == 0) {
1830
1
                        free(ev->pressures[i].var_name);
1831
1
                        free(ev->pressures[i].mode);
1832
1
                        ev->pressures[i] = ev->pressures[--ev->pressure_count];
1833
1
                        break;
1834
1
                    }
1835
1
                }
1836
1
                return eval_ok(value_unit());
1837
1
            }
1838
1839
            /* Evaluate arguments */
1840
2.37k
            size_t argc = expr->as.call.arg_count;
1841
2.37k
            LatValue *args = malloc(argc * sizeof(LatValue));
1842
4.80k
            for (size_t i = 0; i < argc; i++) {
1843
2.42k
                EvalResult ar = eval_expr(ev, expr->as.call.args[i]);
1844
2.42k
                if (!IS_OK(ar)) {
1845
0
                    GC_POP_N(ev, i);
1846
0
                    for (size_t j = 0; j < i; j++) value_free(&args[j]);
1847
0
                    free(args);
1848
0
                    return ar;
1849
0
                }
1850
2.42k
                args[i] = ar.value;
1851
2.42k
                GC_PUSH(ev, &args[i]);
1852
2.42k
            }
1853
2.37k
            GC_POP_N(ev, argc);
1854
            /* Check for named function or built-in */
1855
2.37k
            if (expr->as.call.func->tag == EXPR_IDENT) {
1856
2.37k
                const char *fn_name = expr->as.call.func->as.str_val;
1857
1858
                /* ── Built-in functions ── */
1859
1860
                /// @builtin input(prompt?: String) -> String
1861
                /// @category Core
1862
                /// Read a line of input from stdin, optionally displaying a prompt.
1863
                /// @example input("Name: ")  // reads user input
1864
2.37k
                if (strcmp(fn_name, "input") == 0) {
1865
0
                    const char *prompt = NULL;
1866
0
                    if (argc > 0 && args[0].type == VAL_STR) prompt = args[0].as.str_val;
1867
0
                    char *line = builtin_input(prompt);
1868
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
1869
0
                    free(args);
1870
0
                    if (!line) return eval_ok(value_unit());
1871
0
                    return eval_ok(value_string_owned(line));
1872
0
                }
1873
1874
                /// @builtin is_complete(source: String) -> Bool
1875
                /// @category Metaprogramming
1876
                /// Check if a source string is a complete expression (balanced brackets).
1877
                /// @example is_complete("{ 1 + 2 }")  // true
1878
2.37k
                if (strcmp(fn_name, "is_complete") == 0) {
1879
4
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_complete() expects 1 string argument")); }
1880
4
                    const char *source = args[0].as.str_val;
1881
4
                    Lexer lex = lexer_new(source);
1882
4
                    char *lex_err = NULL;
1883
4
                    LatVec toks = lexer_tokenize(&lex, &lex_err);
1884
4
                    if (lex_err) {
1885
                        /* Lex error (unclosed string etc.) = incomplete */
1886
0
                        free(lex_err);
1887
0
                        lat_vec_free(&toks);
1888
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
1889
0
                        free(args);
1890
0
                        return eval_ok(value_bool(false));
1891
0
                    }
1892
4
                    int depth = 0;
1893
23
                    for (size_t j = 0; j < toks.len; j++) {
1894
19
                        Token *t = lat_vec_get(&toks, j);
1895
19
                        switch (t->type) {
1896
4
                            case TOK_LBRACE: case TOK_LPAREN: case TOK_LBRACKET:
1897
4
                                depth++;
1898
4
                                break;
1899
3
                            case TOK_RBRACE: case TOK_RPAREN: case TOK_RBRACKET:
1900
3
                                depth--;
1901
3
                                break;
1902
12
                            default:
1903
12
                                break;
1904
19
                        }
1905
19
                    }
1906
23
                    for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j));
1907
4
                    lat_vec_free(&toks);
1908
8
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
1909
4
                    free(args);
1910
4
                    return eval_ok(value_bool(depth <= 0));
1911
4
                }
1912
1913
                /// @builtin typeof(val: Any) -> String
1914
                /// @category Core
1915
                /// Returns the type name of a value as a string.
1916
                /// @example typeof(42)  // "Int"
1917
2.37k
                if (strcmp(fn_name, "typeof") == 0) {
1918
72
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("typeof() expects 1 argument")); }
1919
72
                    const char *tn = builtin_typeof_str(&args[0]);
1920
144
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
1921
72
                    free(args);
1922
72
                    return eval_ok(value_string(tn));
1923
72
                }
1924
1925
                /// @builtin struct_name(val: Struct) -> String
1926
                /// @category Reflection
1927
                /// Returns the type name of a struct instance.
1928
                /// @example struct_name(user)  // "User"
1929
2.30k
                if (strcmp(fn_name, "struct_name") == 0) {
1930
1
                    if (argc != 1 || args[0].type != VAL_STRUCT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("struct_name() expects 1 Struct argument")); }
1931
1
                    const char *sn = args[0].as.strct.name;
1932
1
                    LatValue result = value_string(sn);
1933
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
1934
1
                    free(args);
1935
1
                    return eval_ok(result);
1936
1
                }
1937
1938
                /// @builtin struct_fields(val: Struct) -> Array
1939
                /// @category Reflection
1940
                /// Returns an array of field name strings from a struct instance.
1941
                /// @example struct_fields(user)  // ["name", "age"]
1942
2.30k
                if (strcmp(fn_name, "struct_fields") == 0) {
1943
1
                    if (argc != 1 || args[0].type != VAL_STRUCT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("struct_fields() expects 1 Struct argument")); }
1944
1
                    size_t fc = args[0].as.strct.field_count;
1945
1
                    LatValue *elems = malloc((fc > 0 ? fc : 1) * sizeof(LatValue));
1946
3
                    for (size_t j = 0; j < fc; j++) {
1947
2
                        elems[j] = value_string(args[0].as.strct.field_names[j]);
1948
2
                    }
1949
1
                    LatValue result = value_array(elems, fc);
1950
1
                    free(elems);
1951
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
1952
1
                    free(args);
1953
1
                    return eval_ok(result);
1954
1
                }
1955
1956
                /// @builtin struct_to_map(val: Struct) -> Map
1957
                /// @category Reflection
1958
                /// Converts a struct instance to a Map of {field_name: value}.
1959
                /// @example struct_to_map(user).get("name")  // "Alice"
1960
2.30k
                if (strcmp(fn_name, "struct_to_map") == 0) {
1961
2
                    if (argc != 1 || args[0].type != VAL_STRUCT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("struct_to_map() expects 1 Struct argument")); }
1962
2
                    LatValue map = value_map_new();
1963
2
                    size_t fc = args[0].as.strct.field_count;
1964
6
                    for (size_t j = 0; j < fc; j++) {
1965
4
                        LatValue v = value_deep_clone(&args[0].as.strct.field_values[j]);
1966
4
                        lat_map_set(map.as.map.map, args[0].as.strct.field_names[j], &v);
1967
4
                    }
1968
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
1969
2
                    free(args);
1970
2
                    return eval_ok(map);
1971
2
                }
1972
1973
                /// @builtin struct_from_map(name: String, map: Map) -> Struct
1974
                /// @category Reflection
1975
                /// Creates a struct instance from a type name and a Map of field values.
1976
                /// Missing fields default to nil.
1977
                /// @example struct_from_map("User", m)
1978
2.29k
                if (strcmp(fn_name, "struct_from_map") == 0) {
1979
3
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_MAP) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("struct_from_map() expects (name: String, map: Map)")); }
1980
3
                    const char *sname = args[0].as.str_val;
1981
3
                    StructDecl *sd = find_struct(ev, sname);
1982
3
                    if (!sd) {
1983
1
                        char *err = NULL;
1984
1
                        (void)asprintf(&err, "struct_from_map: undefined struct '%s'", sname);
1985
3
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
1986
1
                        free(args);
1987
1
                        return eval_err(err);
1988
1
                    }
1989
2
                    size_t fc = sd->field_count;
1990
2
                    char **names = malloc(fc * sizeof(char *));
1991
2
                    LatValue *vals = malloc(fc * sizeof(LatValue));
1992
6
                    for (size_t j = 0; j < fc; j++) {
1993
4
                        names[j] = sd->fields[j].name;
1994
4
                        LatValue *found = (LatValue *)lat_map_get(args[1].as.map.map, sd->fields[j].name);
1995
4
                        if (found) {
1996
3
                            vals[j] = value_deep_clone(found);
1997
3
                        } else {
1998
1
                            vals[j] = value_nil();
1999
1
                        }
2000
4
                    }
2001
2
                    stats_struct(&ev->stats);
2002
2
                    LatValue st = value_struct(sname, names, vals, fc);
2003
2
                    free(names);
2004
2
                    free(vals);
2005
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2006
2
                    free(args);
2007
2
                    return eval_ok(st);
2008
3
                }
2009
2010
                /// @builtin phase_of(val: Any) -> String
2011
                /// @category Core
2012
                /// Returns the phase of a value ("flux", "fix", or "crystal").
2013
                /// @example phase_of(freeze([1, 2]))  // "crystal"
2014
2.29k
                if (strcmp(fn_name, "phase_of") == 0) {
2015
29
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("phase_of() expects 1 argument")); }
2016
29
                    const char *pn = builtin_phase_of_str(&args[0]);
2017
58
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2018
29
                    free(args);
2019
29
                    return eval_ok(value_string(pn));
2020
29
                }
2021
2022
                /// @builtin to_string(val: Any) -> String
2023
                /// @category Core
2024
                /// Convert any value to its string representation.
2025
                /// @example to_string(42)  // "42"
2026
2.26k
                if (strcmp(fn_name, "to_string") == 0) {
2027
177
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("to_string() expects 1 argument")); }
2028
177
                    char *s = builtin_to_string(&args[0]);
2029
354
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2030
177
                    free(args);
2031
177
                    return eval_ok(value_string_owned(s));
2032
177
                }
2033
2034
                /// @builtin repr(val: Any) -> String
2035
                /// @category Core
2036
                /// Return the repr string of a value.  Strings are quoted,
2037
                /// structs with a `repr` closure field use the custom representation.
2038
                /// @example repr(42)        // "42"
2039
                /// @example repr("hello")   // "\"hello\""
2040
2.08k
                if (strcmp(fn_name, "repr") == 0) {
2041
12
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("repr() expects 1 argument")); }
2042
12
                    char *s = eval_repr(ev, &args[0]);
2043
24
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2044
12
                    free(args);
2045
12
                    return eval_ok(value_string_owned(s));
2046
12
                }
2047
2048
                /// @builtin track(name: String) -> Unit
2049
                /// @category Temporal
2050
                /// Enable phase history tracking for a variable.
2051
                /// @example track("counter")
2052
2.07k
                if (strcmp(fn_name, "track") == 0) {
2053
14
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("track() expects 1 String argument")); }
2054
14
                    const char *vname = args[0].as.str_val;
2055
14
                    LatValue cur;
2056
14
                    if (!env_get(ev->env, vname, &cur)) {
2057
1
                        char *err = NULL; (void)asprintf(&err, "track(): undefined variable '%s'", vname);
2058
2
                        for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
2059
1
                        return eval_err(err);
2060
1
                    }
2061
                    /* Check if already tracked */
2062
14
                    bool already = false;
2063
13
                    for (size_t i = 0; i < ev->tracked_count; i++) {
2064
0
                        if (strcmp(ev->tracked_vars[i].name, vname) == 0) { already = true; break; }
2065
0
                    }
2066
13
                    if (!already) {
2067
13
                        if (ev->tracked_count >= ev->tracked_cap) {
2068
13
                            ev->tracked_cap = ev->tracked_cap ? ev->tracked_cap * 2 : 4;
2069
13
                            ev->tracked_vars = realloc(ev->tracked_vars, ev->tracked_cap * sizeof(TrackedVar));
2070
13
                        }
2071
13
                        TrackedVar *tv = &ev->tracked_vars[ev->tracked_count++];
2072
13
                        tv->name = strdup(vname);
2073
13
                        tv->history.snapshots = NULL;
2074
13
                        tv->history.count = 0;
2075
13
                        tv->history.cap = 0;
2076
                        /* Record initial snapshot */
2077
13
                        const char *phase = builtin_phase_of_str(&cur);
2078
13
                        if (tv->history.count >= tv->history.cap) {
2079
13
                            tv->history.cap = tv->history.cap ? tv->history.cap * 2 : 8;
2080
13
                            tv->history.snapshots = realloc(tv->history.snapshots, tv->history.cap * sizeof(HistorySnapshot));
2081
13
                        }
2082
13
                        tv->history.snapshots[tv->history.count].phase_name = strdup(phase);
2083
13
                        tv->history.snapshots[tv->history.count].value = value_deep_clone(&cur);
2084
13
                        tv->history.snapshots[tv->history.count].line = 0;
2085
13
                        tv->history.snapshots[tv->history.count].fn_name = NULL;
2086
13
                        tv->history.count++;
2087
13
                    }
2088
13
                    value_free(&cur);
2089
26
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
2090
13
                    return eval_ok(value_unit());
2091
14
                }
2092
2093
                /// @builtin phases(name: String) -> Array
2094
                /// @category Temporal
2095
                /// Returns the phase history of a tracked variable as an array of Maps.
2096
                /// @example phases("counter")  // [{phase: "fluid", value: 0}, ...]
2097
2.06k
                if (strcmp(fn_name, "phases") == 0) {
2098
8
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("phases() expects 1 String argument")); }
2099
8
                    const char *vname = args[0].as.str_val;
2100
8
                    VariableHistory *vh = NULL;
2101
8
                    for (size_t i = 0; i < ev->tracked_count; i++) {
2102
7
                        if (strcmp(ev->tracked_vars[i].name, vname) == 0) { vh = &ev->tracked_vars[i].history; break; }
2103
7
                    }
2104
16
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
2105
8
                    if (!vh || vh->count == 0) return eval_ok(value_array(NULL, 0));
2106
7
                    LatValue *elems = malloc(vh->count * sizeof(LatValue));
2107
23
                    for (size_t i = 0; i < vh->count; i++) {
2108
16
                        LatValue m = value_map_new();
2109
16
                        LatValue pv = value_string(vh->snapshots[i].phase_name);
2110
16
                        LatValue vv = value_deep_clone(&vh->snapshots[i].value);
2111
16
                        LatValue lv = value_int(vh->snapshots[i].line);
2112
16
                        LatValue fv = vh->snapshots[i].fn_name ? value_string(vh->snapshots[i].fn_name) : value_nil();
2113
16
                        lat_map_set(m.as.map.map, "phase", &pv);
2114
16
                        lat_map_set(m.as.map.map, "value", &vv);
2115
16
                        lat_map_set(m.as.map.map, "line", &lv);
2116
16
                        lat_map_set(m.as.map.map, "fn", &fv);
2117
16
                        elems[i] = m;
2118
16
                    }
2119
7
                    LatValue result = value_array(elems, vh->count);
2120
7
                    free(elems);
2121
7
                    return eval_ok(result);
2122
8
                }
2123
2124
                /// @builtin history(name: String) -> Array
2125
                /// @category Temporal
2126
                /// Returns the full enriched timeline of a tracked variable as an array of Maps
2127
                /// with keys: phase, value, line, fn.
2128
                /// @example history(x)  // [{phase: "fluid", value: 10, line: 3, fn: "main"}, ...]
2129
2.05k
                if (strcmp(fn_name, "history") == 0) {
2130
4
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("history() expects 1 String argument")); }
2131
4
                    const char *vname = args[0].as.str_val;
2132
4
                    VariableHistory *vh = NULL;
2133
4
                    for (size_t i = 0; i < ev->tracked_count; i++) {
2134
3
                        if (strcmp(ev->tracked_vars[i].name, vname) == 0) { vh = &ev->tracked_vars[i].history; break; }
2135
3
                    }
2136
8
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
2137
4
                    if (!vh || vh->count == 0) return eval_ok(value_array(NULL, 0));
2138
3
                    LatValue *elems = malloc(vh->count * sizeof(LatValue));
2139
10
                    for (size_t i = 0; i < vh->count; i++) {
2140
7
                        LatValue m = value_map_new();
2141
7
                        LatValue pv = value_string(vh->snapshots[i].phase_name);
2142
7
                        LatValue vv = value_deep_clone(&vh->snapshots[i].value);
2143
7
                        LatValue lv = value_int(vh->snapshots[i].line);
2144
7
                        LatValue fv = vh->snapshots[i].fn_name ? value_string(vh->snapshots[i].fn_name) : value_nil();
2145
7
                        lat_map_set(m.as.map.map, "phase", &pv);
2146
7
                        lat_map_set(m.as.map.map, "value", &vv);
2147
7
                        lat_map_set(m.as.map.map, "line", &lv);
2148
7
                        lat_map_set(m.as.map.map, "fn", &fv);
2149
7
                        elems[i] = m;
2150
7
                    }
2151
3
                    LatValue result = value_array(elems, vh->count);
2152
3
                    free(elems);
2153
3
                    return eval_ok(result);
2154
4
                }
2155
2156
                /// @builtin rewind(name: String, n: Int) -> Any
2157
                /// @category Temporal
2158
                /// Returns a deep copy of a tracked variable from n steps ago.
2159
                /// @example rewind("counter", 2)  // value from 2 steps back
2160
2.05k
                if (strcmp(fn_name, "rewind") == 0) {
2161
7
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("rewind() expects (String, Int)")); }
2162
7
                    const char *vname = args[0].as.str_val;
2163
7
                    int64_t steps = args[1].as.int_val;
2164
7
                    VariableHistory *vh = NULL;
2165
7
                    for (size_t i = 0; i < ev->tracked_count; i++) {
2166
7
                        if (strcmp(ev->tracked_vars[i].name, vname) == 0) { vh = &ev->tracked_vars[i].history; break; }
2167
7
                    }
2168
21
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
2169
7
                    if (!vh || steps < 0 || (size_t)steps >= vh->count)
2170
1
                        return eval_ok(value_nil());
2171
6
                    size_t idx = vh->count - 1 - (size_t)steps;
2172
6
                    return eval_ok(value_deep_clone(&vh->snapshots[idx].value));
2173
7
                }
2174
2175
                /// @builtin grow(name: String) -> Any
2176
                /// @category Phase Transitions
2177
                /// Freeze a variable and validate any pending seed contracts.
2178
                /// @example grow(config)  // freeze + validate seeds
2179
2.04k
                if (strcmp(fn_name, "grow") == 0) {
2180
2
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("grow() expects 1 String argument (variable name)")); }
2181
2
                    const char *vname = args[0].as.str_val;
2182
2
                    LatValue val;
2183
2
                    if (!env_get(ev->env, vname, &val)) {
2184
0
                        char *err = NULL; (void)asprintf(&err, "grow(): undefined variable '%s'", vname);
2185
0
                        for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
2186
0
                        return eval_err(err);
2187
0
                    }
2188
                    /* Check and validate all seeds for this variable */
2189
3
                    for (size_t si = 0; si < ev->seed_count; si++) {
2190
2
                        if (strcmp(ev->seeds[si].var_name, vname) != 0) continue;
2191
2
                        LatValue check_val = value_deep_clone(&val);
2192
2
                        LatValue *cb = &ev->seeds[si].contract;
2193
2
                        EvalResult vr = call_closure(ev, cb->as.closure.param_names,
2194
2
                            cb->as.closure.param_count, cb->as.closure.body,
2195
2
                            cb->as.closure.captured_env, &check_val, 1,
2196
2
                            cb->as.closure.default_values, cb->as.closure.has_variadic);
2197
2
                        if (!IS_OK(vr)) {
2198
0
                            char *msg = NULL;
2199
0
                            (void)asprintf(&msg, "grow() seed contract failed: %s", vr.error);
2200
0
                            free(vr.error); value_free(&val);
2201
0
                            for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
2202
0
                            return eval_err(msg);
2203
0
                        }
2204
2
                        if (!value_is_truthy(&vr.value)) {
2205
1
                            value_free(&vr.value); value_free(&val);
2206
2
                            for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
2207
1
                            return eval_err(strdup("grow() seed contract returned false"));
2208
1
                        }
2209
1
                        value_free(&vr.value);
2210
                        /* Remove this seed */
2211
1
                        free(ev->seeds[si].var_name);
2212
1
                        value_free(&ev->seeds[si].contract);
2213
1
                        ev->seeds[si] = ev->seeds[--ev->seed_count];
2214
1
                        si--;  /* re-check this index */
2215
1
                    }
2216
                    /* Freeze the variable */
2217
1
                    val = value_freeze(val);
2218
1
                    freeze_to_region(ev, &val);
2219
1
                    LatValue ret = value_deep_clone(&val);
2220
1
                    env_set(ev->env, vname, val);
2221
1
                    record_history(ev, vname);
2222
1
                    {
2223
1
                        char *cascade_err = freeze_cascade(ev, vname);
2224
1
                        if (cascade_err) {
2225
0
                            for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
2226
0
                            value_free(&ret);
2227
0
                            return eval_err(cascade_err);
2228
0
                        }
2229
1
                    }
2230
1
                    EvalResult fr = fire_reactions(ev, vname, "crystal");
2231
2
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
2232
1
                    if (!IS_OK(fr)) { value_free(&ret); return fr; }
2233
1
                    return eval_ok(ret);
2234
1
                }
2235
2236
                /// @builtin pressure_of(name: String) -> String|Nil
2237
                /// @category Phase Pressure
2238
                /// Returns the current pressure mode of a variable, or nil if none.
2239
                /// @example pressure_of("data")  // "no_grow"
2240
2.04k
                if (strcmp(fn_name, "pressure_of") == 0) {
2241
2
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("pressure_of() expects 1 String argument")); }
2242
2
                    const char *vname = args[0].as.str_val;
2243
2
                    for (size_t i = 0; i < ev->pressure_count; i++) {
2244
1
                        if (strcmp(ev->pressures[i].var_name, vname) == 0) {
2245
1
                            LatValue result = value_string(ev->pressures[i].mode);
2246
2
                            for (size_t j = 0; j < argc; j++) { value_free(&args[j]); } free(args);
2247
1
                            return eval_ok(result);
2248
1
                        }
2249
1
                    }
2250
2
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
2251
1
                    return eval_ok(value_nil());
2252
2
                }
2253
2254
                /// @builtin ord(ch: String) -> Int
2255
                /// @category Type Conversion
2256
                /// Return the Unicode code point of the first character.
2257
                /// @example ord("A")  // 65
2258
2.04k
                if (strcmp(fn_name, "ord") == 0) {
2259
4
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("ord() expects 1 string argument")); }
2260
4
                    int64_t code = builtin_ord(args[0].as.str_val);
2261
8
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2262
4
                    free(args);
2263
4
                    return eval_ok(value_int(code));
2264
4
                }
2265
2266
                /// @builtin chr(code: Int) -> String
2267
                /// @category Type Conversion
2268
                /// Return the character for a Unicode code point.
2269
                /// @example chr(65)  // "A"
2270
2.03k
                if (strcmp(fn_name, "chr") == 0) {
2271
2
                    if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("chr() expects 1 integer argument")); }
2272
2
                    char *s = builtin_chr(args[0].as.int_val);
2273
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2274
2
                    free(args);
2275
2
                    return eval_ok(value_string_owned(s));
2276
2
                }
2277
2278
                /// @builtin read_file(path: String) -> String
2279
                /// @category File System
2280
                /// Read the entire contents of a file as a string.
2281
                /// @example read_file("data.txt")  // "file contents..."
2282
2.03k
                if (strcmp(fn_name, "read_file") == 0) {
2283
4
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("read_file() expects 1 string argument")); }
2284
4
                    char *contents = builtin_read_file(args[0].as.str_val);
2285
8
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2286
4
                    free(args);
2287
4
                    if (!contents) return eval_err(strdup("read_file: could not read file"));
2288
4
                    return eval_ok(value_string_owned(contents));
2289
4
                }
2290
2291
                /// @builtin write_file(path: String, content: String) -> Bool
2292
                /// @category File System
2293
                /// Write a string to a file, creating or overwriting it.
2294
                /// @example write_file("out.txt", "hello")  // true
2295
2.03k
                if (strcmp(fn_name, "write_file") == 0) {
2296
13
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("write_file() expects 2 string arguments")); }
2297
13
                    bool wf_ok = builtin_write_file(args[0].as.str_val, args[1].as.str_val);
2298
39
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2299
13
                    free(args);
2300
13
                    if (!wf_ok) return eval_err(strdup("write_file: could not write file"));
2301
13
                    return eval_ok(value_bool(true));
2302
13
                }
2303
2304
                /// @builtin file_exists(path: String) -> Bool
2305
                /// @category File System
2306
                /// Check if a file or directory exists at the given path.
2307
                /// @example file_exists("data.txt")  // true
2308
2.01k
                if (strcmp(fn_name, "file_exists") == 0) {
2309
8
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("file_exists() expects 1 string argument")); }
2310
8
                    bool exists = fs_file_exists(args[0].as.str_val);
2311
14
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2312
7
                    free(args);
2313
7
                    return eval_ok(value_bool(exists));
2314
8
                }
2315
2316
                /// @builtin delete_file(path: String) -> Bool
2317
                /// @category File System
2318
                /// Delete a file at the given path.
2319
                /// @example delete_file("temp.txt")  // true
2320
2.00k
                if (strcmp(fn_name, "delete_file") == 0) {
2321
13
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("delete_file() expects 1 string argument")); }
2322
12
                    char *df_err = NULL;
2323
12
                    bool df_ok = fs_delete_file(args[0].as.str_val, &df_err);
2324
24
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2325
12
                    free(args);
2326
12
                    if (!df_ok) { char *e = df_err; return eval_err(e); }
2327
11
                    return eval_ok(value_bool(true));
2328
12
                }
2329
2330
                /// @builtin list_dir(path: String) -> Array
2331
                /// @category File System
2332
                /// List entries in a directory, returning an array of filenames.
2333
                /// @example list_dir(".")  // ["file1.txt", "dir1", ...]
2334
1.99k
                if (strcmp(fn_name, "list_dir") == 0) {
2335
3
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("list_dir() expects 1 string argument")); }
2336
2
                    char *ld_err = NULL;
2337
2
                    size_t ld_count = 0;
2338
2
                    char **ld_entries = fs_list_dir(args[0].as.str_val, &ld_count, &ld_err);
2339
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2340
2
                    free(args);
2341
2
                    if (!ld_entries) { char *e = ld_err; return eval_err(e); }
2342
1
                    LatValue *elems = malloc(ld_count * sizeof(LatValue));
2343
1.62k
                    for (size_t i = 0; i < ld_count; i++) {
2344
1.62k
                        elems[i] = value_string(ld_entries[i]);
2345
1.62k
                        free(ld_entries[i]);
2346
1.62k
                    }
2347
1
                    free(ld_entries);
2348
1
                    LatValue arr = value_array(elems, ld_count);
2349
1
                    free(elems);
2350
1
                    return eval_ok(arr);
2351
2
                }
2352
2353
                /// @builtin read_file_bytes(path: String) -> Buffer
2354
                /// @category File System
2355
                /// Read the entire contents of a file as a Buffer.
2356
                /// @example read_file_bytes("data.bin")  // Buffer<...>
2357
1.99k
                if (strcmp(fn_name, "read_file_bytes") == 0) {
2358
0
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("read_file_bytes() expects 1 string argument")); }
2359
0
                    FILE *bf = fopen(args[0].as.str_val, "rb");
2360
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2361
0
                    free(args);
2362
0
                    if (!bf) return eval_err(strdup("read_file_bytes: could not read file"));
2363
0
                    fseek(bf, 0, SEEK_END);
2364
0
                    long bflen = ftell(bf);
2365
0
                    fseek(bf, 0, SEEK_SET);
2366
0
                    if (bflen < 0) { fclose(bf); return eval_err(strdup("read_file_bytes: could not read file")); }
2367
0
                    uint8_t *bfdata = malloc((size_t)bflen);
2368
0
                    size_t bfnread = fread(bfdata, 1, (size_t)bflen, bf);
2369
0
                    fclose(bf);
2370
0
                    LatValue buf = value_buffer(bfdata, bfnread);
2371
0
                    free(bfdata);
2372
0
                    return eval_ok(buf);
2373
0
                }
2374
2375
                /// @builtin write_file_bytes(path: String, buffer: Buffer) -> Bool
2376
                /// @category File System
2377
                /// Write a Buffer to a file.
2378
                /// @example write_file_bytes("out.bin", buf)  // true
2379
1.99k
                if (strcmp(fn_name, "write_file_bytes") == 0) {
2380
0
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_BUFFER) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("write_file_bytes() expects (String, Buffer)")); }
2381
0
                    FILE *wbf = fopen(args[0].as.str_val, "wb");
2382
0
                    if (!wbf) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("write_file_bytes: could not write file")); }
2383
0
                    size_t wbwritten = fwrite(args[1].as.buffer.data, 1, args[1].as.buffer.len, wbf);
2384
0
                    fclose(wbf);
2385
0
                    bool wbok = (wbwritten == args[1].as.buffer.len);
2386
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2387
0
                    free(args);
2388
0
                    return eval_ok(value_bool(wbok));
2389
0
                }
2390
2391
                /// @builtin append_file(path: String, content: String) -> Bool
2392
                /// @category File System
2393
                /// Append a string to the end of a file.
2394
                /// @example append_file("log.txt", "new line\n")  // true
2395
1.99k
                if (strcmp(fn_name, "append_file") == 0) {
2396
3
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("append_file() expects 2 string arguments")); }
2397
2
                    char *af_err = NULL;
2398
2
                    bool af_ok = fs_append_file(args[0].as.str_val, args[1].as.str_val, &af_err);
2399
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2400
2
                    free(args);
2401
2
                    if (!af_ok) { char *e = af_err; return eval_err(e); }
2402
2
                    return eval_ok(value_bool(true));
2403
2
                }
2404
2405
                /// @builtin mkdir(path: String) -> Bool
2406
                /// @category File System
2407
                /// Create a directory at the given path.
2408
                /// @example mkdir("new_dir")  // true
2409
1.99k
                if (strcmp(fn_name, "mkdir") == 0) {
2410
3
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("mkdir() expects 1 string argument")); }
2411
3
                    char *mk_err = NULL;
2412
3
                    bool mk_ok = fs_mkdir(args[0].as.str_val, &mk_err);
2413
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2414
3
                    free(args);
2415
3
                    if (!mk_ok) { free(mk_err); return eval_ok(value_bool(false)); }
2416
3
                    return eval_ok(value_bool(true));
2417
3
                }
2418
2419
                /// @builtin rename(old_path: String, new_path: String) -> Bool
2420
                /// @category File System
2421
                /// Rename or move a file or directory.
2422
                /// @example rename("old.txt", "new.txt")  // true
2423
1.98k
                if (strcmp(fn_name, "rename") == 0) {
2424
1
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("rename() expects 2 string arguments")); }
2425
1
                    char *rn_err = NULL;
2426
1
                    bool rn_ok = fs_rename(args[0].as.str_val, args[1].as.str_val, &rn_err);
2427
3
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2428
1
                    free(args);
2429
1
                    if (!rn_ok) { free(rn_err); return eval_ok(value_bool(false)); }
2430
1
                    return eval_ok(value_bool(true));
2431
1
                }
2432
2433
                /// @builtin is_dir(path: String) -> Bool
2434
                /// @category File System
2435
                /// Check if the path points to a directory.
2436
                /// @example is_dir("/tmp")  // true
2437
1.98k
                if (strcmp(fn_name, "is_dir") == 0) {
2438
5
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_dir() expects 1 string argument")); }
2439
5
                    bool result = fs_is_dir(args[0].as.str_val);
2440
10
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2441
5
                    free(args);
2442
5
                    return eval_ok(value_bool(result));
2443
5
                }
2444
2445
                /// @builtin is_file(path: String) -> Bool
2446
                /// @category File System
2447
                /// Check if the path points to a regular file.
2448
                /// @example is_file("data.txt")  // true
2449
1.98k
                if (strcmp(fn_name, "is_file") == 0) {
2450
3
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_file() expects 1 string argument")); }
2451
3
                    bool result = fs_is_file(args[0].as.str_val);
2452
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2453
3
                    free(args);
2454
3
                    return eval_ok(value_bool(result));
2455
3
                }
2456
2457
                /// @builtin rmdir(path: String) -> Bool
2458
                /// @category File System
2459
                /// Remove a directory (must be empty).
2460
                /// @example rmdir("old_dir")  // true
2461
1.97k
                if (strcmp(fn_name, "rmdir") == 0) {
2462
4
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("rmdir() expects 1 string argument")); }
2463
4
                    char *rm_err = NULL;
2464
4
                    bool rm_ok = fs_rmdir(args[0].as.str_val, &rm_err);
2465
8
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2466
4
                    free(args);
2467
4
                    if (!rm_ok) { char *e = rm_err; return eval_err(e); }
2468
3
                    return eval_ok(value_bool(true));
2469
4
                }
2470
2471
                /// @builtin glob(pattern: String) -> Array
2472
                /// @category File System
2473
                /// Find files matching a glob pattern, returning an array of paths.
2474
                /// @example glob("*.txt")  // ["a.txt", "b.txt"]
2475
1.97k
                if (strcmp(fn_name, "glob") == 0) {
2476
2
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("glob() expects 1 string argument")); }
2477
2
                    char *gl_err = NULL;
2478
2
                    size_t gl_count = 0;
2479
2
                    char **gl_entries = fs_glob(args[0].as.str_val, &gl_count, &gl_err);
2480
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2481
2
                    free(args);
2482
2
                    if (gl_err) { return eval_err(gl_err); }
2483
                    /* Build array of strings */
2484
2
                    LatValue *elems = NULL;
2485
2
                    if (gl_count > 0) {
2486
1
                        elems = malloc(gl_count * sizeof(LatValue));
2487
3
                        for (size_t i = 0; i < gl_count; i++) {
2488
2
                            elems[i] = value_string_owned(gl_entries[i]);
2489
2
                        }
2490
1
                        free(gl_entries);
2491
1
                    }
2492
2
                    LatValue arr = value_array(elems, gl_count);
2493
2
                    free(elems);
2494
2
                    return eval_ok(arr);
2495
2
                }
2496
2497
                /// @builtin stat(path: String) -> Map
2498
                /// @category File System
2499
                /// Get file metadata (size, mtime, type, permissions) as a map.
2500
                /// @example stat("file.txt")  // {size: 1024, mtime: ..., type: "file", permissions: 644}
2501
1.97k
                if (strcmp(fn_name, "stat") == 0) {
2502
3
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("stat() expects 1 string argument")); }
2503
3
                    int64_t st_size = 0, st_mtime = 0, st_mode = 0;
2504
3
                    const char *st_type = NULL;
2505
3
                    char *st_err = NULL;
2506
3
                    bool st_ok = fs_stat(args[0].as.str_val, &st_size, &st_mtime, &st_mode, &st_type, &st_err);
2507
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2508
3
                    free(args);
2509
3
                    if (!st_ok) { return eval_err(st_err); }
2510
                    /* Build result Map — lat_map_set does shallow memcpy,
2511
                     * so the map takes ownership of internal pointers.
2512
                     * Do NOT value_free the temporaries. */
2513
2
                    LatValue map = value_map_new();
2514
2
                    LatValue sz = value_int(st_size);
2515
2
                    lat_map_set(map.as.map.map, "size", &sz);
2516
2
                    LatValue mt = value_int(st_mtime);
2517
2
                    lat_map_set(map.as.map.map, "mtime", &mt);
2518
2
                    LatValue ty = value_string(st_type);
2519
2
                    lat_map_set(map.as.map.map, "type", &ty);
2520
2
                    LatValue pm = value_int(st_mode);
2521
2
                    lat_map_set(map.as.map.map, "permissions", &pm);
2522
2
                    return eval_ok(map);
2523
3
                }
2524
2525
                /// @builtin copy_file(src: String, dest: String) -> Bool
2526
                /// @category File System
2527
                /// Copy a file from source path to destination path.
2528
                /// @example copy_file("a.txt", "b.txt")  // true
2529
1.96k
                if (strcmp(fn_name, "copy_file") == 0) {
2530
2
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("copy_file() expects 2 string arguments")); }
2531
2
                    char *cp_err = NULL;
2532
2
                    bool cp_ok = fs_copy_file(args[0].as.str_val, args[1].as.str_val, &cp_err);
2533
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2534
2
                    free(args);
2535
2
                    if (!cp_ok) { return eval_err(cp_err); }
2536
1
                    return eval_ok(value_bool(true));
2537
2
                }
2538
2539
                /// @builtin realpath(path: String) -> String
2540
                /// @category File System
2541
                /// Resolve a path to its absolute canonical form.
2542
                /// @example realpath("./src/../src")  // "/home/user/src"
2543
1.96k
                if (strcmp(fn_name, "realpath") == 0) {
2544
2
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("realpath() expects 1 string argument")); }
2545
2
                    char *rp_err = NULL;
2546
2
                    char *rp_result = fs_realpath(args[0].as.str_val, &rp_err);
2547
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2548
2
                    free(args);
2549
2
                    if (!rp_result) { return eval_err(rp_err); }
2550
1
                    return eval_ok(value_string_owned(rp_result));
2551
2
                }
2552
2553
                /// @builtin tempdir() -> String
2554
                /// @category File System
2555
                /// Create a temporary directory and return its path.
2556
                /// @example tempdir()  // "/tmp/lat_XXXXXX"
2557
1.96k
                if (strcmp(fn_name, "tempdir") == 0) {
2558
1
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tempdir() expects no arguments")); }
2559
1
                    free(args);
2560
1
                    char *td_err = NULL;
2561
1
                    char *td_result = fs_tempdir(&td_err);
2562
1
                    if (!td_result) { return eval_err(td_err); }
2563
1
                    return eval_ok(value_string_owned(td_result));
2564
1
                }
2565
2566
                /// @builtin tempfile() -> String
2567
                /// @category File System
2568
                /// Create a temporary file and return its path.
2569
                /// @example tempfile()  // "/tmp/lat_XXXXXX"
2570
1.96k
                if (strcmp(fn_name, "tempfile") == 0) {
2571
2
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tempfile() expects no arguments")); }
2572
2
                    free(args);
2573
2
                    char *tf_err = NULL;
2574
2
                    char *tf_result = fs_tempfile(&tf_err);
2575
2
                    if (!tf_result) { return eval_err(tf_err); }
2576
2
                    return eval_ok(value_string_owned(tf_result));
2577
2
                }
2578
2579
                /// @builtin chmod(path: String, mode: Int) -> Bool
2580
                /// @category File System
2581
                /// Change file permissions using a numeric mode.
2582
                /// @example chmod("script.sh", 755)  // true
2583
1.96k
                if (strcmp(fn_name, "chmod") == 0) {
2584
1
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("chmod() expects 2 arguments (string path, integer mode)")); }
2585
1
                    char *ch_err = NULL;
2586
1
                    bool ch_ok = fs_chmod(args[0].as.str_val, (int)args[1].as.int_val, &ch_err);
2587
3
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2588
1
                    free(args);
2589
1
                    if (!ch_ok) { return eval_err(ch_err); }
2590
1
                    return eval_ok(value_bool(true));
2591
1
                }
2592
2593
                /// @builtin file_size(path: String) -> Int
2594
                /// @category File System
2595
                /// Return the size of a file in bytes.
2596
                /// @example file_size("data.bin")  // 4096
2597
1.96k
                if (strcmp(fn_name, "file_size") == 0) {
2598
2
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("file_size() expects 1 string argument")); }
2599
2
                    char *fs_err = NULL;
2600
2
                    int64_t sz = fs_file_size(args[0].as.str_val, &fs_err);
2601
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2602
2
                    free(args);
2603
2
                    if (sz < 0) { return eval_err(fs_err); }
2604
1
                    return eval_ok(value_int(sz));
2605
2
                }
2606
2607
                /* ── Path builtins ── */
2608
2609
                /// @builtin path_join(parts: String...) -> String
2610
                /// @category Path
2611
                /// Join path components into a single path string.
2612
                /// @example path_join("/home", "user", "file.txt")  // "/home/user/file.txt"
2613
1.95k
                if (strcmp(fn_name, "path_join") == 0) {
2614
5
                    if (argc < 1) { free(args); return eval_err(strdup("path_join() expects at least 1 argument")); }
2615
14
                    for (size_t i = 0; i < argc; i++) {
2616
10
                        if (args[i].type != VAL_STR) { for (size_t j = 0; j < argc; j++) { value_free(&args[j]); } free(args); return eval_err(strdup("path_join() expects String arguments")); }
2617
10
                    }
2618
4
                    const char **parts = malloc(argc * sizeof(char*));
2619
13
                    for (size_t i = 0; i < argc; i++) parts[i] = args[i].as.str_val;
2620
4
                    char *result = path_join(parts, argc);
2621
4
                    free(parts);
2622
13
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2623
4
                    free(args);
2624
4
                    return eval_ok(value_string_owned(result));
2625
5
                }
2626
2627
                /// @builtin path_dir(path: String) -> String
2628
                /// @category Path
2629
                /// Return the directory component of a path.
2630
                /// @example path_dir("/home/user/file.txt")  // "/home/user"
2631
1.95k
                if (strcmp(fn_name, "path_dir") == 0) {
2632
5
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("path_dir() expects 1 String argument")); }
2633
4
                    char *result = path_dir(args[0].as.str_val);
2634
8
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2635
4
                    free(args);
2636
4
                    return eval_ok(value_string_owned(result));
2637
5
                }
2638
2639
                /// @builtin path_base(path: String) -> String
2640
                /// @category Path
2641
                /// Return the filename component of a path.
2642
                /// @example path_base("/home/user/file.txt")  // "file.txt"
2643
1.94k
                if (strcmp(fn_name, "path_base") == 0) {
2644
4
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("path_base() expects 1 String argument")); }
2645
3
                    char *result = path_base(args[0].as.str_val);
2646
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2647
3
                    free(args);
2648
3
                    return eval_ok(value_string_owned(result));
2649
4
                }
2650
2651
                /// @builtin path_ext(path: String) -> String
2652
                /// @category Path
2653
                /// Return the file extension of a path (including the dot).
2654
                /// @example path_ext("file.txt")  // ".txt"
2655
1.94k
                if (strcmp(fn_name, "path_ext") == 0) {
2656
6
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("path_ext() expects 1 String argument")); }
2657
5
                    char *result = path_ext(args[0].as.str_val);
2658
10
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2659
5
                    free(args);
2660
5
                    return eval_ok(value_string_owned(result));
2661
6
                }
2662
2663
                /// @builtin require(path: String) -> Bool
2664
                /// @category Metaprogramming
2665
                /// Load and execute a Lattice source file, importing its definitions.
2666
                /// @example require("stdlib.lat")  // true
2667
1.93k
                if (strcmp(fn_name, "require") == 0) {
2668
9
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("require() expects 1 string argument")); }
2669
9
                    const char *raw_path = args[0].as.str_val;
2670
                    /* Build the file path: append .lat if not already present */
2671
9
                    size_t plen = strlen(raw_path);
2672
9
                    char *file_path;
2673
9
                    if (plen >= 4 && strcmp(raw_path + plen - 4, ".lat") == 0) {
2674
2
                        file_path = strdup(raw_path);
2675
7
                    } else {
2676
7
                        file_path = malloc(plen + 5);
2677
7
                        memcpy(file_path, raw_path, plen);
2678
7
                        memcpy(file_path + plen, ".lat", 5);
2679
7
                    }
2680
                    /* Resolve to an absolute path for dedup.
2681
                     * Try cwd-relative first, then script_dir-relative. */
2682
9
                    char resolved[PATH_MAX];
2683
9
                    bool found = (realpath(file_path, resolved) != NULL);
2684
9
                    if (!found && ev->script_dir && file_path[0] != '/') {
2685
                        /* Try relative to the script's directory */
2686
0
                        char script_rel[PATH_MAX];
2687
0
                        snprintf(script_rel, sizeof(script_rel), "%s/%s",
2688
0
                                 ev->script_dir, file_path);
2689
0
                        found = (realpath(script_rel, resolved) != NULL);
2690
0
                    }
2691
9
                    if (!found) {
2692
1
                        char errbuf[512];
2693
1
                        snprintf(errbuf, sizeof(errbuf), "require: cannot find '%s'", file_path);
2694
1
                        free(file_path);
2695
2
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2696
1
                        free(args);
2697
1
                        return eval_err(strdup(errbuf));
2698
1
                    }
2699
8
                    free(file_path);
2700
                    /* Skip if already required */
2701
8
                    if (lat_map_get(&ev->required_files, resolved)) {
2702
4
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2703
2
                        free(args);
2704
2
                        return eval_ok(value_bool(true));
2705
2
                    }
2706
                    /* Mark as required before evaluating (guards against circular requires) */
2707
8
                    bool marker = true;
2708
6
                    lat_map_set(&ev->required_files, resolved, &marker);
2709
                    /* Read the file */
2710
6
                    char *source = builtin_read_file(resolved);
2711
6
                    if (!source) {
2712
0
                        char errbuf[512];
2713
0
                        snprintf(errbuf, sizeof(errbuf), "require: cannot read '%s'", resolved);
2714
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2715
0
                        free(args);
2716
0
                        return eval_err(strdup(errbuf));
2717
0
                    }
2718
                    /* Lex */
2719
6
                    Lexer req_lex = lexer_new(source);
2720
6
                    char *req_lex_err = NULL;
2721
6
                    LatVec req_toks = lexer_tokenize(&req_lex, &req_lex_err);
2722
6
                    free(source);
2723
6
                    if (req_lex_err) {
2724
0
                        char errbuf[1024];
2725
0
                        snprintf(errbuf, sizeof(errbuf), "require '%s': %s", resolved, req_lex_err);
2726
0
                        free(req_lex_err);
2727
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2728
0
                        free(args);
2729
0
                        return eval_err(strdup(errbuf));
2730
0
                    }
2731
                    /* Parse */
2732
6
                    Parser req_parser = parser_new(&req_toks);
2733
6
                    char *req_parse_err = NULL;
2734
6
                    Program req_prog = parser_parse(&req_parser, &req_parse_err);
2735
6
                    if (req_parse_err) {
2736
0
                        char errbuf[1024];
2737
0
                        snprintf(errbuf, sizeof(errbuf), "require '%s': %s", resolved, req_parse_err);
2738
0
                        free(req_parse_err);
2739
0
                        program_free(&req_prog);
2740
0
                        for (size_t j = 0; j < req_toks.len; j++) token_free(lat_vec_get(&req_toks, j));
2741
0
                        lat_vec_free(&req_toks);
2742
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2743
0
                        free(args);
2744
0
                        return eval_err(strdup(errbuf));
2745
0
                    }
2746
                    /* Register functions, structs, traits, impls */
2747
14
                    for (size_t j = 0; j < req_prog.item_count; j++) {
2748
8
                        if (req_prog.items[j].tag == ITEM_STRUCT) {
2749
1
                            StructDecl *ptr = &req_prog.items[j].as.struct_decl;
2750
1
                            lat_map_set(&ev->struct_defs, ptr->name, &ptr);
2751
7
                        } else if (req_prog.items[j].tag == ITEM_FUNCTION) {
2752
6
                            FnDecl *ptr = &req_prog.items[j].as.fn_decl;
2753
6
                            register_fn_overload(&ev->fn_defs, ptr);
2754
6
                        } else if (req_prog.items[j].tag == ITEM_TRAIT) {
2755
0
                            TraitDecl *ptr = &req_prog.items[j].as.trait_decl;
2756
0
                            lat_map_set(&ev->trait_defs, ptr->name, &ptr);
2757
1
                        } else if (req_prog.items[j].tag == ITEM_IMPL) {
2758
0
                            ImplBlock *ptr = &req_prog.items[j].as.impl_block;
2759
0
                            char key[512];
2760
0
                            snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name);
2761
0
                            lat_map_set(&ev->impl_registry, key, &ptr);
2762
0
                        }
2763
8
                    }
2764
                    /* Set script_dir to the required file's directory for nested requires */
2765
6
                    char *prev_script_dir = ev->script_dir;
2766
6
                    char *resolved_copy = strdup(resolved);
2767
6
                    ev->script_dir = strdup(dirname(resolved_copy));
2768
6
                    free(resolved_copy);
2769
                    /* Execute top-level statements */
2770
6
                    size_t saved_scope = ev->lat_eval_scope;
2771
6
                    ev->lat_eval_scope = ev->env->count;
2772
6
                    EvalResult req_r = eval_ok(value_unit());
2773
14
                    for (size_t j = 0; j < req_prog.item_count; j++) {
2774
8
                        if (req_prog.items[j].tag == ITEM_STMT) {
2775
1
                            value_free(&req_r.value);
2776
1
                            req_r = eval_stmt(ev, req_prog.items[j].as.stmt);
2777
1
                            if (!IS_OK(req_r)) break;
2778
1
                        }
2779
8
                    }
2780
6
                    ev->lat_eval_scope = saved_scope;
2781
                    /* Restore previous script_dir */
2782
6
                    free(ev->script_dir);
2783
6
                    ev->script_dir = prev_script_dir;
2784
                    /* Cleanup: free statements, keep decl items alive */
2785
6
                    bool req_has_decls = false;
2786
14
                    for (size_t j = 0; j < req_prog.item_count; j++) {
2787
8
                        if (req_prog.items[j].tag == ITEM_STMT)
2788
1
                            stmt_free(req_prog.items[j].as.stmt);
2789
7
                        else
2790
7
                            req_has_decls = true;
2791
8
                    }
2792
6
                    if (!req_has_decls) free(req_prog.items);
2793
107
                    for (size_t j = 0; j < req_toks.len; j++) token_free(lat_vec_get(&req_toks, j));
2794
6
                    lat_vec_free(&req_toks);
2795
12
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2796
6
                    free(args);
2797
6
                    if (!IS_OK(req_r)) return req_r;
2798
6
                    value_free(&req_r.value);
2799
6
                    return eval_ok(value_bool(true));
2800
6
                }
2801
2802
                /// @builtin require_ext(name: String) -> Map
2803
                /// @category Metaprogramming
2804
                /// Load a native extension (.dylib/.so) and return a Map of its functions.
2805
                /// @example let pg = require_ext("pg")
2806
1.93k
                if (strcmp(fn_name, "require_ext") == 0) {
2807
18
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("require_ext() expects 1 string argument")); }
2808
16
                    const char *ext_name = args[0].as.str_val;
2809
                    /* Check cache */
2810
16
                    LatValue *cached = (LatValue *)lat_map_get(&ev->loaded_extensions, ext_name);
2811
16
                    if (cached) {
2812
0
                        LatValue result = value_deep_clone(cached);
2813
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2814
0
                        free(args);
2815
0
                        return eval_ok(result);
2816
0
                    }
2817
16
                    char *ext_err = NULL;
2818
16
                    LatValue ext_map = ext_load(ev, ext_name, &ext_err);
2819
16
                    if (ext_err) {
2820
2
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2821
1
                        free(args);
2822
1
                        return eval_err(ext_err);
2823
1
                    }
2824
                    /* Cache the extension */
2825
15
                    LatValue cached_copy = value_deep_clone(&ext_map);
2826
15
                    lat_map_set(&ev->loaded_extensions, ext_name, &cached_copy);
2827
30
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2828
15
                    free(args);
2829
15
                    return eval_ok(ext_map);
2830
16
                }
2831
2832
                /// @builtin lat_eval(source: String) -> Any
2833
                /// @category Metaprogramming
2834
                /// Parse and execute a string as Lattice source code, returning the result.
2835
                /// @example lat_eval("1 + 2")  // 3
2836
1.91k
                if (strcmp(fn_name, "lat_eval") == 0) {
2837
13
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("lat_eval() expects 1 string argument")); }
2838
13
                    const char *source = args[0].as.str_val;
2839
13
                    Lexer lex = lexer_new(source);
2840
13
                    char *lex_err = NULL;
2841
13
                    LatVec toks = lexer_tokenize(&lex, &lex_err);
2842
13
                    if (lex_err) {
2843
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2844
0
                        free(args);
2845
0
                        return eval_err(lex_err);
2846
0
                    }
2847
13
                    Parser parser = parser_new(&toks);
2848
13
                    char *parse_err = NULL;
2849
13
                    Program prog = parser_parse(&parser, &parse_err);
2850
13
                    if (parse_err) {
2851
0
                        program_free(&prog);
2852
0
                        for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j));
2853
0
                        lat_vec_free(&toks);
2854
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2855
0
                        free(args);
2856
0
                        return eval_err(parse_err);
2857
0
                    }
2858
                    /* Register functions, structs, traits, impls (same as evaluator_run) */
2859
26
                    for (size_t j = 0; j < prog.item_count; j++) {
2860
13
                        if (prog.items[j].tag == ITEM_STRUCT) {
2861
1
                            StructDecl *ptr = &prog.items[j].as.struct_decl;
2862
1
                            lat_map_set(&ev->struct_defs, ptr->name, &ptr);
2863
12
                        } else if (prog.items[j].tag == ITEM_FUNCTION) {
2864
1
                            FnDecl *ptr = &prog.items[j].as.fn_decl;
2865
1
                            register_fn_overload(&ev->fn_defs, ptr);
2866
11
                        } else if (prog.items[j].tag == ITEM_TRAIT) {
2867
0
                            TraitDecl *ptr = &prog.items[j].as.trait_decl;
2868
0
                            lat_map_set(&ev->trait_defs, ptr->name, &ptr);
2869
11
                        } else if (prog.items[j].tag == ITEM_IMPL) {
2870
0
                            ImplBlock *ptr = &prog.items[j].as.impl_block;
2871
0
                            char key[512];
2872
0
                            snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name);
2873
0
                            lat_map_set(&ev->impl_registry, key, &ptr);
2874
0
                        }
2875
13
                    }
2876
                    /* Execute statements — set lat_eval_scope so top-level
2877
                     * bindings persist in the caller's scope */
2878
13
                    size_t saved_scope = ev->lat_eval_scope;
2879
13
                    ev->lat_eval_scope = ev->env->count;
2880
13
                    EvalResult eval_r = eval_ok(value_unit());
2881
26
                    for (size_t j = 0; j < prog.item_count; j++) {
2882
13
                        if (prog.items[j].tag == ITEM_STMT) {
2883
11
                            value_free(&eval_r.value);
2884
11
                            eval_r = eval_stmt(ev, prog.items[j].as.stmt);
2885
11
                            if (!IS_OK(eval_r)) break;
2886
11
                        }
2887
13
                    }
2888
13
                    ev->lat_eval_scope = saved_scope;
2889
                    /* Free statement items. If fn/struct decls were registered,
2890
                     * keep the items array alive (decls live inline in it).
2891
                     * Otherwise free the whole program. */
2892
13
                    bool has_decls = false;
2893
26
                    for (size_t j = 0; j < prog.item_count; j++) {
2894
13
                        if (prog.items[j].tag == ITEM_STMT)
2895
11
                            stmt_free(prog.items[j].as.stmt);
2896
2
                        else
2897
2
                            has_decls = true;
2898
13
                    }
2899
13
                    if (!has_decls) free(prog.items);
2900
104
                    for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j));
2901
13
                    lat_vec_free(&toks);
2902
26
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2903
13
                    free(args);
2904
13
                    return eval_r;
2905
13
                }
2906
2907
                /// @builtin tokenize(source: String) -> Array
2908
                /// @category Metaprogramming
2909
                /// Tokenize a source string, returning an array of Token structs.
2910
                /// @example tokenize("1 + 2")  // [{type: "INT_LIT", text: "1"}, ...]
2911
1.89k
                if (strcmp(fn_name, "tokenize") == 0) {
2912
1
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tokenize() expects 1 string argument")); }
2913
1
                    const char *source = args[0].as.str_val;
2914
1
                    Lexer lex = lexer_new(source);
2915
1
                    char *lex_err = NULL;
2916
1
                    LatVec toks = lexer_tokenize(&lex, &lex_err);
2917
1
                    if (lex_err) {
2918
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2919
0
                        free(args);
2920
0
                        return eval_err(lex_err);
2921
0
                    }
2922
1
                    size_t tok_count = toks.len > 0 ? toks.len - 1 : 0;
2923
1
                    LatValue *elems = malloc((tok_count > 0 ? tok_count : 1) * sizeof(LatValue));
2924
5
                    for (size_t j = 0; j < tok_count; j++) {
2925
4
                        Token *t = lat_vec_get(&toks, j);
2926
4
                        const char *type_str = token_type_name(t->type);
2927
4
                        char *text;
2928
4
                        if (t->type == TOK_IDENT || t->type == TOK_STRING_LIT || t->type == TOK_MODE_DIRECTIVE) {
2929
1
                            text = strdup(t->as.str_val);
2930
3
                        } else if (t->type == TOK_INT_LIT) {
2931
1
                            (void)asprintf(&text, "%lld", (long long)t->as.int_val);
2932
2
                        } else if (t->type == TOK_FLOAT_LIT) {
2933
0
                            (void)asprintf(&text, "%g", t->as.float_val);
2934
2
                        } else {
2935
2
                            text = strdup(token_type_name(t->type));
2936
2
                        }
2937
4
                        char *fnames[2] = { "type", "text" };
2938
4
                        LatValue fvals[2];
2939
4
                        fvals[0] = value_string(type_str);
2940
4
                        fvals[1] = value_string_owned(text);
2941
4
                        elems[j] = value_struct("Token", fnames, fvals, 2);
2942
4
                    }
2943
6
                    for (size_t j = 0; j < toks.len; j++) token_free(lat_vec_get(&toks, j));
2944
1
                    lat_vec_free(&toks);
2945
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2946
1
                    free(args);
2947
1
                    LatValue arr = value_array(elems, tok_count);
2948
1
                    free(elems);
2949
1
                    return eval_ok(arr);
2950
1
                }
2951
2952
                /// @builtin Map::new() -> Map
2953
                /// @category Type Constructors
2954
                /// Create a new empty map.
2955
                /// @example Map::new()  // {}
2956
1.89k
                if (strcmp(fn_name, "Map::new") == 0) {
2957
411
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2958
411
                    free(args);
2959
411
                    return eval_ok(value_map_new());
2960
411
                }
2961
2962
                /// @builtin Channel::new() -> Channel
2963
                /// @category Type Constructors
2964
                /// Create a new channel for concurrent communication.
2965
                /// @example Channel::new()  // <Channel>
2966
1.48k
                if (strcmp(fn_name, "Channel::new") == 0) {
2967
14
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2968
14
                    free(args);
2969
14
                    LatChannel *ch = channel_new();
2970
14
                    LatValue val = value_channel(ch);
2971
14
                    channel_release(ch);  /* value_channel retained; drop our creation ref */
2972
14
                    return eval_ok(val);
2973
14
                }
2974
2975
                /// @builtin Set::new() -> Set
2976
                /// @category Type Constructors
2977
                /// Create a new empty set.
2978
                /// @example Set::new()  // Set{}
2979
1.47k
                if (strcmp(fn_name, "Set::new") == 0) {
2980
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2981
4
                    free(args);
2982
4
                    return eval_ok(value_set_new());
2983
4
                }
2984
2985
                /// @builtin Set::from(array: Array) -> Set
2986
                /// @category Type Constructors
2987
                /// Create a set from an array (duplicates removed).
2988
                /// @example Set::from([1, 2, 2, 3])  // Set{1, 2, 3}
2989
1.46k
                if (strcmp(fn_name, "Set::from") == 0) {
2990
12
                    if (argc != 1 || args[0].type != VAL_ARRAY) {
2991
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
2992
0
                        free(args);
2993
0
                        return eval_err(strdup("Set::from() expects 1 array argument"));
2994
0
                    }
2995
12
                    LatValue set = value_set_new();
2996
42
                    for (size_t i = 0; i < args[0].as.array.len; i++) {
2997
30
                        LatValue *elem = &args[0].as.array.elems[i];
2998
30
                        char *key = value_display(elem);
2999
30
                        LatValue cloned = value_deep_clone(elem);
3000
30
                        lat_map_set(set.as.set.map, key, &cloned);
3001
30
                        free(key);
3002
30
                    }
3003
24
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3004
12
                    free(args);
3005
12
                    return eval_ok(set);
3006
12
                }
3007
3008
                /// @builtin Buffer::new(size: Int) -> Buffer
3009
                /// @category Type Constructors
3010
                /// Create a new zero-filled buffer of the given size.
3011
                /// @example Buffer::new(16)  // Buffer<16 bytes>
3012
1.45k
                if (strcmp(fn_name, "Buffer::new") == 0) {
3013
8
                    if (argc != 1 || args[0].type != VAL_INT) {
3014
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3015
0
                        free(args);
3016
0
                        return eval_err(strdup("Buffer::new() expects 1 Int argument"));
3017
0
                    }
3018
8
                    int64_t size = args[0].as.int_val;
3019
16
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3020
8
                    free(args);
3021
8
                    return eval_ok(value_buffer_alloc(size < 0 ? 0 : (size_t)size));
3022
8
                }
3023
3024
                /// @builtin Buffer::from(arr: Array) -> Buffer
3025
                /// @category Type Constructors
3026
                /// Create a buffer from an array of byte integers (0-255).
3027
                /// @example Buffer::from([0xFF, 0x00, 0x42])
3028
1.44k
                if (strcmp(fn_name, "Buffer::from") == 0) {
3029
8
                    if (argc != 1 || args[0].type != VAL_ARRAY) {
3030
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3031
0
                        free(args);
3032
0
                        return eval_err(strdup("Buffer::from() expects 1 Array argument"));
3033
0
                    }
3034
8
                    size_t blen = args[0].as.array.len;
3035
8
                    uint8_t *data = malloc(blen > 0 ? blen : 1);
3036
34
                    for (size_t bi = 0; bi < blen; bi++) {
3037
26
                        if (args[0].as.array.elems[bi].type == VAL_INT)
3038
26
                            data[bi] = (uint8_t)(args[0].as.array.elems[bi].as.int_val & 0xFF);
3039
0
                        else
3040
0
                            data[bi] = 0;
3041
26
                    }
3042
8
                    LatValue buf = value_buffer(data, blen);
3043
8
                    free(data);
3044
16
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3045
8
                    free(args);
3046
8
                    return eval_ok(buf);
3047
8
                }
3048
3049
                /// @builtin Buffer::from_string(s: String) -> Buffer
3050
                /// @category Type Constructors
3051
                /// Create a buffer from a UTF-8 string.
3052
                /// @example Buffer::from_string("hello")
3053
1.44k
                if (strcmp(fn_name, "Buffer::from_string") == 0) {
3054
2
                    if (argc != 1 || args[0].type != VAL_STR) {
3055
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3056
0
                        free(args);
3057
0
                        return eval_err(strdup("Buffer::from_string() expects 1 String argument"));
3058
0
                    }
3059
2
                    const char *s = args[0].as.str_val;
3060
2
                    size_t slen = strlen(s);
3061
2
                    LatValue buf = value_buffer((const uint8_t *)s, slen);
3062
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3063
2
                    free(args);
3064
2
                    return eval_ok(buf);
3065
2
                }
3066
3067
                /// @builtin Ref::new(value: Any) -> Ref
3068
                /// @category Type Constructors
3069
                /// Create a new reference-counted shared wrapper around a value.
3070
                /// @example Ref::new({})
3071
1.43k
                if (strcmp(fn_name, "Ref::new") == 0) {
3072
15
                    if (argc != 1) {
3073
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3074
0
                        free(args);
3075
0
                        return eval_err(strdup("Ref::new() expects 1 argument"));
3076
0
                    }
3077
15
                    LatValue ref = value_ref(args[0]);
3078
30
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3079
15
                    free(args);
3080
15
                    return eval_ok(ref);
3081
15
                }
3082
3083
                /// @builtin parse_int(s: String) -> Int
3084
                /// @category Type Conversion
3085
                /// Parse a string as an integer.
3086
                /// @example parse_int("42")  // 42
3087
1.42k
                if (strcmp(fn_name, "parse_int") == 0) {
3088
2
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("parse_int() expects 1 string argument")); }
3089
2
                    bool ok;
3090
2
                    int64_t val = builtin_parse_int(args[0].as.str_val, &ok);
3091
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3092
2
                    free(args);
3093
2
                    if (!ok) return eval_err(strdup("parse_int: invalid integer"));
3094
2
                    return eval_ok(value_int(val));
3095
2
                }
3096
3097
                /// @builtin parse_float(s: String) -> Float
3098
                /// @category Type Conversion
3099
                /// Parse a string as a floating-point number.
3100
                /// @example parse_float("3.14")  // 3.14
3101
1.42k
                if (strcmp(fn_name, "parse_float") == 0) {
3102
1
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("parse_float() expects 1 string argument")); }
3103
1
                    bool ok;
3104
1
                    double val = builtin_parse_float(args[0].as.str_val, &ok);
3105
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3106
1
                    free(args);
3107
1
                    if (!ok) return eval_err(strdup("parse_float: invalid float"));
3108
1
                    return eval_ok(value_float(val));
3109
1
                }
3110
3111
                /// @builtin error(msg: String) -> String
3112
                /// @category Error Handling
3113
                /// Create an error value with the given message.
3114
                /// @example error("something went wrong")  // "EVAL_ERROR:something went wrong"
3115
1.42k
                if (strcmp(fn_name, "error") == 0) {
3116
1
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("error() expects 1 string argument")); }
3117
1
                    char *msg = NULL;
3118
1
                    (void)asprintf(&msg, "EVAL_ERROR:%s", args[0].as.str_val);
3119
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3120
1
                    free(args);
3121
1
                    return eval_ok(value_string_owned(msg));
3122
1
                }
3123
3124
                /// @builtin panic(msg: String) -> Unit
3125
                /// @category Error Handling
3126
                /// Trigger an immediate fatal error that cannot be caught by try/catch.
3127
                /// @example panic("unrecoverable state")
3128
1.42k
                if (strcmp(fn_name, "panic") == 0) {
3129
1
                    const char *msg = (argc >= 1 && args[0].type == VAL_STR) ? args[0].as.str_val : "panic";
3130
1
                    char *err = strdup(msg);
3131
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3132
1
                    free(args);
3133
1
                    return eval_err(err);
3134
1
                }
3135
3136
                /// @builtin is_error(val: Any) -> Bool
3137
                /// @category Error Handling
3138
                /// Check if a value is an error value.
3139
                /// @example is_error(error("oops"))  // true
3140
1.41k
                if (strcmp(fn_name, "is_error") == 0) {
3141
3
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_error() expects 1 argument")); }
3142
3
                    bool is_err = args[0].type == VAL_STR && strncmp(args[0].as.str_val, "EVAL_ERROR:", 11) == 0;
3143
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3144
3
                    free(args);
3145
3
                    return eval_ok(value_bool(is_err));
3146
3
                }
3147
3148
                /// @builtin len(val: String|Array|Map) -> Int
3149
                /// @category Core
3150
                /// Returns the length of a string, array, or map.
3151
                /// @example len("hello")  // 5
3152
                /// @example len([1, 2, 3])  // 3
3153
1.41k
                if (strcmp(fn_name, "len") == 0) {
3154
243
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("len() expects 1 argument")); }
3155
243
                    int64_t l = -1;
3156
243
                    if (args[0].type == VAL_STR) l = (int64_t)strlen(args[0].as.str_val);
3157
168
                    else if (args[0].type == VAL_ARRAY) l = (int64_t)args[0].as.array.len;
3158
2
                    else if (args[0].type == VAL_MAP) l = (int64_t)lat_map_len(args[0].as.map.map);
3159
1
                    else if (args[0].type == VAL_SET) l = (int64_t)lat_map_len(args[0].as.set.map);
3160
1
                    else if (args[0].type == VAL_BUFFER) l = (int64_t)args[0].as.buffer.len;
3161
486
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3162
243
                    free(args);
3163
243
                    if (l < 0) return eval_err(strdup("len() not supported on this type"));
3164
243
                    return eval_ok(value_int(l));
3165
243
                }
3166
3167
                /// @builtin exit(code?: Int) -> Unit
3168
                /// @category Core
3169
                /// Exit the program with an optional exit code (default 0).
3170
                /// @example exit(1)  // exits with code 1
3171
1.17k
                if (strcmp(fn_name, "exit") == 0) {
3172
0
                    int code = 0;
3173
0
                    if (argc > 0 && args[0].type == VAL_INT) code = (int)args[0].as.int_val;
3174
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3175
0
                    free(args);
3176
0
                    exit(code);
3177
0
                }
3178
3179
                /// @builtin version() -> String
3180
                /// @category Core
3181
                /// Return the Lattice interpreter version string.
3182
                /// @example version()  // "0.1.0"
3183
1.17k
                if (strcmp(fn_name, "version") == 0) {
3184
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3185
2
                    free(args);
3186
2
                    return eval_ok(value_string(LATTICE_VERSION));
3187
2
                }
3188
3189
                /// @builtin print_raw(args: Any...) -> Unit
3190
                /// @category Core
3191
                /// Print values separated by spaces without a trailing newline.
3192
                /// @example print_raw("hello", "world")  // prints: hello world
3193
1.17k
                if (strcmp(fn_name, "print_raw") == 0) {
3194
4
                    for (size_t i = 0; i < argc; i++) {
3195
2
                        if (i > 0) printf(" ");
3196
2
                        char *s = value_display(&args[i]);
3197
2
                        printf("%s", s);
3198
2
                        free(s);
3199
2
                    }
3200
2
                    fflush(stdout);
3201
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3202
2
                    free(args);
3203
2
                    return eval_ok(value_unit());
3204
2
                }
3205
3206
                /// @builtin eprint(args: Any...) -> Unit
3207
                /// @category Core
3208
                /// Print values to stderr with a trailing newline.
3209
                /// @example eprint("warning:", msg)  // prints to stderr
3210
1.16k
                if (strcmp(fn_name, "eprint") == 0) {
3211
2
                    for (size_t i = 0; i < argc; i++) {
3212
1
                        if (i > 0) fprintf(stderr, " ");
3213
1
                        char *s = value_display(&args[i]);
3214
1
                        fprintf(stderr, "%s", s);
3215
1
                        free(s);
3216
1
                    }
3217
1
                    fprintf(stderr, "\n");
3218
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3219
1
                    free(args);
3220
1
                    return eval_ok(value_unit());
3221
1
                }
3222
3223
                /* ── TCP networking builtins ── */
3224
3225
                /// @builtin tcp_listen(host: String, port: Int) -> Int
3226
                /// @category Networking
3227
                /// Create a TCP server socket listening on host:port, returning a file descriptor.
3228
                /// @example tcp_listen("0.0.0.0", 8080)  // 3
3229
1.16k
                if (strcmp(fn_name, "tcp_listen") == 0) {
3230
3
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_listen() expects (String host, Int port)")); }
3231
1
                    char *net_err = NULL;
3232
1
                    int fd = net_tcp_listen(args[0].as.str_val, (int)args[1].as.int_val, &net_err);
3233
3
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3234
1
                    free(args);
3235
1
                    if (fd < 0) return eval_err(net_err);
3236
1
                    return eval_ok(value_int(fd));
3237
1
                }
3238
3239
                /// @builtin tcp_accept(server_fd: Int) -> Int
3240
                /// @category Networking
3241
                /// Accept an incoming TCP connection, returning a new client file descriptor.
3242
                /// @example tcp_accept(server_fd)  // 4
3243
1.16k
                if (strcmp(fn_name, "tcp_accept") == 0) {
3244
0
                    if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_accept() expects (Int server_fd)")); }
3245
0
                    char *net_err = NULL;
3246
0
                    int fd = net_tcp_accept((int)args[0].as.int_val, &net_err);
3247
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3248
0
                    free(args);
3249
0
                    if (fd < 0) return eval_err(net_err);
3250
0
                    return eval_ok(value_int(fd));
3251
0
                }
3252
3253
                /// @builtin tcp_connect(host: String, port: Int) -> Int
3254
                /// @category Networking
3255
                /// Connect to a TCP server, returning a file descriptor.
3256
                /// @example tcp_connect("localhost", 8080)  // 3
3257
1.16k
                if (strcmp(fn_name, "tcp_connect") == 0) {
3258
0
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_connect() expects (String host, Int port)")); }
3259
0
                    char *net_err = NULL;
3260
0
                    int fd = net_tcp_connect(args[0].as.str_val, (int)args[1].as.int_val, &net_err);
3261
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3262
0
                    free(args);
3263
0
                    if (fd < 0) return eval_err(net_err);
3264
0
                    return eval_ok(value_int(fd));
3265
0
                }
3266
3267
                /// @builtin tcp_read(fd: Int) -> String
3268
                /// @category Networking
3269
                /// Read data from a TCP socket as a string.
3270
                /// @example tcp_read(client_fd)  // "HTTP/1.1 200 OK..."
3271
1.16k
                if (strcmp(fn_name, "tcp_read") == 0) {
3272
2
                    if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_read() expects (Int fd)")); }
3273
0
                    char *net_err = NULL;
3274
0
                    char *data = net_tcp_read((int)args[0].as.int_val, &net_err);
3275
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3276
0
                    free(args);
3277
0
                    if (!data) return eval_err(net_err);
3278
0
                    return eval_ok(value_string_owned(data));
3279
0
                }
3280
3281
                /// @builtin tcp_read_bytes(fd: Int, n: Int) -> String
3282
                /// @category Networking
3283
                /// Read exactly n bytes from a TCP socket.
3284
                /// @example tcp_read_bytes(fd, 1024)  // "..."
3285
1.16k
                if (strcmp(fn_name, "tcp_read_bytes") == 0) {
3286
0
                    if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_read_bytes() expects (Int fd, Int n)")); }
3287
0
                    char *net_err = NULL;
3288
0
                    char *data = net_tcp_read_bytes((int)args[0].as.int_val, (size_t)args[1].as.int_val, &net_err);
3289
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3290
0
                    free(args);
3291
0
                    if (!data) return eval_err(net_err);
3292
0
                    return eval_ok(value_string_owned(data));
3293
0
                }
3294
3295
                /// @builtin tcp_write(fd: Int, data: String) -> Bool
3296
                /// @category Networking
3297
                /// Write a string to a TCP socket.
3298
                /// @example tcp_write(fd, "GET / HTTP/1.1\r\n\r\n")  // true
3299
1.16k
                if (strcmp(fn_name, "tcp_write") == 0) {
3300
0
                    if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_write() expects (Int fd, String data)")); }
3301
0
                    char *net_err = NULL;
3302
0
                    bool ok = net_tcp_write((int)args[0].as.int_val, args[1].as.str_val, strlen(args[1].as.str_val), &net_err);
3303
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3304
0
                    free(args);
3305
0
                    if (!ok) return eval_err(net_err);
3306
0
                    return eval_ok(value_bool(true));
3307
0
                }
3308
3309
                /// @builtin tcp_close(fd: Int) -> Unit
3310
                /// @category Networking
3311
                /// Close a TCP socket.
3312
                /// @example tcp_close(fd)
3313
1.16k
                if (strcmp(fn_name, "tcp_close") == 0) {
3314
1
                    if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_close() expects (Int fd)")); }
3315
1
                    net_tcp_close((int)args[0].as.int_val);
3316
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3317
1
                    free(args);
3318
1
                    return eval_ok(value_unit());
3319
1
                }
3320
3321
                /// @builtin tcp_peer_addr(fd: Int) -> String
3322
                /// @category Networking
3323
                /// Get the remote address of a connected TCP socket.
3324
                /// @example tcp_peer_addr(client_fd)  // "192.168.1.1:54321"
3325
1.16k
                if (strcmp(fn_name, "tcp_peer_addr") == 0) {
3326
0
                    if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_peer_addr() expects (Int fd)")); }
3327
0
                    char *net_err = NULL;
3328
0
                    char *addr = net_tcp_peer_addr((int)args[0].as.int_val, &net_err);
3329
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3330
0
                    free(args);
3331
0
                    if (!addr) return eval_err(net_err);
3332
0
                    return eval_ok(value_string_owned(addr));
3333
0
                }
3334
3335
                /// @builtin tcp_set_timeout(fd: Int, secs: Int) -> Bool
3336
                /// @category Networking
3337
                /// Set read/write timeout on a TCP socket in seconds.
3338
                /// @example tcp_set_timeout(fd, 30)  // true
3339
1.16k
                if (strcmp(fn_name, "tcp_set_timeout") == 0) {
3340
0
                    if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tcp_set_timeout() expects (Int fd, Int secs)")); }
3341
0
                    char *net_err = NULL;
3342
0
                    bool ok = net_tcp_set_timeout((int)args[0].as.int_val, (int)args[1].as.int_val, &net_err);
3343
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3344
0
                    free(args);
3345
0
                    if (!ok) return eval_err(net_err);
3346
0
                    return eval_ok(value_bool(true));
3347
0
                }
3348
3349
                /* ── TLS networking builtins ── */
3350
3351
                /// @builtin tls_connect(host: String, port: Int) -> Int
3352
                /// @category Networking
3353
                /// Establish a TLS connection to a server, returning a handle.
3354
                /// @example tls_connect("example.com", 443)  // 1
3355
1.16k
                if (strcmp(fn_name, "tls_connect") == 0) {
3356
3
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_connect() expects (String host, Int port)")); }
3357
0
                    char *net_err = NULL;
3358
0
                    int fd = net_tls_connect(args[0].as.str_val, (int)args[1].as.int_val, &net_err);
3359
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3360
0
                    free(args);
3361
0
                    if (fd < 0) return eval_err(net_err);
3362
0
                    return eval_ok(value_int(fd));
3363
0
                }
3364
3365
                /// @builtin tls_read(handle: Int) -> String
3366
                /// @category Networking
3367
                /// Read data from a TLS connection as a string.
3368
                /// @example tls_read(handle)  // "HTTP/1.1 200 OK..."
3369
1.16k
                if (strcmp(fn_name, "tls_read") == 0) {
3370
2
                    if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_read() expects (Int fd)")); }
3371
0
                    char *net_err = NULL;
3372
0
                    char *data = net_tls_read((int)args[0].as.int_val, &net_err);
3373
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3374
0
                    free(args);
3375
0
                    if (!data) return eval_err(net_err);
3376
0
                    return eval_ok(value_string_owned(data));
3377
0
                }
3378
3379
                /// @builtin tls_read_bytes(handle: Int, n: Int) -> String
3380
                /// @category Networking
3381
                /// Read exactly n bytes from a TLS connection.
3382
                /// @example tls_read_bytes(handle, 512)  // "..."
3383
1.16k
                if (strcmp(fn_name, "tls_read_bytes") == 0) {
3384
0
                    if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_read_bytes() expects (Int fd, Int n)")); }
3385
0
                    char *net_err = NULL;
3386
0
                    char *data = net_tls_read_bytes((int)args[0].as.int_val, (size_t)args[1].as.int_val, &net_err);
3387
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3388
0
                    free(args);
3389
0
                    if (!data) return eval_err(net_err);
3390
0
                    return eval_ok(value_string_owned(data));
3391
0
                }
3392
3393
                /// @builtin tls_write(handle: Int, data: String) -> Bool
3394
                /// @category Networking
3395
                /// Write a string to a TLS connection.
3396
                /// @example tls_write(handle, "GET / HTTP/1.1\r\n\r\n")  // true
3397
1.16k
                if (strcmp(fn_name, "tls_write") == 0) {
3398
0
                    if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_write() expects (Int fd, String data)")); }
3399
0
                    char *net_err = NULL;
3400
0
                    bool ok = net_tls_write((int)args[0].as.int_val, args[1].as.str_val, strlen(args[1].as.str_val), &net_err);
3401
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3402
0
                    free(args);
3403
0
                    if (!ok) return eval_err(net_err);
3404
0
                    return eval_ok(value_bool(true));
3405
0
                }
3406
3407
                /// @builtin tls_close(handle: Int) -> Unit
3408
                /// @category Networking
3409
                /// Close a TLS connection.
3410
                /// @example tls_close(handle)
3411
1.16k
                if (strcmp(fn_name, "tls_close") == 0) {
3412
0
                    if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_close() expects (Int fd)")); }
3413
0
                    net_tls_close((int)args[0].as.int_val);
3414
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3415
0
                    free(args);
3416
0
                    return eval_ok(value_unit());
3417
0
                }
3418
3419
                /// @builtin tls_available() -> Bool
3420
                /// @category Networking
3421
                /// Check if TLS support is available (OpenSSL linked).
3422
                /// @example tls_available()  // true
3423
1.16k
                if (strcmp(fn_name, "tls_available") == 0) {
3424
1
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tls_available() expects no arguments")); }
3425
1
                    free(args);
3426
1
                    return eval_ok(value_bool(net_tls_available()));
3427
1
                }
3428
3429
                /* ── JSON builtins ── */
3430
3431
                /// @builtin json_parse(s: String) -> Any
3432
                /// @category JSON
3433
                /// Parse a JSON string into a Lattice value.
3434
                /// @example json_parse("{\"a\": 1}")  // {a: 1}
3435
1.16k
                if (strcmp(fn_name, "json_parse") == 0) {
3436
15
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("json_parse() expects (String)")); }
3437
15
                    char *jerr = NULL;
3438
15
                    LatValue result = json_parse(args[0].as.str_val, &jerr);
3439
30
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3440
15
                    free(args);
3441
15
                    if (jerr) return eval_err(jerr);
3442
14
                    return eval_ok(result);
3443
15
                }
3444
3445
                /// @builtin json_stringify(val: Any) -> String
3446
                /// @category JSON
3447
                /// Serialize a Lattice value to a JSON string.
3448
                /// @example json_stringify([1, 2, 3])  // "[1,2,3]"
3449
1.14k
                if (strcmp(fn_name, "json_stringify") == 0) {
3450
8
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("json_stringify() expects (value)")); }
3451
7
                    char *jerr = NULL;
3452
7
                    char *json = json_stringify(&args[0], &jerr);
3453
14
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3454
7
                    free(args);
3455
7
                    if (!json) return eval_err(jerr);
3456
7
                    return eval_ok(value_string_owned(json));
3457
7
                }
3458
3459
                /* ── HTTP builtins ── */
3460
3461
                /// @builtin http_get(url: String) -> Map
3462
                /// @category HTTP
3463
                /// Perform an HTTP GET request. Returns a map with "status", "headers", and "body".
3464
                /// @example http_get("https://httpbin.org/get")
3465
1.13k
                if (strcmp(fn_name, "http_get") == 0) {
3466
3
                    if (argc != 1 || args[0].type != VAL_STR) {
3467
3
                        for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
3468
2
                        return eval_err(strdup("http_get() expects (url: String)"));
3469
2
                    }
3470
1
                    HttpRequest hreq = {
3471
1
                        .method = "GET", .url = args[0].as.str_val,
3472
1
                        .header_keys = NULL, .header_values = NULL, .header_count = 0,
3473
1
                        .body = NULL, .body_len = 0, .timeout_ms = 0
3474
1
                    };
3475
1
                    char *herr = NULL;
3476
1
                    HttpResponse *hresp = http_execute(&hreq, &herr);
3477
2
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
3478
1
                    if (!hresp) return eval_err(herr ? herr : strdup("http_get failed"));
3479
                    /* Build result map */
3480
0
                    LatValue result = value_map_new();
3481
0
                    LatValue st = value_int(hresp->status_code);
3482
0
                    lat_map_set(result.as.map.map, "status", &st);
3483
0
                    LatValue bd = value_string(hresp->body ? hresp->body : "");
3484
0
                    lat_map_set(result.as.map.map, "body", &bd);
3485
0
                    LatValue hdrs = value_map_new();
3486
0
                    for (size_t i = 0; i < hresp->header_count; i++) {
3487
0
                        LatValue hv = value_string(hresp->header_values[i]);
3488
0
                        lat_map_set(hdrs.as.map.map, hresp->header_keys[i], &hv);
3489
0
                    }
3490
0
                    lat_map_set(result.as.map.map, "headers", &hdrs);
3491
0
                    http_response_free(hresp);
3492
0
                    return eval_ok(result);
3493
1
                }
3494
3495
                /// @builtin http_post(url: String, options: Map) -> Map
3496
                /// @category HTTP
3497
                /// Perform an HTTP POST request. Options map may contain "headers" (Map), "body" (String), and "timeout" (Int ms).
3498
                /// @example http_post("https://httpbin.org/post", {"body": "hello"})
3499
1.13k
                if (strcmp(fn_name, "http_post") == 0) {
3500
2
                    if (argc < 1 || argc > 2 || args[0].type != VAL_STR) {
3501
2
                        for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
3502
1
                        return eval_err(strdup("http_post() expects (url: String, options?: Map)"));
3503
1
                    }
3504
                    /* Extract options */
3505
1
                    const char *body_str = NULL;
3506
1
                    size_t body_len = 0;
3507
1
                    int timeout_ms = 0;
3508
1
                    char **hdr_keys = NULL, **hdr_vals = NULL;
3509
1
                    size_t hdr_count = 0;
3510
1
                    if (argc == 2 && args[1].type == VAL_MAP) {
3511
0
                        LatValue *bv = (LatValue *)lat_map_get(args[1].as.map.map, "body");
3512
0
                        if (bv && bv->type == VAL_STR) { body_str = bv->as.str_val; body_len = strlen(body_str); }
3513
0
                        LatValue *tv = (LatValue *)lat_map_get(args[1].as.map.map, "timeout");
3514
0
                        if (tv && tv->type == VAL_INT) timeout_ms = (int)tv->as.int_val;
3515
0
                        LatValue *hm = (LatValue *)lat_map_get(args[1].as.map.map, "headers");
3516
0
                        if (hm && hm->type == VAL_MAP) {
3517
0
                            hdr_count = lat_map_len(hm->as.map.map);
3518
0
                            hdr_keys = malloc(hdr_count * sizeof(char *));
3519
0
                            hdr_vals = malloc(hdr_count * sizeof(char *));
3520
0
                            size_t hi = 0;
3521
0
                            for (size_t i = 0; i < hm->as.map.map->cap && hi < hdr_count; i++) {
3522
0
                                if (hm->as.map.map->entries[i].state == MAP_OCCUPIED) {
3523
0
                                    hdr_keys[hi] = (char *)hm->as.map.map->entries[i].key;
3524
0
                                    LatValue *v = (LatValue *)hm->as.map.map->entries[i].value;
3525
0
                                    hdr_vals[hi] = v->type == VAL_STR ? v->as.str_val : "";
3526
0
                                    hi++;
3527
0
                                }
3528
0
                            }
3529
0
                        }
3530
0
                    }
3531
1
                    HttpRequest hreq = {
3532
1
                        .method = "POST", .url = args[0].as.str_val,
3533
1
                        .header_keys = hdr_keys, .header_values = hdr_vals, .header_count = hdr_count,
3534
1
                        .body = body_str, .body_len = body_len, .timeout_ms = timeout_ms
3535
1
                    };
3536
1
                    char *herr = NULL;
3537
1
                    HttpResponse *hresp = http_execute(&hreq, &herr);
3538
1
                    free(hdr_keys); free(hdr_vals);
3539
2
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
3540
1
                    if (!hresp) return eval_err(herr ? herr : strdup("http_post failed"));
3541
0
                    LatValue result = value_map_new();
3542
0
                    LatValue st = value_int(hresp->status_code);
3543
0
                    lat_map_set(result.as.map.map, "status", &st);
3544
0
                    LatValue bd = value_string(hresp->body ? hresp->body : "");
3545
0
                    lat_map_set(result.as.map.map, "body", &bd);
3546
0
                    LatValue hdrs = value_map_new();
3547
0
                    for (size_t i = 0; i < hresp->header_count; i++) {
3548
0
                        LatValue hv = value_string(hresp->header_values[i]);
3549
0
                        lat_map_set(hdrs.as.map.map, hresp->header_keys[i], &hv);
3550
0
                    }
3551
0
                    lat_map_set(result.as.map.map, "headers", &hdrs);
3552
0
                    http_response_free(hresp);
3553
0
                    return eval_ok(result);
3554
1
                }
3555
3556
                /// @builtin http_request(method: String, url: String, options?: Map) -> Map
3557
                /// @category HTTP
3558
                /// Perform an HTTP request with a custom method. Options may contain "headers", "body", and "timeout".
3559
                /// @example http_request("PUT", "https://api.example.com/data", {"body": "{}"})
3560
1.13k
                if (strcmp(fn_name, "http_request") == 0) {
3561
3
                    if (argc < 2 || argc > 3 || args[0].type != VAL_STR || args[1].type != VAL_STR) {
3562
5
                        for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
3563
2
                        return eval_err(strdup("http_request() expects (method: String, url: String, options?: Map)"));
3564
2
                    }
3565
1
                    const char *body_str = NULL;
3566
1
                    size_t body_len = 0;
3567
1
                    int timeout_ms = 0;
3568
1
                    char **hdr_keys = NULL, **hdr_vals = NULL;
3569
1
                    size_t hdr_count = 0;
3570
1
                    if (argc == 3 && args[2].type == VAL_MAP) {
3571
0
                        LatValue *bv = (LatValue *)lat_map_get(args[2].as.map.map, "body");
3572
0
                        if (bv && bv->type == VAL_STR) { body_str = bv->as.str_val; body_len = strlen(body_str); }
3573
0
                        LatValue *tv = (LatValue *)lat_map_get(args[2].as.map.map, "timeout");
3574
0
                        if (tv && tv->type == VAL_INT) timeout_ms = (int)tv->as.int_val;
3575
0
                        LatValue *hm = (LatValue *)lat_map_get(args[2].as.map.map, "headers");
3576
0
                        if (hm && hm->type == VAL_MAP) {
3577
0
                            hdr_count = lat_map_len(hm->as.map.map);
3578
0
                            hdr_keys = malloc(hdr_count * sizeof(char *));
3579
0
                            hdr_vals = malloc(hdr_count * sizeof(char *));
3580
0
                            size_t hi = 0;
3581
0
                            for (size_t i = 0; i < hm->as.map.map->cap && hi < hdr_count; i++) {
3582
0
                                if (hm->as.map.map->entries[i].state == MAP_OCCUPIED) {
3583
0
                                    hdr_keys[hi] = (char *)hm->as.map.map->entries[i].key;
3584
0
                                    LatValue *v = (LatValue *)hm->as.map.map->entries[i].value;
3585
0
                                    hdr_vals[hi] = v->type == VAL_STR ? v->as.str_val : "";
3586
0
                                    hi++;
3587
0
                                }
3588
0
                            }
3589
0
                        }
3590
0
                    }
3591
1
                    HttpRequest hreq = {
3592
1
                        .method = args[0].as.str_val, .url = args[1].as.str_val,
3593
1
                        .header_keys = hdr_keys, .header_values = hdr_vals, .header_count = hdr_count,
3594
1
                        .body = body_str, .body_len = body_len, .timeout_ms = timeout_ms
3595
1
                    };
3596
1
                    char *herr = NULL;
3597
1
                    HttpResponse *hresp = http_execute(&hreq, &herr);
3598
1
                    free(hdr_keys); free(hdr_vals);
3599
3
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
3600
1
                    if (!hresp) return eval_err(herr ? herr : strdup("http_request failed"));
3601
0
                    LatValue result = value_map_new();
3602
0
                    LatValue st = value_int(hresp->status_code);
3603
0
                    lat_map_set(result.as.map.map, "status", &st);
3604
0
                    LatValue bd = value_string(hresp->body ? hresp->body : "");
3605
0
                    lat_map_set(result.as.map.map, "body", &bd);
3606
0
                    LatValue hdrs = value_map_new();
3607
0
                    for (size_t i = 0; i < hresp->header_count; i++) {
3608
0
                        LatValue hv = value_string(hresp->header_values[i]);
3609
0
                        lat_map_set(hdrs.as.map.map, hresp->header_keys[i], &hv);
3610
0
                    }
3611
0
                    lat_map_set(result.as.map.map, "headers", &hdrs);
3612
0
                    http_response_free(hresp);
3613
0
                    return eval_ok(result);
3614
1
                }
3615
3616
                /* ── Math builtins ── */
3617
3618
                /// @builtin abs(x: Int|Float) -> Int|Float
3619
                /// @category Math
3620
                /// Return the absolute value of a number.
3621
                /// @example abs(-5)  // 5
3622
1.13k
                if (strcmp(fn_name, "abs") == 0) {
3623
3
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("abs() expects (Int|Float)")); }
3624
3
                    char *merr = NULL;
3625
3
                    LatValue result = math_abs(&args[0], &merr);
3626
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3627
3
                    free(args);
3628
3
                    if (merr) return eval_err(merr);
3629
3
                    return eval_ok(result);
3630
3
                }
3631
3632
                /// @builtin floor(x: Int|Float) -> Int
3633
                /// @category Math
3634
                /// Round down to the nearest integer.
3635
                /// @example floor(3.7)  // 3
3636
1.12k
                if (strcmp(fn_name, "floor") == 0) {
3637
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("floor() expects (Int|Float)")); }
3638
1
                    char *merr = NULL;
3639
1
                    LatValue result = math_floor(&args[0], &merr);
3640
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3641
1
                    free(args);
3642
1
                    if (merr) return eval_err(merr);
3643
1
                    return eval_ok(result);
3644
1
                }
3645
3646
                /// @builtin ceil(x: Int|Float) -> Int
3647
                /// @category Math
3648
                /// Round up to the nearest integer.
3649
                /// @example ceil(3.2)  // 4
3650
1.12k
                if (strcmp(fn_name, "ceil") == 0) {
3651
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("ceil() expects (Int|Float)")); }
3652
1
                    char *merr = NULL;
3653
1
                    LatValue result = math_ceil(&args[0], &merr);
3654
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3655
1
                    free(args);
3656
1
                    if (merr) return eval_err(merr);
3657
1
                    return eval_ok(result);
3658
1
                }
3659
3660
                /// @builtin round(x: Int|Float) -> Int
3661
                /// @category Math
3662
                /// Round to the nearest integer.
3663
                /// @example round(3.5)  // 4
3664
1.12k
                if (strcmp(fn_name, "round") == 0) {
3665
2
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("round() expects (Int|Float)")); }
3666
2
                    char *merr = NULL;
3667
2
                    LatValue result = math_round(&args[0], &merr);
3668
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3669
2
                    free(args);
3670
2
                    if (merr) return eval_err(merr);
3671
2
                    return eval_ok(result);
3672
2
                }
3673
3674
                /// @builtin sqrt(x: Int|Float) -> Float
3675
                /// @category Math
3676
                /// Return the square root of a number.
3677
                /// @example sqrt(16)  // 4.0
3678
1.12k
                if (strcmp(fn_name, "sqrt") == 0) {
3679
3
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sqrt() expects (Int|Float)")); }
3680
3
                    char *merr = NULL;
3681
3
                    LatValue result = math_sqrt(&args[0], &merr);
3682
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3683
3
                    free(args);
3684
3
                    if (merr) return eval_err(merr);
3685
2
                    return eval_ok(result);
3686
3
                }
3687
3688
                /// @builtin pow(base: Int|Float, exp: Int|Float) -> Float
3689
                /// @category Math
3690
                /// Raise base to the power of exp.
3691
                /// @example pow(2, 10)  // 1024.0
3692
1.12k
                if (strcmp(fn_name, "pow") == 0) {
3693
2
                    if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("pow() expects (Int|Float, Int|Float)")); }
3694
2
                    char *merr = NULL;
3695
2
                    LatValue result = math_pow(&args[0], &args[1], &merr);
3696
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3697
2
                    free(args);
3698
2
                    if (merr) return eval_err(merr);
3699
2
                    return eval_ok(result);
3700
2
                }
3701
3702
                /// @builtin min(a: Int|Float, b: Int|Float) -> Int|Float
3703
                /// @category Math
3704
                /// Return the smaller of two numbers.
3705
                /// @example min(3, 7)  // 3
3706
1.11k
                if (strcmp(fn_name, "min") == 0) {
3707
2
                    if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("min() expects (Int|Float, Int|Float)")); }
3708
2
                    char *merr = NULL;
3709
2
                    LatValue result = math_min(&args[0], &args[1], &merr);
3710
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3711
2
                    free(args);
3712
2
                    if (merr) return eval_err(merr);
3713
2
                    return eval_ok(result);
3714
2
                }
3715
3716
                /// @builtin max(a: Int|Float, b: Int|Float) -> Int|Float
3717
                /// @category Math
3718
                /// Return the larger of two numbers.
3719
                /// @example max(3, 7)  // 7
3720
1.11k
                if (strcmp(fn_name, "max") == 0) {
3721
2
                    if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("max() expects (Int|Float, Int|Float)")); }
3722
2
                    char *merr = NULL;
3723
2
                    LatValue result = math_max(&args[0], &args[1], &merr);
3724
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3725
2
                    free(args);
3726
2
                    if (merr) return eval_err(merr);
3727
2
                    return eval_ok(result);
3728
2
                }
3729
3730
                /// @builtin random() -> Float
3731
                /// @category Math
3732
                /// Return a random float between 0.0 (inclusive) and 1.0 (exclusive).
3733
                /// @example random()  // 0.7231...
3734
1.11k
                if (strcmp(fn_name, "random") == 0) {
3735
1
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("random() expects no arguments")); }
3736
1
                    free(args);
3737
1
                    return eval_ok(math_random());
3738
1
                }
3739
3740
                /// @builtin random_int(min: Int, max: Int) -> Int
3741
                /// @category Math
3742
                /// Return a random integer in the range [min, max).
3743
                /// @example random_int(1, 100)  // 42
3744
1.11k
                if (strcmp(fn_name, "random_int") == 0) {
3745
1
                    if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("random_int() expects (Int, Int)")); }
3746
1
                    char *merr = NULL;
3747
1
                    LatValue result = math_random_int(&args[0], &args[1], &merr);
3748
3
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3749
1
                    free(args);
3750
1
                    if (merr) return eval_err(merr);
3751
1
                    return eval_ok(result);
3752
1
                }
3753
3754
                /// @builtin log(x: Int|Float) -> Float
3755
                /// @category Math
3756
                /// Return the natural logarithm (base e) of a number.
3757
                /// @example log(math_e())  // 1.0
3758
1.11k
                if (strcmp(fn_name, "log") == 0) {
3759
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("log() expects (Int|Float)")); }
3760
1
                    char *merr = NULL;
3761
1
                    LatValue result = math_log(&args[0], &merr);
3762
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3763
1
                    free(args);
3764
1
                    if (merr) return eval_err(merr);
3765
1
                    return eval_ok(result);
3766
1
                }
3767
3768
                /// @builtin log2(x: Int|Float) -> Float
3769
                /// @category Math
3770
                /// Return the base-2 logarithm of a number.
3771
                /// @example log2(8)  // 3.0
3772
1.11k
                if (strcmp(fn_name, "log2") == 0) {
3773
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("log2() expects (Int|Float)")); }
3774
1
                    char *merr = NULL;
3775
1
                    LatValue result = math_log2(&args[0], &merr);
3776
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3777
1
                    free(args);
3778
1
                    if (merr) return eval_err(merr);
3779
1
                    return eval_ok(result);
3780
1
                }
3781
3782
                /// @builtin log10(x: Int|Float) -> Float
3783
                /// @category Math
3784
                /// Return the base-10 logarithm of a number.
3785
                /// @example log10(1000)  // 3.0
3786
1.11k
                if (strcmp(fn_name, "log10") == 0) {
3787
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("log10() expects (Int|Float)")); }
3788
1
                    char *merr = NULL;
3789
1
                    LatValue result = math_log10(&args[0], &merr);
3790
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3791
1
                    free(args);
3792
1
                    if (merr) return eval_err(merr);
3793
1
                    return eval_ok(result);
3794
1
                }
3795
3796
                /// @builtin sin(x: Int|Float) -> Float
3797
                /// @category Math
3798
                /// Return the sine of an angle in radians.
3799
                /// @example sin(0)  // 0.0
3800
1.10k
                if (strcmp(fn_name, "sin") == 0) {
3801
3
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sin() expects (Int|Float)")); }
3802
3
                    char *merr = NULL;
3803
3
                    LatValue result = math_sin(&args[0], &merr);
3804
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3805
3
                    free(args);
3806
3
                    if (merr) return eval_err(merr);
3807
3
                    return eval_ok(result);
3808
3
                }
3809
3810
                /// @builtin cos(x: Int|Float) -> Float
3811
                /// @category Math
3812
                /// Return the cosine of an angle in radians.
3813
                /// @example cos(0)  // 1.0
3814
1.10k
                if (strcmp(fn_name, "cos") == 0) {
3815
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("cos() expects (Int|Float)")); }
3816
1
                    char *merr = NULL;
3817
1
                    LatValue result = math_cos(&args[0], &merr);
3818
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3819
1
                    free(args);
3820
1
                    if (merr) return eval_err(merr);
3821
1
                    return eval_ok(result);
3822
1
                }
3823
3824
                /// @builtin tan(x: Int|Float) -> Float
3825
                /// @category Math
3826
                /// Return the tangent of an angle in radians.
3827
                /// @example tan(0)  // 0.0
3828
1.10k
                if (strcmp(fn_name, "tan") == 0) {
3829
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tan() expects (Int|Float)")); }
3830
1
                    char *merr = NULL;
3831
1
                    LatValue result = math_tan(&args[0], &merr);
3832
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3833
1
                    free(args);
3834
1
                    if (merr) return eval_err(merr);
3835
1
                    return eval_ok(result);
3836
1
                }
3837
3838
                /// @builtin atan2(y: Int|Float, x: Int|Float) -> Float
3839
                /// @category Math
3840
                /// Return the two-argument arctangent in radians.
3841
                /// @example atan2(1, 1)  // 0.7853...
3842
1.10k
                if (strcmp(fn_name, "atan2") == 0) {
3843
1
                    if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("atan2() expects (Int|Float, Int|Float)")); }
3844
1
                    char *merr = NULL;
3845
1
                    LatValue result = math_atan2(&args[0], &args[1], &merr);
3846
3
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3847
1
                    free(args);
3848
1
                    if (merr) return eval_err(merr);
3849
1
                    return eval_ok(result);
3850
1
                }
3851
3852
                /// @builtin clamp(x: Int|Float, lo: Int|Float, hi: Int|Float) -> Int|Float
3853
                /// @category Math
3854
                /// Clamp a value between a minimum and maximum.
3855
                /// @example clamp(15, 0, 10)  // 10
3856
1.10k
                if (strcmp(fn_name, "clamp") == 0) {
3857
3
                    if (argc != 3) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("clamp() expects (Int|Float, Int|Float, Int|Float)")); }
3858
3
                    char *merr = NULL;
3859
3
                    LatValue result = math_clamp(&args[0], &args[1], &args[2], &merr);
3860
12
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3861
3
                    free(args);
3862
3
                    if (merr) return eval_err(merr);
3863
3
                    return eval_ok(result);
3864
3
                }
3865
3866
                /// @builtin math_pi() -> Float
3867
                /// @category Math
3868
                /// Return the mathematical constant pi.
3869
                /// @example math_pi()  // 3.14159265358979...
3870
1.10k
                if (strcmp(fn_name, "math_pi") == 0) {
3871
1
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("math_pi() expects no arguments")); }
3872
1
                    free(args);
3873
1
                    return eval_ok(math_pi());
3874
1
                }
3875
3876
                /// @builtin math_e() -> Float
3877
                /// @category Math
3878
                /// Return Euler's number (e).
3879
                /// @example math_e()  // 2.71828182845904...
3880
1.09k
                if (strcmp(fn_name, "math_e") == 0) {
3881
2
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("math_e() expects no arguments")); }
3882
2
                    free(args);
3883
2
                    return eval_ok(math_e());
3884
2
                }
3885
3886
                /// @builtin asin(x: Int|Float) -> Float
3887
                /// @category Math
3888
                /// Return the arcsine in radians.
3889
                /// @example asin(1)  // 1.5707...
3890
1.09k
                if (strcmp(fn_name, "asin") == 0) {
3891
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("asin() expects (Int|Float)")); }
3892
1
                    char *merr = NULL;
3893
1
                    LatValue result = math_asin(&args[0], &merr);
3894
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3895
1
                    free(args);
3896
1
                    if (merr) return eval_err(merr);
3897
1
                    return eval_ok(result);
3898
1
                }
3899
3900
                /// @builtin acos(x: Int|Float) -> Float
3901
                /// @category Math
3902
                /// Return the arccosine in radians.
3903
                /// @example acos(1)  // 0.0
3904
1.09k
                if (strcmp(fn_name, "acos") == 0) {
3905
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("acos() expects (Int|Float)")); }
3906
1
                    char *merr = NULL;
3907
1
                    LatValue result = math_acos(&args[0], &merr);
3908
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3909
1
                    free(args);
3910
1
                    if (merr) return eval_err(merr);
3911
1
                    return eval_ok(result);
3912
1
                }
3913
3914
                /// @builtin atan(x: Int|Float) -> Float
3915
                /// @category Math
3916
                /// Return the arctangent in radians.
3917
                /// @example atan(1)  // 0.7853...
3918
1.09k
                if (strcmp(fn_name, "atan") == 0) {
3919
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("atan() expects (Int|Float)")); }
3920
1
                    char *merr = NULL;
3921
1
                    LatValue result = math_atan(&args[0], &merr);
3922
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3923
1
                    free(args);
3924
1
                    if (merr) return eval_err(merr);
3925
1
                    return eval_ok(result);
3926
1
                }
3927
3928
                /// @builtin exp(x: Int|Float) -> Float
3929
                /// @category Math
3930
                /// Return e raised to the power of x.
3931
                /// @example exp(1)  // 2.71828...
3932
1.09k
                if (strcmp(fn_name, "exp") == 0) {
3933
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("exp() expects (Int|Float)")); }
3934
1
                    char *merr = NULL;
3935
1
                    LatValue result = math_exp(&args[0], &merr);
3936
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3937
1
                    free(args);
3938
1
                    if (merr) return eval_err(merr);
3939
1
                    return eval_ok(result);
3940
1
                }
3941
3942
                /// @builtin sign(x: Int|Float) -> Int
3943
                /// @category Math
3944
                /// Return -1, 0, or 1 indicating the sign of a number.
3945
                /// @example sign(-42)  // -1
3946
1.09k
                if (strcmp(fn_name, "sign") == 0) {
3947
3
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sign() expects (Int|Float)")); }
3948
3
                    char *merr = NULL;
3949
3
                    LatValue result = math_sign(&args[0], &merr);
3950
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3951
3
                    free(args);
3952
3
                    if (merr) return eval_err(merr);
3953
3
                    return eval_ok(result);
3954
3
                }
3955
3956
                /// @builtin gcd(a: Int, b: Int) -> Int
3957
                /// @category Math
3958
                /// Return the greatest common divisor of two integers.
3959
                /// @example gcd(12, 8)  // 4
3960
1.09k
                if (strcmp(fn_name, "gcd") == 0) {
3961
1
                    if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("gcd() expects (Int, Int)")); }
3962
1
                    char *merr = NULL;
3963
1
                    LatValue result = math_gcd(&args[0], &args[1], &merr);
3964
3
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3965
1
                    free(args);
3966
1
                    if (merr) return eval_err(merr);
3967
1
                    return eval_ok(result);
3968
1
                }
3969
3970
                /// @builtin lcm(a: Int, b: Int) -> Int
3971
                /// @category Math
3972
                /// Return the least common multiple of two integers.
3973
                /// @example lcm(4, 6)  // 12
3974
1.08k
                if (strcmp(fn_name, "lcm") == 0) {
3975
1
                    if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("lcm() expects (Int, Int)")); }
3976
1
                    char *merr = NULL;
3977
1
                    LatValue result = math_lcm(&args[0], &args[1], &merr);
3978
3
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3979
1
                    free(args);
3980
1
                    if (merr) return eval_err(merr);
3981
1
                    return eval_ok(result);
3982
1
                }
3983
3984
                /// @builtin float_to_bits(x: Float) -> Int
3985
                /// @category Math
3986
                /// Reinterpret a float as its IEEE 754 bit pattern (64-bit integer).
3987
                /// @example float_to_bits(1.0)  // 4607182418800017408
3988
1.08k
                if (strcmp(fn_name, "float_to_bits") == 0) {
3989
0
                    if (argc != 1 || args[0].type != VAL_FLOAT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("float_to_bits() expects 1 Float argument")); }
3990
0
                    double d = args[0].as.float_val;
3991
0
                    uint64_t bits; memcpy(&bits, &d, 8);
3992
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
3993
0
                    free(args);
3994
0
                    return eval_ok(value_int((int64_t)bits));
3995
0
                }
3996
3997
                /// @builtin bits_to_float(x: Int) -> Float
3998
                /// @category Math
3999
                /// Reinterpret a 64-bit integer as an IEEE 754 float.
4000
                /// @example bits_to_float(4607182418800017408)  // 1.0
4001
1.08k
                if (strcmp(fn_name, "bits_to_float") == 0) {
4002
0
                    if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("bits_to_float() expects 1 Int argument")); }
4003
0
                    uint64_t bits = (uint64_t)args[0].as.int_val;
4004
0
                    double d; memcpy(&d, &bits, 8);
4005
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4006
0
                    free(args);
4007
0
                    return eval_ok(value_float(d));
4008
0
                }
4009
4010
                /// @builtin is_nan(x: Int|Float) -> Bool
4011
                /// @category Math
4012
                /// Check if a value is NaN (not a number).
4013
                /// @example is_nan(0.0 / 0.0)  // true
4014
1.08k
                if (strcmp(fn_name, "is_nan") == 0) {
4015
2
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_nan() expects (Int|Float)")); }
4016
2
                    char *merr = NULL;
4017
2
                    LatValue result = math_is_nan(&args[0], &merr);
4018
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4019
2
                    free(args);
4020
2
                    if (merr) return eval_err(merr);
4021
2
                    return eval_ok(result);
4022
2
                }
4023
4024
                /// @builtin is_inf(x: Int|Float) -> Bool
4025
                /// @category Math
4026
                /// Check if a value is positive or negative infinity.
4027
                /// @example is_inf(1.0 / 0.0)  // true
4028
1.08k
                if (strcmp(fn_name, "is_inf") == 0) {
4029
2
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("is_inf() expects (Int|Float)")); }
4030
2
                    char *merr = NULL;
4031
2
                    LatValue result = math_is_inf(&args[0], &merr);
4032
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4033
2
                    free(args);
4034
2
                    if (merr) return eval_err(merr);
4035
2
                    return eval_ok(result);
4036
2
                }
4037
4038
                /// @builtin sinh(x: Int|Float) -> Float
4039
                /// @category Math
4040
                /// Return the hyperbolic sine.
4041
                /// @example sinh(1)  // 1.1752...
4042
1.08k
                if (strcmp(fn_name, "sinh") == 0) {
4043
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sinh() expects (Int|Float)")); }
4044
1
                    char *merr = NULL;
4045
1
                    LatValue result = math_sinh(&args[0], &merr);
4046
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4047
1
                    free(args);
4048
1
                    if (merr) return eval_err(merr);
4049
1
                    return eval_ok(result);
4050
1
                }
4051
4052
                /// @builtin cosh(x: Int|Float) -> Float
4053
                /// @category Math
4054
                /// Return the hyperbolic cosine.
4055
                /// @example cosh(0)  // 1.0
4056
1.08k
                if (strcmp(fn_name, "cosh") == 0) {
4057
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("cosh() expects (Int|Float)")); }
4058
1
                    char *merr = NULL;
4059
1
                    LatValue result = math_cosh(&args[0], &merr);
4060
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4061
1
                    free(args);
4062
1
                    if (merr) return eval_err(merr);
4063
1
                    return eval_ok(result);
4064
1
                }
4065
4066
                /// @builtin tanh(x: Int|Float) -> Float
4067
                /// @category Math
4068
                /// Return the hyperbolic tangent.
4069
                /// @example tanh(0)  // 0.0
4070
1.08k
                if (strcmp(fn_name, "tanh") == 0) {
4071
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("tanh() expects (Int|Float)")); }
4072
1
                    char *merr = NULL;
4073
1
                    LatValue result = math_tanh(&args[0], &merr);
4074
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4075
1
                    free(args);
4076
1
                    if (merr) return eval_err(merr);
4077
1
                    return eval_ok(result);
4078
1
                }
4079
4080
                /// @builtin lerp(a: Int|Float, b: Int|Float, t: Int|Float) -> Float
4081
                /// @category Math
4082
                /// Linear interpolation between a and b by factor t.
4083
                /// @example lerp(0, 10, 0.5)  // 5.0
4084
1.08k
                if (strcmp(fn_name, "lerp") == 0) {
4085
2
                    if (argc != 3) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("lerp() expects (Int|Float, Int|Float, Int|Float)")); }
4086
2
                    char *merr = NULL;
4087
2
                    LatValue result = math_lerp(&args[0], &args[1], &args[2], &merr);
4088
8
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4089
2
                    free(args);
4090
2
                    if (merr) return eval_err(merr);
4091
2
                    return eval_ok(result);
4092
2
                }
4093
4094
                /* ── range() builtin ── */
4095
4096
                /// @builtin range(start: Int, end: Int, step?: Int) -> Array
4097
                /// @category Type Constructors
4098
                /// Generate an array of integers from start (inclusive) to end (exclusive).
4099
                /// @example range(0, 5)  // [0, 1, 2, 3, 4]
4100
                /// @example range(0, 10, 2)  // [0, 2, 4, 6, 8]
4101
1.07k
                if (strcmp(fn_name, "range") == 0) {
4102
7
                    if (argc < 2 || argc > 3) {
4103
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4104
0
                        free(args);
4105
0
                        return eval_err(strdup("range() expects 2 or 3 integer arguments (start, end, step?)"));
4106
0
                    }
4107
7
                    if (args[0].type != VAL_INT || args[1].type != VAL_INT) {
4108
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4109
0
                        free(args);
4110
0
                        return eval_err(strdup("range() start and end must be integers"));
4111
0
                    }
4112
7
                    int64_t rstart = args[0].as.int_val;
4113
7
                    int64_t rend = args[1].as.int_val;
4114
7
                    int64_t rstep = (rstart <= rend) ? 1 : -1;
4115
7
                    if (argc == 3) {
4116
4
                        if (args[2].type != VAL_INT) {
4117
0
                            for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4118
0
                            free(args);
4119
0
                            return eval_err(strdup("range() step must be an integer"));
4120
0
                        }
4121
4
                        rstep = args[2].as.int_val;
4122
4
                    }
4123
25
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4124
7
                    free(args);
4125
7
                    if (rstep == 0) {
4126
1
                        return eval_err(strdup("range() step cannot be 0"));
4127
1
                    }
4128
                    /* Calculate count and build array */
4129
6
                    size_t rcount = 0;
4130
6
                    if (rstep > 0 && rstart < rend) {
4131
2
                        rcount = (size_t)((rend - rstart + rstep - 1) / rstep);
4132
4
                    } else if (rstep < 0 && rstart > rend) {
4133
2
                        rcount = (size_t)((rstart - rend + (-rstep) - 1) / (-rstep));
4134
2
                    }
4135
6
                    LatValue *relems = malloc((rcount > 0 ? rcount : 1) * sizeof(LatValue));
4136
6
                    int64_t rcur = rstart;
4137
25
                    for (size_t ri = 0; ri < rcount; ri++) {
4138
19
                        relems[ri] = value_int(rcur);
4139
19
                        rcur += rstep;
4140
19
                    }
4141
6
                    LatValue range_arr = value_array(relems, rcount);
4142
6
                    free(relems);
4143
6
                    return eval_ok(range_arr);
4144
7
                }
4145
4146
                /* ── Type coercion builtins ── */
4147
4148
                /// @builtin to_int(val: Any) -> Int
4149
                /// @category Type Conversion
4150
                /// Convert a value to an integer (truncates floats, parses strings).
4151
                /// @example to_int(3.9)  // 3
4152
1.07k
                if (strcmp(fn_name, "to_int") == 0) {
4153
0
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("to_int() expects (value)")); }
4154
0
                    char *terr = NULL;
4155
0
                    LatValue result = type_to_int(&args[0], &terr);
4156
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4157
0
                    free(args);
4158
0
                    if (terr) return eval_err(terr);
4159
0
                    return eval_ok(result);
4160
0
                }
4161
4162
                /// @builtin to_float(val: Any) -> Float
4163
                /// @category Type Conversion
4164
                /// Convert a value to a floating-point number.
4165
                /// @example to_float(42)  // 42.0
4166
1.07k
                if (strcmp(fn_name, "to_float") == 0) {
4167
0
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("to_float() expects (value)")); }
4168
0
                    char *terr = NULL;
4169
0
                    LatValue result = type_to_float(&args[0], &terr);
4170
0
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4171
0
                    free(args);
4172
0
                    if (terr) return eval_err(terr);
4173
0
                    return eval_ok(result);
4174
0
                }
4175
4176
                /* ── Environment variable builtins ── */
4177
4178
                /// @builtin env(name: String) -> String|Unit
4179
                /// @category Environment
4180
                /// Get an environment variable's value, or unit if not set.
4181
                /// @example env("HOME")  // "/home/user"
4182
1.07k
                if (strcmp(fn_name, "env") == 0) {
4183
4
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("env() expects (String)")); }
4184
3
                    char *val = envvar_get(args[0].as.str_val);
4185
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4186
3
                    free(args);
4187
3
                    if (!val) return eval_ok(value_unit());
4188
2
                    return eval_ok(value_string_owned(val));
4189
3
                }
4190
4191
                /// @builtin env_set(name: String, value: String) -> Unit
4192
                /// @category Environment
4193
                /// Set an environment variable.
4194
                /// @example env_set("MY_VAR", "hello")
4195
1.06k
                if (strcmp(fn_name, "env_set") == 0) {
4196
3
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("env_set() expects (String, String)")); }
4197
2
                    char *eerr = NULL;
4198
2
                    bool ok = envvar_set(args[0].as.str_val, args[1].as.str_val, &eerr);
4199
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4200
2
                    free(args);
4201
2
                    if (!ok) return eval_err(eerr);
4202
2
                    return eval_ok(value_unit());
4203
2
                }
4204
4205
                /// @builtin env_keys() -> Array
4206
                /// @category Environment
4207
                /// Return an array of all environment variable names.
4208
                /// @example env_keys()  // ["HOME", "PATH", ...]
4209
1.06k
                if (strcmp(fn_name, "env_keys") == 0) {
4210
1
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("env_keys() expects no arguments")); }
4211
1
                    free(args);
4212
1
                    char **keys = NULL;
4213
1
                    size_t key_count = 0;
4214
1
                    envvar_keys(&keys, &key_count);
4215
1
                    LatValue *elems = NULL;
4216
1
                    if (key_count > 0) {
4217
1
                        elems = malloc(key_count * sizeof(LatValue));
4218
76
                        for (size_t i = 0; i < key_count; i++) {
4219
75
                            elems[i] = value_string_owned(keys[i]);
4220
75
                        }
4221
1
                    }
4222
1
                    free(keys);
4223
1
                    LatValue arr = value_array(elems, key_count);
4224
1
                    free(elems);
4225
1
                    return eval_ok(arr);
4226
1
                }
4227
4228
                /* ── Time builtins ── */
4229
4230
                /// @builtin time() -> Int
4231
                /// @category Date & Time
4232
                /// Return the current Unix timestamp in milliseconds.
4233
                /// @example time()  // 1700000000000
4234
1.06k
                if (strcmp(fn_name, "time") == 0) {
4235
17
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("time() expects no arguments")); }
4236
16
                    free(args);
4237
16
                    return eval_ok(value_int(time_now_ms()));
4238
17
                }
4239
4240
                /// @builtin sleep(ms: Int) -> Unit
4241
                /// @category Date & Time
4242
                /// Pause execution for the given number of milliseconds.
4243
                /// @example sleep(1000)  // sleeps for 1 second
4244
1.04k
                if (strcmp(fn_name, "sleep") == 0) {
4245
2
                    if (argc != 1 || args[0].type != VAL_INT) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sleep() expects (Int milliseconds)")); }
4246
1
                    char *terr = NULL;
4247
1
                    bool ok = time_sleep_ms(args[0].as.int_val, &terr);
4248
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4249
1
                    free(args);
4250
1
                    if (!ok) return eval_err(terr);
4251
1
                    return eval_ok(value_unit());
4252
1
                }
4253
4254
                /* ── Process/system builtins ── */
4255
4256
                /// @builtin cwd() -> String
4257
                /// @category Process
4258
                /// Return the current working directory.
4259
                /// @example cwd()  // "/home/user/project"
4260
1.04k
                if (strcmp(fn_name, "cwd") == 0) {
4261
1
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("cwd() expects no arguments")); }
4262
1
                    free(args);
4263
1
                    char *cwd_err = NULL;
4264
1
                    char *dir = process_cwd(&cwd_err);
4265
1
                    if (!dir) return eval_err(cwd_err);
4266
1
                    return eval_ok(value_string_owned(dir));
4267
1
                }
4268
4269
                /// @builtin exec(cmd: String) -> Map
4270
                /// @category Process
4271
                /// Execute a command directly (no shell), returning {stdout, stderr, status}.
4272
                /// @example exec("ls -la")  // {stdout: "...", stderr: "", status: 0}
4273
1.04k
                if (strcmp(fn_name, "exec") == 0) {
4274
1
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("exec() expects 1 argument")); }
4275
1
                    if (args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("exec() expects a string command")); }
4276
1
                    char *exec_err = NULL;
4277
1
                    LatValue result = process_exec(args[0].as.str_val, &exec_err);
4278
2
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4279
1
                    free(args);
4280
1
                    if (exec_err) return eval_err(exec_err);
4281
1
                    return eval_ok(result);
4282
1
                }
4283
4284
                /// @builtin shell(cmd: String) -> Map
4285
                /// @category Process
4286
                /// Execute a command via the system shell, returning {stdout, stderr, status}.
4287
                /// @example shell("echo hello")  // {stdout: "hello\n", stderr: "", status: 0}
4288
1.04k
                if (strcmp(fn_name, "shell") == 0) {
4289
3
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("shell() expects 1 argument")); }
4290
3
                    if (args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("shell() expects a string command")); }
4291
3
                    char *shell_err = NULL;
4292
3
                    LatValue result = process_shell(args[0].as.str_val, &shell_err);
4293
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4294
3
                    free(args);
4295
3
                    if (shell_err) return eval_err(shell_err);
4296
3
                    return eval_ok(result);
4297
3
                }
4298
4299
                /// @builtin args() -> Array
4300
                /// @category Process
4301
                /// Return command-line arguments as an array of strings.
4302
                /// @example args()  // ["script.lat", "--flag"]
4303
1.04k
                if (strcmp(fn_name, "args") == 0) {
4304
1
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("args() expects no arguments")); }
4305
1
                    free(args);
4306
#ifdef __EMSCRIPTEN__
4307
                    LatValue arr = value_array(NULL, 0);
4308
                    return eval_ok(arr);
4309
#else
4310
1
                    int ac = ev->prog_argc;
4311
1
                    char **av = ev->prog_argv;
4312
1
                    LatValue *elems = NULL;
4313
1
                    if (ac > 0) {
4314
0
                        elems = malloc((size_t)ac * sizeof(LatValue));
4315
0
                        for (int i = 0; i < ac; i++)
4316
0
                            elems[i] = value_string(av[i]);
4317
0
                    }
4318
1
                    LatValue arr = value_array(elems, (size_t)ac);
4319
1
                    free(elems);
4320
1
                    return eval_ok(arr);
4321
1
#endif
4322
1
                }
4323
4324
                /// @builtin platform() -> String
4325
                /// @category Process
4326
                /// Return the operating system name ("darwin", "linux", etc.).
4327
                /// @example platform()  // "darwin"
4328
1.03k
                if (strcmp(fn_name, "platform") == 0) {
4329
2
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("platform() expects no arguments")); }
4330
2
                    free(args);
4331
2
                    return eval_ok(value_string(process_platform()));
4332
2
                }
4333
4334
                /// @builtin hostname() -> String
4335
                /// @category Process
4336
                /// Return the system hostname.
4337
                /// @example hostname()  // "my-machine"
4338
1.03k
                if (strcmp(fn_name, "hostname") == 0) {
4339
1
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("hostname() expects no arguments")); }
4340
1
                    free(args);
4341
1
                    char *h_err = NULL;
4342
1
                    char *name = process_hostname(&h_err);
4343
1
                    if (!name) return eval_err(h_err);
4344
1
                    LatValue v = value_string_owned(name);
4345
1
                    return eval_ok(v);
4346
1
                }
4347
4348
                /// @builtin pid() -> Int
4349
                /// @category Process
4350
                /// Return the current process ID.
4351
                /// @example pid()  // 12345
4352
1.03k
                if (strcmp(fn_name, "pid") == 0) {
4353
1
                    if (argc != 0) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("pid() expects no arguments")); }
4354
1
                    free(args);
4355
1
                    return eval_ok(value_int((int64_t)process_pid()));
4356
1
                }
4357
4358
                /* ── URL encoding builtins ── */
4359
4360
                /// @builtin url_encode(s: String) -> String
4361
                /// @category URL
4362
                /// Percent-encode a string for use in URLs.
4363
                /// @example url_encode("hello world")  // "hello%20world"
4364
1.03k
                if (strcmp(fn_name, "url_encode") == 0) {
4365
2
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("url_encode() expects (String)")); }
4366
2
                    const char *src = args[0].as.str_val;
4367
2
                    size_t slen = strlen(src);
4368
                    /* Worst case: every byte becomes %XX (3x expansion) */
4369
2
                    size_t cap = slen * 3 + 1;
4370
2
                    char *out = malloc(cap);
4371
2
                    size_t j = 0;
4372
26
                    for (size_t i = 0; i < slen; i++) {
4373
24
                        unsigned char c = (unsigned char)src[i];
4374
24
                        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
4375
24
                            (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~') {
4376
20
                            out[j++] = (char)c;
4377
20
                        } else {
4378
4
                            snprintf(out + j, 4, "%%%02X", c);
4379
4
                            j += 3;
4380
4
                        }
4381
24
                    }
4382
2
                    out[j] = '\0';
4383
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4384
2
                    free(args);
4385
2
                    return eval_ok(value_string_owned(out));
4386
2
                }
4387
4388
                /// @builtin url_decode(s: String) -> String
4389
                /// @category URL
4390
                /// Decode a percent-encoded URL string.
4391
                /// @example url_decode("hello%20world")  // "hello world"
4392
1.03k
                if (strcmp(fn_name, "url_decode") == 0) {
4393
2
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("url_decode() expects (String)")); }
4394
2
                    const char *src = args[0].as.str_val;
4395
2
                    size_t slen = strlen(src);
4396
2
                    char *out = malloc(slen + 1);
4397
2
                    size_t j = 0;
4398
20
                    for (size_t i = 0; i < slen; i++) {
4399
18
                        if (src[i] == '%' && i + 2 < slen) {
4400
1
                            char hex[3] = { src[i+1], src[i+2], '\0' };
4401
1
                            char *end = NULL;
4402
1
                            unsigned long val = strtoul(hex, &end, 16);
4403
1
                            if (end == hex + 2) {
4404
1
                                out[j++] = (char)val;
4405
1
                                i += 2;
4406
1
                            } else {
4407
0
                                out[j++] = src[i];
4408
0
                            }
4409
17
                        } else if (src[i] == '+') {
4410
1
                            out[j++] = ' ';
4411
16
                        } else {
4412
16
                            out[j++] = src[i];
4413
16
                        }
4414
18
                    }
4415
2
                    out[j] = '\0';
4416
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4417
2
                    free(args);
4418
2
                    return eval_ok(value_string_owned(out));
4419
2
                }
4420
4421
                /* ── CSV builtins ── */
4422
4423
                /// @builtin csv_parse(s: String) -> Array
4424
                /// @category CSV
4425
                /// Parse a CSV string into an array of arrays (rows of fields).
4426
                /// @example csv_parse("a,b\n1,2")  // [["a", "b"], ["1", "2"]]
4427
1.03k
                if (strcmp(fn_name, "csv_parse") == 0) {
4428
3
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("csv_parse() expects (String)")); }
4429
3
                    const char *input = args[0].as.str_val;
4430
3
                    size_t pos = 0;
4431
3
                    size_t input_len = strlen(input);
4432
4433
                    /* Collect rows into a temporary array */
4434
3
                    size_t rows_cap = 8;
4435
3
                    size_t rows_len = 0;
4436
3
                    LatValue *rows = malloc(rows_cap * sizeof(LatValue));
4437
4438
9
                    while (pos < input_len) {
4439
                        /* Parse one row: collect fields */
4440
6
                        size_t fields_cap = 8;
4441
6
                        size_t fields_len = 0;
4442
6
                        LatValue *fields = malloc(fields_cap * sizeof(LatValue));
4443
4444
14
                        for (;;) {
4445
                            /* Parse one field */
4446
14
                            size_t field_cap = 64;
4447
14
                            size_t field_len = 0;
4448
14
                            char *field = malloc(field_cap);
4449
4450
14
                            if (pos < input_len && input[pos] == '"') {
4451
                                /* Quoted field */
4452
2
                                pos++; /* skip opening quote */
4453
17
                                for (;;) {
4454
17
                                    if (pos >= input_len) break;
4455
17
                                    if (input[pos] == '"') {
4456
2
                                        if (pos + 1 < input_len && input[pos + 1] == '"') {
4457
                                            /* Escaped quote */
4458
0
                                            if (field_len + 1 >= field_cap) { field_cap *= 2; field = realloc(field, field_cap); }
4459
0
                                            field[field_len++] = '"';
4460
0
                                            pos += 2;
4461
2
                                        } else {
4462
                                            /* End of quoted field */
4463
2
                                            pos++; /* skip closing quote */
4464
2
                                            break;
4465
2
                                        }
4466
15
                                    } else {
4467
15
                                        if (field_len + 1 >= field_cap) { field_cap *= 2; field = realloc(field, field_cap); }
4468
15
                                        field[field_len++] = input[pos++];
4469
15
                                    }
4470
17
                                }
4471
12
                            } else {
4472
                                /* Unquoted field */
4473
30
                                while (pos < input_len && input[pos] != ',' && input[pos] != '\n' && input[pos] != '\r') {
4474
18
                                    if (field_len + 1 >= field_cap) { field_cap *= 2; field = realloc(field, field_cap); }
4475
18
                                    field[field_len++] = input[pos++];
4476
18
                                }
4477
12
                            }
4478
4479
14
                            field[field_len] = '\0';
4480
4481
                            /* Add field to fields array */
4482
14
                            if (fields_len >= fields_cap) { fields_cap *= 2; fields = realloc(fields, fields_cap * sizeof(LatValue)); }
4483
14
                            fields[fields_len++] = value_string_owned(field);
4484
4485
                            /* Check what follows */
4486
14
                            if (pos < input_len && input[pos] == ',') {
4487
8
                                pos++; /* skip comma, continue to next field */
4488
8
                            } else {
4489
6
                                break; /* end of row */
4490
6
                            }
4491
14
                        }
4492
4493
                        /* Skip line ending */
4494
6
                        if (pos < input_len && input[pos] == '\r') pos++;
4495
6
                        if (pos < input_len && input[pos] == '\n') pos++;
4496
4497
                        /* Build row array and add to rows (value_array does shallow copy, so don't free elements) */
4498
6
                        LatValue row = value_array(fields, fields_len);
4499
6
                        free(fields);
4500
4501
6
                        if (rows_len >= rows_cap) { rows_cap *= 2; rows = realloc(rows, rows_cap * sizeof(LatValue)); }
4502
6
                        rows[rows_len++] = row;
4503
6
                    }
4504
4505
3
                    LatValue result = value_array(rows, rows_len);
4506
3
                    free(rows);
4507
4508
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4509
3
                    free(args);
4510
3
                    return eval_ok(result);
4511
3
                }
4512
4513
                /// @builtin csv_stringify(rows: Array) -> String
4514
                /// @category CSV
4515
                /// Convert an array of arrays into a CSV string.
4516
                /// @example csv_stringify([["a", "b"], ["1", "2"]])  // "a,b\n1,2\n"
4517
1.02k
                if (strcmp(fn_name, "csv_stringify") == 0) {
4518
2
                    if (argc != 1 || args[0].type != VAL_ARRAY) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("csv_stringify() expects (Array)")); }
4519
4520
2
                    LatValue *data = &args[0];
4521
2
                    size_t out_cap = 256;
4522
2
                    size_t out_len = 0;
4523
2
                    char *out = malloc(out_cap);
4524
4525
6
                    for (size_t r = 0; r < data->as.array.len; r++) {
4526
4
                        LatValue *row = &data->as.array.elems[r];
4527
4
                        if (row->type != VAL_ARRAY) {
4528
0
                            free(out);
4529
0
                            for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4530
0
                            free(args);
4531
0
                            return eval_err(strdup("csv_stringify(): each row must be an Array"));
4532
0
                        }
4533
12
                        for (size_t c = 0; c < row->as.array.len; c++) {
4534
8
                            if (c > 0) {
4535
4
                                if (out_len + 1 >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); }
4536
4
                                out[out_len++] = ',';
4537
4
                            }
4538
4539
8
                            char *field_str = value_display(&row->as.array.elems[c]);
4540
8
                            size_t flen = strlen(field_str);
4541
4542
                            /* Check if quoting is needed */
4543
8
                            bool needs_quote = false;
4544
26
                            for (size_t k = 0; k < flen; k++) {
4545
18
                                if (field_str[k] == ',' || field_str[k] == '"' || field_str[k] == '\n' || field_str[k] == '\r') {
4546
0
                                    needs_quote = true;
4547
0
                                    break;
4548
0
                                }
4549
18
                            }
4550
4551
8
                            if (needs_quote) {
4552
                                /* Count quotes for escaped size */
4553
0
                                size_t extra = 0;
4554
0
                                for (size_t k = 0; k < flen; k++) {
4555
0
                                    if (field_str[k] == '"') extra++;
4556
0
                                }
4557
0
                                size_t needed = flen + extra + 2; /* +2 for surrounding quotes */
4558
0
                                while (out_len + needed >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); }
4559
0
                                out[out_len++] = '"';
4560
0
                                for (size_t k = 0; k < flen; k++) {
4561
0
                                    if (field_str[k] == '"') out[out_len++] = '"';
4562
0
                                    out[out_len++] = field_str[k];
4563
0
                                }
4564
0
                                out[out_len++] = '"';
4565
8
                            } else {
4566
8
                                while (out_len + flen >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); }
4567
8
                                memcpy(out + out_len, field_str, flen);
4568
8
                                out_len += flen;
4569
8
                            }
4570
8
                            free(field_str);
4571
8
                        }
4572
                        /* Append newline */
4573
4
                        if (out_len + 1 >= out_cap) { out_cap *= 2; out = realloc(out, out_cap); }
4574
4
                        out[out_len++] = '\n';
4575
4
                    }
4576
2
                    out[out_len] = '\0';
4577
4578
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4579
2
                    free(args);
4580
2
                    return eval_ok(value_string_owned(out));
4581
2
                }
4582
4583
                /* ── TOML builtins ── */
4584
4585
                /// @builtin toml_parse(s: String) -> Map
4586
                /// @category Data Formats
4587
                /// Parse a TOML string into a Lattice Map.
4588
                /// @example toml_parse("[server]\nhost = \"localhost\"\nport = 8080")
4589
1.02k
                if (strcmp(fn_name, "toml_parse") == 0) {
4590
6
                    if (argc != 1 || args[0].type != VAL_STR) {
4591
2
                        for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
4592
1
                        return eval_err(strdup("toml_parse() expects (String)"));
4593
1
                    }
4594
5
                    char *terr = NULL;
4595
5
                    LatValue result = toml_ops_parse(args[0].as.str_val, &terr);
4596
10
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
4597
5
                    if (terr) return eval_err(terr);
4598
5
                    return eval_ok(result);
4599
5
                }
4600
4601
                /// @builtin toml_stringify(val: Map) -> String
4602
                /// @category Data Formats
4603
                /// Serialize a Lattice Map to a TOML string.
4604
                /// @example toml_stringify({"host": "localhost", "port": 8080})
4605
1.02k
                if (strcmp(fn_name, "toml_stringify") == 0) {
4606
2
                    if (argc != 1) {
4607
0
                        for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
4608
0
                        return eval_err(strdup("toml_stringify() expects (Map)"));
4609
0
                    }
4610
2
                    char *terr = NULL;
4611
2
                    char *toml = toml_ops_stringify(&args[0], &terr);
4612
4
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
4613
2
                    if (terr) { free(toml); return eval_err(terr); }
4614
1
                    return eval_ok(value_string_owned(toml));
4615
2
                }
4616
4617
                /* ── YAML builtins ── */
4618
4619
                /// @builtin yaml_parse(s: String) -> Map|Array
4620
                /// @category Data Formats
4621
                /// Parse a YAML string into a Lattice value.
4622
                /// @example yaml_parse("name: Alice\nage: 30")
4623
1.01k
                if (strcmp(fn_name, "yaml_parse") == 0) {
4624
7
                    if (argc != 1 || args[0].type != VAL_STR) {
4625
2
                        for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
4626
1
                        return eval_err(strdup("yaml_parse() expects (String)"));
4627
1
                    }
4628
6
                    char *yerr = NULL;
4629
6
                    LatValue result = yaml_ops_parse(args[0].as.str_val, &yerr);
4630
12
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
4631
6
                    if (yerr) return eval_err(yerr);
4632
6
                    return eval_ok(result);
4633
6
                }
4634
4635
                /// @builtin yaml_stringify(val: Map|Array) -> String
4636
                /// @category Data Formats
4637
                /// Serialize a Lattice value to a YAML string.
4638
                /// @example yaml_stringify({"name": "Alice", "age": 30})
4639
1.01k
                if (strcmp(fn_name, "yaml_stringify") == 0) {
4640
2
                    if (argc != 1) {
4641
0
                        for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
4642
0
                        return eval_err(strdup("yaml_stringify() expects (value)"));
4643
0
                    }
4644
2
                    if (args[0].type != VAL_MAP && args[0].type != VAL_ARRAY) {
4645
2
                        for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
4646
1
                        return eval_err(strdup("yaml_stringify: value must be a Map or Array"));
4647
1
                    }
4648
1
                    char *yerr = NULL;
4649
1
                    char *yaml = yaml_ops_stringify(&args[0], &yerr);
4650
2
                    for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args);
4651
1
                    if (yerr) { free(yaml); return eval_err(yerr); }
4652
1
                    return eval_ok(value_string_owned(yaml));
4653
1
                }
4654
4655
                /* ── Regex builtins ── */
4656
4657
                /// @builtin regex_match(pattern: String, str: String) -> Bool
4658
                /// @category Regex
4659
                /// Test if a string matches a regular expression pattern.
4660
                /// @example regex_match("^[0-9]+$", "123")  // true
4661
1.00k
                if (strcmp(fn_name, "regex_match") == 0) {
4662
4
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("regex_match() expects (String pattern, String str)")); }
4663
4
                    char *rerr = NULL;
4664
4
                    LatValue result = regex_match(args[0].as.str_val, args[1].as.str_val, &rerr);
4665
12
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4666
4
                    free(args);
4667
4
                    if (rerr) return eval_err(rerr);
4668
3
                    return eval_ok(result);
4669
4
                }
4670
4671
                /// @builtin regex_find_all(pattern: String, str: String) -> Array
4672
                /// @category Regex
4673
                /// Find all matches of a pattern in a string, returning an array.
4674
                /// @example regex_find_all("[0-9]+", "a1b2c3")  // ["1", "2", "3"]
4675
1.00k
                if (strcmp(fn_name, "regex_find_all") == 0) {
4676
3
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("regex_find_all() expects (String pattern, String str)")); }
4677
3
                    char *rerr = NULL;
4678
3
                    LatValue result = regex_find_all(args[0].as.str_val, args[1].as.str_val, &rerr);
4679
9
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4680
3
                    free(args);
4681
3
                    if (rerr) return eval_err(rerr);
4682
3
                    return eval_ok(result);
4683
3
                }
4684
4685
                /// @builtin regex_replace(pattern: String, str: String, replacement: String) -> String
4686
                /// @category Regex
4687
                /// Replace all matches of a pattern in a string.
4688
                /// @example regex_replace("[0-9]", "a1b2", "X")  // "aXbX"
4689
1.00k
                if (strcmp(fn_name, "regex_replace") == 0) {
4690
4
                    if (argc != 3 || args[0].type != VAL_STR || args[1].type != VAL_STR || args[2].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("regex_replace() expects (String pattern, String str, String replacement)")); }
4691
4
                    char *rerr = NULL;
4692
4
                    char *result = regex_replace(args[0].as.str_val, args[1].as.str_val, args[2].as.str_val, &rerr);
4693
16
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4694
4
                    free(args);
4695
4
                    if (rerr) return eval_err(rerr);
4696
4
                    return eval_ok(value_string_owned(result));
4697
4
                }
4698
4699
                /// @builtin format(fmt: String, args: Any...) -> String
4700
                /// @category String Formatting
4701
                /// Format a string with placeholders replaced by arguments.
4702
                /// @example format("{} is {}", "sky", "blue")  // "sky is blue"
4703
998
                if (strcmp(fn_name, "format") == 0) {
4704
26
                    if (argc < 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("format() expects (String fmt, ...)")); }
4705
25
                    char *ferr = NULL;
4706
25
                    char *result = format_string(args[0].as.str_val, args + 1, argc - 1, &ferr);
4707
84
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4708
25
                    free(args);
4709
25
                    if (ferr) return eval_err(ferr);
4710
24
                    return eval_ok(value_string_owned(result));
4711
25
                }
4712
4713
                /* ── Crypto builtins ── */
4714
4715
                /// @builtin sha256(s: String) -> String
4716
                /// @category Crypto
4717
                /// Compute the SHA-256 hash of a string, returned as hex.
4718
                /// @example sha256("hello")  // "2cf24dba..."
4719
972
                if (strcmp(fn_name, "sha256") == 0) {
4720
3
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("sha256() expects (String)")); }
4721
2
                    char *cerr = NULL;
4722
2
                    char *result = crypto_sha256(args[0].as.str_val, strlen(args[0].as.str_val), &cerr);
4723
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4724
2
                    free(args);
4725
2
                    if (cerr) return eval_err(cerr);
4726
2
                    return eval_ok(value_string_owned(result));
4727
2
                }
4728
4729
                /// @builtin md5(s: String) -> String
4730
                /// @category Crypto
4731
                /// Compute the MD5 hash of a string, returned as hex.
4732
                /// @example md5("hello")  // "5d41402a..."
4733
969
                if (strcmp(fn_name, "md5") == 0) {
4734
3
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("md5() expects (String)")); }
4735
2
                    char *cerr = NULL;
4736
2
                    char *result = crypto_md5(args[0].as.str_val, strlen(args[0].as.str_val), &cerr);
4737
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4738
2
                    free(args);
4739
2
                    if (cerr) return eval_err(cerr);
4740
2
                    return eval_ok(value_string_owned(result));
4741
2
                }
4742
4743
                /// @builtin base64_encode(s: String) -> String
4744
                /// @category Crypto
4745
                /// Encode a string to Base64.
4746
                /// @example base64_encode("hello")  // "aGVsbG8="
4747
966
                if (strcmp(fn_name, "base64_encode") == 0) {
4748
9
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("base64_encode() expects (String)")); }
4749
8
                    char *result = crypto_base64_encode(args[0].as.str_val, strlen(args[0].as.str_val));
4750
16
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4751
8
                    free(args);
4752
8
                    return eval_ok(value_string_owned(result));
4753
9
                }
4754
4755
                /// @builtin base64_decode(s: String) -> String
4756
                /// @category Crypto
4757
                /// Decode a Base64 string.
4758
                /// @example base64_decode("aGVsbG8=")  // "hello"
4759
957
                if (strcmp(fn_name, "base64_decode") == 0) {
4760
6
                    if (argc != 1 || args[0].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("base64_decode() expects (String)")); }
4761
5
                    char *cerr = NULL;
4762
5
                    size_t decoded_len = 0;
4763
5
                    char *result = crypto_base64_decode(args[0].as.str_val, strlen(args[0].as.str_val), &decoded_len, &cerr);
4764
10
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4765
5
                    free(args);
4766
5
                    if (cerr) return eval_err(cerr);
4767
4
                    return eval_ok(value_string_owned(result));
4768
5
                }
4769
4770
                /* ── Date/time formatting builtins ── */
4771
4772
                /// @builtin time_format(epoch_ms: Int, fmt: String) -> String
4773
                /// @category Date & Time
4774
                /// Format a Unix timestamp (ms) using a strftime format string.
4775
                /// @example time_format(0, "%Y-%m-%d")  // "1970-01-01"
4776
951
                if (strcmp(fn_name, "time_format") == 0) {
4777
5
                    if (argc != 2 || args[0].type != VAL_INT || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("time_format() expects (Int epoch_ms, String fmt)")); }
4778
4
                    char *terr = NULL;
4779
4
                    char *result = datetime_format(args[0].as.int_val, args[1].as.str_val, &terr);
4780
12
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4781
4
                    free(args);
4782
4
                    if (terr) return eval_err(terr);
4783
4
                    return eval_ok(value_string_owned(result));
4784
4
                }
4785
4786
                /// @builtin time_parse(datetime: String, fmt: String) -> Int
4787
                /// @category Date & Time
4788
                /// Parse a datetime string into a Unix timestamp (ms).
4789
                /// @example time_parse("2024-01-01", "%Y-%m-%d")  // 1704067200000
4790
946
                if (strcmp(fn_name, "time_parse") == 0) {
4791
5
                    if (argc != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("time_parse() expects (String datetime, String fmt)")); }
4792
4
                    char *terr = NULL;
4793
4
                    int64_t result = datetime_parse(args[0].as.str_val, args[1].as.str_val, &terr);
4794
12
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4795
4
                    free(args);
4796
4
                    if (terr) return eval_err(terr);
4797
3
                    return eval_ok(value_int(result));
4798
4
                }
4799
4800
                /* ── Assertion builtin ── */
4801
4802
                /// @builtin assert(cond: Any, msg?: String) -> Unit
4803
                /// @category Core
4804
                /// Assert that a condition is truthy, or raise an error with an optional message.
4805
                /// @example assert(1 == 1, "math works")
4806
941
                if (strcmp(fn_name, "assert") == 0) {
4807
92
                    if (argc < 1 || argc > 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("assert() expects 1 or 2 arguments")); }
4808
92
                    bool truthy = value_is_truthy(&args[0]);
4809
92
                    if (!truthy) {
4810
3
                        char *msg = NULL;
4811
3
                        if (argc == 2 && args[1].type == VAL_STR)
4812
3
                            msg = strdup(args[1].as.str_val);
4813
0
                        else
4814
0
                            msg = strdup("assertion failed");
4815
9
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4816
3
                        free(args);
4817
3
                        return eval_err(msg);
4818
3
                    }
4819
185
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4820
89
                    free(args);
4821
89
                    return eval_ok(value_unit());
4822
92
                }
4823
4824
                /// @builtin debug_assert(cond: Any, msg?: String) -> Unit
4825
                /// @category Core
4826
                /// Assert that a condition is truthy (no-op when assertions are disabled via --no-assertions).
4827
                /// @example debug_assert(x > 0, "x must be positive")
4828
849
                if (strcmp(fn_name, "debug_assert") == 0) {
4829
2
                    if (argc < 1 || argc > 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("debug_assert() expects 1 or 2 arguments")); }
4830
2
                    if (ev->assertions_enabled) {
4831
2
                        bool truthy = value_is_truthy(&args[0]);
4832
2
                        if (!truthy) {
4833
1
                            char *msg = NULL;
4834
1
                            if (argc == 2 && args[1].type == VAL_STR)
4835
1
                                msg = strdup(args[1].as.str_val);
4836
0
                            else
4837
0
                                msg = strdup("debug assertion failed");
4838
3
                            for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4839
1
                            free(args);
4840
1
                            return eval_err(msg);
4841
1
                        }
4842
2
                    }
4843
3
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4844
1
                    free(args);
4845
1
                    return eval_ok(value_unit());
4846
2
                }
4847
4848
                /* ── Functional programming builtins ── */
4849
4850
                /// @builtin identity(val: Any) -> Any
4851
                /// @category Functional
4852
                /// Return the argument unchanged.
4853
                /// @example identity(42)  // 42
4854
847
                if (strcmp(fn_name, "identity") == 0) {
4855
2
                    if (argc != 1) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("identity() expects 1 argument")); }
4856
2
                    LatValue result = value_deep_clone(&args[0]);
4857
4
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4858
2
                    free(args);
4859
2
                    return eval_ok(result);
4860
2
                }
4861
4862
                /// @builtin pipe(val: Any, fns: Closure...) -> Any
4863
                /// @category Functional
4864
                /// Thread a value through a series of functions left to right.
4865
                /// @example pipe(5, |x| { x * 2 }, |x| { x + 1 })  // 11
4866
845
                if (strcmp(fn_name, "pipe") == 0) {
4867
2
                    if (argc < 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("pipe() expects a value and at least one function")); }
4868
7
                    for (size_t i = 1; i < argc; i++) {
4869
5
                        if (args[i].type != VAL_CLOSURE) {
4870
0
                            char *err = NULL;
4871
0
                            (void)asprintf(&err, "pipe() argument %zu is not a function", i + 1);
4872
0
                            for (size_t j = 0; j < argc; j++) value_free(&args[j]);
4873
0
                            free(args);
4874
0
                            return eval_err(err);
4875
0
                        }
4876
5
                    }
4877
2
                    LatValue current = value_deep_clone(&args[0]);
4878
7
                    for (size_t i = 1; i < argc; i++) {
4879
5
                        LatValue call_arg = current;
4880
5
                        EvalResult r = call_closure(ev,
4881
5
                            args[i].as.closure.param_names,
4882
5
                            args[i].as.closure.param_count,
4883
5
                            args[i].as.closure.body,
4884
5
                            args[i].as.closure.captured_env,
4885
5
                            &call_arg, 1,
4886
5
                            args[i].as.closure.default_values, args[i].as.closure.has_variadic);
4887
5
                        if (!IS_OK(r)) {
4888
0
                            for (size_t j = 0; j < argc; j++) value_free(&args[j]);
4889
0
                            free(args);
4890
0
                            return r;
4891
0
                        }
4892
5
                        current = r.value;
4893
5
                    }
4894
9
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4895
2
                    free(args);
4896
2
                    return eval_ok(current);
4897
2
                }
4898
4899
                /// @builtin compose(f: Closure, g: Closure) -> Closure
4900
                /// @category Functional
4901
                /// Compose two functions: compose(f, g)(x) calls f(g(x)).
4902
                /// @example compose(|x| { x + 1 }, |x| { x * 2 })(3)  // 7
4903
843
                if (strcmp(fn_name, "compose") == 0) {
4904
2
                    if (argc != 2) { for (size_t i = 0; i < argc; i++) { value_free(&args[i]); } free(args); return eval_err(strdup("compose() expects 2 arguments (both closures)")); }
4905
2
                    if (args[0].type != VAL_CLOSURE || args[1].type != VAL_CLOSURE) {
4906
0
                        for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4907
0
                        free(args);
4908
0
                        return eval_err(strdup("compose() arguments must be closures"));
4909
0
                    }
4910
                    /* Build closure |x| { __compose_f(__compose_g(x)) }
4911
                     * where f = args[0], g = args[1] */
4912
2
                    Env *cenv = env_clone(ev->env);
4913
2
                    env_push_scope(cenv);
4914
2
                    env_define(cenv, "__compose_f", value_deep_clone(&args[0]));
4915
2
                    env_define(cenv, "__compose_g", value_deep_clone(&args[1]));
4916
4917
                    /* AST: __compose_f(__compose_g(x))  (intentionally leaked — borrowed by closure) */
4918
2
                    Expr *x_var = expr_ident(strdup("x"));
4919
2
                    Expr **g_cargs = malloc(sizeof(Expr *));
4920
2
                    g_cargs[0] = x_var;
4921
2
                    Expr *g_call = expr_call(expr_ident(strdup("__compose_g")), g_cargs, 1);
4922
2
                    Expr **f_cargs = malloc(sizeof(Expr *));
4923
2
                    f_cargs[0] = g_call;
4924
2
                    Expr *body = expr_call(expr_ident(strdup("__compose_f")), f_cargs, 1);
4925
4926
2
                    char **params = malloc(sizeof(char *));
4927
2
                    params[0] = strdup("x");
4928
2
                    LatValue closure = value_closure(params, 1, body, cenv, NULL, false);
4929
4930
6
                    for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4931
2
                    free(args);
4932
2
                    return eval_ok(closure);
4933
2
                }
4934
4935
                /* ── Named function lookup ── */
4936
841
                FnDecl *fd_head = find_fn(ev, fn_name);
4937
841
                if (fd_head) {
4938
422
                    FnDecl *fd = fd_head;
4939
422
                    if (fd_head->next_overload) {
4940
3
                        fd = resolve_overload(fd_head, args, argc);
4941
3
                        if (!fd) {
4942
0
                            char *err = NULL;
4943
0
                            (void)asprintf(&err, "no matching overload for '%s' with given argument phases", fn_name);
4944
0
                            for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4945
0
                            free(args);
4946
0
                            return eval_err(err);
4947
0
                        }
4948
3
                    }
4949
                    /* Allocate write-back slots for fluid parameters */
4950
422
                    LatValue **writeback = calloc(argc, sizeof(LatValue *));
4951
422
                    EvalResult res = call_fn(ev, fd, args, argc, writeback);
4952
                    /* Write back fluid parameters to caller's env */
4953
422
                    if (IS_OK(res)) {
4954
1.13k
                        for (size_t i = 0; i < argc; i++) {
4955
719
                            if (writeback[i] && expr->as.call.args[i]->tag == EXPR_IDENT) {
4956
4
                                env_set(ev->env, expr->as.call.args[i]->as.str_val, *writeback[i]);
4957
4
                                free(writeback[i]);
4958
4
                                writeback[i] = NULL;
4959
4
                            }
4960
719
                        }
4961
411
                    }
4962
                    /* Clean up any unused writebacks */
4963
1.15k
                    for (size_t i = 0; i < argc; i++) {
4964
728
                        if (writeback[i]) {
4965
0
                            value_free(writeback[i]);
4966
0
                            free(writeback[i]);
4967
0
                        }
4968
728
                    }
4969
422
                    free(writeback);
4970
422
                    free(args);
4971
422
                    return res;
4972
422
                }
4973
841
            }
4974
            /* Otherwise evaluate callee — re-protect args since eval may trigger GC */
4975
891
            for (size_t i = 0; i < argc; i++) GC_PUSH(ev, &args[i]);
4976
419
            EvalResult callee_r = eval_expr(ev, expr->as.call.func);
4977
419
            GC_POP_N(ev, argc);
4978
419
            if (!IS_OK(callee_r)) {
4979
4
                for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4980
2
                free(args);
4981
2
                return callee_r;
4982
2
            }
4983
417
            if (callee_r.value.type != VAL_CLOSURE) {
4984
0
                char *err = NULL;
4985
0
                (void)asprintf(&err, "'%s' is not callable", value_type_name(&callee_r.value));
4986
0
                value_free(&callee_r.value);
4987
0
                for (size_t i = 0; i < argc; i++) value_free(&args[i]);
4988
0
                free(args);
4989
0
                return eval_err(err);
4990
0
            }
4991
            /* Derive a name for stack traces */
4992
417
            const char *closure_name = (expr->as.call.func->tag == EXPR_IDENT)
4993
417
                ? expr->as.call.func->as.str_val : "<closure>";
4994
            /* Native closure dispatch */
4995
417
            if (callee_r.value.as.closure.native_fn && !callee_r.value.as.closure.body) {
4996
69
                ev_push_frame(ev, closure_name);
4997
69
                EvalResult res;
4998
69
                if (callee_r.value.as.closure.default_values == (struct Expr **)(uintptr_t)0x1) {
4999
                    /* VM-style native (VMNativeFn signature) — used by builtin modules */
5000
5
                    typedef LatValue (*VMNativeFn)(LatValue *, int);
5001
5
                    VMNativeFn fn = (VMNativeFn)callee_r.value.as.closure.native_fn;
5002
                    /* Ensure current_rt exists for native error reporting */
5003
5
                    LatRuntime *prev_rt = lat_runtime_current();
5004
5
                    LatRuntime tmp_rt;
5005
5
                    if (!prev_rt) {
5006
5
                        memset(&tmp_rt, 0, sizeof(tmp_rt));
5007
5
                        lat_runtime_set_current(&tmp_rt);
5008
5
                    }
5009
5
                    LatRuntime *rt = lat_runtime_current();
5010
5
                    LatValue result = fn(args, (int)argc);
5011
5
                    if (rt->error) {
5012
0
                        char *msg = rt->error;
5013
0
                        rt->error = NULL;
5014
0
                        value_free(&result);
5015
0
                        res = eval_err(msg);
5016
5
                    } else {
5017
5
                        res = eval_ok(result);
5018
5
                    }
5019
5
                    if (!prev_rt) lat_runtime_set_current(NULL);
5020
64
                } else {
5021
                    /* Extension native (LatExtFn signature) */
5022
64
                    res = call_native_closure(ev,
5023
64
                        callee_r.value.as.closure.native_fn, args, argc);
5024
64
                }
5025
69
                if (!IS_ERR(res)) ev_pop_frame(ev);
5026
176
                for (size_t i = 0; i < argc; i++) value_free(&args[i]);
5027
69
                value_free(&callee_r.value);
5028
69
                free(args);
5029
69
                return res;
5030
69
            }
5031
            /* Root callee and args so GC inside block-body closures won't sweep them */
5032
348
            GC_PUSH(ev, &callee_r.value);
5033
711
            for (size_t i = 0; i < argc; i++) GC_PUSH(ev, &args[i]);
5034
348
            ev_push_frame(ev, closure_name);
5035
348
            EvalResult res = call_closure(ev,
5036
348
                callee_r.value.as.closure.param_names,
5037
348
                callee_r.value.as.closure.param_count,
5038
348
                callee_r.value.as.closure.body,
5039
348
                callee_r.value.as.closure.captured_env,
5040
348
                args, argc,
5041
348
                callee_r.value.as.closure.default_values, callee_r.value.as.closure.has_variadic);
5042
348
            if (!IS_ERR(res)) ev_pop_frame(ev);
5043
348
            GC_POP_N(ev, argc);
5044
348
            GC_POP(ev);
5045
348
            value_free(&callee_r.value);
5046
348
            free(args);
5047
348
            return res;
5048
417
        }
5049
5050
2.81k
        case EXPR_METHOD_CALL: {
5051
            /* Handle .push() specially - needs to mutate the binding in env */
5052
2.81k
            if (strcmp(expr->as.method_call.method, "push") == 0 &&
5053
2.81k
                expr->as.method_call.object->tag == EXPR_IDENT &&
5054
2.81k
                expr->as.method_call.arg_count == 1) {
5055
134
                const char *var_name = expr->as.method_call.object->as.str_val;
5056
134
                LatValue existing;
5057
134
                if (!env_get(ev->env, var_name, &existing)) {
5058
0
                    char *err = NULL;
5059
0
                    (void)asprintf(&err, "undefined variable '%s'", var_name);
5060
0
                    return eval_err(err);
5061
0
                }
5062
134
                if (existing.type != VAL_ARRAY && existing.type != VAL_BUFFER && existing.type != VAL_REF) {
5063
0
                    value_free(&existing);
5064
0
                    return eval_err(strdup(".push() is not defined on non-array"));
5065
0
                }
5066
                /* Ref/Buffer push: handle via resolve_lvalue path below */
5067
134
                if (existing.type == VAL_BUFFER || existing.type == VAL_REF) {
5068
3
                    value_free(&existing);
5069
3
                    goto buffer_mutating_methods;
5070
3
                }
5071
131
                if (value_is_crystal(&existing)) {
5072
0
                    value_free(&existing);
5073
0
                    return eval_err(strdup("cannot push to a crystal array"));
5074
0
                }
5075
131
                if (existing.phase == VTAG_SUBLIMATED) {
5076
1
                    value_free(&existing);
5077
1
                    return eval_err(strdup("cannot push to a sublimated array"));
5078
1
                }
5079
130
                {
5080
130
                    const char *pmode = find_pressure(ev, var_name);
5081
130
                    if (pressure_blocks_grow(pmode)) {
5082
2
                        value_free(&existing);
5083
2
                        char *err = NULL;
5084
2
                        (void)asprintf(&err, "pressurized (%s): cannot push to '%s'", pmode, var_name);
5085
2
                        return eval_err(err);
5086
2
                    }
5087
130
                }
5088
128
                GC_PUSH(ev, &existing);
5089
128
                EvalResult ar = eval_expr(ev, expr->as.method_call.args[0]);
5090
128
                GC_POP(ev);
5091
128
                if (!IS_OK(ar)) { value_free(&existing); return ar; }
5092
                /* Grow the array */
5093
128
                if (existing.as.array.len >= existing.as.array.cap) {
5094
7
                    size_t old_cap = existing.as.array.cap;
5095
7
                    existing.as.array.cap = old_cap < 4 ? 4 : old_cap * 2;
5096
7
                    LatValue *new_buf = fluid_alloc(ev->heap->fluid,
5097
7
                        existing.as.array.cap * sizeof(LatValue));
5098
7
                    memcpy(new_buf, existing.as.array.elems, old_cap * sizeof(LatValue));
5099
7
                    if (!fluid_dealloc(ev->heap->fluid, existing.as.array.elems))
5100
0
                        free(existing.as.array.elems);
5101
7
                    existing.as.array.elems = new_buf;
5102
7
                }
5103
128
                existing.as.array.elems[existing.as.array.len++] = ar.value;
5104
128
                env_set(ev->env, var_name, existing);
5105
128
                return eval_ok(value_unit());
5106
128
            }
5107
            /// @method Map.set(key: String, value: Any) -> Unit
5108
            /// @category Map Methods
5109
            /// Set a key-value pair in the map (mutates in place).
5110
            /// @example m.set("name", "Alice")
5111
            /* Handle .set() on maps - needs mutation like .push() */
5112
2.68k
            if (strcmp(expr->as.method_call.method, "set") == 0 &&
5113
2.68k
                expr->as.method_call.arg_count == 2) {
5114
955
                char *lv_err = NULL;
5115
955
                LatValue *map_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5116
955
                if (map_lv && map_lv->type == VAL_REF) map_lv = &map_lv->as.ref.ref->value;
5117
955
                if (map_lv && map_lv->type == VAL_MAP) {
5118
955
                    if (map_lv->phase == VTAG_SUBLIMATED)
5119
0
                        return eval_err(strdup("cannot set on a sublimated map"));
5120
955
                    EvalResult kr = eval_expr(ev, expr->as.method_call.args[0]);
5121
955
                    if (!IS_OK(kr)) return kr;
5122
955
                    if (kr.value.type != VAL_STR) {
5123
0
                        value_free(&kr.value);
5124
0
                        return eval_err(strdup(".set() key must be a string"));
5125
0
                    }
5126
955
                    GC_PUSH(ev, &kr.value);
5127
955
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[1]);
5128
955
                    GC_POP(ev);
5129
955
                    if (!IS_OK(vr)) { value_free(&kr.value); return vr; }
5130
                    /* Free old value if key exists */
5131
955
                    LatValue *old = (LatValue *)lat_map_get(map_lv->as.map.map, kr.value.as.str_val);
5132
955
                    if (old) value_free(old);
5133
955
                    lat_map_set(map_lv->as.map.map, kr.value.as.str_val, &vr.value);
5134
955
                    value_free(&kr.value);
5135
955
                    return eval_ok(value_unit());
5136
955
                }
5137
0
                if (lv_err) free(lv_err);
5138
0
            }
5139
            /* Handle .pop() on arrays - needs mutation */
5140
1.72k
            if (strcmp(expr->as.method_call.method, "pop") == 0 &&
5141
1.72k
                expr->as.method_call.arg_count == 0) {
5142
4
                char *lv_err = NULL;
5143
4
                LatValue *arr_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5144
4
                if (arr_lv && arr_lv->type == VAL_REF) arr_lv = &arr_lv->as.ref.ref->value;
5145
4
                if (arr_lv && arr_lv->type == VAL_ARRAY) {
5146
4
                    if (value_is_crystal(arr_lv))
5147
0
                        return eval_err(strdup("cannot pop from a crystal array"));
5148
4
                    if (arr_lv->phase == VTAG_SUBLIMATED)
5149
1
                        return eval_err(strdup("cannot pop from a sublimated array"));
5150
3
                    {
5151
3
                        const char *vn = get_method_obj_varname(expr->as.method_call.object);
5152
3
                        if (vn) {
5153
3
                            const char *pmode = find_pressure(ev, vn);
5154
3
                            if (pressure_blocks_shrink(pmode)) {
5155
1
                                char *err = NULL;
5156
1
                                (void)asprintf(&err, "pressurized (%s): cannot pop from '%s'", pmode, vn);
5157
1
                                return eval_err(err);
5158
1
                            }
5159
3
                        }
5160
3
                    }
5161
2
                    if (arr_lv->as.array.len == 0)
5162
0
                        return eval_err(strdup("pop on empty array"));
5163
2
                    LatValue popped = arr_lv->as.array.elems[--arr_lv->as.array.len];
5164
2
                    return eval_ok(popped);
5165
2
                }
5166
0
                if (lv_err) free(lv_err);
5167
0
            }
5168
            /* Handle .insert() on arrays - needs mutation */
5169
1.72k
            if (strcmp(expr->as.method_call.method, "insert") == 0 &&
5170
1.72k
                expr->as.method_call.arg_count == 2) {
5171
1
                char *lv_err = NULL;
5172
1
                LatValue *arr_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5173
1
                if (arr_lv && arr_lv->type == VAL_REF) arr_lv = &arr_lv->as.ref.ref->value;
5174
1
                if (arr_lv && arr_lv->type == VAL_ARRAY) {
5175
1
                    if (value_is_crystal(arr_lv))
5176
0
                        return eval_err(strdup("cannot insert into a crystal array"));
5177
1
                    if (arr_lv->phase == VTAG_SUBLIMATED)
5178
0
                        return eval_err(strdup("cannot insert into a sublimated array"));
5179
1
                    {
5180
1
                        const char *vn = get_method_obj_varname(expr->as.method_call.object);
5181
1
                        if (vn) {
5182
1
                            const char *pmode = find_pressure(ev, vn);
5183
1
                            if (pressure_blocks_grow(pmode)) {
5184
0
                                char *err = NULL;
5185
0
                                (void)asprintf(&err, "pressurized (%s): cannot insert into '%s'", pmode, vn);
5186
0
                                return eval_err(err);
5187
0
                            }
5188
1
                        }
5189
1
                    }
5190
1
                    EvalResult ir = eval_expr(ev, expr->as.method_call.args[0]);
5191
1
                    if (!IS_OK(ir)) return ir;
5192
1
                    if (ir.value.type != VAL_INT) {
5193
0
                        value_free(&ir.value);
5194
0
                        return eval_err(strdup(".insert() index must be an integer"));
5195
0
                    }
5196
1
                    int64_t idx = ir.value.as.int_val;
5197
1
                    value_free(&ir.value);
5198
1
                    if (idx < 0 || (size_t)idx > arr_lv->as.array.len)
5199
0
                        return eval_err(strdup(".insert() index out of bounds"));
5200
1
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[1]);
5201
1
                    if (!IS_OK(vr)) return vr;
5202
                    /* Grow if needed */
5203
1
                    if (arr_lv->as.array.len >= arr_lv->as.array.cap) {
5204
0
                        size_t old_cap = arr_lv->as.array.cap;
5205
0
                        arr_lv->as.array.cap = old_cap < 4 ? 4 : old_cap * 2;
5206
0
                        LatValue *new_buf = fluid_alloc(ev->heap->fluid,
5207
0
                            arr_lv->as.array.cap * sizeof(LatValue));
5208
0
                        memcpy(new_buf, arr_lv->as.array.elems, old_cap * sizeof(LatValue));
5209
0
                        if (!fluid_dealloc(ev->heap->fluid, arr_lv->as.array.elems))
5210
0
                            free(arr_lv->as.array.elems);
5211
0
                        arr_lv->as.array.elems = new_buf;
5212
0
                    }
5213
                    /* Shift elements right */
5214
1
                    memmove(&arr_lv->as.array.elems[(size_t)idx + 1],
5215
1
                            &arr_lv->as.array.elems[(size_t)idx],
5216
1
                            (arr_lv->as.array.len - (size_t)idx) * sizeof(LatValue));
5217
1
                    arr_lv->as.array.elems[(size_t)idx] = vr.value;
5218
1
                    arr_lv->as.array.len++;
5219
1
                    return eval_ok(value_unit());
5220
1
                }
5221
0
                if (lv_err) free(lv_err);
5222
0
            }
5223
            /* Handle .remove_at() on arrays - needs mutation */
5224
1.72k
            if (strcmp(expr->as.method_call.method, "remove_at") == 0 &&
5225
1.72k
                expr->as.method_call.arg_count == 1) {
5226
1
                char *lv_err = NULL;
5227
1
                LatValue *arr_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5228
1
                if (arr_lv && arr_lv->type == VAL_REF) arr_lv = &arr_lv->as.ref.ref->value;
5229
1
                if (arr_lv && arr_lv->type == VAL_ARRAY) {
5230
1
                    if (value_is_crystal(arr_lv))
5231
0
                        return eval_err(strdup("cannot remove from a crystal array"));
5232
1
                    if (arr_lv->phase == VTAG_SUBLIMATED)
5233
0
                        return eval_err(strdup("cannot remove from a sublimated array"));
5234
1
                    {
5235
1
                        const char *vn = get_method_obj_varname(expr->as.method_call.object);
5236
1
                        if (vn) {
5237
1
                            const char *pmode = find_pressure(ev, vn);
5238
1
                            if (pressure_blocks_shrink(pmode)) {
5239
0
                                char *err = NULL;
5240
0
                                (void)asprintf(&err, "pressurized (%s): cannot remove from '%s'", pmode, vn);
5241
0
                                return eval_err(err);
5242
0
                            }
5243
1
                        }
5244
1
                    }
5245
1
                    EvalResult ir = eval_expr(ev, expr->as.method_call.args[0]);
5246
1
                    if (!IS_OK(ir)) return ir;
5247
1
                    if (ir.value.type != VAL_INT) {
5248
0
                        value_free(&ir.value);
5249
0
                        return eval_err(strdup(".remove_at() index must be an integer"));
5250
0
                    }
5251
1
                    int64_t idx = ir.value.as.int_val;
5252
1
                    value_free(&ir.value);
5253
1
                    if (idx < 0 || (size_t)idx >= arr_lv->as.array.len)
5254
0
                        return eval_err(strdup(".remove_at() index out of bounds"));
5255
1
                    LatValue removed = arr_lv->as.array.elems[(size_t)idx];
5256
                    /* Shift elements left */
5257
1
                    memmove(&arr_lv->as.array.elems[(size_t)idx],
5258
1
                            &arr_lv->as.array.elems[(size_t)idx + 1],
5259
1
                            (arr_lv->as.array.len - (size_t)idx - 1) * sizeof(LatValue));
5260
1
                    arr_lv->as.array.len--;
5261
1
                    return eval_ok(removed);
5262
1
                }
5263
0
                if (lv_err) free(lv_err);
5264
0
            }
5265
            /* Handle .merge() on maps - needs mutation */
5266
1.72k
            if (strcmp(expr->as.method_call.method, "merge") == 0 &&
5267
1.72k
                expr->as.method_call.arg_count == 1) {
5268
1
                char *lv_err = NULL;
5269
1
                LatValue *map_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5270
1
                if (map_lv && map_lv->type == VAL_REF) map_lv = &map_lv->as.ref.ref->value;
5271
1
                if (map_lv && map_lv->type == VAL_MAP) {
5272
1
                    if (map_lv->phase == VTAG_SUBLIMATED)
5273
0
                        return eval_err(strdup("cannot merge into a sublimated map"));
5274
1
                    {
5275
1
                        const char *vn = get_method_obj_varname(expr->as.method_call.object);
5276
1
                        if (vn) {
5277
1
                            const char *pmode = find_pressure(ev, vn);
5278
1
                            if (pressure_blocks_grow(pmode)) {
5279
0
                                char *err = NULL;
5280
0
                                (void)asprintf(&err, "pressurized (%s): cannot merge into '%s'", pmode, vn);
5281
0
                                return eval_err(err);
5282
0
                            }
5283
1
                        }
5284
1
                    }
5285
1
                    EvalResult mr = eval_expr(ev, expr->as.method_call.args[0]);
5286
1
                    if (!IS_OK(mr)) return mr;
5287
1
                    if (mr.value.type != VAL_MAP) {
5288
0
                        value_free(&mr.value);
5289
0
                        return eval_err(strdup(".merge() argument must be a Map"));
5290
0
                    }
5291
1
                    LatMap *other = mr.value.as.map.map;
5292
17
                    for (size_t i = 0; i < other->cap; i++) {
5293
16
                        if (other->entries[i].state == MAP_OCCUPIED) {
5294
1
                            LatValue cloned = value_deep_clone((LatValue *)other->entries[i].value);
5295
1
                            LatValue *old = (LatValue *)lat_map_get(map_lv->as.map.map, other->entries[i].key);
5296
1
                            if (old) value_free(old);
5297
1
                            lat_map_set(map_lv->as.map.map, other->entries[i].key, &cloned);
5298
1
                        }
5299
16
                    }
5300
1
                    value_free(&mr.value);
5301
1
                    return eval_ok(value_unit());
5302
1
                }
5303
0
                if (lv_err) free(lv_err);
5304
0
            }
5305
            /// @method Map.remove(key: String) -> Unit
5306
            /// @category Map Methods
5307
            /// Remove a key from the map (mutates in place).
5308
            /// @example m.remove("name")
5309
            /* Handle .remove() on maps - needs mutation */
5310
1.72k
            if (strcmp(expr->as.method_call.method, "remove") == 0 &&
5311
1.72k
                expr->as.method_call.arg_count == 1) {
5312
2
                char *lv_err = NULL;
5313
2
                LatValue *map_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5314
2
                if (map_lv && map_lv->type == VAL_REF) map_lv = &map_lv->as.ref.ref->value;
5315
2
                if (map_lv && map_lv->type == VAL_MAP) {
5316
1
                    if (map_lv->phase == VTAG_SUBLIMATED)
5317
0
                        return eval_err(strdup("cannot remove from a sublimated map"));
5318
1
                    {
5319
1
                        const char *vn = get_method_obj_varname(expr->as.method_call.object);
5320
1
                        if (vn) {
5321
1
                            const char *pmode = find_pressure(ev, vn);
5322
1
                            if (pressure_blocks_shrink(pmode)) {
5323
0
                                char *err = NULL;
5324
0
                                (void)asprintf(&err, "pressurized (%s): cannot remove from '%s'", pmode, vn);
5325
0
                                return eval_err(err);
5326
0
                            }
5327
1
                        }
5328
1
                    }
5329
1
                    EvalResult kr = eval_expr(ev, expr->as.method_call.args[0]);
5330
1
                    if (!IS_OK(kr)) return kr;
5331
1
                    if (kr.value.type != VAL_STR) {
5332
0
                        value_free(&kr.value);
5333
0
                        return eval_err(strdup(".remove() key must be a string"));
5334
0
                    }
5335
                    /* Free old value if key exists */
5336
1
                    LatValue *old = (LatValue *)lat_map_get(map_lv->as.map.map, kr.value.as.str_val);
5337
1
                    if (old) value_free(old);
5338
1
                    lat_map_remove(map_lv->as.map.map, kr.value.as.str_val);
5339
1
                    value_free(&kr.value);
5340
1
                    return eval_ok(value_unit());
5341
1
                }
5342
1
                if (lv_err) free(lv_err);
5343
1
            }
5344
5345
            /// @method Set.add(value: Any) -> Unit
5346
            /// @category Set Methods
5347
            /// Add an element to the set (mutates in place).
5348
            /// @example s.add(42)
5349
1.72k
            if (strcmp(expr->as.method_call.method, "add") == 0 &&
5350
1.72k
                expr->as.method_call.arg_count == 1) {
5351
6
                char *lv_err = NULL;
5352
6
                LatValue *set_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5353
6
                if (set_lv && set_lv->type == VAL_SET) {
5354
5
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]);
5355
5
                    if (!IS_OK(vr)) return vr;
5356
5
                    char *key = value_display(&vr.value);
5357
                    /* If key already exists, free old value */
5358
5
                    LatValue *old = (LatValue *)lat_map_get(set_lv->as.set.map, key);
5359
5
                    if (old) value_free(old);
5360
5
                    lat_map_set(set_lv->as.set.map, key, &vr.value);
5361
5
                    free(key);
5362
5
                    return eval_ok(value_unit());
5363
5
                }
5364
1
                if (lv_err) free(lv_err);
5365
1
            }
5366
5367
            /// @method Set.remove(value: Any) -> Unit
5368
            /// @category Set Methods
5369
            /// Remove an element from the set (mutates in place).
5370
            /// @example s.remove(42)
5371
1.71k
            if (strcmp(expr->as.method_call.method, "remove") == 0 &&
5372
1.71k
                expr->as.method_call.arg_count == 1) {
5373
1
                char *lv_err = NULL;
5374
1
                LatValue *set_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5375
1
                if (set_lv && set_lv->type == VAL_SET) {
5376
1
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]);
5377
1
                    if (!IS_OK(vr)) return vr;
5378
1
                    char *key = value_display(&vr.value);
5379
1
                    LatValue *old = (LatValue *)lat_map_get(set_lv->as.set.map, key);
5380
1
                    if (old) value_free(old);
5381
1
                    lat_map_remove(set_lv->as.set.map, key);
5382
1
                    free(key);
5383
1
                    value_free(&vr.value);
5384
1
                    return eval_ok(value_unit());
5385
1
                }
5386
0
                if (lv_err) free(lv_err);
5387
0
            }
5388
5389
            /* ── Buffer mutating methods ── */
5390
1.71k
            buffer_mutating_methods:
5391
1.71k
            if (strcmp(expr->as.method_call.method, "push") == 0 &&
5392
1.71k
                expr->as.method_call.arg_count == 1) {
5393
3
                char *lv_err = NULL;
5394
3
                LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5395
3
                if (buf_lv && buf_lv->type == VAL_REF) buf_lv = &buf_lv->as.ref.ref->value;
5396
3
                if (buf_lv && buf_lv->type == VAL_ARRAY) {
5397
                    /* Ref-wrapped array push via resolve_lvalue */
5398
1
                    if (value_is_crystal(buf_lv))
5399
0
                        return eval_err(strdup("cannot push to a crystal array"));
5400
1
                    EvalResult ar = eval_expr(ev, expr->as.method_call.args[0]);
5401
1
                    if (!IS_OK(ar)) return ar;
5402
1
                    if (buf_lv->as.array.len >= buf_lv->as.array.cap) {
5403
0
                        size_t old_cap = buf_lv->as.array.cap;
5404
0
                        buf_lv->as.array.cap = old_cap < 4 ? 4 : old_cap * 2;
5405
0
                        buf_lv->as.array.elems = realloc(buf_lv->as.array.elems, buf_lv->as.array.cap * sizeof(LatValue));
5406
0
                    }
5407
1
                    buf_lv->as.array.elems[buf_lv->as.array.len++] = ar.value;
5408
1
                    return eval_ok(value_unit());
5409
1
                }
5410
2
                if (buf_lv && buf_lv->type == VAL_BUFFER) {
5411
2
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]);
5412
2
                    if (!IS_OK(vr)) return vr;
5413
2
                    uint8_t byte = (vr.value.type == VAL_INT) ? (uint8_t)(vr.value.as.int_val & 0xFF) : 0;
5414
2
                    value_free(&vr.value);
5415
2
                    if (buf_lv->as.buffer.len >= buf_lv->as.buffer.cap) {
5416
0
                        buf_lv->as.buffer.cap = buf_lv->as.buffer.cap ? buf_lv->as.buffer.cap * 2 : 8;
5417
0
                        buf_lv->as.buffer.data = realloc(buf_lv->as.buffer.data, buf_lv->as.buffer.cap);
5418
0
                    }
5419
2
                    buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = byte;
5420
2
                    return eval_ok(value_unit());
5421
2
                }
5422
0
                if (lv_err) free(lv_err);
5423
0
            }
5424
1.71k
            if (strcmp(expr->as.method_call.method, "push_u16") == 0 &&
5425
1.71k
                expr->as.method_call.arg_count == 1) {
5426
1
                char *lv_err = NULL;
5427
1
                LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5428
1
                if (buf_lv && buf_lv->type == VAL_BUFFER) {
5429
1
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]);
5430
1
                    if (!IS_OK(vr)) return vr;
5431
1
                    uint16_t v = (vr.value.type == VAL_INT) ? (uint16_t)(vr.value.as.int_val & 0xFFFF) : 0;
5432
1
                    value_free(&vr.value);
5433
1
                    while (buf_lv->as.buffer.len + 2 > buf_lv->as.buffer.cap) {
5434
0
                        buf_lv->as.buffer.cap = buf_lv->as.buffer.cap ? buf_lv->as.buffer.cap * 2 : 8;
5435
0
                        buf_lv->as.buffer.data = realloc(buf_lv->as.buffer.data, buf_lv->as.buffer.cap);
5436
0
                    }
5437
1
                    buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)(v & 0xFF);
5438
1
                    buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)((v >> 8) & 0xFF);
5439
1
                    return eval_ok(value_unit());
5440
1
                }
5441
0
                if (lv_err) free(lv_err);
5442
0
            }
5443
1.71k
            if (strcmp(expr->as.method_call.method, "push_u32") == 0 &&
5444
1.71k
                expr->as.method_call.arg_count == 1) {
5445
1
                char *lv_err = NULL;
5446
1
                LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5447
1
                if (buf_lv && buf_lv->type == VAL_BUFFER) {
5448
1
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]);
5449
1
                    if (!IS_OK(vr)) return vr;
5450
1
                    uint32_t v = (vr.value.type == VAL_INT) ? (uint32_t)(vr.value.as.int_val & 0xFFFFFFFF) : 0;
5451
1
                    value_free(&vr.value);
5452
1
                    while (buf_lv->as.buffer.len + 4 > buf_lv->as.buffer.cap) {
5453
0
                        buf_lv->as.buffer.cap = buf_lv->as.buffer.cap ? buf_lv->as.buffer.cap * 2 : 8;
5454
0
                        buf_lv->as.buffer.data = realloc(buf_lv->as.buffer.data, buf_lv->as.buffer.cap);
5455
0
                    }
5456
1
                    buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)(v & 0xFF);
5457
1
                    buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)((v >> 8) & 0xFF);
5458
1
                    buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)((v >> 16) & 0xFF);
5459
1
                    buf_lv->as.buffer.data[buf_lv->as.buffer.len++] = (uint8_t)((v >> 24) & 0xFF);
5460
1
                    return eval_ok(value_unit());
5461
1
                }
5462
0
                if (lv_err) free(lv_err);
5463
0
            }
5464
1.71k
            if (strcmp(expr->as.method_call.method, "write_u8") == 0 &&
5465
1.71k
                expr->as.method_call.arg_count == 2) {
5466
0
                char *lv_err = NULL;
5467
0
                LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5468
0
                if (buf_lv && buf_lv->type == VAL_BUFFER) {
5469
0
                    EvalResult ir = eval_expr(ev, expr->as.method_call.args[0]);
5470
0
                    if (!IS_OK(ir)) return ir;
5471
0
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[1]);
5472
0
                    if (!IS_OK(vr)) { value_free(&ir.value); return vr; }
5473
0
                    if (ir.value.type != VAL_INT || ir.value.as.int_val < 0 || (size_t)ir.value.as.int_val >= buf_lv->as.buffer.len) {
5474
0
                        value_free(&ir.value); value_free(&vr.value);
5475
0
                        return eval_err(strdup("Buffer.write_u8: index out of bounds"));
5476
0
                    }
5477
0
                    buf_lv->as.buffer.data[ir.value.as.int_val] = (uint8_t)(vr.value.as.int_val & 0xFF);
5478
0
                    value_free(&ir.value); value_free(&vr.value);
5479
0
                    return eval_ok(value_unit());
5480
0
                }
5481
0
                if (lv_err) free(lv_err);
5482
0
            }
5483
1.71k
            if (strcmp(expr->as.method_call.method, "write_u16") == 0 &&
5484
1.71k
                expr->as.method_call.arg_count == 2) {
5485
1
                char *lv_err = NULL;
5486
1
                LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5487
1
                if (buf_lv && buf_lv->type == VAL_BUFFER) {
5488
1
                    EvalResult ir = eval_expr(ev, expr->as.method_call.args[0]);
5489
1
                    if (!IS_OK(ir)) return ir;
5490
1
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[1]);
5491
1
                    if (!IS_OK(vr)) { value_free(&ir.value); return vr; }
5492
1
                    size_t idx = (size_t)ir.value.as.int_val;
5493
1
                    if (ir.value.type != VAL_INT || idx + 2 > buf_lv->as.buffer.len) {
5494
0
                        value_free(&ir.value); value_free(&vr.value);
5495
0
                        return eval_err(strdup("Buffer.write_u16: index out of bounds"));
5496
0
                    }
5497
1
                    uint16_t v = (uint16_t)(vr.value.as.int_val & 0xFFFF);
5498
1
                    buf_lv->as.buffer.data[idx] = (uint8_t)(v & 0xFF);
5499
1
                    buf_lv->as.buffer.data[idx+1] = (uint8_t)((v >> 8) & 0xFF);
5500
1
                    value_free(&ir.value); value_free(&vr.value);
5501
1
                    return eval_ok(value_unit());
5502
1
                }
5503
0
                if (lv_err) free(lv_err);
5504
0
            }
5505
1.71k
            if (strcmp(expr->as.method_call.method, "write_u32") == 0 &&
5506
1.71k
                expr->as.method_call.arg_count == 2) {
5507
1
                char *lv_err = NULL;
5508
1
                LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5509
1
                if (buf_lv && buf_lv->type == VAL_BUFFER) {
5510
1
                    EvalResult ir = eval_expr(ev, expr->as.method_call.args[0]);
5511
1
                    if (!IS_OK(ir)) return ir;
5512
1
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[1]);
5513
1
                    if (!IS_OK(vr)) { value_free(&ir.value); return vr; }
5514
1
                    size_t idx = (size_t)ir.value.as.int_val;
5515
1
                    if (ir.value.type != VAL_INT || idx + 4 > buf_lv->as.buffer.len) {
5516
0
                        value_free(&ir.value); value_free(&vr.value);
5517
0
                        return eval_err(strdup("Buffer.write_u32: index out of bounds"));
5518
0
                    }
5519
1
                    uint32_t v = (uint32_t)(vr.value.as.int_val & 0xFFFFFFFF);
5520
1
                    buf_lv->as.buffer.data[idx]   = (uint8_t)(v & 0xFF);
5521
1
                    buf_lv->as.buffer.data[idx+1] = (uint8_t)((v >> 8) & 0xFF);
5522
1
                    buf_lv->as.buffer.data[idx+2] = (uint8_t)((v >> 16) & 0xFF);
5523
1
                    buf_lv->as.buffer.data[idx+3] = (uint8_t)((v >> 24) & 0xFF);
5524
1
                    value_free(&ir.value); value_free(&vr.value);
5525
1
                    return eval_ok(value_unit());
5526
1
                }
5527
0
                if (lv_err) free(lv_err);
5528
0
            }
5529
1.71k
            if (strcmp(expr->as.method_call.method, "clear") == 0 &&
5530
1.71k
                expr->as.method_call.arg_count == 0) {
5531
1
                char *lv_err = NULL;
5532
1
                LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5533
1
                if (buf_lv && buf_lv->type == VAL_BUFFER) {
5534
1
                    buf_lv->as.buffer.len = 0;
5535
1
                    return eval_ok(value_unit());
5536
1
                }
5537
0
                if (lv_err) free(lv_err);
5538
0
            }
5539
1.71k
            if (strcmp(expr->as.method_call.method, "fill") == 0 &&
5540
1.71k
                expr->as.method_call.arg_count == 1) {
5541
1
                char *lv_err = NULL;
5542
1
                LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5543
1
                if (buf_lv && buf_lv->type == VAL_BUFFER) {
5544
1
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]);
5545
1
                    if (!IS_OK(vr)) return vr;
5546
1
                    uint8_t byte = (vr.value.type == VAL_INT) ? (uint8_t)(vr.value.as.int_val & 0xFF) : 0;
5547
1
                    value_free(&vr.value);
5548
1
                    memset(buf_lv->as.buffer.data, byte, buf_lv->as.buffer.len);
5549
1
                    return eval_ok(value_unit());
5550
1
                }
5551
0
                if (lv_err) free(lv_err);
5552
0
            }
5553
1.70k
            if (strcmp(expr->as.method_call.method, "resize") == 0 &&
5554
1.70k
                expr->as.method_call.arg_count == 1) {
5555
1
                char *lv_err = NULL;
5556
1
                LatValue *buf_lv = resolve_lvalue(ev, expr->as.method_call.object, &lv_err);
5557
1
                if (buf_lv && buf_lv->type == VAL_BUFFER) {
5558
1
                    EvalResult vr = eval_expr(ev, expr->as.method_call.args[0]);
5559
1
                    if (!IS_OK(vr)) return vr;
5560
1
                    if (vr.value.type != VAL_INT || vr.value.as.int_val < 0) {
5561
0
                        value_free(&vr.value);
5562
0
                        return eval_ok(value_unit());
5563
0
                    }
5564
1
                    size_t new_len = (size_t)vr.value.as.int_val;
5565
1
                    value_free(&vr.value);
5566
1
                    if (new_len > buf_lv->as.buffer.cap) {
5567
0
                        buf_lv->as.buffer.cap = new_len;
5568
0
                        buf_lv->as.buffer.data = realloc(buf_lv->as.buffer.data, buf_lv->as.buffer.cap);
5569
0
                    }
5570
1
                    if (new_len > buf_lv->as.buffer.len)
5571
1
                        memset(buf_lv->as.buffer.data + buf_lv->as.buffer.len, 0, new_len - buf_lv->as.buffer.len);
5572
1
                    buf_lv->as.buffer.len = new_len;
5573
1
                    return eval_ok(value_unit());
5574
1
                }
5575
0
                if (lv_err) free(lv_err);
5576
0
            }
5577
5578
1.70k
            EvalResult objr = eval_expr(ev, expr->as.method_call.object);
5579
1.70k
            if (!IS_OK(objr)) return objr;
5580
            /* Optional chaining: if receiver is nil, return nil */
5581
1.70k
            if (expr->as.method_call.optional && objr.value.type == VAL_NIL) {
5582
1
                value_free(&objr.value);
5583
1
                return eval_ok(value_nil());
5584
1
            }
5585
1.70k
            GC_PUSH(ev, &objr.value);
5586
1.70k
            size_t argc = expr->as.method_call.arg_count;
5587
1.70k
            LatValue *args = malloc((argc < 1 ? 1 : argc) * sizeof(LatValue));
5588
3.33k
            for (size_t i = 0; i < argc; i++) {
5589
1.62k
                EvalResult ar = eval_expr(ev, expr->as.method_call.args[i]);
5590
1.62k
                if (!IS_OK(ar)) {
5591
0
                    GC_POP_N(ev, i);  /* args */
5592
0
                    GC_POP(ev);       /* objr */
5593
0
                    for (size_t j = 0; j < i; j++) value_free(&args[j]);
5594
0
                    free(args);
5595
0
                    value_free(&objr.value);
5596
0
                    return ar;
5597
0
                }
5598
1.62k
                args[i] = ar.value;
5599
1.62k
                GC_PUSH(ev, &args[i]);
5600
1.62k
            }
5601
1.70k
            EvalResult res = eval_method_call(ev, objr.value, expr->as.method_call.method, args, argc);
5602
1.70k
            GC_POP_N(ev, argc);  /* args */
5603
1.70k
            GC_POP(ev);          /* objr */
5604
1.70k
            value_free(&objr.value);
5605
3.33k
            for (size_t i = 0; i < argc; i++) value_free(&args[i]);
5606
1.70k
            free(args);
5607
1.70k
            return res;
5608
1.70k
        }
5609
5610
267
        case EXPR_FIELD_ACCESS: {
5611
267
            EvalResult objr = eval_expr(ev, expr->as.field_access.object);
5612
267
            if (!IS_OK(objr)) return objr;
5613
            /* Optional chaining: if receiver is nil, return nil */
5614
267
            if (expr->as.field_access.optional && objr.value.type == VAL_NIL) {
5615
5
                value_free(&objr.value);
5616
5
                return eval_ok(value_nil());
5617
5
            }
5618
            /* Tuple numeric field access: tuple.0, tuple.1, etc. */
5619
262
            if (objr.value.type == VAL_TUPLE) {
5620
6
                const char *field = expr->as.field_access.field;
5621
6
                char *endptr;
5622
6
                long idx = strtol(field, &endptr, 10);
5623
6
                if (*endptr != '\0' || idx < 0) {
5624
0
                    char *err = NULL;
5625
0
                    (void)asprintf(&err, "tuple field must be a non-negative integer, got '%s'", field);
5626
0
                    value_free(&objr.value);
5627
0
                    return eval_err(err);
5628
0
                }
5629
6
                if ((size_t)idx >= objr.value.as.tuple.len) {
5630
0
                    char *err = NULL;
5631
0
                    (void)asprintf(&err, "tuple index %ld out of bounds (len=%zu)",
5632
0
                                   idx, objr.value.as.tuple.len);
5633
0
                    value_free(&objr.value);
5634
0
                    return eval_err(err);
5635
0
                }
5636
6
                LatValue result = value_deep_clone(&objr.value.as.tuple.elems[idx]);
5637
6
                value_free(&objr.value);
5638
6
                return eval_ok(result);
5639
6
            }
5640
256
            if (objr.value.type == VAL_MAP) {
5641
3
                const char *field = expr->as.field_access.field;
5642
3
                LatValue *val = (LatValue *)lat_map_get(objr.value.as.map.map, field);
5643
3
                if (val) {
5644
2
                    LatValue result = value_deep_clone(val);
5645
2
                    value_free(&objr.value);
5646
2
                    return eval_ok(result);
5647
2
                }
5648
1
                char *err = NULL;
5649
1
                (void)asprintf(&err, "map has no key '%s'", field);
5650
1
                value_free(&objr.value);
5651
1
                return eval_err(err);
5652
3
            }
5653
253
            if (objr.value.type != VAL_STRUCT) {
5654
0
                char *err = NULL;
5655
0
                (void)asprintf(&err, "cannot access field '%s' on %s",
5656
0
                               expr->as.field_access.field, value_type_name(&objr.value));
5657
0
                value_free(&objr.value);
5658
0
                return eval_err(err);
5659
0
            }
5660
323
            for (size_t i = 0; i < objr.value.as.strct.field_count; i++) {
5661
323
                if (strcmp(objr.value.as.strct.field_names[i], expr->as.field_access.field) == 0) {
5662
253
                    LatValue result = value_deep_clone(&objr.value.as.strct.field_values[i]);
5663
253
                    value_free(&objr.value);
5664
253
                    return eval_ok(result);
5665
253
                }
5666
323
            }
5667
0
            char *err = NULL;
5668
0
            (void)asprintf(&err, "struct has no field '%s'", expr->as.field_access.field);
5669
0
            value_free(&objr.value);
5670
0
            return eval_err(err);
5671
253
        }
5672
5673
314
        case EXPR_INDEX: {
5674
314
            EvalResult objr = eval_expr(ev, expr->as.index.object);
5675
314
            if (!IS_OK(objr)) return objr;
5676
            /* Optional chaining: if receiver is nil, return nil */
5677
314
            if (expr->as.index.optional && objr.value.type == VAL_NIL) {
5678
1
                value_free(&objr.value);
5679
1
                return eval_ok(value_nil());
5680
1
            }
5681
313
            GC_PUSH(ev, &objr.value);
5682
313
            EvalResult idxr = eval_expr(ev, expr->as.index.index);
5683
313
            GC_POP(ev);
5684
313
            if (!IS_OK(idxr)) { value_free(&objr.value); return idxr; }
5685
313
            if (objr.value.type == VAL_ARRAY && idxr.value.type == VAL_INT) {
5686
237
                size_t idx = (size_t)idxr.value.as.int_val;
5687
237
                value_free(&idxr.value);
5688
237
                if (idx >= objr.value.as.array.len) {
5689
0
                    char *err = NULL;
5690
0
                    (void)asprintf(&err, "index %zu out of bounds (length %zu)",
5691
0
                                   idx, objr.value.as.array.len);
5692
0
                    value_free(&objr.value);
5693
0
                    return eval_err(err);
5694
0
                }
5695
237
                LatValue result = value_deep_clone(&objr.value.as.array.elems[idx]);
5696
237
                value_free(&objr.value);
5697
237
                return eval_ok(result);
5698
237
            }
5699
76
            if (objr.value.type == VAL_STR && idxr.value.type == VAL_INT) {
5700
3
                size_t idx = (size_t)idxr.value.as.int_val;
5701
3
                value_free(&idxr.value);
5702
3
                size_t slen = strlen(objr.value.as.str_val);
5703
3
                if (idx >= slen) {
5704
0
                    char *err = NULL;
5705
0
                    (void)asprintf(&err, "string index %zu out of bounds (length %zu)", idx, slen);
5706
0
                    value_free(&objr.value);
5707
0
                    return eval_err(err);
5708
0
                }
5709
3
                char buf[2] = { objr.value.as.str_val[idx], '\0' };
5710
3
                value_free(&objr.value);
5711
3
                return eval_ok(value_string(buf));
5712
3
            }
5713
            /* String slicing with range */
5714
73
            if (objr.value.type == VAL_STR && idxr.value.type == VAL_RANGE) {
5715
2
                char *sliced = lat_str_substring(objr.value.as.str_val,
5716
2
                                                  idxr.value.as.range.start,
5717
2
                                                  idxr.value.as.range.end);
5718
2
                value_free(&objr.value);
5719
2
                value_free(&idxr.value);
5720
2
                return eval_ok(value_string_owned(sliced));
5721
2
            }
5722
            /* Map indexing: map["key"] */
5723
71
            if (objr.value.type == VAL_MAP && idxr.value.type == VAL_STR) {
5724
38
                LatValue *found = (LatValue *)lat_map_get(objr.value.as.map.map, idxr.value.as.str_val);
5725
38
                LatValue result = found ? value_deep_clone(found) : value_unit();
5726
38
                value_free(&objr.value);
5727
38
                value_free(&idxr.value);
5728
38
                return eval_ok(result);
5729
38
            }
5730
            /* Buffer indexing: buf[i] -> Int (0-255) */
5731
33
            if (objr.value.type == VAL_BUFFER && idxr.value.type == VAL_INT) {
5732
28
                size_t bidx = (size_t)idxr.value.as.int_val;
5733
28
                value_free(&idxr.value);
5734
28
                if (bidx >= objr.value.as.buffer.len) {
5735
0
                    char *berr = NULL;
5736
0
                    (void)asprintf(&berr, "buffer index %zu out of bounds (length %zu)",
5737
0
                                   bidx, objr.value.as.buffer.len);
5738
0
                    value_free(&objr.value);
5739
0
                    return eval_err(berr);
5740
0
                }
5741
28
                LatValue result = value_int(objr.value.as.buffer.data[bidx]);
5742
28
                value_free(&objr.value);
5743
28
                return eval_ok(result);
5744
28
            }
5745
            /* Ref proxy: delegate indexing to inner value */
5746
5
            if (objr.value.type == VAL_REF) {
5747
5
                LatValue *inner = &objr.value.as.ref.ref->value;
5748
5
                if (inner->type == VAL_MAP && idxr.value.type == VAL_STR) {
5749
3
                    LatValue *found = (LatValue *)lat_map_get(inner->as.map.map, idxr.value.as.str_val);
5750
3
                    LatValue result = found ? value_deep_clone(found) : value_unit();
5751
3
                    value_free(&objr.value);
5752
3
                    value_free(&idxr.value);
5753
3
                    return eval_ok(result);
5754
3
                }
5755
2
                if (inner->type == VAL_ARRAY && idxr.value.type == VAL_INT) {
5756
2
                    size_t idx = (size_t)idxr.value.as.int_val;
5757
2
                    value_free(&idxr.value);
5758
2
                    if (idx >= inner->as.array.len) {
5759
0
                        char *berr = NULL;
5760
0
                        (void)asprintf(&berr, "index %zu out of bounds (length %zu)",
5761
0
                                       idx, inner->as.array.len);
5762
0
                        value_free(&objr.value);
5763
0
                        return eval_err(berr);
5764
0
                    }
5765
2
                    LatValue result = value_deep_clone(&inner->as.array.elems[idx]);
5766
2
                    value_free(&objr.value);
5767
2
                    return eval_ok(result);
5768
2
                }
5769
2
            }
5770
0
            char *err = NULL;
5771
0
            (void)asprintf(&err, "cannot index %s with %s",
5772
0
                           value_type_name(&objr.value), value_type_name(&idxr.value));
5773
0
            value_free(&objr.value);
5774
0
            value_free(&idxr.value);
5775
0
            return eval_err(err);
5776
5
        }
5777
5778
3.67k
        case EXPR_ARRAY: {
5779
3.67k
            size_t n = expr->as.array.count;
5780
3.67k
            size_t cap = n > 0 ? n : 4;
5781
3.67k
            size_t out = 0;
5782
3.67k
            LatValue *elems = malloc(cap * sizeof(LatValue));
5783
3.67k
            size_t gc_count = 0;
5784
12.6k
            for (size_t i = 0; i < n; i++) {
5785
9.00k
                if (expr->as.array.elems[i]->tag == EXPR_SPREAD) {
5786
9
                    EvalResult er = eval_expr(ev, expr->as.array.elems[i]->as.spread_expr);
5787
9
                    if (!IS_OK(er)) {
5788
0
                        GC_POP_N(ev, gc_count);
5789
0
                        for (size_t j = 0; j < out; j++) value_free(&elems[j]);
5790
0
                        free(elems);
5791
0
                        return er;
5792
0
                    }
5793
9
                    if (er.value.type != VAL_ARRAY) {
5794
0
                        char *msg = NULL;
5795
0
                        (void)asprintf(&msg, "cannot spread non-array value of type %s",
5796
0
                                       value_type_name(&er.value));
5797
0
                        GC_POP_N(ev, gc_count);
5798
0
                        for (size_t j = 0; j < out; j++) value_free(&elems[j]);
5799
0
                        free(elems);
5800
0
                        value_free(&er.value);
5801
0
                        return eval_err(msg);
5802
0
                    }
5803
9
                    size_t slen = er.value.as.array.len;
5804
15
                    while (out + slen > cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); }
5805
27
                    for (size_t j = 0; j < slen; j++) {
5806
18
                        elems[out] = value_deep_clone(&er.value.as.array.elems[j]);
5807
18
                        GC_PUSH(ev, &elems[out]);
5808
18
                        gc_count++;
5809
18
                        out++;
5810
18
                    }
5811
9
                    value_free(&er.value);
5812
8.99k
                } else {
5813
8.99k
                    EvalResult er = eval_expr(ev, expr->as.array.elems[i]);
5814
8.99k
                    if (!IS_OK(er)) {
5815
0
                        GC_POP_N(ev, gc_count);
5816
0
                        for (size_t j = 0; j < out; j++) value_free(&elems[j]);
5817
0
                        free(elems);
5818
0
                        return er;
5819
0
                    }
5820
8.99k
                    if (out >= cap) { cap *= 2; elems = realloc(elems, cap * sizeof(LatValue)); }
5821
8.99k
                    elems[out] = er.value;
5822
8.99k
                    GC_PUSH(ev, &elems[out]);
5823
8.99k
                    gc_count++;
5824
8.99k
                    out++;
5825
8.99k
                }
5826
9.00k
            }
5827
3.67k
            GC_POP_N(ev, gc_count);
5828
3.67k
            stats_array(&ev->stats);
5829
3.67k
            LatValue arr = value_array(elems, out);
5830
3.67k
            free(elems);
5831
3.67k
            return eval_ok(arr);
5832
3.67k
        }
5833
5834
11
        case EXPR_TUPLE: {
5835
11
            size_t n = expr->as.tuple.count;
5836
11
            LatValue *elems = malloc(n * sizeof(LatValue));
5837
42
            for (size_t i = 0; i < n; i++) {
5838
31
                EvalResult er = eval_expr(ev, expr->as.tuple.elems[i]);
5839
31
                if (!IS_OK(er)) {
5840
0
                    GC_POP_N(ev, i);
5841
0
                    for (size_t j = 0; j < i; j++) value_free(&elems[j]);
5842
0
                    free(elems);
5843
0
                    return er;
5844
0
                }
5845
31
                elems[i] = er.value;
5846
31
                GC_PUSH(ev, &elems[i]);
5847
31
            }
5848
11
            GC_POP_N(ev, n);
5849
11
            LatValue tup = value_tuple(elems, n);
5850
11
            free(elems);
5851
11
            return eval_ok(tup);
5852
11
        }
5853
5854
194
        case EXPR_STRUCT_LIT: {
5855
194
            const char *sname = expr->as.struct_lit.name;
5856
194
            size_t fc = expr->as.struct_lit.field_count;
5857
            /* Validate fields if struct def is registered */
5858
194
            StructDecl *sd = find_struct(ev, sname);
5859
194
            if (sd) {
5860
578
                for (size_t i = 0; i < fc; i++) {
5861
384
                    bool found = false;
5862
578
                    for (size_t j = 0; j < sd->field_count; j++) {
5863
578
                        if (strcmp(expr->as.struct_lit.fields[i].name, sd->fields[j].name) == 0) {
5864
384
                            found = true; break;
5865
384
                        }
5866
578
                    }
5867
384
                    if (!found) {
5868
0
                        char *err = NULL;
5869
0
                        (void)asprintf(&err, "struct '%s' has no field '%s'",
5870
0
                                       sname, expr->as.struct_lit.fields[i].name);
5871
0
                        return eval_err(err);
5872
0
                    }
5873
384
                }
5874
194
            }
5875
194
            char **names = malloc(fc * sizeof(char *));
5876
194
            LatValue *vals = malloc(fc * sizeof(LatValue));
5877
578
            for (size_t i = 0; i < fc; i++) {
5878
384
                names[i] = expr->as.struct_lit.fields[i].name;
5879
384
                EvalResult er = eval_expr(ev, expr->as.struct_lit.fields[i].value);
5880
384
                if (!IS_OK(er)) {
5881
0
                    GC_POP_N(ev, i);
5882
0
                    for (size_t j = 0; j < i; j++) value_free(&vals[j]);
5883
0
                    free(names); free(vals);
5884
0
                    return er;
5885
0
                }
5886
384
                vals[i] = er.value;
5887
384
                GC_PUSH(ev, &vals[i]);
5888
384
            }
5889
194
            GC_POP_N(ev, fc);
5890
194
            stats_struct(&ev->stats);
5891
194
            LatValue st = value_struct(sname, names, vals, fc);
5892
194
            free(names); free(vals);
5893
            /* Alloy enforcement: apply per-field phase from struct declaration */
5894
194
            if (sd) {
5895
194
                bool has_phase_decl = false;
5896
572
                for (size_t i = 0; i < sd->field_count; i++) {
5897
381
                    if (sd->fields[i].ty.phase != PHASE_UNSPECIFIED) { has_phase_decl = true; break; }
5898
381
                }
5899
194
                if (has_phase_decl) {
5900
3
                    st.as.strct.field_phases = calloc(st.as.strct.field_count, sizeof(PhaseTag));
5901
9
                    for (size_t i = 0; i < st.as.strct.field_count; i++) {
5902
                        /* Find matching decl field */
5903
9
                        for (size_t j = 0; j < sd->field_count; j++) {
5904
9
                            if (strcmp(st.as.strct.field_names[i], sd->fields[j].name) == 0) {
5905
6
                                if (sd->fields[j].ty.phase == PHASE_CRYSTAL) {
5906
3
                                    st.as.strct.field_values[i] = value_freeze(st.as.strct.field_values[i]);
5907
3
                                    st.as.strct.field_phases[i] = VTAG_CRYSTAL;
5908
3
                                } else if (sd->fields[j].ty.phase == PHASE_FLUID) {
5909
3
                                    st.as.strct.field_phases[i] = VTAG_FLUID;
5910
3
                                } else {
5911
0
                                    st.as.strct.field_phases[i] = st.phase;
5912
0
                                }
5913
6
                                break;
5914
6
                            }
5915
9
                        }
5916
6
                    }
5917
3
                }
5918
194
            }
5919
194
            return eval_ok(st);
5920
194
        }
5921
5922
        /// @builtin freeze(val: Any) -> Any
5923
        /// @category Phase Transitions
5924
        /// Transition a value to the crystal (immutable) phase.
5925
        /// @example freeze([1, 2, 3])  // crystal [1, 2, 3]
5926
317
        case EXPR_FREEZE: {
5927
317
            stats_freeze(&ev->stats);
5928
5929
            /* Partial crystallization: freeze(s.field) or freeze(m["key"]) */
5930
317
            if (expr->as.freeze.expr->tag == EXPR_FIELD_ACCESS) {
5931
3
                char *lv_err = NULL;
5932
                /* Resolve parent struct */
5933
3
                LatValue *parent = resolve_lvalue(ev, expr->as.freeze.expr->as.field_access.object, &lv_err);
5934
3
                if (!parent) return eval_err(lv_err);
5935
3
                if (parent->type != VAL_STRUCT) {
5936
0
                    return eval_err(strdup("partial freeze requires a struct"));
5937
0
                }
5938
3
                if (parent->phase == VTAG_CRYSTAL) {
5939
0
                    return eval_ok(value_deep_clone(parent));  /* already fully frozen */
5940
0
                }
5941
3
                const char *fname = expr->as.freeze.expr->as.field_access.field;
5942
3
                size_t fi = (size_t)-1;
5943
3
                for (size_t i = 0; i < parent->as.strct.field_count; i++) {
5944
3
                    if (strcmp(parent->as.strct.field_names[i], fname) == 0) { fi = i; break; }
5945
3
                }
5946
3
                if (fi == (size_t)-1) {
5947
0
                    char *err = NULL;
5948
0
                    (void)asprintf(&err, "struct has no field '%s'", fname);
5949
0
                    return eval_err(err);
5950
0
                }
5951
                /* Run contract if present */
5952
3
                if (expr->as.freeze.contract) {
5953
1
                    EvalResult cr = eval_expr(ev, expr->as.freeze.contract);
5954
1
                    if (!IS_OK(cr)) return cr;
5955
1
                    LatValue check_val = value_deep_clone(&parent->as.strct.field_values[fi]);
5956
1
                    EvalResult vr = call_closure(ev, cr.value.as.closure.param_names,
5957
1
                        cr.value.as.closure.param_count, cr.value.as.closure.body,
5958
1
                        cr.value.as.closure.captured_env, &check_val, 1,
5959
1
                        cr.value.as.closure.default_values, cr.value.as.closure.has_variadic);
5960
1
                    value_free(&cr.value);
5961
1
                    if (!IS_OK(vr)) {
5962
0
                        char *msg = NULL;
5963
0
                        (void)asprintf(&msg, "freeze contract failed: %s", vr.error);
5964
0
                        free(vr.error);
5965
0
                        return eval_err(msg);
5966
0
                    }
5967
1
                    value_free(&vr.value);
5968
1
                }
5969
                /* Freeze the field value */
5970
3
                parent->as.strct.field_values[fi] = value_freeze(parent->as.strct.field_values[fi]);
5971
                /* Lazy-allocate field_phases */
5972
3
                if (!parent->as.strct.field_phases) {
5973
3
                    parent->as.strct.field_phases = calloc(parent->as.strct.field_count, sizeof(PhaseTag));
5974
3
                }
5975
3
                parent->as.strct.field_phases[fi] = VTAG_CRYSTAL;
5976
3
                return eval_ok(value_deep_clone(&parent->as.strct.field_values[fi]));
5977
3
            }
5978
314
            if (expr->as.freeze.expr->tag == EXPR_INDEX) {
5979
2
                const Expr *idx_expr = expr->as.freeze.expr;
5980
                /* Evaluate key first */
5981
2
                EvalResult kr = eval_expr(ev, idx_expr->as.index.index);
5982
2
                if (!IS_OK(kr)) return kr;
5983
2
                if (kr.value.type != VAL_STR) {
5984
0
                    value_free(&kr.value);
5985
0
                    return eval_err(strdup("partial freeze: map key must be a string"));
5986
0
                }
5987
2
                char *key = strdup(kr.value.as.str_val);
5988
2
                value_free(&kr.value);
5989
                /* Resolve parent map */
5990
2
                char *lv_err = NULL;
5991
2
                LatValue *parent = resolve_lvalue(ev, idx_expr->as.index.object, &lv_err);
5992
2
                if (!parent) { free(key); return eval_err(lv_err); }
5993
2
                if (parent->type != VAL_MAP) {
5994
0
                    free(key);
5995
0
                    return eval_err(strdup("partial freeze requires a map"));
5996
0
                }
5997
2
                if (parent->phase == VTAG_CRYSTAL) {
5998
0
                    free(key);
5999
0
                    return eval_ok(value_deep_clone(parent));
6000
0
                }
6001
2
                LatValue *val_ptr = (LatValue *)lat_map_get(parent->as.map.map, key);
6002
2
                if (!val_ptr) {
6003
0
                    char *err = NULL;
6004
0
                    (void)asprintf(&err, "map has no key '%s'", key);
6005
0
                    free(key);
6006
0
                    return eval_err(err);
6007
0
                }
6008
                /* Run contract if present */
6009
2
                if (expr->as.freeze.contract) {
6010
0
                    EvalResult cr = eval_expr(ev, expr->as.freeze.contract);
6011
0
                    if (!IS_OK(cr)) { free(key); return cr; }
6012
0
                    LatValue check_val = value_deep_clone(val_ptr);
6013
0
                    EvalResult vr = call_closure(ev, cr.value.as.closure.param_names,
6014
0
                        cr.value.as.closure.param_count, cr.value.as.closure.body,
6015
0
                        cr.value.as.closure.captured_env, &check_val, 1,
6016
0
                        cr.value.as.closure.default_values, cr.value.as.closure.has_variadic);
6017
0
                    value_free(&cr.value);
6018
0
                    if (!IS_OK(vr)) {
6019
0
                        char *msg = NULL;
6020
0
                        (void)asprintf(&msg, "freeze contract failed: %s", vr.error);
6021
0
                        free(vr.error);
6022
0
                        free(key);
6023
0
                        return eval_err(msg);
6024
0
                    }
6025
0
                    value_free(&vr.value);
6026
0
                }
6027
                /* Freeze the value at this key */
6028
2
                *val_ptr = value_freeze(*val_ptr);
6029
                /* Lazy-allocate key_phases */
6030
2
                if (!parent->as.map.key_phases) {
6031
2
                    parent->as.map.key_phases = calloc(1, sizeof(LatMap));
6032
2
                    *parent->as.map.key_phases = lat_map_new(sizeof(PhaseTag));
6033
2
                }
6034
2
                PhaseTag crystal = VTAG_CRYSTAL;
6035
2
                lat_map_set(parent->as.map.key_phases, key, &crystal);
6036
2
                LatValue ret = value_deep_clone(val_ptr);
6037
2
                free(key);
6038
2
                return eval_ok(ret);
6039
2
            }
6040
6041
312
            if (expr->as.freeze.expr->tag == EXPR_IDENT) {
6042
277
                const char *name = expr->as.freeze.expr->as.str_val;
6043
277
                if (ev->mode == MODE_STRICT) {
6044
                    /* Strict mode: consuming freeze removes the binding */
6045
3
                    LatValue val;
6046
3
                    if (!env_remove(ev->env, name, &val)) {
6047
0
                        char *err = NULL;
6048
0
                        (void)asprintf(&err, "undefined variable '%s'", name);
6049
0
                        return eval_err(err);
6050
0
                    }
6051
3
                    if (val.type == VAL_CHANNEL) {
6052
0
                        value_free(&val);
6053
0
                        return eval_err(strdup("cannot freeze a Channel"));
6054
0
                    }
6055
                    /* Run crystallization contract if present */
6056
3
                    if (expr->as.freeze.contract) {
6057
0
                        EvalResult cr = eval_expr(ev, expr->as.freeze.contract);
6058
0
                        if (!IS_OK(cr)) { value_free(&val); return cr; }
6059
0
                        LatValue check_val = value_deep_clone(&val);
6060
0
                        EvalResult vr = call_closure(ev, cr.value.as.closure.param_names,
6061
0
                            cr.value.as.closure.param_count, cr.value.as.closure.body,
6062
0
                            cr.value.as.closure.captured_env, &check_val, 1,
6063
0
                            cr.value.as.closure.default_values, cr.value.as.closure.has_variadic);
6064
0
                        value_free(&cr.value);
6065
0
                        if (!IS_OK(vr)) {
6066
0
                            char *msg = NULL;
6067
0
                            (void)asprintf(&msg, "freeze contract failed: %s", vr.error);
6068
0
                            free(vr.error);
6069
0
                            value_free(&val);
6070
0
                            return eval_err(msg);
6071
0
                        }
6072
0
                        value_free(&vr.value);
6073
0
                    }
6074
3
                    uint64_t ft0 = now_ns();
6075
3
                    val = value_freeze(val);
6076
3
                    freeze_to_region(ev, &val);
6077
3
                    ev->stats.freeze_total_ns += now_ns() - ft0;
6078
3
                    record_history(ev, name);
6079
3
                    {
6080
3
                        char *cascade_err = freeze_cascade(ev, name);
6081
3
                        if (cascade_err) { value_free(&val); return eval_err(cascade_err); }
6082
3
                    }
6083
3
                    EvalResult fr = fire_reactions(ev, name, "crystal");
6084
3
                    if (!IS_OK(fr)) { value_free(&val); return fr; }
6085
3
                    return eval_ok(val);
6086
3
                }
6087
                /* Casual mode: freeze the binding in-place */
6088
274
                LatValue val;
6089
274
                if (!env_get(ev->env, name, &val)) {
6090
0
                    char *err = NULL;
6091
0
                    (void)asprintf(&err, "undefined variable '%s'", name);
6092
0
                    return eval_err(err);
6093
0
                }
6094
274
                if (val.type == VAL_CHANNEL) {
6095
1
                    value_free(&val);
6096
1
                    return eval_err(strdup("cannot freeze a Channel"));
6097
1
                }
6098
                /* Freeze-except: selectively freeze struct fields/map keys */
6099
273
                if (expr->as.freeze.except_count > 0) {
6100
                    /* Evaluate except field names */
6101
3
                    char **except_names = malloc(expr->as.freeze.except_count * sizeof(char *));
6102
6
                    for (size_t i = 0; i < expr->as.freeze.except_count; i++) {
6103
3
                        EvalResult er = eval_expr(ev, expr->as.freeze.except_fields[i]);
6104
3
                        if (!IS_OK(er)) {
6105
0
                            for (size_t j = 0; j < i; j++) free(except_names[j]);
6106
0
                            free(except_names);
6107
0
                            value_free(&val);
6108
0
                            return er;
6109
0
                        }
6110
3
                        if (er.value.type != VAL_STR) {
6111
0
                            for (size_t j = 0; j < i; j++) free(except_names[j]);
6112
0
                            free(except_names);
6113
0
                            value_free(&val);
6114
0
                            value_free(&er.value);
6115
0
                            return eval_err(strdup("freeze except: field names must be strings"));
6116
0
                        }
6117
3
                        except_names[i] = strdup(er.value.as.str_val);
6118
3
                        value_free(&er.value);
6119
3
                    }
6120
3
                    if (val.type == VAL_STRUCT) {
6121
                        /* Lazy-allocate field_phases */
6122
2
                        if (!val.as.strct.field_phases) {
6123
2
                            val.as.strct.field_phases = calloc(val.as.strct.field_count, sizeof(PhaseTag));
6124
8
                            for (size_t i = 0; i < val.as.strct.field_count; i++)
6125
6
                                val.as.strct.field_phases[i] = val.phase;
6126
2
                        }
6127
8
                        for (size_t i = 0; i < val.as.strct.field_count; i++) {
6128
6
                            bool exempted = false;
6129
10
                            for (size_t j = 0; j < expr->as.freeze.except_count; j++) {
6130
6
                                if (strcmp(val.as.strct.field_names[i], except_names[j]) == 0) {
6131
2
                                    exempted = true; break;
6132
2
                                }
6133
6
                            }
6134
6
                            if (!exempted) {
6135
4
                                val.as.strct.field_values[i] = value_freeze(val.as.strct.field_values[i]);
6136
4
                                val.as.strct.field_phases[i] = VTAG_CRYSTAL;
6137
4
                            } else {
6138
2
                                val.as.strct.field_phases[i] = VTAG_FLUID;
6139
2
                            }
6140
6
                        }
6141
2
                    } else if (val.type == VAL_MAP) {
6142
                        /* Lazy-allocate key_phases */
6143
1
                        if (!val.as.map.key_phases) {
6144
1
                            val.as.map.key_phases = calloc(1, sizeof(LatMap));
6145
1
                            *val.as.map.key_phases = lat_map_new(sizeof(PhaseTag));
6146
1
                        }
6147
17
                        for (size_t i = 0; i < val.as.map.map->cap; i++) {
6148
16
                            if (val.as.map.map->entries[i].state != MAP_OCCUPIED) continue;
6149
3
                            const char *key = val.as.map.map->entries[i].key;
6150
3
                            bool exempted = false;
6151
5
                            for (size_t j = 0; j < expr->as.freeze.except_count; j++) {
6152
3
                                if (strcmp(key, except_names[j]) == 0) {
6153
1
                                    exempted = true; break;
6154
1
                                }
6155
3
                            }
6156
3
                            PhaseTag phase;
6157
3
                            if (!exempted) {
6158
2
                                LatValue *vp = (LatValue *)val.as.map.map->entries[i].value;
6159
2
                                *vp = value_freeze(*vp);
6160
2
                                phase = VTAG_CRYSTAL;
6161
2
                            } else {
6162
1
                                phase = VTAG_FLUID;
6163
1
                            }
6164
3
                            lat_map_set(val.as.map.key_phases, key, &phase);
6165
3
                        }
6166
1
                    } else {
6167
0
                        for (size_t j = 0; j < expr->as.freeze.except_count; j++) free(except_names[j]);
6168
0
                        free(except_names);
6169
0
                        value_free(&val);
6170
0
                        return eval_err(strdup("freeze except requires a struct or map"));
6171
0
                    }
6172
6
                    for (size_t j = 0; j < expr->as.freeze.except_count; j++) free(except_names[j]);
6173
3
                    free(except_names);
6174
3
                    LatValue ret = value_deep_clone(&val);
6175
3
                    env_set(ev->env, name, val);
6176
3
                    record_history(ev, name);
6177
3
                    return eval_ok(ret);
6178
3
                }
6179
                /* Validate pending seed contracts */
6180
270
                for (size_t si = 0; si < ev->seed_count; si++) {
6181
1
                    if (strcmp(ev->seeds[si].var_name, name) == 0) {
6182
1
                        LatValue check_val = value_deep_clone(&val);
6183
1
                        EvalResult vr = call_closure(ev,
6184
1
                            ev->seeds[si].contract.as.closure.param_names,
6185
1
                            ev->seeds[si].contract.as.closure.param_count,
6186
1
                            ev->seeds[si].contract.as.closure.body,
6187
1
                            ev->seeds[si].contract.as.closure.captured_env,
6188
1
                            &check_val, 1,
6189
1
                            ev->seeds[si].contract.as.closure.default_values,
6190
1
                            ev->seeds[si].contract.as.closure.has_variadic);
6191
1
                        if (!IS_OK(vr)) {
6192
0
                            char *msg = NULL;
6193
0
                            (void)asprintf(&msg, "seed contract failed on freeze: %s", vr.error);
6194
0
                            free(vr.error);
6195
0
                            value_free(&val);
6196
0
                            return eval_err(msg);
6197
0
                        }
6198
1
                        if (!value_is_truthy(&vr.value)) {
6199
1
                            value_free(&vr.value);
6200
1
                            value_free(&val);
6201
1
                            return eval_err(strdup("seed contract failed on freeze: contract returned false"));
6202
1
                        }
6203
0
                        value_free(&vr.value);
6204
0
                    }
6205
1
                }
6206
                /* Run crystallization contract if present */
6207
269
                if (expr->as.freeze.contract) {
6208
6
                    EvalResult cr = eval_expr(ev, expr->as.freeze.contract);
6209
6
                    if (!IS_OK(cr)) { value_free(&val); return cr; }
6210
6
                    LatValue check_val = value_deep_clone(&val);
6211
6
                    EvalResult vr = call_closure(ev, cr.value.as.closure.param_names,
6212
6
                        cr.value.as.closure.param_count, cr.value.as.closure.body,
6213
6
                        cr.value.as.closure.captured_env, &check_val, 1,
6214
6
                        cr.value.as.closure.default_values, cr.value.as.closure.has_variadic);
6215
6
                    value_free(&cr.value);
6216
6
                    if (!IS_OK(vr)) {
6217
3
                        char *msg = NULL;
6218
3
                        (void)asprintf(&msg, "freeze contract failed: %s", vr.error);
6219
3
                        free(vr.error);
6220
3
                        value_free(&val);
6221
3
                        return eval_err(msg);
6222
3
                    }
6223
3
                    value_free(&vr.value);
6224
3
                }
6225
266
                uint64_t ft0 = now_ns();
6226
266
                val = value_freeze(val);
6227
266
                freeze_to_region(ev, &val);
6228
266
                ev->stats.freeze_total_ns += now_ns() - ft0;
6229
266
                LatValue ret = value_deep_clone(&val);
6230
266
                env_set(ev->env, name, val);
6231
266
                record_history(ev, name);
6232
266
                {
6233
266
                    char *cascade_err = freeze_cascade(ev, name);
6234
266
                    if (cascade_err) { value_free(&ret); return eval_err(cascade_err); }
6235
266
                }
6236
265
                EvalResult fr = fire_reactions(ev, name, "crystal");
6237
265
                if (!IS_OK(fr)) { value_free(&ret); return fr; }
6238
264
                return eval_ok(ret);
6239
265
            }
6240
35
            EvalResult er = eval_expr(ev, expr->as.freeze.expr);
6241
35
            if (!IS_OK(er)) return er;
6242
35
            if (er.value.type == VAL_CHANNEL) {
6243
0
                value_free(&er.value);
6244
0
                return eval_err(strdup("cannot freeze a Channel"));
6245
0
            }
6246
            /* Run crystallization contract if present */
6247
35
            if (expr->as.freeze.contract) {
6248
1
                EvalResult cr = eval_expr(ev, expr->as.freeze.contract);
6249
1
                if (!IS_OK(cr)) { value_free(&er.value); return cr; }
6250
1
                LatValue check_val = value_deep_clone(&er.value);
6251
1
                EvalResult vr = call_closure(ev, cr.value.as.closure.param_names,
6252
1
                    cr.value.as.closure.param_count, cr.value.as.closure.body,
6253
1
                    cr.value.as.closure.captured_env, &check_val, 1,
6254
1
                    cr.value.as.closure.default_values, cr.value.as.closure.has_variadic);
6255
1
                value_free(&cr.value);
6256
1
                if (!IS_OK(vr)) {
6257
0
                    char *msg = NULL;
6258
0
                    (void)asprintf(&msg, "freeze contract failed: %s", vr.error);
6259
0
                    free(vr.error);
6260
0
                    value_free(&er.value);
6261
0
                    return eval_err(msg);
6262
0
                }
6263
1
                value_free(&vr.value);
6264
1
            }
6265
35
            { uint64_t ft0 = now_ns();
6266
35
            er.value = value_freeze(er.value);
6267
35
            freeze_to_region(ev, &er.value);
6268
35
            ev->stats.freeze_total_ns += now_ns() - ft0; }
6269
35
            return eval_ok(er.value);
6270
35
        }
6271
6272
        /// @builtin thaw(val: Any) -> Any
6273
        /// @category Phase Transitions
6274
        /// Transition a crystal value back to the flux (mutable) phase.
6275
        /// @example thaw(freeze([1, 2]))  // flux [1, 2]
6276
189
        case EXPR_THAW: {
6277
189
            stats_thaw(&ev->stats);
6278
189
            if (expr->as.freeze_expr->tag == EXPR_IDENT) {
6279
189
                const char *name = expr->as.freeze_expr->as.str_val;
6280
                /* Thaw the binding in-place */
6281
189
                LatValue val;
6282
189
                if (!env_get(ev->env, name, &val)) {
6283
0
                    char *err = NULL;
6284
0
                    (void)asprintf(&err, "undefined variable '%s'", name);
6285
0
                    return eval_err(err);
6286
0
                }
6287
189
                uint64_t tt0 = now_ns();
6288
189
                LatValue thawed = value_thaw(&val);
6289
189
                ev->stats.thaw_total_ns += now_ns() - tt0;
6290
189
                value_free(&val);
6291
189
                LatValue ret = value_deep_clone(&thawed);
6292
189
                env_set(ev->env, name, thawed);
6293
189
                record_history(ev, name);
6294
189
                EvalResult fr = fire_reactions(ev, name, "fluid");
6295
189
                if (!IS_OK(fr)) { value_free(&ret); return fr; }
6296
189
                return eval_ok(ret);
6297
189
            }
6298
0
            EvalResult er = eval_expr(ev, expr->as.freeze_expr);
6299
0
            if (!IS_OK(er)) return er;
6300
0
            { uint64_t tt0 = now_ns();
6301
0
            LatValue thawed = value_thaw(&er.value);
6302
0
            ev->stats.thaw_total_ns += now_ns() - tt0;
6303
0
            value_free(&er.value);
6304
0
            return eval_ok(thawed); }
6305
0
        }
6306
6307
        /// @builtin clone(val: Any) -> Any
6308
        /// @category Phase Transitions
6309
        /// Create a deep copy of a value.
6310
        /// @example clone(my_array)  // independent copy
6311
3
        case EXPR_CLONE: {
6312
3
            stats_deep_clone(&ev->stats);
6313
3
            EvalResult er = eval_expr(ev, expr->as.freeze_expr);
6314
3
            if (!IS_OK(er)) return er;
6315
3
            LatValue cloned = value_deep_clone(&er.value);
6316
3
            value_free(&er.value);
6317
3
            return eval_ok(cloned);
6318
3
        }
6319
6320
        /// @builtin anneal(val) |transform| { ... } -> Any
6321
        /// @category Phase Transitions
6322
        /// Atomically thaw a crystal value, apply a transformation, and refreeze.
6323
        /// @example anneal(frozen_map) |m| { m["key"] = "value"; m }
6324
7
        case EXPR_ANNEAL: {
6325
7
            stats_thaw(&ev->stats);
6326
7
            stats_freeze(&ev->stats);
6327
6328
            /* Evaluate the closure expression */
6329
7
            EvalResult clr = eval_expr(ev, expr->as.anneal.closure);
6330
7
            if (!IS_OK(clr)) return clr;
6331
6332
            /* Special handling for identifier targets (in-place update) */
6333
7
            if (expr->as.anneal.expr->tag == EXPR_IDENT) {
6334
6
                const char *name = expr->as.anneal.expr->as.str_val;
6335
6
                LatValue val;
6336
6
                if (!env_get(ev->env, name, &val)) {
6337
0
                    value_free(&clr.value);
6338
0
                    char *err = NULL;
6339
0
                    (void)asprintf(&err, "undefined variable '%s'", name);
6340
0
                    return eval_err(err);
6341
0
                }
6342
6
                if (val.phase != VTAG_CRYSTAL) {
6343
1
                    value_free(&val);
6344
1
                    value_free(&clr.value);
6345
1
                    return eval_err(strdup("anneal requires a crystal value"));
6346
1
                }
6347
                /* Thaw */
6348
5
                uint64_t tt0 = now_ns();
6349
5
                LatValue thawed = value_thaw(&val);
6350
5
                ev->stats.thaw_total_ns += now_ns() - tt0;
6351
5
                value_free(&val);
6352
6353
                /* Call the transformation closure */
6354
5
                EvalResult tr = call_closure(ev,
6355
5
                    clr.value.as.closure.param_names,
6356
5
                    clr.value.as.closure.param_count,
6357
5
                    clr.value.as.closure.body,
6358
5
                    clr.value.as.closure.captured_env,
6359
5
                    &thawed, 1,
6360
5
                    clr.value.as.closure.default_values,
6361
5
                    clr.value.as.closure.has_variadic);
6362
5
                value_free(&clr.value);
6363
6364
5
                if (!IS_OK(tr)) {
6365
1
                    char *msg = NULL;
6366
1
                    (void)asprintf(&msg, "anneal failed: %s", tr.error);
6367
1
                    free(tr.error);
6368
1
                    return eval_err(msg);
6369
1
                }
6370
6371
                /* Refreeze */
6372
4
                uint64_t ft0 = now_ns();
6373
4
                tr.value = value_freeze(tr.value);
6374
4
                freeze_to_region(ev, &tr.value);
6375
4
                ev->stats.freeze_total_ns += now_ns() - ft0;
6376
6377
                /* Update binding in-place */
6378
4
                LatValue ret = value_deep_clone(&tr.value);
6379
4
                env_set(ev->env, name, tr.value);
6380
4
                record_history(ev, name);
6381
4
                {
6382
4
                    char *cascade_err = freeze_cascade(ev, name);
6383
4
                    if (cascade_err) { value_free(&ret); return eval_err(cascade_err); }
6384
4
                }
6385
4
                EvalResult fr = fire_reactions(ev, name, "crystal");
6386
4
                if (!IS_OK(fr)) { value_free(&ret); return fr; }
6387
4
                return eval_ok(ret);
6388
4
            }
6389
6390
            /* General expression path */
6391
1
            EvalResult er = eval_expr(ev, expr->as.anneal.expr);
6392
1
            if (!IS_OK(er)) { value_free(&clr.value); return er; }
6393
6394
1
            if (er.value.phase != VTAG_CRYSTAL) {
6395
0
                value_free(&er.value);
6396
0
                value_free(&clr.value);
6397
0
                return eval_err(strdup("anneal requires a crystal value"));
6398
0
            }
6399
6400
            /* Thaw */
6401
1
            uint64_t tt0 = now_ns();
6402
1
            LatValue thawed = value_thaw(&er.value);
6403
1
            ev->stats.thaw_total_ns += now_ns() - tt0;
6404
1
            value_free(&er.value);
6405
6406
            /* Call transformation */
6407
1
            EvalResult tr = call_closure(ev,
6408
1
                clr.value.as.closure.param_names,
6409
1
                clr.value.as.closure.param_count,
6410
1
                clr.value.as.closure.body,
6411
1
                clr.value.as.closure.captured_env,
6412
1
                &thawed, 1,
6413
1
                clr.value.as.closure.default_values,
6414
1
                clr.value.as.closure.has_variadic);
6415
1
            value_free(&clr.value);
6416
6417
1
            if (!IS_OK(tr)) {
6418
0
                char *msg = NULL;
6419
0
                (void)asprintf(&msg, "anneal failed: %s", tr.error);
6420
0
                free(tr.error);
6421
0
                return eval_err(msg);
6422
0
            }
6423
6424
            /* Refreeze */
6425
1
            uint64_t ft0 = now_ns();
6426
1
            tr.value = value_freeze(tr.value);
6427
1
            freeze_to_region(ev, &tr.value);
6428
1
            ev->stats.freeze_total_ns += now_ns() - ft0;
6429
1
            return eval_ok(tr.value);
6430
1
        }
6431
6432
4
        case EXPR_CRYSTALLIZE: {
6433
            /* crystallize(expr) { body } — temporarily freeze, execute body, restore */
6434
4
            if (expr->as.crystallize.expr->tag != EXPR_IDENT) {
6435
0
                return eval_err(strdup("crystallize() target must be a variable name"));
6436
0
            }
6437
4
            const char *name = expr->as.crystallize.expr->as.str_val;
6438
4
            LatValue val;
6439
4
            if (!env_get(ev->env, name, &val)) {
6440
0
                char *err = NULL;
6441
0
                (void)asprintf(&err, "crystallize(): undefined variable '%s'", name);
6442
0
                return eval_err(err);
6443
0
            }
6444
4
            PhaseTag saved_phase = val.phase;
6445
            /* If already crystal, just run the body */
6446
4
            if (saved_phase != VTAG_CRYSTAL) {
6447
3
                val = value_freeze(val);
6448
3
                env_set(ev->env, name, val);
6449
3
            } else {
6450
1
                value_free(&val);
6451
1
            }
6452
            /* Execute body */
6453
4
            stats_scope_push(&ev->stats);
6454
4
            env_push_scope(ev->env);
6455
4
            EvalResult result = eval_block_stmts(ev, expr->as.crystallize.body, expr->as.crystallize.body_count);
6456
4
            env_pop_scope(ev->env);
6457
4
            stats_scope_pop(&ev->stats);
6458
            /* Restore original phase */
6459
4
            if (saved_phase != VTAG_CRYSTAL) {
6460
3
                LatValue cur;
6461
3
                if (env_get(ev->env, name, &cur)) {
6462
3
                    LatValue thawed = value_thaw(&cur);
6463
3
                    value_free(&cur);
6464
3
                    thawed.phase = saved_phase;
6465
3
                    env_set(ev->env, name, thawed);
6466
3
                }
6467
3
            }
6468
4
            if (!IS_OK(result)) return result;
6469
4
            return eval_ok(result.value);
6470
4
        }
6471
6472
5
        case EXPR_SUBLIMATE: {
6473
            /* sublimate(expr) — shallow freeze: top-level locked, children mutable */
6474
5
            if (expr->as.freeze_expr->tag == EXPR_IDENT) {
6475
5
                const char *name = expr->as.freeze_expr->as.str_val;
6476
5
                LatValue val;
6477
5
                if (!env_get(ev->env, name, &val)) {
6478
0
                    char *err = NULL;
6479
0
                    (void)asprintf(&err, "sublimate(): undefined variable '%s'", name);
6480
0
                    return eval_err(err);
6481
0
                }
6482
5
                val.phase = VTAG_SUBLIMATED;  /* Only set top-level phase, don't recurse */
6483
5
                LatValue ret = value_deep_clone(&val);
6484
5
                env_set(ev->env, name, val);
6485
5
                record_history(ev, name);
6486
5
                EvalResult fr = fire_reactions(ev, name, "sublimated");
6487
5
                if (!IS_OK(fr)) { value_free(&ret); return fr; }
6488
5
                return eval_ok(ret);
6489
5
            }
6490
0
            EvalResult er = eval_expr(ev, expr->as.freeze_expr);
6491
0
            if (!IS_OK(er)) return er;
6492
0
            er.value.phase = VTAG_SUBLIMATED;
6493
0
            return eval_ok(er.value);
6494
0
        }
6495
6496
1
        case EXPR_FORGE: {
6497
1
            stats_forge(&ev->stats);
6498
1
            stats_scope_push(&ev->stats);
6499
1
            env_push_scope(ev->env);
6500
1
            EvalResult result = eval_block_stmts(ev, expr->as.block.stmts, expr->as.block.count);
6501
1
            env_pop_scope(ev->env);
6502
1
            stats_scope_pop(&ev->stats);
6503
1
            stats_freeze(&ev->stats);
6504
1
            if (IS_OK(result)) {
6505
1
                uint64_t ft0 = now_ns();
6506
1
                result.value = value_freeze(result.value);
6507
1
                freeze_to_region(ev, &result.value);
6508
1
                ev->stats.freeze_total_ns += now_ns() - ft0;
6509
1
                return eval_ok(result.value);
6510
1
            }
6511
0
            if (IS_SIGNAL(result) && result.cf.tag == CF_RETURN) {
6512
0
                uint64_t ft0 = now_ns();
6513
0
                result.cf.value = value_freeze(result.cf.value);
6514
0
                freeze_to_region(ev, &result.cf.value);
6515
0
                ev->stats.freeze_total_ns += now_ns() - ft0;
6516
0
                return eval_ok(result.cf.value);
6517
0
            }
6518
0
            return result;
6519
0
        }
6520
6521
1.33k
        case EXPR_IF: {
6522
1.33k
            EvalResult condr = eval_expr(ev, expr->as.if_expr.cond);
6523
1.33k
            if (!IS_OK(condr)) return condr;
6524
1.33k
            bool truthy = value_is_truthy(&condr.value);
6525
1.33k
            value_free(&condr.value);
6526
1.33k
            if (truthy) {
6527
362
                stats_scope_push(&ev->stats);
6528
362
                env_push_scope(ev->env);
6529
362
                EvalResult r = eval_block_stmts(ev, expr->as.if_expr.then_stmts,
6530
362
                                                 expr->as.if_expr.then_count);
6531
362
                env_pop_scope(ev->env);
6532
362
                stats_scope_pop(&ev->stats);
6533
362
                return r;
6534
969
            } else if (expr->as.if_expr.else_stmts) {
6535
194
                stats_scope_push(&ev->stats);
6536
194
                env_push_scope(ev->env);
6537
194
                EvalResult r = eval_block_stmts(ev, expr->as.if_expr.else_stmts,
6538
194
                                                 expr->as.if_expr.else_count);
6539
194
                env_pop_scope(ev->env);
6540
194
                stats_scope_pop(&ev->stats);
6541
194
                return r;
6542
194
            }
6543
775
            return eval_ok(value_unit());
6544
1.33k
        }
6545
6546
645
        case EXPR_BLOCK: {
6547
645
            stats_scope_push(&ev->stats);
6548
645
            env_push_scope(ev->env);
6549
645
            EvalResult r = eval_block_stmts(ev, expr->as.block.stmts, expr->as.block.count);
6550
645
            env_pop_scope(ev->env);
6551
645
            stats_scope_pop(&ev->stats);
6552
645
            return r;
6553
1.33k
        }
6554
6555
148
        case EXPR_CLOSURE: {
6556
148
            stats_closure(&ev->stats);
6557
148
            gc_maybe_collect(ev);
6558
148
            Env *captured = env_clone(ev->env);
6559
148
            return eval_ok(value_closure(
6560
148
                expr->as.closure.params,
6561
148
                expr->as.closure.param_count,
6562
148
                expr->as.closure.body,
6563
148
                captured,
6564
148
                expr->as.closure.default_values,
6565
148
                expr->as.closure.has_variadic));
6566
1.33k
        }
6567
6568
22
        case EXPR_RANGE: {
6569
22
            EvalResult sr = eval_expr(ev, expr->as.range.start);
6570
22
            if (!IS_OK(sr)) return sr;
6571
22
            GC_PUSH(ev, &sr.value);
6572
22
            EvalResult er = eval_expr(ev, expr->as.range.end);
6573
22
            GC_POP(ev);
6574
22
            if (!IS_OK(er)) { value_free(&sr.value); return er; }
6575
22
            if (sr.value.type != VAL_INT || er.value.type != VAL_INT) {
6576
0
                value_free(&sr.value); value_free(&er.value);
6577
0
                return eval_err(strdup("range bounds must be integers"));
6578
0
            }
6579
22
            int64_t s = sr.value.as.int_val, e = er.value.as.int_val;
6580
22
            return eval_ok(value_range(s, e));
6581
22
        }
6582
6583
        /// @builtin print(args: Any...) -> Unit
6584
        /// @category Core
6585
        /// Print values separated by spaces with a trailing newline.
6586
        /// @example print("hello", "world")  // prints: hello world
6587
1.01k
        case EXPR_PRINT: {
6588
2.03k
            for (size_t i = 0; i < expr->as.print.arg_count; i++) {
6589
1.02k
                if (i > 0) printf(" ");
6590
1.02k
                EvalResult er = eval_expr(ev, expr->as.print.args[i]);
6591
1.02k
                if (!IS_OK(er)) return er;
6592
1.01k
                char *s = value_display(&er.value);
6593
1.01k
                printf("%s", s);
6594
1.01k
                free(s);
6595
1.01k
                value_free(&er.value);
6596
1.01k
            }
6597
1.01k
            printf("\n");
6598
1.01k
            return eval_ok(value_unit());
6599
1.01k
        }
6600
6601
26
        case EXPR_TRY_CATCH: {
6602
26
            stats_scope_push(&ev->stats);
6603
26
            env_push_scope(ev->env);
6604
26
            EvalResult tr = eval_block_stmts(ev, expr->as.try_catch.try_stmts,
6605
26
                                              expr->as.try_catch.try_count);
6606
26
            env_pop_scope(ev->env);
6607
26
            stats_scope_pop(&ev->stats);
6608
26
            if (IS_ERR(tr)) {
6609
                /* Error: bind to catch variable and execute catch block */
6610
21
                stats_scope_push(&ev->stats);
6611
21
                env_push_scope(ev->env);
6612
21
                env_define(ev->env, expr->as.try_catch.catch_var, value_string(tr.error));
6613
21
                free(tr.error);
6614
21
                EvalResult cr = eval_block_stmts(ev, expr->as.try_catch.catch_stmts,
6615
21
                                                  expr->as.try_catch.catch_count);
6616
21
                env_pop_scope(ev->env);
6617
21
                stats_scope_pop(&ev->stats);
6618
21
                return cr;
6619
21
            }
6620
5
            return tr;
6621
26
        }
6622
6623
1
        case EXPR_SPAWN: {
6624
1
            stats_scope_push(&ev->stats);
6625
1
            env_push_scope(ev->env);
6626
1
            EvalResult result = eval_block_stmts(ev, expr->as.block.stmts, expr->as.block.count);
6627
1
            env_pop_scope(ev->env);
6628
1
            stats_scope_pop(&ev->stats);
6629
1
            if (IS_SIGNAL(result) && result.cf.tag == CF_RETURN) {
6630
1
                return eval_ok(result.cf.value);
6631
1
            }
6632
0
            return result;
6633
1
        }
6634
6635
3
        case EXPR_SCOPE: {
6636
#ifdef __EMSCRIPTEN__
6637
            /* WASM fallback: run as a regular block */
6638
            stats_scope_push(&ev->stats);
6639
            env_push_scope(ev->env);
6640
            EvalResult result = eval_block_stmts(ev, expr->as.block.stmts, expr->as.block.count);
6641
            env_pop_scope(ev->env);
6642
            stats_scope_pop(&ev->stats);
6643
            return result;
6644
#else
6645
            /* Count spawn statements */
6646
3
            size_t total = expr->as.block.count;
6647
3
            size_t spawn_count = 0;
6648
9
            for (size_t i = 0; i < total; i++) {
6649
6
                Stmt *s = expr->as.block.stmts[i];
6650
6
                if (s->tag == STMT_EXPR && s->as.expr->tag == EXPR_SPAWN)
6651
3
                    spawn_count++;
6652
6
            }
6653
6654
3
            if (spawn_count == 0) {
6655
                /* No spawns — run as regular block */
6656
1
                stats_scope_push(&ev->stats);
6657
1
                env_push_scope(ev->env);
6658
1
                EvalResult result = eval_block_stmts(ev, expr->as.block.stmts, total);
6659
1
                env_pop_scope(ev->env);
6660
1
                stats_scope_pop(&ev->stats);
6661
1
                return result;
6662
1
            }
6663
6664
            /* Run non-spawn statements synchronously, spawn tasks in parallel */
6665
2
            SpawnTask *tasks = calloc(spawn_count, sizeof(SpawnTask));
6666
2
            size_t task_idx = 0;
6667
2
            char *first_error = NULL;
6668
6669
2
            stats_scope_push(&ev->stats);
6670
2
            env_push_scope(ev->env);
6671
6672
            /* Execute non-spawn statements first, create tasks for spawns */
6673
5
            for (size_t i = 0; i < total; i++) {
6674
3
                Stmt *s = expr->as.block.stmts[i];
6675
3
                if (s->tag == STMT_EXPR && s->as.expr->tag == EXPR_SPAWN) {
6676
3
                    Expr *spawn_expr = s->as.expr;
6677
3
                    tasks[task_idx].stmts = spawn_expr->as.block.stmts;
6678
3
                    tasks[task_idx].stmt_count = spawn_expr->as.block.count;
6679
3
                    tasks[task_idx].child_ev = create_child_evaluator(ev);
6680
3
                    tasks[task_idx].error = NULL;
6681
3
                    task_idx++;
6682
3
                } else {
6683
0
                    if (!first_error) {
6684
0
                        EvalResult r = eval_stmt(ev, s);
6685
0
                        if (IS_ERR(r)) {
6686
0
                            first_error = r.error;
6687
0
                        } else if (IS_SIGNAL(r)) {
6688
0
                            first_error = strdup("unexpected control flow in scope");
6689
0
                            value_free(&r.cf.value);
6690
0
                        } else {
6691
0
                            value_free(&r.value);
6692
0
                        }
6693
0
                    }
6694
0
                }
6695
3
            }
6696
6697
            /* Launch all spawn threads */
6698
5
            for (size_t i = 0; i < task_idx; i++) {
6699
3
                pthread_create(&tasks[i].thread, NULL, spawn_thread_fn, &tasks[i]);
6700
3
            }
6701
6702
            /* Join all threads */
6703
5
            for (size_t i = 0; i < task_idx; i++) {
6704
3
                pthread_join(tasks[i].thread, NULL);
6705
3
            }
6706
6707
            /* Restore parent TLS heap */
6708
2
            value_set_heap(ev->heap);
6709
2
            value_set_arena(NULL);
6710
6711
            /* Collect first error from child threads */
6712
5
            for (size_t i = 0; i < task_idx; i++) {
6713
3
                if (tasks[i].error && !first_error) {
6714
1
                    first_error = tasks[i].error;
6715
2
                } else if (tasks[i].error) {
6716
0
                    free(tasks[i].error);
6717
0
                }
6718
3
                free_child_evaluator(tasks[i].child_ev);
6719
3
            }
6720
2
            free(tasks);
6721
6722
2
            env_pop_scope(ev->env);
6723
2
            stats_scope_pop(&ev->stats);
6724
6725
2
            if (first_error) return eval_err(first_error);
6726
1
            return eval_ok(value_unit());
6727
2
#endif
6728
2
        }
6729
6730
18
        case EXPR_INTERP_STRING: {
6731
18
            size_t count = expr->as.interp.count;
6732
            /* Estimate buffer size */
6733
18
            size_t buf_cap = 64;
6734
18
            size_t buf_len = 0;
6735
18
            char *buf = malloc(buf_cap);
6736
48
            for (size_t i = 0; i < count; i++) {
6737
                /* Append string segment */
6738
30
                const char *part = expr->as.interp.parts[i];
6739
30
                size_t plen = strlen(part);
6740
30
                while (buf_len + plen + 1 >= buf_cap) { buf_cap *= 2; buf = realloc(buf, buf_cap); }
6741
30
                memcpy(buf + buf_len, part, plen);
6742
30
                buf_len += plen;
6743
                /* Evaluate expression and convert to string */
6744
30
                EvalResult er = eval_expr(ev, expr->as.interp.exprs[i]);
6745
30
                if (!IS_OK(er)) { free(buf); return er; }
6746
30
                char *s = value_display(&er.value);
6747
30
                value_free(&er.value);
6748
30
                size_t slen = strlen(s);
6749
30
                while (buf_len + slen + 1 >= buf_cap) { buf_cap *= 2; buf = realloc(buf, buf_cap); }
6750
30
                memcpy(buf + buf_len, s, slen);
6751
30
                buf_len += slen;
6752
30
                free(s);
6753
30
            }
6754
            /* Append trailing segment */
6755
18
            const char *last_part = expr->as.interp.parts[count];
6756
18
            size_t lplen = strlen(last_part);
6757
18
            while (buf_len + lplen + 1 >= buf_cap) { buf_cap *= 2; buf = realloc(buf, buf_cap); }
6758
18
            memcpy(buf + buf_len, last_part, lplen);
6759
18
            buf_len += lplen;
6760
18
            buf[buf_len] = '\0';
6761
18
            return eval_ok(value_string_owned(buf));
6762
18
        }
6763
15
        case EXPR_MATCH: {
6764
15
            EvalResult scr = eval_expr(ev, expr->as.match_expr.scrutinee);
6765
15
            if (!IS_OK(scr)) return scr;
6766
15
            GC_PUSH(ev, &scr.value);
6767
6768
22
            for (size_t i = 0; i < expr->as.match_expr.arm_count; i++) {
6769
22
                MatchArm *arm = &expr->as.match_expr.arms[i];
6770
22
                bool matched = false;
6771
22
                char *bind_name = NULL;
6772
22
                LatValue bind_val = value_unit();
6773
6774
22
                switch (arm->pattern->tag) {
6775
7
                    case PAT_WILDCARD:
6776
7
                        matched = true;
6777
7
                        break;
6778
5
                    case PAT_BINDING:
6779
5
                        matched = true;
6780
5
                        bind_name = arm->pattern->as.binding_name;
6781
5
                        bind_val = value_deep_clone(&scr.value);
6782
5
                        break;
6783
8
                    case PAT_LITERAL: {
6784
8
                        EvalResult pr = eval_expr(ev, arm->pattern->as.literal);
6785
8
                        if (!IS_OK(pr)) { GC_POP(ev); value_free(&scr.value); return pr; }
6786
8
                        matched = value_equal(&scr.value, &pr.value);
6787
8
                        value_free(&pr.value);
6788
8
                        break;
6789
8
                    }
6790
2
                    case PAT_RANGE: {
6791
2
                        EvalResult sr = eval_expr(ev, arm->pattern->as.range.start);
6792
2
                        if (!IS_OK(sr)) { GC_POP(ev); value_free(&scr.value); return sr; }
6793
2
                        EvalResult er = eval_expr(ev, arm->pattern->as.range.end);
6794
2
                        if (!IS_OK(er)) { GC_POP(ev); value_free(&scr.value); value_free(&sr.value); return er; }
6795
2
                        if (scr.value.type == VAL_INT && sr.value.type == VAL_INT && er.value.type == VAL_INT) {
6796
2
                            matched = scr.value.as.int_val >= sr.value.as.int_val &&
6797
2
                                      scr.value.as.int_val <= er.value.as.int_val;
6798
2
                        }
6799
2
                        value_free(&sr.value);
6800
2
                        value_free(&er.value);
6801
2
                        break;
6802
2
                    }
6803
22
                }
6804
6805
                /* Check phase qualifier */
6806
22
                if (matched && arm->pattern->phase_qualifier != PHASE_UNSPECIFIED) {
6807
7
                    bool phase_ok = false;
6808
7
                    if (arm->pattern->phase_qualifier == PHASE_FLUID)
6809
3
                        phase_ok = (scr.value.phase == VTAG_FLUID || scr.value.phase == VTAG_UNPHASED);
6810
4
                    else if (arm->pattern->phase_qualifier == PHASE_CRYSTAL)
6811
4
                        phase_ok = (scr.value.phase == VTAG_CRYSTAL);
6812
7
                    if (!phase_ok) {
6813
3
                        if (bind_name) { value_free(&bind_val); bind_name = NULL; }
6814
3
                        continue;
6815
3
                    }
6816
7
                }
6817
6818
19
                if (!matched) continue;
6819
6820
                /* Check guard */
6821
15
                if (arm->guard) {
6822
2
                    env_push_scope(ev->env);
6823
2
                    if (bind_name) env_define(ev->env, bind_name, value_deep_clone(&bind_val));
6824
2
                    EvalResult gr = eval_expr(ev, arm->guard);
6825
2
                    env_pop_scope(ev->env);
6826
2
                    if (!IS_OK(gr)) {
6827
0
                        if (bind_name) value_free(&bind_val);
6828
0
                        GC_POP(ev); value_free(&scr.value);
6829
0
                        return gr;
6830
0
                    }
6831
2
                    bool guard_pass = gr.value.type == VAL_BOOL && gr.value.as.bool_val;
6832
2
                    value_free(&gr.value);
6833
2
                    if (!guard_pass) {
6834
0
                        if (bind_name) value_free(&bind_val);
6835
0
                        continue;
6836
0
                    }
6837
2
                }
6838
6839
                /* Execute arm body */
6840
15
                env_push_scope(ev->env);
6841
15
                if (bind_name) env_define(ev->env, bind_name, bind_val);
6842
15
                EvalResult result = eval_ok(value_unit());
6843
31
                for (size_t j = 0; j < arm->body_count; j++) {
6844
16
                    value_free(&result.value);
6845
16
                    result = eval_stmt(ev, arm->body[j]);
6846
16
                    if (!IS_OK(result)) break;
6847
16
                }
6848
15
                env_pop_scope(ev->env);
6849
15
                GC_POP(ev); value_free(&scr.value);
6850
15
                return result;
6851
15
            }
6852
6853
0
            GC_POP(ev); value_free(&scr.value);
6854
0
            return eval_ok(value_nil());
6855
15
        }
6856
6857
488
        case EXPR_ENUM_VARIANT: {
6858
488
            const char *enum_name = expr->as.enum_variant.enum_name;
6859
488
            const char *variant_name = expr->as.enum_variant.variant_name;
6860
6861
488
            EnumDecl *ed = find_enum(ev, enum_name);
6862
488
            if (!ed) {
6863
                /* Fall back to static call: Name::method(args)
6864
                 * Build a temporary EXPR_CALL node and evaluate it so that
6865
                 * builtins like Map::new(), Channel::new() etc. work. */
6866
474
                size_t ac = expr->as.enum_variant.arg_count;
6867
474
                size_t nlen = strlen(enum_name) + 2 + strlen(variant_name) + 1;
6868
474
                char *full = malloc(nlen);
6869
474
                snprintf(full, nlen, "%s::%s", enum_name, variant_name);
6870
6871
                /* Borrow the arg expressions for the temp node */
6872
474
                Expr **arg_refs = NULL;
6873
474
                if (ac > 0) {
6874
45
                    arg_refs = malloc(ac * sizeof(Expr *));
6875
90
                    for (size_t i = 0; i < ac; i++)
6876
45
                        arg_refs[i] = expr->as.enum_variant.args[i];
6877
45
                }
6878
6879
                /* Build a temporary EXPR_CALL:  full(args...)
6880
                 * Use a stack-allocated EXPR_IDENT for the func. */
6881
474
                Expr tmp_ident;
6882
474
                tmp_ident.tag = EXPR_IDENT;
6883
474
                tmp_ident.as.str_val = full;
6884
6885
474
                Expr tmp_call;
6886
474
                tmp_call.tag = EXPR_CALL;
6887
474
                tmp_call.as.call.func = &tmp_ident;
6888
474
                tmp_call.as.call.args = arg_refs;
6889
474
                tmp_call.as.call.arg_count = ac;
6890
6891
474
                EvalResult cr = eval_expr(ev, &tmp_call);
6892
6893
                /* Don't free arg expressions — they belong to the original node */
6894
474
                free(arg_refs);
6895
474
                free(full);
6896
474
                return cr;
6897
474
            }
6898
6899
14
            VariantDecl *vd = find_variant(ed, variant_name);
6900
14
            if (!vd) {
6901
0
                char *err2 = NULL;
6902
0
                (void)asprintf(&err2, "enum '%s' has no variant '%s'", enum_name, variant_name);
6903
0
                return eval_err(err2);
6904
0
            }
6905
6906
14
            size_t provided = expr->as.enum_variant.arg_count;
6907
14
            if (provided != vd->param_count) {
6908
0
                char *err2 = NULL;
6909
0
                (void)asprintf(&err2, "variant '%s::%s' expects %zu argument%s, got %zu",
6910
0
                               enum_name, variant_name, vd->param_count,
6911
0
                               vd->param_count == 1 ? "" : "s", provided);
6912
0
                return eval_err(err2);
6913
0
            }
6914
6915
14
            LatValue *payload = NULL;
6916
14
            if (provided > 0) {
6917
4
                payload = malloc(provided * sizeof(LatValue));
6918
10
                for (size_t i = 0; i < provided; i++) {
6919
6
                    EvalResult er = eval_expr(ev, expr->as.enum_variant.args[i]);
6920
6
                    if (!IS_OK(er)) {
6921
0
                        for (size_t j = 0; j < i; j++) value_free(&payload[j]);
6922
0
                        free(payload);
6923
0
                        return er;
6924
0
                    }
6925
6
                    payload[i] = er.value;
6926
6
                }
6927
4
            }
6928
14
            LatValue enm = value_enum(enum_name, variant_name, payload, provided);
6929
14
            if (payload) {
6930
10
                for (size_t i = 0; i < provided; i++) value_free(&payload[i]);
6931
4
                free(payload);
6932
4
            }
6933
14
            return eval_ok(enm);
6934
14
        }
6935
6936
6
        case EXPR_TRY_PROPAGATE: {
6937
6
            EvalResult inner = eval_expr(ev, expr->as.try_propagate_expr);
6938
6
            if (!inner.ok) return inner;
6939
6
            if (inner.value.type != VAL_MAP) {
6940
1
                value_free(&inner.value);
6941
1
                return eval_err(strdup("? operator requires a Result map (got non-Map value)"));
6942
1
            }
6943
5
            LatValue *tag = lat_map_get(inner.value.as.map.map, "tag");
6944
5
            if (!tag || tag->type != VAL_STR) {
6945
0
                value_free(&inner.value);
6946
0
                return eval_err(strdup("? operator requires a Map with a string \"tag\" field"));
6947
0
            }
6948
5
            if (strcmp(tag->as.str_val, "ok") == 0) {
6949
3
                LatValue *val = lat_map_get(inner.value.as.map.map, "value");
6950
3
                LatValue result = val ? value_deep_clone(val) : value_nil();
6951
3
                value_free(&inner.value);
6952
3
                return eval_ok(result);
6953
3
            }
6954
2
            if (strcmp(tag->as.str_val, "err") == 0) {
6955
                /* Propagate: return from enclosing function with the err Map */
6956
2
                return eval_signal(CF_RETURN, inner.value);
6957
2
            }
6958
0
            value_free(&inner.value);
6959
0
            return eval_err(strdup("? operator: tag must be \"ok\" or \"err\""));
6960
2
        }
6961
6962
5
        case EXPR_SELECT: {
6963
#ifdef __EMSCRIPTEN__
6964
            return eval_err(strdup("select is not supported in WASM builds"));
6965
#else
6966
5
            size_t arm_count = expr->as.select_expr.arm_count;
6967
5
            SelectArm *arms = expr->as.select_expr.arms;
6968
6969
            /* Find default and timeout arms */
6970
5
            int default_idx = -1;
6971
5
            int timeout_idx = -1;
6972
14
            for (size_t i = 0; i < arm_count; i++) {
6973
9
                if (arms[i].is_default) default_idx = (int)i;
6974
9
                if (arms[i].is_timeout) timeout_idx = (int)i;
6975
9
            }
6976
6977
            /* Evaluate all channel expressions upfront */
6978
5
            LatChannel **channels = calloc(arm_count, sizeof(LatChannel *));
6979
14
            for (size_t i = 0; i < arm_count; i++) {
6980
9
                if (arms[i].is_default || arms[i].is_timeout) continue;
6981
6
                EvalResult cer = eval_expr(ev, arms[i].channel_expr);
6982
6
                if (!IS_OK(cer)) { free(channels); return cer; }
6983
6
                if (cer.value.type != VAL_CHANNEL) {
6984
0
                    value_free(&cer.value);
6985
0
                    free(channels);
6986
0
                    return eval_err(strdup("select arm: expression is not a Channel"));
6987
0
                }
6988
6
                channels[i] = cer.value.as.channel.ch;
6989
6
                channel_retain(channels[i]);
6990
6
                value_free(&cer.value);
6991
6
            }
6992
6993
            /* Evaluate timeout if present */
6994
5
            long timeout_ms = -1;
6995
5
            if (timeout_idx >= 0) {
6996
0
                EvalResult ter = eval_expr(ev, arms[timeout_idx].timeout_expr);
6997
0
                if (!IS_OK(ter)) {
6998
0
                    for (size_t i = 0; i < arm_count; i++)
6999
0
                        if (channels[i]) channel_release(channels[i]);
7000
0
                    free(channels);
7001
0
                    return ter;
7002
0
                }
7003
0
                if (ter.value.type != VAL_INT) {
7004
0
                    value_free(&ter.value);
7005
0
                    for (size_t i = 0; i < arm_count; i++)
7006
0
                        if (channels[i]) channel_release(channels[i]);
7007
0
                    free(channels);
7008
0
                    return eval_err(strdup("select timeout must be an integer (milliseconds)"));
7009
0
                }
7010
0
                timeout_ms = (long)ter.value.as.int_val;
7011
0
                value_free(&ter.value);
7012
0
            }
7013
7014
            /* Build shuffled index array for fairness */
7015
5
            size_t ch_arm_count = 0;
7016
5
            size_t *indices = malloc(arm_count * sizeof(size_t));
7017
14
            for (size_t i = 0; i < arm_count; i++) {
7018
9
                if (!arms[i].is_default && !arms[i].is_timeout)
7019
6
                    indices[ch_arm_count++] = i;
7020
9
            }
7021
            /* Fisher-Yates shuffle */
7022
6
            for (size_t i = ch_arm_count; i > 1; i--) {
7023
1
                size_t j = (size_t)rand() % i;
7024
1
                size_t tmp = indices[i-1];
7025
1
                indices[i-1] = indices[j];
7026
1
                indices[j] = tmp;
7027
1
            }
7028
7029
            /* Set up waiter for blocking */
7030
5
            pthread_mutex_t sel_mutex = PTHREAD_MUTEX_INITIALIZER;
7031
5
            pthread_cond_t  sel_cond  = PTHREAD_COND_INITIALIZER;
7032
5
            LatSelectWaiter waiter = {
7033
5
                .mutex = &sel_mutex,
7034
5
                .cond  = &sel_cond,
7035
5
                .next  = NULL,
7036
5
            };
7037
7038
5
            EvalResult select_result = eval_ok(value_unit());
7039
5
            bool found = false;
7040
7041
            /* Compute deadline for timeout */
7042
5
            struct timespec deadline;
7043
5
            if (timeout_ms >= 0) {
7044
0
                clock_gettime(CLOCK_REALTIME, &deadline);
7045
0
                deadline.tv_sec  += timeout_ms / 1000;
7046
0
                deadline.tv_nsec += (timeout_ms % 1000) * 1000000L;
7047
0
                if (deadline.tv_nsec >= 1000000000L) {
7048
0
                    deadline.tv_sec++;
7049
0
                    deadline.tv_nsec -= 1000000000L;
7050
0
                }
7051
0
            }
7052
7053
5
            for (;;) {
7054
                /* Try non-blocking recv on each channel arm (shuffled order) */
7055
5
                bool all_closed = true;
7056
9
                for (size_t k = 0; k < ch_arm_count; k++) {
7057
6
                    size_t i = indices[k];
7058
6
                    LatChannel *ch = channels[i];
7059
6
                    LatValue recv_val;
7060
6
                    bool closed = false;
7061
6
                    if (channel_try_recv(ch, &recv_val, &closed)) {
7062
                        /* Got a value — bind and execute body */
7063
2
                        env_push_scope(ev->env);
7064
2
                        if (arms[i].binding_name)
7065
2
                            env_define(ev->env, arms[i].binding_name, recv_val);
7066
0
                        else
7067
0
                            value_free(&recv_val);
7068
2
                        select_result = eval_block_stmts(ev, arms[i].body, arms[i].body_count);
7069
2
                        env_pop_scope(ev->env);
7070
2
                        found = true;
7071
2
                        break;
7072
2
                    }
7073
4
                    if (!closed) all_closed = false;
7074
4
                }
7075
5
                if (found) break;
7076
7077
3
                if (all_closed && ch_arm_count > 0) {
7078
                    /* All channels closed — execute default if present, otherwise return unit */
7079
2
                    if (default_idx >= 0) {
7080
1
                        env_push_scope(ev->env);
7081
1
                        select_result = eval_block_stmts(ev, arms[default_idx].body, arms[default_idx].body_count);
7082
1
                        env_pop_scope(ev->env);
7083
1
                    }
7084
2
                    break;
7085
2
                }
7086
7087
                /* If there's a default arm, execute it immediately (non-blocking select) */
7088
1
                if (default_idx >= 0) {
7089
1
                    env_push_scope(ev->env);
7090
1
                    select_result = eval_block_stmts(ev, arms[default_idx].body, arms[default_idx].body_count);
7091
1
                    env_pop_scope(ev->env);
7092
1
                    break;
7093
1
                }
7094
7095
                /* Block: register waiter on all channels, then wait */
7096
0
                for (size_t k = 0; k < ch_arm_count; k++)
7097
0
                    channel_add_waiter(channels[indices[k]], &waiter);
7098
7099
0
                pthread_mutex_lock(&sel_mutex);
7100
0
                if (timeout_ms >= 0) {
7101
0
                    int rc = pthread_cond_timedwait(&sel_cond, &sel_mutex, &deadline);
7102
0
                    if (rc != 0) {
7103
                        /* Timeout expired */
7104
0
                        pthread_mutex_unlock(&sel_mutex);
7105
0
                        for (size_t k = 0; k < ch_arm_count; k++)
7106
0
                            channel_remove_waiter(channels[indices[k]], &waiter);
7107
0
                        if (timeout_idx >= 0) {
7108
0
                            env_push_scope(ev->env);
7109
0
                            select_result = eval_block_stmts(ev, arms[timeout_idx].body, arms[timeout_idx].body_count);
7110
0
                            env_pop_scope(ev->env);
7111
0
                        }
7112
0
                        break;
7113
0
                    }
7114
0
                } else {
7115
0
                    pthread_cond_wait(&sel_cond, &sel_mutex);
7116
0
                }
7117
0
                pthread_mutex_unlock(&sel_mutex);
7118
7119
                /* Remove waiters and retry */
7120
0
                for (size_t k = 0; k < ch_arm_count; k++)
7121
0
                    channel_remove_waiter(channels[indices[k]], &waiter);
7122
0
            }
7123
7124
5
            pthread_mutex_destroy(&sel_mutex);
7125
5
            pthread_cond_destroy(&sel_cond);
7126
5
            free(indices);
7127
14
            for (size_t i = 0; i < arm_count; i++)
7128
9
                if (channels[i]) channel_release(channels[i]);
7129
5
            free(channels);
7130
5
            return select_result;
7131
5
#endif
7132
5
        }
7133
7134
0
        case EXPR_SPREAD:
7135
0
            return eval_err(strdup("spread operator ... can only be used inside array literals"));
7136
68.3k
    }
7137
0
    return eval_err(strdup("unknown expression type"));
7138
68.3k
}
7139
7140
/* ── Module loading ── */
7141
7142
94
static EvalResult load_module(Evaluator *ev, const char *raw_path) {
7143
    /* Check for built-in stdlib module */
7144
94
    LatValue builtin_mod;
7145
94
    if (rt_try_builtin_import(raw_path, &builtin_mod)) {
7146
11
        return eval_ok(builtin_mod);
7147
11
    }
7148
7149
    /* Resolve file path: append .lat if not present */
7150
83
    size_t plen = strlen(raw_path);
7151
83
    char *file_path;
7152
83
    if (plen >= 4 && strcmp(raw_path + plen - 4, ".lat") == 0) {
7153
0
        file_path = strdup(raw_path);
7154
83
    } else {
7155
83
        file_path = malloc(plen + 5);
7156
83
        memcpy(file_path, raw_path, plen);
7157
83
        memcpy(file_path + plen, ".lat", 5);
7158
83
    }
7159
7160
    /* Resolve to an absolute path */
7161
83
    char resolved[PATH_MAX];
7162
83
    bool found = (realpath(file_path, resolved) != NULL);
7163
83
    if (!found && ev->script_dir && file_path[0] != '/') {
7164
0
        char script_rel[PATH_MAX];
7165
0
        snprintf(script_rel, sizeof(script_rel), "%s/%s", ev->script_dir, file_path);
7166
0
        found = (realpath(script_rel, resolved) != NULL);
7167
0
    }
7168
83
    if (!found) {
7169
1
        char errbuf[512];
7170
1
        snprintf(errbuf, sizeof(errbuf), "import: cannot find '%s'", file_path);
7171
1
        free(file_path);
7172
1
        return eval_err(strdup(errbuf));
7173
1
    }
7174
82
    free(file_path);
7175
7176
    /* Check module cache */
7177
82
    LatValue *cached = (LatValue *)lat_map_get(&ev->module_cache, resolved);
7178
82
    if (cached) {
7179
1
        return eval_ok(value_deep_clone(cached));
7180
1
    }
7181
7182
    /* Check for circular imports */
7183
81
    if (lat_map_get(&ev->required_files, resolved)) {
7184
0
        char errbuf[512];
7185
0
        snprintf(errbuf, sizeof(errbuf), "import: circular dependency on '%s'", resolved);
7186
0
        return eval_err(strdup(errbuf));
7187
0
    }
7188
7189
    /* Mark as loading */
7190
81
    bool marker = true;
7191
81
    lat_map_set(&ev->required_files, resolved, &marker);
7192
7193
    /* Read the file */
7194
81
    char *source = builtin_read_file(resolved);
7195
81
    if (!source) {
7196
0
        char errbuf[512];
7197
0
        snprintf(errbuf, sizeof(errbuf), "import: cannot read '%s'", resolved);
7198
0
        return eval_err(strdup(errbuf));
7199
0
    }
7200
7201
    /* Lex */
7202
81
    Lexer mod_lex = lexer_new(source);
7203
81
    char *mod_lex_err = NULL;
7204
81
    LatVec mod_toks = lexer_tokenize(&mod_lex, &mod_lex_err);
7205
81
    free(source);
7206
81
    if (mod_lex_err) {
7207
0
        char errbuf[1024];
7208
0
        snprintf(errbuf, sizeof(errbuf), "import '%s': %s", resolved, mod_lex_err);
7209
0
        free(mod_lex_err);
7210
0
        return eval_err(strdup(errbuf));
7211
0
    }
7212
7213
    /* Parse */
7214
81
    Parser mod_parser = parser_new(&mod_toks);
7215
81
    char *mod_parse_err = NULL;
7216
81
    Program mod_prog = parser_parse(&mod_parser, &mod_parse_err);
7217
81
    if (mod_parse_err) {
7218
0
        char errbuf[1024];
7219
0
        snprintf(errbuf, sizeof(errbuf), "import '%s': %s", resolved, mod_parse_err);
7220
0
        free(mod_parse_err);
7221
0
        program_free(&mod_prog);
7222
0
        for (size_t j = 0; j < mod_toks.len; j++) token_free(lat_vec_get(&mod_toks, j));
7223
0
        lat_vec_free(&mod_toks);
7224
0
        return eval_err(strdup(errbuf));
7225
0
    }
7226
7227
    /* Register functions, structs, enums, traits, impls globally */
7228
1.85k
    for (size_t j = 0; j < mod_prog.item_count; j++) {
7229
1.77k
        if (mod_prog.items[j].tag == ITEM_STRUCT) {
7230
2
            StructDecl *ptr = &mod_prog.items[j].as.struct_decl;
7231
2
            lat_map_set(&ev->struct_defs, ptr->name, &ptr);
7232
1.77k
        } else if (mod_prog.items[j].tag == ITEM_FUNCTION) {
7233
1.75k
            FnDecl *ptr = &mod_prog.items[j].as.fn_decl;
7234
1.75k
            register_fn_overload(&ev->fn_defs, ptr);
7235
1.75k
        } else if (mod_prog.items[j].tag == ITEM_ENUM) {
7236
0
            EnumDecl *ptr = &mod_prog.items[j].as.enum_decl;
7237
0
            lat_map_set(&ev->enum_defs, ptr->name, &ptr);
7238
18
        } else if (mod_prog.items[j].tag == ITEM_TRAIT) {
7239
0
            TraitDecl *ptr = &mod_prog.items[j].as.trait_decl;
7240
0
            lat_map_set(&ev->trait_defs, ptr->name, &ptr);
7241
18
        } else if (mod_prog.items[j].tag == ITEM_IMPL) {
7242
0
            ImplBlock *ptr = &mod_prog.items[j].as.impl_block;
7243
0
            char key[512];
7244
0
            snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name);
7245
0
            lat_map_set(&ev->impl_registry, key, &ptr);
7246
0
        }
7247
1.77k
    }
7248
7249
    /* Push a module scope and execute statements */
7250
81
    env_push_scope(ev->env);
7251
7252
81
    char *prev_script_dir = ev->script_dir;
7253
81
    char *resolved_copy = strdup(resolved);
7254
81
    ev->script_dir = strdup(dirname(resolved_copy));
7255
81
    free(resolved_copy);
7256
7257
81
    EvalResult exec_r = eval_ok(value_unit());
7258
1.85k
    for (size_t j = 0; j < mod_prog.item_count; j++) {
7259
1.77k
        if (mod_prog.items[j].tag == ITEM_STMT) {
7260
18
            value_free(&exec_r.value);
7261
18
            exec_r = eval_stmt(ev, mod_prog.items[j].as.stmt);
7262
18
            if (!IS_OK(exec_r)) break;
7263
18
        }
7264
1.77k
    }
7265
7266
81
    free(ev->script_dir);
7267
81
    ev->script_dir = prev_script_dir;
7268
7269
81
    if (!IS_OK(exec_r)) {
7270
0
        env_pop_scope(ev->env);
7271
0
        for (size_t j = 0; j < mod_toks.len; j++) token_free(lat_vec_get(&mod_toks, j));
7272
0
        lat_vec_free(&mod_toks);
7273
        /* Don't free prog items since decls may be registered */
7274
0
        return exec_r;
7275
0
    }
7276
81
    value_free(&exec_r.value);
7277
7278
    /* Build module Map from scope bindings and functions */
7279
81
    LatValue module_map = value_map_new();
7280
7281
    /* Export top-level variable bindings from module scope */
7282
81
    Scope *mod_scope = &ev->env->scopes[ev->env->count - 1];
7283
1.37k
    for (size_t i = 0; i < mod_scope->cap; i++) {
7284
1.29k
        if (mod_scope->entries[i].state == MAP_OCCUPIED) {
7285
18
            const char *name = mod_scope->entries[i].key;
7286
18
            if (!module_should_export(name,
7287
18
                    (const char **)mod_prog.export_names,
7288
18
                    mod_prog.export_count, mod_prog.has_exports))
7289
5
                continue;
7290
13
            LatValue *val_ptr = (LatValue *)mod_scope->entries[i].value;
7291
13
            LatValue exported = value_deep_clone(val_ptr);
7292
13
            lat_map_set(module_map.as.map.map, name, &exported);
7293
13
        }
7294
1.29k
    }
7295
7296
    /* Export functions as closures */
7297
1.85k
    for (size_t j = 0; j < mod_prog.item_count; j++) {
7298
1.77k
        if (mod_prog.items[j].tag == ITEM_FUNCTION) {
7299
1.75k
            FnDecl *fn = &mod_prog.items[j].as.fn_decl;
7300
7301
            /* Filter based on export declarations */
7302
1.75k
            if (!module_should_export(fn->name,
7303
1.75k
                    (const char **)mod_prog.export_names,
7304
1.75k
                    mod_prog.export_count, mod_prog.has_exports))
7305
5
                continue;
7306
7307
            /* Create an expr_block wrapping the function body.
7308
             * This borrows fn->body (kept alive via program items). */
7309
1.74k
            Expr *body = calloc(1, sizeof(Expr));
7310
1.74k
            body->tag = EXPR_BLOCK;
7311
1.74k
            body->as.block.stmts = fn->body;
7312
1.74k
            body->as.block.count = fn->body_count;
7313
            /* Track this Expr so it stays alive */
7314
1.74k
            lat_vec_push(&ev->module_exprs, &body);
7315
7316
1.74k
            Env *captured = env_clone(ev->env);
7317
1.74k
            Expr **defaults = NULL;
7318
1.74k
            bool has_variadic = false;
7319
1.74k
            if (fn->param_count > 0) {
7320
1.64k
                defaults = malloc(fn->param_count * sizeof(Expr *));
7321
4.43k
                for (size_t k = 0; k < fn->param_count; k++) {
7322
2.78k
                    defaults[k] = fn->params[k].default_value;
7323
2.78k
                    if (fn->params[k].is_variadic) has_variadic = true;
7324
2.78k
                }
7325
1.64k
            }
7326
7327
1.74k
            char **param_names = malloc(fn->param_count * sizeof(char *));
7328
4.53k
            for (size_t k = 0; k < fn->param_count; k++) {
7329
2.78k
                param_names[k] = fn->params[k].name;
7330
2.78k
            }
7331
1.74k
            LatValue closure = value_closure(param_names, fn->param_count, body,
7332
1.74k
                                             captured, defaults, has_variadic);
7333
1.74k
            free(param_names);
7334
            /* Don't free defaults — value_closure borrows it. Track for cleanup. */
7335
1.74k
            if (defaults) lat_vec_push(&ev->module_exprs, &defaults);
7336
1.74k
            lat_map_set(module_map.as.map.map, fn->name, &closure);
7337
1.74k
        }
7338
1.77k
    }
7339
7340
81
    env_pop_scope(ev->env);
7341
7342
    /* Cache the module */
7343
81
    LatValue cached_copy = value_deep_clone(&module_map);
7344
81
    lat_map_set(&ev->module_cache, resolved, &cached_copy);
7345
7346
    /* Cleanup: free statements, keep decl items alive */
7347
1.85k
    for (size_t j = 0; j < mod_prog.item_count; j++) {
7348
1.77k
        if (mod_prog.items[j].tag == ITEM_STMT)
7349
18
            stmt_free(mod_prog.items[j].as.stmt);
7350
1.77k
    }
7351
    /* Don't free prog.items since fn/struct/enum decls are still referenced */
7352
7353
117k
    for (size_t j = 0; j < mod_toks.len; j++) token_free(lat_vec_get(&mod_toks, j));
7354
81
    lat_vec_free(&mod_toks);
7355
7356
81
    return eval_ok(module_map);
7357
81
}
7358
7359
/* ── Statement evaluation ── */
7360
7361
15.1k
static EvalResult eval_stmt(Evaluator *ev, const Stmt *stmt) {
7362
15.1k
    switch (stmt->tag) {
7363
4.69k
        case STMT_BINDING: {
7364
4.69k
            EvalResult vr = eval_expr(ev, stmt->as.binding.value);
7365
4.69k
            if (!IS_OK(vr)) return vr;
7366
7367
4.67k
            if (ev->mode == MODE_CASUAL) {
7368
4.66k
                switch (stmt->as.binding.phase) {
7369
1.84k
                    case PHASE_FLUID: vr.value.phase = VTAG_FLUID; break;
7370
20
                    case PHASE_CRYSTAL:
7371
20
                        stats_freeze(&ev->stats);
7372
20
                        { uint64_t ft0 = now_ns();
7373
20
                        vr.value = value_freeze(vr.value);
7374
20
                        freeze_to_region(ev, &vr.value);
7375
20
                        ev->stats.freeze_total_ns += now_ns() - ft0; }
7376
20
                        break;
7377
2.80k
                    case PHASE_UNSPECIFIED: break;
7378
4.66k
                }
7379
4.66k
            } else { /* MODE_STRICT */
7380
9
                switch (stmt->as.binding.phase) {
7381
5
                    case PHASE_FLUID:
7382
5
                        if (value_is_crystal(&vr.value)) {
7383
0
                            char *err = NULL;
7384
0
                            (void)asprintf(&err, "strict mode: 'flux' binding '%s' produced a crystal value",
7385
0
                                           stmt->as.binding.name);
7386
0
                            value_free(&vr.value);
7387
0
                            return eval_err(err);
7388
0
                        }
7389
5
                        vr.value.phase = VTAG_FLUID;
7390
5
                        break;
7391
4
                    case PHASE_CRYSTAL:
7392
4
                        stats_freeze(&ev->stats);
7393
4
                        { uint64_t ft0 = now_ns();
7394
4
                        vr.value = value_freeze(vr.value);
7395
4
                        freeze_to_region(ev, &vr.value);
7396
4
                        ev->stats.freeze_total_ns += now_ns() - ft0; }
7397
4
                        break;
7398
0
                    case PHASE_UNSPECIFIED: {
7399
0
                        char *err = NULL;
7400
0
                        (void)asprintf(&err, "strict mode: binding '%s' requires an explicit phase (flux/fix)",
7401
0
                                       stmt->as.binding.name);
7402
0
                        value_free(&vr.value);
7403
0
                        return eval_err(err);
7404
5
                    }
7405
9
                }
7406
9
            }
7407
4.67k
            stats_binding(&ev->stats);
7408
            /* In lat_eval context, top-level bindings go to the root scope
7409
             * so they persist across calls (needed for REPL) */
7410
4.67k
            if (ev->lat_eval_scope > 0 && ev->env->count == ev->lat_eval_scope)
7411
3
                env_define_at(ev->env, 0, stmt->as.binding.name, vr.value);
7412
4.67k
            else
7413
4.67k
                env_define(ev->env, stmt->as.binding.name, vr.value);
7414
4.67k
            return eval_ok(value_unit());
7415
4.67k
        }
7416
7417
5.21k
        case STMT_ASSIGN: {
7418
5.21k
            EvalResult valr = eval_expr(ev, stmt->as.assign.value);
7419
5.21k
            if (!IS_OK(valr)) return valr;
7420
7421
            /* Simple ident assignment uses env_set for proper scoping */
7422
5.21k
            if (stmt->as.assign.target->tag == EXPR_IDENT) {
7423
5.16k
                const char *name = stmt->as.assign.target->as.str_val;
7424
5.16k
                if (ev->mode == MODE_STRICT) {
7425
1
                    LatValue existing;
7426
1
                    if (env_get(ev->env, name, &existing)) {
7427
1
                        bool is_crys = value_is_crystal(&existing);
7428
1
                        value_free(&existing);
7429
1
                        if (is_crys) {
7430
0
                            char *err = NULL;
7431
0
                            (void)asprintf(&err, "strict mode: cannot assign to crystal binding '%s'", name);
7432
0
                            value_free(&valr.value);
7433
0
                            return eval_err(err);
7434
0
                        }
7435
1
                    }
7436
1
                }
7437
5.16k
                if (!env_set(ev->env, name, valr.value)) {
7438
0
                    char *err = NULL;
7439
0
                    (void)asprintf(&err, "undefined variable '%s'", name);
7440
0
                    return eval_err(err);
7441
0
                }
7442
5.16k
                record_history(ev, name);
7443
5.16k
                return eval_ok(value_unit());
7444
5.16k
            }
7445
7446
            /* Buffer index assignment: buf[i] = byte (must be handled before resolve_lvalue
7447
             * since buffers store raw bytes, not LatValues) */
7448
49
            if (stmt->as.assign.target->tag == EXPR_INDEX) {
7449
35
                char *buf_chk_err = NULL;
7450
35
                LatValue *buf_chk = resolve_lvalue(ev, stmt->as.assign.target->as.index.object, &buf_chk_err);
7451
35
                if (buf_chk_err) free(buf_chk_err);
7452
35
                if (buf_chk && buf_chk->type == VAL_BUFFER) {
7453
2
                    EvalResult buf_idxr = eval_expr(ev, stmt->as.assign.target->as.index.index);
7454
2
                    if (!IS_OK(buf_idxr)) { value_free(&valr.value); return buf_idxr; }
7455
2
                    if (buf_idxr.value.type != VAL_INT) {
7456
0
                        value_free(&buf_idxr.value); value_free(&valr.value);
7457
0
                        return eval_err(strdup("buffer index must be an integer"));
7458
0
                    }
7459
2
                    size_t bidx = (size_t)buf_idxr.value.as.int_val;
7460
2
                    value_free(&buf_idxr.value);
7461
2
                    if (bidx >= buf_chk->as.buffer.len) {
7462
0
                        value_free(&valr.value);
7463
0
                        char *berr = NULL;
7464
0
                        (void)asprintf(&berr, "buffer index %zu out of bounds (length %zu)",
7465
0
                                       bidx, buf_chk->as.buffer.len);
7466
0
                        return eval_err(berr);
7467
0
                    }
7468
2
                    if (valr.value.type != VAL_INT) {
7469
0
                        value_free(&valr.value);
7470
0
                        return eval_err(strdup("buffer element must be an integer"));
7471
0
                    }
7472
2
                    buf_chk->as.buffer.data[bidx] = (uint8_t)(valr.value.as.int_val & 0xFF);
7473
2
                    value_free(&valr.value);
7474
2
                    return eval_ok(value_unit());
7475
2
                }
7476
35
            }
7477
7478
            /* For field access, index, and nested chains: use resolve_lvalue */
7479
47
            char *lv_err = NULL;
7480
47
            LatValue *target = resolve_lvalue(ev, stmt->as.assign.target, &lv_err);
7481
47
            if (!target) {
7482
0
                value_free(&valr.value);
7483
0
                return eval_err(lv_err);
7484
0
            }
7485
47
            if (ev->mode == MODE_STRICT && value_is_crystal(target)) {
7486
0
                value_free(&valr.value);
7487
0
                return eval_err(strdup("strict mode: cannot assign to crystal value"));
7488
0
            }
7489
            /* Check sublimated phase for direct parent */
7490
47
            if (stmt->as.assign.target->tag == EXPR_FIELD_ACCESS) {
7491
14
                LatValue *parent = resolve_lvalue(ev, stmt->as.assign.target->as.field_access.object, &lv_err);
7492
14
                if (parent && parent->phase == VTAG_SUBLIMATED) {
7493
0
                    const char *fname = stmt->as.assign.target->as.field_access.field;
7494
0
                    char *err = NULL;
7495
0
                    (void)asprintf(&err, "cannot assign to field '%s' of sublimated value", fname);
7496
0
                    value_free(&valr.value);
7497
0
                    return eval_err(err);
7498
0
                }
7499
14
            }
7500
47
            if (stmt->as.assign.target->tag == EXPR_INDEX) {
7501
33
                LatValue *parent = resolve_lvalue(ev, stmt->as.assign.target->as.index.object, &lv_err);
7502
33
                if (parent && parent->phase == VTAG_SUBLIMATED) {
7503
1
                    value_free(&valr.value);
7504
1
                    return eval_err(strdup("cannot assign to index of sublimated value"));
7505
1
                }
7506
33
            }
7507
            /* Check per-field phase for struct field assignments */
7508
46
            if (stmt->as.assign.target->tag == EXPR_FIELD_ACCESS) {
7509
14
                LatValue *parent = resolve_lvalue(ev, stmt->as.assign.target->as.field_access.object, &lv_err);
7510
14
                if (parent && parent->type == VAL_STRUCT && parent->phase == VTAG_CRYSTAL) {
7511
1
                    const char *fname = stmt->as.assign.target->as.field_access.field;
7512
1
                    char *err = NULL;
7513
1
                    (void)asprintf(&err, "cannot assign to field '%s' of frozen struct", fname);
7514
1
                    value_free(&valr.value);
7515
1
                    return eval_err(err);
7516
1
                }
7517
13
                if (parent && parent->type == VAL_STRUCT && parent->as.strct.field_phases) {
7518
7
                    const char *fname = stmt->as.assign.target->as.field_access.field;
7519
12
                    for (size_t fi = 0; fi < parent->as.strct.field_count; fi++) {
7520
12
                        if (strcmp(parent->as.strct.field_names[fi], fname) == 0) {
7521
7
                            if (parent->as.strct.field_phases[fi] == VTAG_CRYSTAL) {
7522
3
                                char *err = NULL;
7523
3
                                (void)asprintf(&err, "cannot assign to frozen field '%s'", fname);
7524
3
                                value_free(&valr.value);
7525
3
                                return eval_err(err);
7526
3
                            }
7527
4
                            break;
7528
7
                        }
7529
12
                    }
7530
7
                }
7531
13
            }
7532
            /* Check per-key phase for map key assignments */
7533
42
            if (stmt->as.assign.target->tag == EXPR_INDEX) {
7534
32
                LatValue *parent = resolve_lvalue(ev, stmt->as.assign.target->as.index.object, &lv_err);
7535
32
                if (parent && parent->type == VAL_REF && parent->phase == VTAG_CRYSTAL) {
7536
1
                    value_free(&valr.value);
7537
1
                    return eval_err(strdup("cannot assign index on a frozen Ref"));
7538
1
                }
7539
31
                if (parent && parent->type == VAL_MAP && parent->phase == VTAG_CRYSTAL) {
7540
0
                    value_free(&valr.value);
7541
0
                    return eval_err(strdup("cannot assign to key of frozen map"));
7542
0
                }
7543
31
                if (parent && parent->type == VAL_MAP && parent->as.map.key_phases) {
7544
3
                    EvalResult kidxr = eval_expr(ev, stmt->as.assign.target->as.index.index);
7545
3
                    if (IS_OK(kidxr) && kidxr.value.type == VAL_STR) {
7546
3
                        PhaseTag *kp = (PhaseTag *)lat_map_get(parent->as.map.key_phases, kidxr.value.as.str_val);
7547
3
                        if (kp && *kp == VTAG_CRYSTAL) {
7548
1
                            char *err = NULL;
7549
1
                            (void)asprintf(&err, "cannot assign to frozen key '%s'", kidxr.value.as.str_val);
7550
1
                            value_free(&kidxr.value);
7551
1
                            value_free(&valr.value);
7552
1
                            return eval_err(err);
7553
1
                        }
7554
3
                    }
7555
2
                    value_free(&kidxr.value);
7556
2
                }
7557
31
            }
7558
40
            value_free(target);
7559
40
            *target = valr.value;
7560
            /* Record history for root variable of field/index chain */
7561
40
            if (ev->tracked_count > 0) {
7562
0
                const Expr *root = stmt->as.assign.target;
7563
0
                while (root->tag == EXPR_FIELD_ACCESS) root = root->as.field_access.object;
7564
0
                while (root->tag == EXPR_INDEX) root = root->as.index.object;
7565
0
                if (root->tag == EXPR_IDENT)
7566
0
                    record_history(ev, root->as.str_val);
7567
0
            }
7568
40
            return eval_ok(value_unit());
7569
42
        }
7570
7571
4.16k
        case STMT_EXPR:
7572
4.16k
            return eval_expr(ev, stmt->as.expr);
7573
7574
783
        case STMT_RETURN: {
7575
783
            if (stmt->as.return_expr) {
7576
782
                EvalResult er = eval_expr(ev, stmt->as.return_expr);
7577
782
                if (!IS_OK(er)) return er;
7578
782
                return eval_signal(CF_RETURN, er.value);
7579
782
            }
7580
1
            return eval_signal(CF_RETURN, value_unit());
7581
783
        }
7582
7583
59
        case STMT_FOR: {
7584
59
            EvalResult iter_r = eval_expr(ev, stmt->as.for_loop.iter);
7585
59
            if (!IS_OK(iter_r)) return iter_r;
7586
7587
59
            if (iter_r.value.type == VAL_RANGE) {
7588
20
                int64_t s = iter_r.value.as.range.start;
7589
20
                int64_t e = iter_r.value.as.range.end;
7590
20
                value_free(&iter_r.value);
7591
654
                for (int64_t i = s; i < e; i++) {
7592
634
                    stats_scope_push(&ev->stats);
7593
634
                    env_push_scope(ev->env);
7594
634
                    env_define(ev->env, stmt->as.for_loop.var, value_int(i));
7595
634
                    EvalResult r = eval_block_stmts(ev, stmt->as.for_loop.body,
7596
634
                                                     stmt->as.for_loop.body_count);
7597
634
                    env_pop_scope(ev->env);
7598
634
                    stats_scope_pop(&ev->stats);
7599
634
                    if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break;
7600
634
                    if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue;
7601
629
                    if (!IS_OK(r)) return r;
7602
629
                    value_free(&r.value);
7603
629
                }
7604
39
            } else if (iter_r.value.type == VAL_ARRAY) {
7605
37
                GC_PUSH(ev, &iter_r.value);
7606
37
                size_t len = iter_r.value.as.array.len;
7607
112
                for (size_t i = 0; i < len; i++) {
7608
75
                    stats_scope_push(&ev->stats);
7609
75
                    env_push_scope(ev->env);
7610
75
                    LatValue elem = value_deep_clone(&iter_r.value.as.array.elems[i]);
7611
75
                    env_define(ev->env, stmt->as.for_loop.var, elem);
7612
75
                    EvalResult r = eval_block_stmts(ev, stmt->as.for_loop.body,
7613
75
                                                     stmt->as.for_loop.body_count);
7614
75
                    env_pop_scope(ev->env);
7615
75
                    stats_scope_pop(&ev->stats);
7616
75
                    if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break;
7617
75
                    if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue;
7618
75
                    if (!IS_OK(r)) { GC_POP(ev); value_free(&iter_r.value); return r; }
7619
75
                    value_free(&r.value);
7620
75
                }
7621
37
                GC_POP(ev);
7622
37
                value_free(&iter_r.value);
7623
37
            } else if (iter_r.value.type == VAL_MAP) {
7624
                /* Iterate over map keys */
7625
1
                GC_PUSH(ev, &iter_r.value);
7626
17
                for (size_t i = 0; i < iter_r.value.as.map.map->cap; i++) {
7627
16
                    if (iter_r.value.as.map.map->entries[i].state != MAP_OCCUPIED) continue;
7628
1
                    stats_scope_push(&ev->stats);
7629
1
                    env_push_scope(ev->env);
7630
1
                    env_define(ev->env, stmt->as.for_loop.var,
7631
1
                               value_string(iter_r.value.as.map.map->entries[i].key));
7632
1
                    EvalResult r = eval_block_stmts(ev, stmt->as.for_loop.body,
7633
1
                                                     stmt->as.for_loop.body_count);
7634
1
                    env_pop_scope(ev->env);
7635
1
                    stats_scope_pop(&ev->stats);
7636
1
                    if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break;
7637
1
                    if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue;
7638
1
                    if (!IS_OK(r)) { GC_POP(ev); value_free(&iter_r.value); return r; }
7639
1
                    value_free(&r.value);
7640
1
                }
7641
1
                GC_POP(ev);
7642
1
                value_free(&iter_r.value);
7643
1
            } else if (iter_r.value.type == VAL_SET) {
7644
                /* Iterate over set elements */
7645
1
                GC_PUSH(ev, &iter_r.value);
7646
17
                for (size_t i = 0; i < iter_r.value.as.set.map->cap; i++) {
7647
16
                    if (iter_r.value.as.set.map->entries[i].state != MAP_OCCUPIED) continue;
7648
1
                    stats_scope_push(&ev->stats);
7649
1
                    env_push_scope(ev->env);
7650
1
                    LatValue *sv = (LatValue *)iter_r.value.as.set.map->entries[i].value;
7651
1
                    env_define(ev->env, stmt->as.for_loop.var, value_deep_clone(sv));
7652
1
                    EvalResult r = eval_block_stmts(ev, stmt->as.for_loop.body,
7653
1
                                                     stmt->as.for_loop.body_count);
7654
1
                    env_pop_scope(ev->env);
7655
1
                    stats_scope_pop(&ev->stats);
7656
1
                    if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break;
7657
1
                    if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue;
7658
1
                    if (!IS_OK(r)) { GC_POP(ev); value_free(&iter_r.value); return r; }
7659
1
                    value_free(&r.value);
7660
1
                }
7661
1
                GC_POP(ev);
7662
1
                value_free(&iter_r.value);
7663
1
            } else {
7664
0
                char *err = NULL;
7665
0
                (void)asprintf(&err, "cannot iterate over %s", value_type_name(&iter_r.value));
7666
0
                value_free(&iter_r.value);
7667
0
                return eval_err(err);
7668
0
            }
7669
59
            return eval_ok(value_unit());
7670
59
        }
7671
7672
32
        case STMT_WHILE: {
7673
3.14k
            for (;;) {
7674
3.14k
                EvalResult condr = eval_expr(ev, stmt->as.while_loop.cond);
7675
3.14k
                if (!IS_OK(condr)) return condr;
7676
3.14k
                bool truthy = value_is_truthy(&condr.value);
7677
3.14k
                value_free(&condr.value);
7678
3.14k
                if (!truthy) break;
7679
3.11k
                stats_scope_push(&ev->stats);
7680
3.11k
                env_push_scope(ev->env);
7681
3.11k
                EvalResult r = eval_block_stmts(ev, stmt->as.while_loop.body,
7682
3.11k
                                                 stmt->as.while_loop.body_count);
7683
3.11k
                env_pop_scope(ev->env);
7684
3.11k
                stats_scope_pop(&ev->stats);
7685
3.11k
                if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break;
7686
3.11k
                if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue;
7687
3.11k
                if (!IS_OK(r)) return r;
7688
3.11k
                value_free(&r.value);
7689
3.11k
            }
7690
32
            return eval_ok(value_unit());
7691
32
        }
7692
7693
26
        case STMT_LOOP: {
7694
195
            for (;;) {
7695
195
                stats_scope_push(&ev->stats);
7696
195
                env_push_scope(ev->env);
7697
195
                EvalResult r = eval_block_stmts(ev, stmt->as.loop.body, stmt->as.loop.body_count);
7698
195
                env_pop_scope(ev->env);
7699
195
                stats_scope_pop(&ev->stats);
7700
195
                if (IS_SIGNAL(r) && r.cf.tag == CF_BREAK) break;
7701
181
                if (IS_SIGNAL(r) && r.cf.tag == CF_CONTINUE) continue;
7702
181
                if (!IS_OK(r)) return r;
7703
169
                value_free(&r.value);
7704
169
            }
7705
14
            return eval_ok(value_unit());
7706
26
        }
7707
7708
18
        case STMT_BREAK:    return eval_signal(CF_BREAK, value_unit());
7709
7
        case STMT_CONTINUE: return eval_signal(CF_CONTINUE, value_unit());
7710
7711
8
        case STMT_DEFER: {
7712
            /* Push to defer stack — don't execute now */
7713
8
            if (ev->defer_count >= ev->defer_cap) {
7714
4
                ev->defer_cap = ev->defer_cap < 8 ? 8 : ev->defer_cap * 2;
7715
4
                ev->defer_stack = realloc(ev->defer_stack, ev->defer_cap * sizeof(DeferEntry));
7716
4
            }
7717
8
            ev->defer_stack[ev->defer_count].body = stmt->as.defer.body;
7718
8
            ev->defer_stack[ev->defer_count].body_count = stmt->as.defer.body_count;
7719
8
            ev->defer_stack[ev->defer_count].scope_depth = ev->stats.current_scope_depth;
7720
8
            ev->defer_count++;
7721
8
            return eval_ok(value_unit());
7722
26
        }
7723
7724
8
        case STMT_DESTRUCTURE: {
7725
8
            EvalResult vr = eval_expr(ev, stmt->as.destructure.value);
7726
8
            if (!IS_OK(vr)) return vr;
7727
7728
8
            if (stmt->as.destructure.kind == DESTRUCT_ARRAY) {
7729
6
                if (vr.value.type != VAL_ARRAY) {
7730
0
                    char *err = NULL;
7731
0
                    (void)asprintf(&err, "cannot destructure %s as array",
7732
0
                                   value_type_name(&vr.value));
7733
0
                    value_free(&vr.value);
7734
0
                    return eval_err(err);
7735
0
                }
7736
6
                size_t arr_len = vr.value.as.array.len;
7737
6
                size_t name_count = stmt->as.destructure.name_count;
7738
6
                bool has_rest = (stmt->as.destructure.rest_name != NULL);
7739
7740
6
                if (!has_rest && arr_len != name_count) {
7741
0
                    char *err = NULL;
7742
0
                    (void)asprintf(&err, "array destructure: expected %zu elements, got %zu",
7743
0
                                   name_count, arr_len);
7744
0
                    value_free(&vr.value);
7745
0
                    return eval_err(err);
7746
0
                }
7747
6
                if (has_rest && arr_len < name_count) {
7748
0
                    char *err = NULL;
7749
0
                    (void)asprintf(&err, "array destructure: expected at least %zu elements, got %zu",
7750
0
                                   name_count, arr_len);
7751
0
                    value_free(&vr.value);
7752
0
                    return eval_err(err);
7753
0
                }
7754
7755
                /* Bind named elements */
7756
18
                for (size_t i = 0; i < name_count; i++) {
7757
12
                    LatValue elem = value_deep_clone(&vr.value.as.array.elems[i]);
7758
                    /* Apply phase */
7759
12
                    if (stmt->as.destructure.phase == PHASE_FLUID)
7760
2
                        elem.phase = VTAG_FLUID;
7761
10
                    else if (stmt->as.destructure.phase == PHASE_CRYSTAL) {
7762
0
                        stats_freeze(&ev->stats);
7763
0
                        elem = value_freeze(elem);
7764
0
                        freeze_to_region(ev, &elem);
7765
0
                    }
7766
12
                    stats_binding(&ev->stats);
7767
12
                    env_define(ev->env, stmt->as.destructure.names[i], elem);
7768
12
                }
7769
7770
                /* Bind rest */
7771
6
                if (has_rest) {
7772
3
                    size_t rest_count = arr_len - name_count;
7773
3
                    LatValue *rest_elems = malloc(rest_count * sizeof(LatValue));
7774
9
                    for (size_t i = 0; i < rest_count; i++)
7775
6
                        rest_elems[i] = value_deep_clone(&vr.value.as.array.elems[name_count + i]);
7776
3
                    LatValue rest_arr = value_array(rest_elems, rest_count);
7777
3
                    free(rest_elems);
7778
3
                    if (stmt->as.destructure.phase == PHASE_FLUID)
7779
0
                        rest_arr.phase = VTAG_FLUID;
7780
3
                    else if (stmt->as.destructure.phase == PHASE_CRYSTAL) {
7781
0
                        stats_freeze(&ev->stats);
7782
0
                        rest_arr = value_freeze(rest_arr);
7783
0
                        freeze_to_region(ev, &rest_arr);
7784
0
                    }
7785
3
                    stats_binding(&ev->stats);
7786
3
                    env_define(ev->env, stmt->as.destructure.rest_name, rest_arr);
7787
3
                }
7788
6
                value_free(&vr.value);
7789
7790
6
            } else {
7791
                /* DESTRUCT_STRUCT */
7792
2
                if (vr.value.type != VAL_STRUCT && vr.value.type != VAL_MAP) {
7793
0
                    char *err = NULL;
7794
0
                    (void)asprintf(&err, "cannot destructure %s as struct",
7795
0
                                   value_type_name(&vr.value));
7796
0
                    value_free(&vr.value);
7797
0
                    return eval_err(err);
7798
0
                }
7799
7800
6
                for (size_t i = 0; i < stmt->as.destructure.name_count; i++) {
7801
4
                    const char *fname = stmt->as.destructure.names[i];
7802
4
                    LatValue elem = value_unit();
7803
4
                    bool found = false;
7804
7805
4
                    if (vr.value.type == VAL_STRUCT) {
7806
3
                        for (size_t j = 0; j < vr.value.as.strct.field_count; j++) {
7807
3
                            if (strcmp(vr.value.as.strct.field_names[j], fname) == 0) {
7808
2
                                elem = value_deep_clone(&vr.value.as.strct.field_values[j]);
7809
2
                                found = true;
7810
2
                                break;
7811
2
                            }
7812
3
                        }
7813
2
                    } else {
7814
                        /* VAL_MAP */
7815
2
                        LatValue *mval = lat_map_get(vr.value.as.map.map, fname);
7816
2
                        if (mval) {
7817
2
                            elem = value_deep_clone(mval);
7818
2
                            found = true;
7819
2
                        }
7820
2
                    }
7821
7822
4
                    if (!found) {
7823
0
                        char *err = NULL;
7824
0
                        (void)asprintf(&err, "destructure: field '%s' not found", fname);
7825
0
                        value_free(&vr.value);
7826
0
                        return eval_err(err);
7827
0
                    }
7828
7829
4
                    if (stmt->as.destructure.phase == PHASE_FLUID)
7830
0
                        elem.phase = VTAG_FLUID;
7831
4
                    else if (stmt->as.destructure.phase == PHASE_CRYSTAL) {
7832
0
                        stats_freeze(&ev->stats);
7833
0
                        elem = value_freeze(elem);
7834
0
                        freeze_to_region(ev, &elem);
7835
0
                    }
7836
4
                    stats_binding(&ev->stats);
7837
4
                    env_define(ev->env, fname, elem);
7838
4
                }
7839
2
                value_free(&vr.value);
7840
2
            }
7841
8
            return eval_ok(value_unit());
7842
8
        }
7843
7844
94
        case STMT_IMPORT: {
7845
94
            const char *path = stmt->as.import.module_path;
7846
94
            const char *alias = stmt->as.import.alias;
7847
94
            char **selective = stmt->as.import.selective_names;
7848
94
            size_t sel_count = stmt->as.import.selective_count;
7849
7850
94
            EvalResult mod_r = load_module(ev, path);
7851
94
            if (!IS_OK(mod_r)) return mod_r;
7852
7853
93
            LatValue module_map = mod_r.value;
7854
7855
            /* Selective import: import { x, y } from "path" */
7856
93
            if (selective) {
7857
35
                for (size_t i = 0; i < sel_count; i++) {
7858
20
                    const char *name = selective[i];
7859
20
                    LatValue *exported = (LatValue *)lat_map_get(module_map.as.map.map, name);
7860
20
                    if (!exported) {
7861
2
                        char *err = NULL;
7862
2
                        (void)asprintf(&err, "module '%s' does not export '%s'", path, name);
7863
2
                        value_free(&module_map);
7864
2
                        return eval_err(err);
7865
2
                    }
7866
18
                    env_define(ev->env, name, value_deep_clone(exported));
7867
18
                }
7868
15
                value_free(&module_map);
7869
15
                return eval_ok(value_unit());
7870
17
            }
7871
7872
            /* Full import: import "path" as name */
7873
76
            if (!alias) {
7874
0
                value_free(&module_map);
7875
0
                return eval_err(strdup("import requires 'as <name>' or selective '{ ... } from'"));
7876
0
            }
7877
7878
76
            env_define(ev->env, alias, module_map);
7879
76
            return eval_ok(value_unit());
7880
76
        }
7881
15.1k
    }
7882
0
    return eval_err(strdup("unknown statement type"));
7883
15.1k
}
7884
7885
/* Run deferred blocks for the current scope depth (LIFO) */
7886
6.54k
static EvalResult run_defers_for_scope(Evaluator *ev, size_t scope_depth) {
7887
6.54k
    EvalResult first_err = { .ok = true, .error = NULL };
7888
6.54k
    first_err.value = value_unit();
7889
6.54k
    first_err.cf.tag = CF_NONE;
7890
6.55k
    while (ev->defer_count > 0 && ev->defer_stack[ev->defer_count - 1].scope_depth >= scope_depth) {
7891
8
        DeferEntry de = ev->defer_stack[--ev->defer_count];
7892
8
        EvalResult dr = eval_block_stmts(ev, de.body, de.body_count);
7893
8
        if (!IS_OK(dr) && first_err.ok) {
7894
0
            first_err = dr;
7895
8
        } else if (!IS_OK(dr)) {
7896
0
            if (IS_ERR(dr)) free(dr.error);
7897
0
        }
7898
8
    }
7899
6.54k
    return first_err;
7900
6.54k
}
7901
7902
6.54k
static EvalResult eval_block_stmts(Evaluator *ev, Stmt **stmts, size_t count) {
7903
6.54k
    size_t defer_base = ev->defer_count;
7904
6.54k
    size_t scope_depth = ev->stats.current_scope_depth;
7905
6.54k
    LatValue last = value_unit();
7906
6.54k
    GC_PUSH(ev, &last);
7907
20.3k
    for (size_t i = 0; i < count; i++) {
7908
14.8k
        gc_maybe_collect(ev);
7909
14.8k
        value_free(&last);
7910
14.8k
        EvalResult r = eval_stmt(ev, stmts[i]);
7911
14.8k
        if (!IS_OK(r)) {
7912
            /* Run defers before propagating error/signal */
7913
1.04k
            EvalResult dr = run_defers_for_scope(ev, scope_depth);
7914
1.04k
            GC_POP(ev);
7915
1.04k
            if (!dr.ok && r.ok) return dr;
7916
1.04k
            if (!dr.ok && IS_ERR(dr)) free(dr.error);
7917
1.04k
            return r;
7918
1.04k
        }
7919
13.7k
        last = r.value;
7920
13.7k
    }
7921
    /* Run defers on normal exit */
7922
5.50k
    EvalResult dr = run_defers_for_scope(ev, scope_depth);
7923
5.50k
    (void)defer_base;
7924
5.50k
    GC_POP(ev);
7925
5.50k
    if (!dr.ok) { value_free(&last); return dr; }
7926
5.50k
    return eval_ok(last);
7927
5.50k
}
7928
7929
/* ── Method calls ── */
7930
7931
static EvalResult eval_method_call(Evaluator *ev, LatValue obj, const char *method,
7932
1.70k
                                   LatValue *args, size_t arg_count) {
7933
    /* ── Enum methods ── */
7934
1.70k
    if (obj.type == VAL_ENUM) {
7935
6
        if (strcmp(method, "variant_name") == 0) {
7936
1
            if (arg_count != 0) return eval_err(strdup("variant_name() takes no arguments"));
7937
1
            return eval_ok(value_string(obj.as.enm.variant_name));
7938
1
        }
7939
5
        if (strcmp(method, "enum_name") == 0) {
7940
1
            if (arg_count != 0) return eval_err(strdup("enum_name() takes no arguments"));
7941
1
            return eval_ok(value_string(obj.as.enm.enum_name));
7942
1
        }
7943
4
        if (strcmp(method, "is_variant") == 0) {
7944
2
            if (arg_count != 1)
7945
0
                return eval_err(strdup("is_variant() expects 1 argument"));
7946
2
            if (args[0].type != VAL_STR)
7947
0
                return eval_err(strdup("is_variant() expects a String argument"));
7948
2
            bool match = (strcmp(obj.as.enm.variant_name, args[0].as.str_val) == 0);
7949
2
            return eval_ok(value_bool(match));
7950
2
        }
7951
2
        if (strcmp(method, "payload") == 0) {
7952
2
            if (arg_count != 0) return eval_err(strdup("payload() takes no arguments"));
7953
2
            LatValue r;
7954
2
            if (obj.as.enm.payload_count > 0) {
7955
2
                LatValue *elems = malloc(obj.as.enm.payload_count * sizeof(LatValue));
7956
5
                for (size_t i = 0; i < obj.as.enm.payload_count; i++)
7957
3
                    elems[i] = value_deep_clone(&obj.as.enm.payload[i]);
7958
2
                r = value_array(elems, obj.as.enm.payload_count);
7959
2
                free(elems);
7960
2
            } else {
7961
0
                r = value_array(NULL, 0);
7962
0
            }
7963
2
            return eval_ok(r);
7964
2
        }
7965
0
        char *err2 = NULL;
7966
0
        (void)asprintf(&err2, "Enum has no method '%s'", method);
7967
0
        return eval_err(err2);
7968
2
    }
7969
7970
    /* ── Set methods ── */
7971
1.70k
    if (obj.type == VAL_SET) {
7972
        /// @method Set.has(value: Any) -> Bool
7973
        /// @category Set Methods
7974
        /// Check if the set contains the value.
7975
        /// @example s.has(42)
7976
21
        if (strcmp(method, "has") == 0) {
7977
7
            if (arg_count != 1) return eval_err(strdup(".has() expects 1 argument"));
7978
7
            char *key = value_display(&args[0]);
7979
7
            bool result = lat_map_contains(obj.as.set.map, key);
7980
7
            free(key);
7981
7
            return eval_ok(value_bool(result));
7982
7
        }
7983
        /// @method Set.len() -> Int
7984
        /// @category Set Methods
7985
        /// Return the number of elements in the set.
7986
        /// @example s.len()
7987
14
        if (strcmp(method, "len") == 0) {
7988
7
            if (arg_count != 0) return eval_err(strdup(".len() takes no arguments"));
7989
7
            return eval_ok(value_int((int64_t)lat_map_len(obj.as.set.map)));
7990
7
        }
7991
        /// @method Set.to_array() -> Array
7992
        /// @category Set Methods
7993
        /// Convert the set to an array of its elements.
7994
        /// @example s.to_array()
7995
7
        if (strcmp(method, "to_array") == 0) {
7996
1
            if (arg_count != 0) return eval_err(strdup(".to_array() takes no arguments"));
7997
1
            size_t n = lat_map_len(obj.as.set.map);
7998
1
            LatValue *elems = malloc((n > 0 ? n : 1) * sizeof(LatValue));
7999
1
            size_t ei = 0;
8000
17
            for (size_t i = 0; i < obj.as.set.map->cap; i++) {
8001
16
                if (obj.as.set.map->entries[i].state != MAP_OCCUPIED) continue;
8002
1
                LatValue *sv = (LatValue *)obj.as.set.map->entries[i].value;
8003
1
                elems[ei++] = value_deep_clone(sv);
8004
1
            }
8005
1
            LatValue arr = value_array(elems, ei);
8006
1
            free(elems);
8007
1
            return eval_ok(arr);
8008
1
        }
8009
        /// @method Set.union(other: Set) -> Set
8010
        /// @category Set Methods
8011
        /// Return a new set containing all elements from both sets.
8012
        /// @example s1.union(s2)
8013
6
        if (strcmp(method, "union") == 0) {
8014
1
            if (arg_count != 1 || args[0].type != VAL_SET)
8015
0
                return eval_err(strdup(".union() expects 1 Set argument"));
8016
1
            LatValue result = value_set_new();
8017
17
            for (size_t i = 0; i < obj.as.set.map->cap; i++) {
8018
16
                if (obj.as.set.map->entries[i].state == MAP_OCCUPIED) {
8019
2
                    LatValue *sv = (LatValue *)obj.as.set.map->entries[i].value;
8020
2
                    LatValue cloned = value_deep_clone(sv);
8021
2
                    lat_map_set(result.as.set.map, obj.as.set.map->entries[i].key, &cloned);
8022
2
                }
8023
16
            }
8024
17
            for (size_t i = 0; i < args[0].as.set.map->cap; i++) {
8025
16
                if (args[0].as.set.map->entries[i].state == MAP_OCCUPIED) {
8026
2
                    if (!lat_map_contains(result.as.set.map, args[0].as.set.map->entries[i].key)) {
8027
1
                        LatValue *sv = (LatValue *)args[0].as.set.map->entries[i].value;
8028
1
                        LatValue cloned = value_deep_clone(sv);
8029
1
                        lat_map_set(result.as.set.map, args[0].as.set.map->entries[i].key, &cloned);
8030
1
                    }
8031
2
                }
8032
16
            }
8033
1
            return eval_ok(result);
8034
1
        }
8035
        /// @method Set.intersection(other: Set) -> Set
8036
        /// @category Set Methods
8037
        /// Return a new set containing only elements in both sets.
8038
        /// @example s1.intersection(s2)
8039
5
        if (strcmp(method, "intersection") == 0) {
8040
1
            if (arg_count != 1 || args[0].type != VAL_SET)
8041
0
                return eval_err(strdup(".intersection() expects 1 Set argument"));
8042
1
            LatValue result = value_set_new();
8043
17
            for (size_t i = 0; i < obj.as.set.map->cap; i++) {
8044
16
                if (obj.as.set.map->entries[i].state == MAP_OCCUPIED) {
8045
3
                    const char *key = obj.as.set.map->entries[i].key;
8046
3
                    if (lat_map_contains(args[0].as.set.map, key)) {
8047
2
                        LatValue *sv = (LatValue *)obj.as.set.map->entries[i].value;
8048
2
                        LatValue cloned = value_deep_clone(sv);
8049
2
                        lat_map_set(result.as.set.map, key, &cloned);
8050
2
                    }
8051
3
                }
8052
16
            }
8053
1
            return eval_ok(result);
8054
1
        }
8055
        /// @method Set.difference(other: Set) -> Set
8056
        /// @category Set Methods
8057
        /// Return a new set with elements in this set but not in other.
8058
        /// @example s1.difference(s2)
8059
4
        if (strcmp(method, "difference") == 0) {
8060
1
            if (arg_count != 1 || args[0].type != VAL_SET)
8061
0
                return eval_err(strdup(".difference() expects 1 Set argument"));
8062
1
            LatValue result = value_set_new();
8063
17
            for (size_t i = 0; i < obj.as.set.map->cap; i++) {
8064
16
                if (obj.as.set.map->entries[i].state == MAP_OCCUPIED) {
8065
3
                    const char *key = obj.as.set.map->entries[i].key;
8066
3
                    if (!lat_map_contains(args[0].as.set.map, key)) {
8067
1
                        LatValue *sv = (LatValue *)obj.as.set.map->entries[i].value;
8068
1
                        LatValue cloned = value_deep_clone(sv);
8069
1
                        lat_map_set(result.as.set.map, key, &cloned);
8070
1
                    }
8071
3
                }
8072
16
            }
8073
1
            return eval_ok(result);
8074
1
        }
8075
        /// @method Set.is_subset(other: Set) -> Bool
8076
        /// @category Set Methods
8077
        /// Check if this set is a subset of other.
8078
        /// @example s1.is_subset(s2)
8079
3
        if (strcmp(method, "is_subset") == 0) {
8080
2
            if (arg_count != 1 || args[0].type != VAL_SET)
8081
0
                return eval_err(strdup(".is_subset() expects 1 Set argument"));
8082
20
            for (size_t i = 0; i < obj.as.set.map->cap; i++) {
8083
19
                if (obj.as.set.map->entries[i].state == MAP_OCCUPIED) {
8084
3
                    if (!lat_map_contains(args[0].as.set.map, obj.as.set.map->entries[i].key))
8085
1
                        return eval_ok(value_bool(false));
8086
3
                }
8087
19
            }
8088
1
            return eval_ok(value_bool(true));
8089
2
        }
8090
        /// @method Set.is_superset(other: Set) -> Bool
8091
        /// @category Set Methods
8092
        /// Check if this set is a superset of other.
8093
        /// @example s1.is_superset(s2)
8094
1
        if (strcmp(method, "is_superset") == 0) {
8095
1
            if (arg_count != 1 || args[0].type != VAL_SET)
8096
0
                return eval_err(strdup(".is_superset() expects 1 Set argument"));
8097
17
            for (size_t i = 0; i < args[0].as.set.map->cap; i++) {
8098
16
                if (args[0].as.set.map->entries[i].state == MAP_OCCUPIED) {
8099
2
                    if (!lat_map_contains(obj.as.set.map, args[0].as.set.map->entries[i].key))
8100
0
                        return eval_ok(value_bool(false));
8101
2
                }
8102
16
            }
8103
1
            return eval_ok(value_bool(true));
8104
1
        }
8105
0
        char *err2 = NULL;
8106
0
        (void)asprintf(&err2, "Set has no method '%s'", method);
8107
0
        return eval_err(err2);
8108
1
    }
8109
8110
    /* ── Buffer methods ── */
8111
1.68k
    if (obj.type == VAL_BUFFER) {
8112
        /// @method Buffer.len() -> Int
8113
        /// @category Buffer Methods
8114
        /// Return the number of bytes in the buffer.
8115
        /// @example buf.len()
8116
16
        if (strcmp(method, "len") == 0) {
8117
10
            if (arg_count != 0) return eval_err(strdup(".len() takes no arguments"));
8118
10
            return eval_ok(value_int((int64_t)obj.as.buffer.len));
8119
10
        }
8120
        /// @method Buffer.capacity() -> Int
8121
        /// @category Buffer Methods
8122
        /// Return the current capacity of the buffer.
8123
        /// @example buf.capacity()
8124
6
        if (strcmp(method, "capacity") == 0) {
8125
0
            if (arg_count != 0) return eval_err(strdup(".capacity() takes no arguments"));
8126
0
            return eval_ok(value_int((int64_t)obj.as.buffer.cap));
8127
0
        }
8128
        /// @method Buffer.push(byte: Int) -> Unit
8129
        /// @category Buffer Methods
8130
        /// Append a single byte (0-255) to the buffer.
8131
        /// @example buf.push(0x42)
8132
6
        if (strcmp(method, "push") == 0) {
8133
0
            if (arg_count != 1) return eval_err(strdup("Buffer.push() expects 1 argument"));
8134
            /* Note: in tree-walker, push on copy doesn't mutate original */
8135
0
            return eval_ok(value_unit());
8136
0
        }
8137
        /// @method Buffer.push_u16(val: Int) -> Unit
8138
        /// @category Buffer Methods
8139
        /// Append a 16-bit value as 2 bytes (little-endian).
8140
        /// @example buf.push_u16(0x1234)
8141
6
        if (strcmp(method, "push_u16") == 0) {
8142
0
            return eval_ok(value_unit());
8143
0
        }
8144
        /// @method Buffer.push_u32(val: Int) -> Unit
8145
        /// @category Buffer Methods
8146
        /// Append a 32-bit value as 4 bytes (little-endian).
8147
        /// @example buf.push_u32(0x12345678)
8148
6
        if (strcmp(method, "push_u32") == 0) {
8149
0
            return eval_ok(value_unit());
8150
0
        }
8151
        /// @method Buffer.read_u8(idx: Int) -> Int
8152
        /// @category Buffer Methods
8153
        /// Read a single byte at the given index.
8154
        /// @example buf.read_u8(0)
8155
6
        if (strcmp(method, "read_u8") == 0) {
8156
0
            if (arg_count != 1 || args[0].type != VAL_INT) return eval_err(strdup("Buffer.read_u8() expects 1 Int argument"));
8157
0
            size_t i = (size_t)args[0].as.int_val;
8158
0
            if (i >= obj.as.buffer.len) return eval_err(strdup("Buffer.read_u8: index out of bounds"));
8159
0
            return eval_ok(value_int(obj.as.buffer.data[i]));
8160
0
        }
8161
        /// @method Buffer.write_u8(idx: Int, val: Int) -> Unit
8162
        /// @category Buffer Methods
8163
        /// Write a single byte at the given index.
8164
        /// @example buf.write_u8(0, 42)
8165
6
        if (strcmp(method, "write_u8") == 0) {
8166
0
            return eval_ok(value_unit());
8167
0
        }
8168
        /// @method Buffer.read_u16(idx: Int) -> Int
8169
        /// @category Buffer Methods
8170
        /// Read a 16-bit value (little-endian) at the given index.
8171
        /// @example buf.read_u16(0)
8172
6
        if (strcmp(method, "read_u16") == 0) {
8173
1
            if (arg_count != 1 || args[0].type != VAL_INT) return eval_err(strdup("Buffer.read_u16() expects 1 Int argument"));
8174
1
            size_t i = (size_t)args[0].as.int_val;
8175
1
            if (i + 2 > obj.as.buffer.len) return eval_err(strdup("Buffer.read_u16: index out of bounds"));
8176
1
            uint16_t v = (uint16_t)(obj.as.buffer.data[i] | (obj.as.buffer.data[i+1] << 8));
8177
1
            return eval_ok(value_int(v));
8178
1
        }
8179
        /// @method Buffer.write_u16(idx: Int, val: Int) -> Unit
8180
        /// @category Buffer Methods
8181
        /// Write a 16-bit value (little-endian) at the given index.
8182
        /// @example buf.write_u16(0, 0x1234)
8183
5
        if (strcmp(method, "write_u16") == 0) {
8184
0
            return eval_ok(value_unit());
8185
0
        }
8186
        /// @method Buffer.read_u32(idx: Int) -> Int
8187
        /// @category Buffer Methods
8188
        /// Read a 32-bit value (little-endian) at the given index.
8189
        /// @example buf.read_u32(0)
8190
5
        if (strcmp(method, "read_u32") == 0) {
8191
1
            if (arg_count != 1 || args[0].type != VAL_INT) return eval_err(strdup("Buffer.read_u32() expects 1 Int argument"));
8192
1
            size_t i = (size_t)args[0].as.int_val;
8193
1
            if (i + 4 > obj.as.buffer.len) return eval_err(strdup("Buffer.read_u32: index out of bounds"));
8194
1
            uint32_t v = (uint32_t)obj.as.buffer.data[i]
8195
1
                       | ((uint32_t)obj.as.buffer.data[i+1] << 8)
8196
1
                       | ((uint32_t)obj.as.buffer.data[i+2] << 16)
8197
1
                       | ((uint32_t)obj.as.buffer.data[i+3] << 24);
8198
1
            return eval_ok(value_int((int64_t)v));
8199
1
        }
8200
        /// @method Buffer.write_u32(idx: Int, val: Int) -> Unit
8201
        /// @category Buffer Methods
8202
        /// Write a 32-bit value (little-endian) at the given index.
8203
        /// @example buf.write_u32(0, 0x12345678)
8204
4
        if (strcmp(method, "write_u32") == 0) {
8205
0
            return eval_ok(value_unit());
8206
0
        }
8207
        /// @method Buffer.slice(start: Int, end: Int) -> Buffer
8208
        /// @category Buffer Methods
8209
        /// Return a new buffer containing bytes from start (inclusive) to end (exclusive).
8210
        /// @example buf.slice(0, 4)
8211
4
        if (strcmp(method, "slice") == 0) {
8212
1
            if (arg_count != 2) return eval_err(strdup("Buffer.slice() expects 2 arguments"));
8213
1
            if (args[0].type != VAL_INT || args[1].type != VAL_INT) return eval_err(strdup("Buffer.slice() expects Int arguments"));
8214
1
            int64_t s = args[0].as.int_val, e = args[1].as.int_val;
8215
1
            if (s < 0) s = 0;
8216
1
            if (e > (int64_t)obj.as.buffer.len) e = (int64_t)obj.as.buffer.len;
8217
1
            if (s >= e) return eval_ok(value_buffer(NULL, 0));
8218
1
            return eval_ok(value_buffer(obj.as.buffer.data + s, (size_t)(e - s)));
8219
1
        }
8220
        /// @method Buffer.clear() -> Unit
8221
        /// @category Buffer Methods
8222
        /// Set the buffer length to 0 (capacity unchanged).
8223
        /// @example buf.clear()
8224
3
        if (strcmp(method, "clear") == 0) {
8225
0
            return eval_ok(value_unit());
8226
0
        }
8227
        /// @method Buffer.fill(byte: Int) -> Unit
8228
        /// @category Buffer Methods
8229
        /// Fill all bytes in the buffer with the given value.
8230
        /// @example buf.fill(0)
8231
3
        if (strcmp(method, "fill") == 0) {
8232
0
            return eval_ok(value_unit());
8233
0
        }
8234
        /// @method Buffer.resize(new_len: Int) -> Unit
8235
        /// @category Buffer Methods
8236
        /// Change the buffer length. New bytes are zero-filled.
8237
        /// @example buf.resize(32)
8238
3
        if (strcmp(method, "resize") == 0) {
8239
0
            return eval_ok(value_unit());
8240
0
        }
8241
        /// @method Buffer.to_string() -> String
8242
        /// @category Buffer Methods
8243
        /// Interpret the buffer contents as a UTF-8 string.
8244
        /// @example Buffer::from_string("hi").to_string()  // "hi"
8245
3
        if (strcmp(method, "to_string") == 0) {
8246
1
            if (arg_count != 0) return eval_err(strdup(".to_string() takes no arguments"));
8247
1
            char *s = malloc(obj.as.buffer.len + 1);
8248
1
            memcpy(s, obj.as.buffer.data, obj.as.buffer.len);
8249
1
            s[obj.as.buffer.len] = '\0';
8250
1
            return eval_ok(value_string_owned(s));
8251
1
        }
8252
        /// @method Buffer.to_array() -> Array
8253
        /// @category Buffer Methods
8254
        /// Convert the buffer to an array of integers (0-255).
8255
        /// @example buf.to_array()
8256
2
        if (strcmp(method, "to_array") == 0) {
8257
1
            if (arg_count != 0) return eval_err(strdup(".to_array() takes no arguments"));
8258
1
            size_t blen = obj.as.buffer.len;
8259
1
            LatValue *elems = malloc((blen > 0 ? blen : 1) * sizeof(LatValue));
8260
4
            for (size_t i = 0; i < blen; i++)
8261
3
                elems[i] = value_int(obj.as.buffer.data[i]);
8262
1
            LatValue arr = value_array(elems, blen);
8263
1
            free(elems);
8264
1
            return eval_ok(arr);
8265
1
        }
8266
        /// @method Buffer.to_hex() -> String
8267
        /// @category Buffer Methods
8268
        /// Convert the buffer contents to a hexadecimal string.
8269
        /// @example Buffer::from([0x48, 0x69]).to_hex()  // "4869"
8270
1
        if (strcmp(method, "to_hex") == 0) {
8271
1
            if (arg_count != 0) return eval_err(strdup(".to_hex() takes no arguments"));
8272
1
            size_t blen = obj.as.buffer.len;
8273
1
            char *hex = malloc(blen * 2 + 1);
8274
4
            for (size_t i = 0; i < blen; i++)
8275
3
                snprintf(hex + i * 2, 3, "%02x", obj.as.buffer.data[i]);
8276
1
            hex[blen * 2] = '\0';
8277
1
            return eval_ok(value_string_owned(hex));
8278
1
        }
8279
0
        char *berr2 = NULL;
8280
0
        (void)asprintf(&berr2, "Buffer has no method '%s'", method);
8281
0
        return eval_err(berr2);
8282
1
    }
8283
8284
    /// @method Array.push(val: Any) -> Unit
8285
    /// @category Array Methods
8286
    /// Append a value to the end of the array (mutates in place).
8287
    /// @example arr.push(42)
8288
1.66k
    if (strcmp(method, "push") == 0) {
8289
0
        if (obj.type != VAL_ARRAY)
8290
0
            return eval_err(strdup(".push() is not defined on non-array"));
8291
0
        if (value_is_crystal(&obj))
8292
0
            return eval_err(strdup("cannot push to a crystal array"));
8293
0
        if (arg_count != 1)
8294
0
            return eval_err(strdup(".push() expects exactly 1 argument"));
8295
        /* We need to push to the actual array in the env. The obj here is already freed copy.
8296
           For method calls that mutate, we'd need the original. Instead, let's not support
8297
           standalone push -- in the Rust version it uses Rc<RefCell> for shared mutation.
8298
           For C, we'll handle array.push by finding the original binding. */
8299
        /* This is a simplification - we handle push on the env directly */
8300
0
        return eval_ok(value_unit());
8301
0
    }
8302
    /// @method Array.len() -> Int
8303
    /// @method String.len() -> Int
8304
    /// @method Map.len() -> Int
8305
    /// @category Array Methods
8306
    /// Return the number of elements or characters.
8307
    /// @example [1, 2, 3].len()  // 3
8308
1.66k
    if (strcmp(method, "len") == 0) {
8309
41
        if (obj.type == VAL_ARRAY) return eval_ok(value_int((int64_t)obj.as.array.len));
8310
20
        if (obj.type == VAL_STR) return eval_ok(value_int((int64_t)strlen(obj.as.str_val)));
8311
11
        if (obj.type == VAL_MAP) return eval_ok(value_int((int64_t)lat_map_len(obj.as.map.map)));
8312
5
        if (obj.type == VAL_TUPLE) return eval_ok(value_int((int64_t)obj.as.tuple.len));
8313
4
        if (obj.type == VAL_BUFFER) return eval_ok(value_int((int64_t)obj.as.buffer.len));
8314
4
        if (obj.type == VAL_REF) {
8315
3
            LatValue *inner = &obj.as.ref.ref->value;
8316
3
            if (inner->type == VAL_ARRAY) return eval_ok(value_int((int64_t)inner->as.array.len));
8317
1
            if (inner->type == VAL_STR) return eval_ok(value_int((int64_t)strlen(inner->as.str_val)));
8318
1
            if (inner->type == VAL_MAP) return eval_ok(value_int((int64_t)lat_map_len(inner->as.map.map)));
8319
0
            if (inner->type == VAL_BUFFER) return eval_ok(value_int((int64_t)inner->as.buffer.len));
8320
0
        }
8321
1
        return eval_err(strdup(".len() is not defined on this type"));
8322
4
    }
8323
    /// @method Array.map(fn: Closure) -> Array
8324
    /// @category Array Methods
8325
    /// Apply a function to each element, returning a new array of results.
8326
    /// @example [1, 2, 3].map(|x| { x * 2 })  // [2, 4, 6]
8327
1.62k
    if (strcmp(method, "map") == 0 && obj.type == VAL_ARRAY) {
8328
7
        if (arg_count != 1) return eval_err(strdup(".map() expects exactly 1 argument (a closure)"));
8329
7
        if (args[0].type != VAL_CLOSURE) return eval_err(strdup(".map() argument must be a closure"));
8330
8331
7
        size_t n = obj.as.array.len;
8332
7
        LatValue *results = malloc(n * sizeof(LatValue));
8333
139
        for (size_t i = 0; i < n; i++) {
8334
132
            LatValue elem = value_deep_clone(&obj.as.array.elems[i]);
8335
132
            EvalResult r = call_closure(ev,
8336
132
                args[0].as.closure.param_names,
8337
132
                args[0].as.closure.param_count,
8338
132
                args[0].as.closure.body,
8339
132
                args[0].as.closure.captured_env,
8340
132
                &elem, 1,
8341
132
                args[0].as.closure.default_values, args[0].as.closure.has_variadic);
8342
132
            if (!IS_OK(r)) {
8343
0
                GC_POP_N(ev, i);  /* accumulated results */
8344
0
                for (size_t j = 0; j < i; j++) value_free(&results[j]);
8345
0
                free(results);
8346
0
                return r;
8347
0
            }
8348
132
            results[i] = r.value;
8349
132
            GC_PUSH(ev, &results[i]);
8350
132
        }
8351
7
        GC_POP_N(ev, n);  /* accumulated results */
8352
7
        LatValue arr = value_array(results, n);
8353
7
        free(results);
8354
7
        return eval_ok(arr);
8355
7
    }
8356
    /// @method Array.join(sep?: String) -> String
8357
    /// @category Array Methods
8358
    /// Join array elements into a string with an optional separator.
8359
    /// @example ["a", "b", "c"].join(", ")  // "a, b, c"
8360
1.61k
    if (strcmp(method, "join") == 0) {
8361
1
        if (obj.type != VAL_ARRAY) return eval_err(strdup(".join() is not defined on non-array"));
8362
1
        const char *sep = "";
8363
1
        if (arg_count > 0) {
8364
1
            if (args[0].type != VAL_STR) return eval_err(strdup(".join() separator must be a string"));
8365
1
            sep = args[0].as.str_val;
8366
1
        }
8367
1
        size_t total = 0;
8368
1
        char **parts = malloc(obj.as.array.len * sizeof(char *));
8369
5
        for (size_t i = 0; i < obj.as.array.len; i++) {
8370
4
            parts[i] = value_display(&obj.as.array.elems[i]);
8371
4
            total += strlen(parts[i]);
8372
4
        }
8373
1
        size_t sep_len = strlen(sep);
8374
1
        if (obj.as.array.len > 0) total += sep_len * (obj.as.array.len - 1);
8375
1
        char *result = malloc(total + 1);
8376
1
        size_t pos = 0;
8377
5
        for (size_t i = 0; i < obj.as.array.len; i++) {
8378
4
            if (i > 0) { memcpy(result + pos, sep, sep_len); pos += sep_len; }
8379
4
            size_t pl = strlen(parts[i]);
8380
4
            memcpy(result + pos, parts[i], pl);
8381
4
            pos += pl;
8382
4
            free(parts[i]);
8383
4
        }
8384
1
        result[pos] = '\0';
8385
1
        free(parts);
8386
1
        return eval_ok(value_string_owned(result));
8387
1
    }
8388
    /// @method Array.filter(fn: Closure) -> Array
8389
    /// @category Array Methods
8390
    /// Return a new array containing only elements for which fn returns true.
8391
    /// @example [1, 2, 3, 4].filter(|x| { x > 2 })  // [3, 4]
8392
    /* ── Array: filter ── */
8393
1.61k
    if (strcmp(method, "filter") == 0 && obj.type == VAL_ARRAY) {
8394
3
        if (arg_count != 1 || args[0].type != VAL_CLOSURE) return eval_err(strdup(".filter() expects 1 closure argument"));
8395
3
        size_t n = obj.as.array.len;
8396
3
        LatValue *results = malloc((n > 0 ? n : 1) * sizeof(LatValue));
8397
3
        size_t rcount = 0;
8398
75
        for (size_t i = 0; i < n; i++) {
8399
72
            LatValue elem = value_deep_clone(&obj.as.array.elems[i]);
8400
72
            EvalResult r = call_closure(ev,
8401
72
                args[0].as.closure.param_names,
8402
72
                args[0].as.closure.param_count,
8403
72
                args[0].as.closure.body,
8404
72
                args[0].as.closure.captured_env,
8405
72
                &elem, 1,
8406
72
                args[0].as.closure.default_values, args[0].as.closure.has_variadic);
8407
72
            if (!IS_OK(r)) {
8408
0
                GC_POP_N(ev, rcount);  /* accumulated results */
8409
0
                for (size_t j = 0; j < rcount; j++) value_free(&results[j]);
8410
0
                free(results);
8411
0
                return r;
8412
0
            }
8413
72
            if (value_is_truthy(&r.value)) {
8414
33
                results[rcount++] = value_deep_clone(&obj.as.array.elems[i]);
8415
33
                GC_PUSH(ev, &results[rcount - 1]);
8416
33
            }
8417
72
            value_free(&r.value);
8418
72
        }
8419
3
        GC_POP_N(ev, rcount);  /* accumulated results */
8420
3
        LatValue arr = value_array(results, rcount);
8421
3
        free(results);
8422
3
        return eval_ok(arr);
8423
3
    }
8424
    /// @method Array.for_each(fn: Closure) -> Unit
8425
    /// @category Array Methods
8426
    /// Call a function for each element (for side effects).
8427
    /// @example [1, 2, 3].for_each(|x| { print(x) })
8428
    /* ── Array: for_each ── */
8429
1.61k
    if (strcmp(method, "for_each") == 0 && obj.type == VAL_ARRAY) {
8430
1
        if (arg_count != 1 || args[0].type != VAL_CLOSURE) return eval_err(strdup(".for_each() expects 1 closure argument"));
8431
4
        for (size_t i = 0; i < obj.as.array.len; i++) {
8432
3
            LatValue elem = value_deep_clone(&obj.as.array.elems[i]);
8433
3
            EvalResult r = call_closure(ev,
8434
3
                args[0].as.closure.param_names,
8435
3
                args[0].as.closure.param_count,
8436
3
                args[0].as.closure.body,
8437
3
                args[0].as.closure.captured_env,
8438
3
                &elem, 1,
8439
3
                args[0].as.closure.default_values, args[0].as.closure.has_variadic);
8440
3
            if (!IS_OK(r)) return r;
8441
3
            value_free(&r.value);
8442
3
        }
8443
1
        return eval_ok(value_unit());
8444
1
    }
8445
    /// @method Array.find(fn: Closure) -> Any|Unit
8446
    /// @category Array Methods
8447
    /// Return the first element for which fn returns true, or unit if not found.
8448
    /// @example [1, 2, 3].find(|x| { x > 1 })  // 2
8449
    /* ── Array: find ── */
8450
1.61k
    if (strcmp(method, "find") == 0) {
8451
2
        if (obj.type != VAL_ARRAY) return eval_err(strdup(".find() is not defined on non-array"));
8452
2
        if (arg_count != 1 || args[0].type != VAL_CLOSURE) return eval_err(strdup(".find() expects 1 closure argument"));
8453
8
        for (size_t i = 0; i < obj.as.array.len; i++) {
8454
7
            LatValue elem = value_deep_clone(&obj.as.array.elems[i]);
8455
7
            EvalResult r = call_closure(ev,
8456
7
                args[0].as.closure.param_names,
8457
7
                args[0].as.closure.param_count,
8458
7
                args[0].as.closure.body,
8459
7
                args[0].as.closure.captured_env,
8460
7
                &elem, 1,
8461
7
                args[0].as.closure.default_values, args[0].as.closure.has_variadic);
8462
7
            if (!IS_OK(r)) return r;
8463
7
            if (value_is_truthy(&r.value)) {
8464
1
                value_free(&r.value);
8465
1
                return eval_ok(value_deep_clone(&obj.as.array.elems[i]));
8466
1
            }
8467
6
            value_free(&r.value);
8468
6
        }
8469
1
        return eval_ok(value_unit());
8470
2
    }
8471
    /// @method Array.contains(val: Any) -> Bool
8472
    /// @category Array Methods
8473
    /// Check if the array contains a value.
8474
    /// @example [1, 2, 3].contains(2)  // true
8475
    /* ── Array: contains ── */
8476
1.60k
    if (strcmp(method, "contains") == 0 && obj.type == VAL_ARRAY) {
8477
8
        if (arg_count != 1) return eval_err(strdup(".contains() expects 1 argument"));
8478
2.61k
        for (size_t i = 0; i < obj.as.array.len; i++) {
8479
2.61k
            if (value_eq(&obj.as.array.elems[i], &args[0])) {
8480
6
                return eval_ok(value_bool(true));
8481
6
            }
8482
2.61k
        }
8483
2
        return eval_ok(value_bool(false));
8484
8
    }
8485
    /// @method Array.reverse() -> Array
8486
    /// @category Array Methods
8487
    /// Return a new array with elements in reverse order.
8488
    /// @example [1, 2, 3].reverse()  // [3, 2, 1]
8489
    /* ── Array: reverse ── */
8490
1.60k
    if (strcmp(method, "reverse") == 0 && obj.type == VAL_ARRAY) {
8491
1
        size_t n = obj.as.array.len;
8492
1
        LatValue *reversed = malloc((n > 0 ? n : 1) * sizeof(LatValue));
8493
4
        for (size_t i = 0; i < n; i++) {
8494
3
            reversed[i] = value_deep_clone(&obj.as.array.elems[n - 1 - i]);
8495
3
        }
8496
1
        LatValue arr = value_array(reversed, n);
8497
1
        free(reversed);
8498
1
        return eval_ok(arr);
8499
1
    }
8500
    /// @method Array.enumerate() -> Array
8501
    /// @category Array Methods
8502
    /// Return an array of [index, value] pairs.
8503
    /// @example ["a", "b"].enumerate()  // [[0, "a"], [1, "b"]]
8504
    /* ── Array: enumerate ── */
8505
1.60k
    if (strcmp(method, "enumerate") == 0) {
8506
1
        if (obj.type != VAL_ARRAY) return eval_err(strdup(".enumerate() is not defined on non-array"));
8507
1
        size_t n = obj.as.array.len;
8508
1
        LatValue *pairs = malloc((n > 0 ? n : 1) * sizeof(LatValue));
8509
4
        for (size_t i = 0; i < n; i++) {
8510
3
            LatValue pair_elems[2];
8511
3
            pair_elems[0] = value_int((int64_t)i);
8512
3
            pair_elems[1] = value_deep_clone(&obj.as.array.elems[i]);
8513
3
            pairs[i] = value_array(pair_elems, 2);
8514
3
        }
8515
1
        LatValue arr = value_array(pairs, n);
8516
1
        free(pairs);
8517
1
        return eval_ok(arr);
8518
1
    }
8519
    /// @method Array.sort() -> Array
8520
    /// @category Array Methods
8521
    /// Return a new sorted array (elements must be comparable).
8522
    /// @example [3, 1, 2].sort()  // [1, 2, 3]
8523
    /* ── Array: sort ── */
8524
1.59k
    if (strcmp(method, "sort") == 0 && obj.type == VAL_ARRAY) {
8525
5
        if (arg_count != 0) return eval_err(strdup(".sort() takes no arguments"));
8526
5
        char *sort_err = NULL;
8527
5
        LatValue sorted = array_sort(&obj, &sort_err);
8528
5
        if (sort_err) return eval_err(sort_err);
8529
4
        return eval_ok(sorted);
8530
5
    }
8531
    /// @method Array.flat() -> Array
8532
    /// @category Array Methods
8533
    /// Flatten one level of nested arrays.
8534
    /// @example [[1, 2], [3, 4]].flat()  // [1, 2, 3, 4]
8535
    /* ── Array: flat ── */
8536
1.59k
    if (strcmp(method, "flat") == 0 && obj.type == VAL_ARRAY) {
8537
4
        if (arg_count != 0) return eval_err(strdup(".flat() takes no arguments"));
8538
4
        return eval_ok(array_flat(&obj));
8539
4
    }
8540
    /// @method Array.reduce(fn: Closure, init: Any) -> Any
8541
    /// @category Array Methods
8542
    /// Reduce an array to a single value by applying fn(acc, elem) for each element.
8543
    /// @example [1, 2, 3].reduce(|a, b| { a + b }, 0)  // 6
8544
    /* ── Array: reduce ── */
8545
1.59k
    if (strcmp(method, "reduce") == 0 && obj.type == VAL_ARRAY) {
8546
4
        if (arg_count != 2) return eval_err(strdup(".reduce() expects 2 arguments (closure, initial_value)"));
8547
4
        if (args[0].type != VAL_CLOSURE) return eval_err(strdup(".reduce() first argument must be a closure"));
8548
4
        LatValue acc = value_deep_clone(&args[1]);
8549
4
        GC_PUSH(ev, &acc);
8550
14
        for (size_t i = 0; i < obj.as.array.len; i++) {
8551
10
            LatValue call_args[2];
8552
10
            call_args[0] = acc;
8553
10
            call_args[1] = value_deep_clone(&obj.as.array.elems[i]);
8554
10
            EvalResult r = call_closure(ev,
8555
10
                args[0].as.closure.param_names,
8556
10
                args[0].as.closure.param_count,
8557
10
                args[0].as.closure.body,
8558
10
                args[0].as.closure.captured_env,
8559
10
                call_args, 2,
8560
10
                args[0].as.closure.default_values, args[0].as.closure.has_variadic);
8561
10
            if (!IS_OK(r)) { GC_POP(ev); return r; }
8562
10
            acc = r.value;
8563
10
        }
8564
4
        GC_POP(ev);
8565
4
        return eval_ok(acc);
8566
4
    }
8567
    /// @method Array.slice(start: Int, end: Int) -> Array
8568
    /// @category Array Methods
8569
    /// Return a sub-array from start (inclusive) to end (exclusive).
8570
    /// @example [1, 2, 3, 4, 5].slice(1, 4)  // [2, 3, 4]
8571
    /* ── Array: slice ── */
8572
1.58k
    if (strcmp(method, "slice") == 0 && obj.type == VAL_ARRAY) {
8573
4
        if (arg_count != 2) return eval_err(strdup(".slice() expects 2 arguments (start, end)"));
8574
4
        if (args[0].type != VAL_INT || args[1].type != VAL_INT)
8575
0
            return eval_err(strdup(".slice() arguments must be integers"));
8576
4
        char *slice_err = NULL;
8577
4
        LatValue sliced = array_slice(&obj, args[0].as.int_val, args[1].as.int_val, &slice_err);
8578
4
        if (slice_err) return eval_err(slice_err);
8579
4
        return eval_ok(sliced);
8580
4
    }
8581
    /// @method Array.take(n: Int) -> Array
8582
    /// @category Array Methods
8583
    /// Return the first n elements of the array.
8584
    /// @example [1, 2, 3, 4].take(2)  // [1, 2]
8585
    /* ── Array: take ── */
8586
1.58k
    if (strcmp(method, "take") == 0 && obj.type == VAL_ARRAY) {
8587
3
        if (arg_count != 1 || args[0].type != VAL_INT)
8588
0
            return eval_err(strdup(".take() expects 1 integer argument"));
8589
3
        int64_t n = args[0].as.int_val;
8590
3
        if (n <= 0) {
8591
1
            return eval_ok(value_array(NULL, 0));
8592
1
        }
8593
2
        size_t take_count = (size_t)n;
8594
2
        if (take_count > obj.as.array.len) take_count = obj.as.array.len;
8595
2
        LatValue *elems = malloc((take_count > 0 ? take_count : 1) * sizeof(LatValue));
8596
7
        for (size_t i = 0; i < take_count; i++) {
8597
5
            elems[i] = value_deep_clone(&obj.as.array.elems[i]);
8598
5
        }
8599
2
        LatValue arr = value_array(elems, take_count);
8600
2
        free(elems);
8601
2
        return eval_ok(arr);
8602
3
    }
8603
    /// @method Array.drop(n: Int) -> Array
8604
    /// @category Array Methods
8605
    /// Return the array with the first n elements removed.
8606
    /// @example [1, 2, 3, 4].drop(2)  // [3, 4]
8607
    /* ── Array: drop ── */
8608
1.57k
    if (strcmp(method, "drop") == 0 && obj.type == VAL_ARRAY) {
8609
3
        if (arg_count != 1 || args[0].type != VAL_INT)
8610
0
            return eval_err(strdup(".drop() expects 1 integer argument"));
8611
3
        int64_t n = args[0].as.int_val;
8612
3
        if (n <= 0) {
8613
1
            LatValue *elems = malloc((obj.as.array.len > 0 ? obj.as.array.len : 1) * sizeof(LatValue));
8614
4
            for (size_t i = 0; i < obj.as.array.len; i++) {
8615
3
                elems[i] = value_deep_clone(&obj.as.array.elems[i]);
8616
3
            }
8617
1
            LatValue arr = value_array(elems, obj.as.array.len);
8618
1
            free(elems);
8619
1
            return eval_ok(arr);
8620
1
        }
8621
2
        size_t start = (size_t)n;
8622
2
        if (start >= obj.as.array.len) {
8623
1
            return eval_ok(value_array(NULL, 0));
8624
1
        }
8625
1
        size_t drop_count = obj.as.array.len - start;
8626
1
        LatValue *elems = malloc(drop_count * sizeof(LatValue));
8627
4
        for (size_t i = 0; i < drop_count; i++) {
8628
3
            elems[i] = value_deep_clone(&obj.as.array.elems[start + i]);
8629
3
        }
8630
1
        LatValue arr = value_array(elems, drop_count);
8631
1
        free(elems);
8632
1
        return eval_ok(arr);
8633
2
    }
8634
    /// @method Array.pop() -> Any
8635
    /// @category Array Methods
8636
    /// Remove and return the last element of the array.
8637
    /// @example [1, 2, 3].pop()  // 3
8638
    /* ── Array: pop ── */
8639
1.57k
    if (strcmp(method, "pop") == 0 && obj.type == VAL_ARRAY) {
8640
0
        if (arg_count != 0) return eval_err(strdup(".pop() takes no arguments"));
8641
0
        if (obj.as.array.len == 0) return eval_err(strdup("pop on empty array"));
8642
0
        LatValue removed = value_deep_clone(&obj.as.array.elems[obj.as.array.len - 1]);
8643
0
        return eval_ok(removed);
8644
0
    }
8645
    /// @method Array.index_of(val: Any) -> Int
8646
    /// @category Array Methods
8647
    /// Return the index of the first occurrence of val, or -1 if not found.
8648
    /// @example [10, 20, 30].index_of(20)  // 1
8649
    /* ── Array: index_of ── */
8650
1.57k
    if (strcmp(method, "index_of") == 0 && obj.type == VAL_ARRAY) {
8651
2
        if (arg_count != 1) return eval_err(strdup(".index_of() expects 1 argument"));
8652
6
        for (size_t i = 0; i < obj.as.array.len; i++) {
8653
5
            if (value_eq(&obj.as.array.elems[i], &args[0])) {
8654
1
                return eval_ok(value_int((int64_t)i));
8655
1
            }
8656
5
        }
8657
1
        return eval_ok(value_int(-1));
8658
2
    }
8659
    /// @method Array.any(fn: Closure) -> Bool
8660
    /// @category Array Methods
8661
    /// Return true if fn returns true for any element.
8662
    /// @example [1, 2, 3].any(|x| { x > 2 })  // true
8663
    /* ── Array: any ── */
8664
1.57k
    if (strcmp(method, "any") == 0 && obj.type == VAL_ARRAY) {
8665
2
        if (arg_count != 1 || args[0].type != VAL_CLOSURE)
8666
0
            return eval_err(strdup(".any() expects 1 closure argument"));
8667
7
        for (size_t i = 0; i < obj.as.array.len; i++) {
8668
6
            LatValue elem = value_deep_clone(&obj.as.array.elems[i]);
8669
6
            EvalResult r = call_closure(ev,
8670
6
                args[0].as.closure.param_names,
8671
6
                args[0].as.closure.param_count,
8672
6
                args[0].as.closure.body,
8673
6
                args[0].as.closure.captured_env,
8674
6
                &elem, 1,
8675
6
                args[0].as.closure.default_values, args[0].as.closure.has_variadic);
8676
6
            if (!IS_OK(r)) return r;
8677
6
            if (value_is_truthy(&r.value)) {
8678
1
                value_free(&r.value);
8679
1
                return eval_ok(value_bool(true));
8680
1
            }
8681
5
            value_free(&r.value);
8682
5
        }
8683
1
        return eval_ok(value_bool(false));
8684
2
    }
8685
    /// @method Array.all(fn: Closure) -> Bool
8686
    /// @category Array Methods
8687
    /// Return true if fn returns true for all elements.
8688
    /// @example [2, 4, 6].all(|x| { x % 2 == 0 })  // true
8689
    /* ── Array: all ── */
8690
1.57k
    if (strcmp(method, "all") == 0 && obj.type == VAL_ARRAY) {
8691
2
        if (arg_count != 1 || args[0].type != VAL_CLOSURE)
8692
0
            return eval_err(strdup(".all() expects 1 closure argument"));
8693
5
        for (size_t i = 0; i < obj.as.array.len; i++) {
8694
4
            LatValue elem = value_deep_clone(&obj.as.array.elems[i]);
8695
4
            EvalResult r = call_closure(ev,
8696
4
                args[0].as.closure.param_names,
8697
4
                args[0].as.closure.param_count,
8698
4
                args[0].as.closure.body,
8699
4
                args[0].as.closure.captured_env,
8700
4
                &elem, 1,
8701
4
                args[0].as.closure.default_values, args[0].as.closure.has_variadic);
8702
4
            if (!IS_OK(r)) return r;
8703
4
            if (!value_is_truthy(&r.value)) {
8704
1
                value_free(&r.value);
8705
1
                return eval_ok(value_bool(false));
8706
1
            }
8707
3
            value_free(&r.value);
8708
3
        }
8709
1
        return eval_ok(value_bool(true));
8710
2
    }
8711
    /// @method Array.zip(other: Array) -> Array
8712
    /// @category Array Methods
8713
    /// Combine two arrays into an array of [a, b] pairs.
8714
    /// @example [1, 2].zip(["a", "b"])  // [[1, "a"], [2, "b"]]
8715
    /* ── Array: zip ── */
8716
1.57k
    if (strcmp(method, "zip") == 0 && obj.type == VAL_ARRAY) {
8717
1
        if (arg_count != 1) return eval_err(strdup(".zip() expects 1 argument"));
8718
1
        if (args[0].type != VAL_ARRAY) return eval_err(strdup(".zip() argument must be an array"));
8719
1
        size_t n = obj.as.array.len < args[0].as.array.len
8720
1
                 ? obj.as.array.len : args[0].as.array.len;
8721
1
        LatValue *pairs = malloc((n > 0 ? n : 1) * sizeof(LatValue));
8722
3
        for (size_t i = 0; i < n; i++) {
8723
2
            LatValue pair_elems[2];
8724
2
            pair_elems[0] = value_deep_clone(&obj.as.array.elems[i]);
8725
2
            pair_elems[1] = value_deep_clone(&args[0].as.array.elems[i]);
8726
2
            pairs[i] = value_array(pair_elems, 2);
8727
2
        }
8728
1
        LatValue arr = value_array(pairs, n);
8729
1
        free(pairs);
8730
1
        return eval_ok(arr);
8731
1
    }
8732
    /// @method Array.unique() -> Array
8733
    /// @category Array Methods
8734
    /// Return a new array with duplicate elements removed.
8735
    /// @example [1, 2, 2, 3, 1].unique()  // [1, 2, 3]
8736
    /* ── Array: unique ── */
8737
1.56k
    if (strcmp(method, "unique") == 0 && obj.type == VAL_ARRAY) {
8738
1
        if (arg_count != 0) return eval_err(strdup(".unique() takes no arguments"));
8739
1
        size_t n = obj.as.array.len;
8740
1
        LatValue *results = malloc((n > 0 ? n : 1) * sizeof(LatValue));
8741
1
        size_t rcount = 0;
8742
7
        for (size_t i = 0; i < n; i++) {
8743
6
            bool found = false;
8744
13
            for (size_t j = 0; j < rcount; j++) {
8745
9
                if (value_eq(&obj.as.array.elems[i], &results[j])) {
8746
2
                    found = true;
8747
2
                    break;
8748
2
                }
8749
9
            }
8750
6
            if (!found) {
8751
4
                results[rcount++] = value_deep_clone(&obj.as.array.elems[i]);
8752
4
            }
8753
6
        }
8754
1
        LatValue arr = value_array(results, rcount);
8755
1
        free(results);
8756
1
        return eval_ok(arr);
8757
1
    }
8758
    /// @method Array.insert(index: Int, val: Any) -> Unit
8759
    /// @category Array Methods
8760
    /// Insert a value at the given index (mutates in place).
8761
    /// @example arr.insert(1, "x")
8762
    /* ── Array: insert ── */
8763
1.56k
    if (strcmp(method, "insert") == 0 && obj.type == VAL_ARRAY) {
8764
0
        if (arg_count != 2) return eval_err(strdup(".insert() expects 2 arguments (index, value)"));
8765
0
        if (args[0].type != VAL_INT) return eval_err(strdup(".insert() index must be an integer"));
8766
0
        int64_t idx = args[0].as.int_val;
8767
0
        size_t len = obj.as.array.len;
8768
0
        if (idx < 0 || (size_t)idx > len) {
8769
0
            char *err = NULL;
8770
0
            (void)asprintf(&err, ".insert() index %lld out of bounds (length %zu)",
8771
0
                           (long long)idx, len);
8772
0
            return eval_err(err);
8773
0
        }
8774
0
        return eval_ok(value_unit());
8775
0
    }
8776
    /// @method Array.remove_at(index: Int) -> Any
8777
    /// @category Array Methods
8778
    /// Remove and return the element at the given index.
8779
    /// @example [1, 2, 3].remove_at(1)  // 2
8780
    /* ── Array: remove_at ── */
8781
1.56k
    if (strcmp(method, "remove_at") == 0 && obj.type == VAL_ARRAY) {
8782
0
        if (arg_count != 1) return eval_err(strdup(".remove_at() expects 1 argument (index)"));
8783
0
        if (args[0].type != VAL_INT) return eval_err(strdup(".remove_at() index must be an integer"));
8784
0
        int64_t idx = args[0].as.int_val;
8785
0
        size_t len = obj.as.array.len;
8786
0
        if (idx < 0 || (size_t)idx >= len) {
8787
0
            char *err = NULL;
8788
0
            (void)asprintf(&err, ".remove_at() index %lld out of bounds (length %zu)",
8789
0
                           (long long)idx, len);
8790
0
            return eval_err(err);
8791
0
        }
8792
0
        LatValue removed = value_deep_clone(&obj.as.array.elems[(size_t)idx]);
8793
0
        return eval_ok(removed);
8794
0
    }
8795
    /// @method Array.sort_by(cmp: Closure) -> Array
8796
    /// @category Array Methods
8797
    /// Sort using a custom comparator that returns a negative, zero, or positive Int.
8798
    /// @example ["bb", "a", "ccc"].sort_by(|a, b| { len(a) - len(b) })  // ["a", "bb", "ccc"]
8799
    /* ── Array: sort_by ── */
8800
1.56k
    if (strcmp(method, "sort_by") == 0 && obj.type == VAL_ARRAY) {
8801
2
        if (arg_count != 1 || args[0].type != VAL_CLOSURE)
8802
0
            return eval_err(strdup(".sort_by() expects 1 closure argument"));
8803
2
        size_t n = obj.as.array.len;
8804
        /* Deep-clone elements into a working buffer */
8805
2
        LatValue *buf = malloc((n > 0 ? n : 1) * sizeof(LatValue));
8806
12
        for (size_t i = 0; i < n; i++) {
8807
10
            buf[i] = value_deep_clone(&obj.as.array.elems[i]);
8808
10
        }
8809
        /* Insertion sort using the closure as comparator */
8810
10
        for (size_t i = 1; i < n; i++) {
8811
8
            LatValue key = buf[i];
8812
8
            size_t j = i;
8813
17
            while (j > 0) {
8814
14
                LatValue call_args[2];
8815
14
                call_args[0] = value_deep_clone(&key);
8816
14
                call_args[1] = value_deep_clone(&buf[j - 1]);
8817
14
                EvalResult r = call_closure(ev,
8818
14
                    args[0].as.closure.param_names,
8819
14
                    args[0].as.closure.param_count,
8820
14
                    args[0].as.closure.body,
8821
14
                    args[0].as.closure.captured_env,
8822
14
                    call_args, 2,
8823
14
                    args[0].as.closure.default_values, args[0].as.closure.has_variadic);
8824
14
                if (!IS_OK(r)) {
8825
0
                    for (size_t k = 0; k < j; k++) value_free(&buf[k]);
8826
0
                    value_free(&key);
8827
0
                    for (size_t k = j + 1; k < n; k++) value_free(&buf[k]);
8828
0
                    free(buf);
8829
0
                    return r;
8830
0
                }
8831
14
                if (r.value.type != VAL_INT) {
8832
0
                    value_free(&r.value);
8833
0
                    for (size_t k = 0; k < j; k++) value_free(&buf[k]);
8834
0
                    value_free(&key);
8835
0
                    for (size_t k = j + 1; k < n; k++) value_free(&buf[k]);
8836
0
                    free(buf);
8837
0
                    return eval_err(strdup(".sort_by() comparator must return an Int"));
8838
0
                }
8839
14
                int64_t cmp = r.value.as.int_val;
8840
14
                value_free(&r.value);
8841
14
                if (cmp >= 0) break;
8842
9
                buf[j] = buf[j - 1];
8843
9
                j--;
8844
9
            }
8845
8
            buf[j] = key;
8846
8
        }
8847
2
        LatValue arr = value_array(buf, n);
8848
2
        free(buf);
8849
2
        return eval_ok(arr);
8850
2
    }
8851
    /// @method Array.flat_map(fn: Closure) -> Array
8852
    /// @category Array Methods
8853
    /// Map each element to an array, then flatten one level.
8854
    /// @example [1, 2].flat_map(|x| { [x, x * 10] })  // [1, 10, 2, 20]
8855
    /* ── Array: flat_map ── */
8856
1.56k
    if (strcmp(method, "flat_map") == 0 && obj.type == VAL_ARRAY) {
8857
3
        if (arg_count != 1 || args[0].type != VAL_CLOSURE)
8858
0
            return eval_err(strdup(".flat_map() expects 1 closure argument"));
8859
3
        size_t n = obj.as.array.len;
8860
3
        LatValue *mapped = malloc((n > 0 ? n : 1) * sizeof(LatValue));
8861
9
        for (size_t i = 0; i < n; i++) {
8862
6
            LatValue elem = value_deep_clone(&obj.as.array.elems[i]);
8863
6
            EvalResult r = call_closure(ev,
8864
6
                args[0].as.closure.param_names,
8865
6
                args[0].as.closure.param_count,
8866
6
                args[0].as.closure.body,
8867
6
                args[0].as.closure.captured_env,
8868
6
                &elem, 1,
8869
6
                args[0].as.closure.default_values, args[0].as.closure.has_variadic);
8870
6
            if (!IS_OK(r)) {
8871
0
                for (size_t j = 0; j < i; j++) value_free(&mapped[j]);
8872
0
                free(mapped);
8873
0
                return r;
8874
0
            }
8875
6
            mapped[i] = r.value;
8876
6
        }
8877
3
        size_t total = 0;
8878
9
        for (size_t i = 0; i < n; i++) {
8879
6
            if (mapped[i].type == VAL_ARRAY)
8880
3
                total += mapped[i].as.array.len;
8881
3
            else
8882
3
                total += 1;
8883
6
        }
8884
3
        LatValue *fm_buf = malloc((total > 0 ? total : 1) * sizeof(LatValue));
8885
3
        size_t fm_pos = 0;
8886
9
        for (size_t i = 0; i < n; i++) {
8887
6
            if (mapped[i].type == VAL_ARRAY) {
8888
9
                for (size_t j = 0; j < mapped[i].as.array.len; j++) {
8889
6
                    fm_buf[fm_pos++] = value_deep_clone(&mapped[i].as.array.elems[j]);
8890
6
                }
8891
3
            } else {
8892
3
                fm_buf[fm_pos++] = value_deep_clone(&mapped[i]);
8893
3
            }
8894
6
        }
8895
9
        for (size_t i = 0; i < n; i++) value_free(&mapped[i]);
8896
3
        free(mapped);
8897
3
        LatValue fm_arr = value_array(fm_buf, fm_pos);
8898
3
        free(fm_buf);
8899
3
        return eval_ok(fm_arr);
8900
3
    }
8901
    /// @method Array.chunk(size: Int) -> Array
8902
    /// @category Array Methods
8903
    /// Split the array into sub-arrays of the given size.
8904
    /// @example [1, 2, 3, 4, 5].chunk(2)  // [[1, 2], [3, 4], [5]]
8905
    /* ── Array: chunk ── */
8906
1.56k
    if (strcmp(method, "chunk") == 0 && obj.type == VAL_ARRAY) {
8907
5
        if (arg_count != 1 || args[0].type != VAL_INT)
8908
0
            return eval_err(strdup(".chunk() expects 1 integer argument"));
8909
5
        int64_t chunk_size = args[0].as.int_val;
8910
5
        if (chunk_size <= 0)
8911
0
            return eval_err(strdup(".chunk() size must be positive"));
8912
5
        size_t n = obj.as.array.len;
8913
5
        size_t num_chunks = (n > 0) ? (n + (size_t)chunk_size - 1) / (size_t)chunk_size : 0;
8914
5
        LatValue *chunks = malloc((num_chunks > 0 ? num_chunks : 1) * sizeof(LatValue));
8915
14
        for (size_t ci = 0; ci < num_chunks; ci++) {
8916
9
            size_t cstart = ci * (size_t)chunk_size;
8917
9
            size_t cend = cstart + (size_t)chunk_size;
8918
9
            if (cend > n) cend = n;
8919
9
            size_t clen = cend - cstart;
8920
9
            LatValue *celems = malloc(clen * sizeof(LatValue));
8921
25
            for (size_t j = 0; j < clen; j++) {
8922
16
                celems[j] = value_deep_clone(&obj.as.array.elems[cstart + j]);
8923
16
            }
8924
9
            chunks[ci] = value_array(celems, clen);
8925
9
            free(celems);
8926
9
        }
8927
5
        LatValue chunk_arr = value_array(chunks, num_chunks);
8928
5
        free(chunks);
8929
5
        return eval_ok(chunk_arr);
8930
5
    }
8931
    /// @method Array.group_by(fn: Closure) -> Map
8932
    /// @category Array Methods
8933
    /// Group elements by the result of fn, returning a map of key to arrays.
8934
    /// @example [1, 2, 3, 4].group_by(|x| { x % 2 })  // {0: [2, 4], 1: [1, 3]}
8935
    /* ── Array: group_by ── */
8936
1.55k
    if (strcmp(method, "group_by") == 0 && obj.type == VAL_ARRAY) {
8937
1
        if (arg_count != 1 || args[0].type != VAL_CLOSURE)
8938
0
            return eval_err(strdup(".group_by() expects 1 closure argument"));
8939
1
        LatValue grp_map = value_map_new();
8940
6
        for (size_t i = 0; i < obj.as.array.len; i++) {
8941
5
            LatValue elem = value_deep_clone(&obj.as.array.elems[i]);
8942
5
            EvalResult r = call_closure(ev,
8943
5
                args[0].as.closure.param_names,
8944
5
                args[0].as.closure.param_count,
8945
5
                args[0].as.closure.body,
8946
5
                args[0].as.closure.captured_env,
8947
5
                &elem, 1,
8948
5
                args[0].as.closure.default_values, args[0].as.closure.has_variadic);
8949
5
            if (!IS_OK(r)) {
8950
0
                value_free(&grp_map);
8951
0
                return r;
8952
0
            }
8953
5
            char *grp_key = value_display(&r.value);
8954
5
            value_free(&r.value);
8955
5
            LatValue *existing = (LatValue *)lat_map_get(grp_map.as.map.map, grp_key);
8956
5
            if (existing) {
8957
3
                size_t old_len = existing->as.array.len;
8958
3
                LatValue *new_elems = malloc((old_len + 1) * sizeof(LatValue));
8959
7
                for (size_t j = 0; j < old_len; j++) {
8960
4
                    new_elems[j] = value_deep_clone(&existing->as.array.elems[j]);
8961
4
                }
8962
3
                new_elems[old_len] = value_deep_clone(&obj.as.array.elems[i]);
8963
3
                LatValue new_grp_arr = value_array(new_elems, old_len + 1);
8964
3
                free(new_elems);
8965
3
                lat_map_set(grp_map.as.map.map, grp_key, &new_grp_arr);
8966
3
            } else {
8967
2
                LatValue cloned = value_deep_clone(&obj.as.array.elems[i]);
8968
2
                LatValue new_grp_arr = value_array(&cloned, 1);
8969
2
                lat_map_set(grp_map.as.map.map, grp_key, &new_grp_arr);
8970
2
            }
8971
5
            free(grp_key);
8972
5
        }
8973
1
        return eval_ok(grp_map);
8974
1
    }
8975
    /// @method Array.sum() -> Int|Float
8976
    /// @category Array Methods
8977
    /// Return the sum of all numeric elements.
8978
    /// @example [1, 2, 3].sum()  // 6
8979
    /* ── Array: sum ── */
8980
1.55k
    if (strcmp(method, "sum") == 0 && obj.type == VAL_ARRAY) {
8981
3
        if (arg_count != 0) return eval_err(strdup(".sum() takes no arguments"));
8982
3
        bool sum_has_float = false;
8983
3
        int64_t isum = 0;
8984
3
        double fsum = 0.0;
8985
11
        for (size_t i = 0; i < obj.as.array.len; i++) {
8986
8
            if (obj.as.array.elems[i].type == VAL_INT) {
8987
5
                isum += obj.as.array.elems[i].as.int_val;
8988
5
                fsum += (double)obj.as.array.elems[i].as.int_val;
8989
5
            } else if (obj.as.array.elems[i].type == VAL_FLOAT) {
8990
3
                sum_has_float = true;
8991
3
                fsum += obj.as.array.elems[i].as.float_val;
8992
3
            } else {
8993
0
                return eval_err(strdup(".sum() requires all elements to be numeric"));
8994
0
            }
8995
8
        }
8996
3
        if (sum_has_float) return eval_ok(value_float(fsum));
8997
2
        return eval_ok(value_int(isum));
8998
3
    }
8999
    /// @method Array.min() -> Int|Float
9000
    /// @category Array Methods
9001
    /// Return the minimum element (all elements must be numeric).
9002
    /// @example [3, 1, 2].min()  // 1
9003
    /* ── Array: min ── */
9004
1.55k
    if (strcmp(method, "min") == 0 && obj.type == VAL_ARRAY) {
9005
3
        if (arg_count != 0) return eval_err(strdup(".min() takes no arguments"));
9006
3
        if (obj.as.array.len == 0) return eval_err(strdup(".min() on empty array"));
9007
3
        bool min_has_float = false;
9008
10
        for (size_t i = 0; i < obj.as.array.len; i++) {
9009
8
            if (obj.as.array.elems[i].type == VAL_FLOAT) min_has_float = true;
9010
5
            else if (obj.as.array.elems[i].type != VAL_INT)
9011
0
                return eval_err(strdup(".min() requires all elements to be numeric"));
9012
8
        }
9013
2
        if (min_has_float) {
9014
1
            double fmin = (obj.as.array.elems[0].type == VAL_FLOAT)
9015
1
                ? obj.as.array.elems[0].as.float_val
9016
1
                : (double)obj.as.array.elems[0].as.int_val;
9017
3
            for (size_t i = 1; i < obj.as.array.len; i++) {
9018
2
                double v = (obj.as.array.elems[i].type == VAL_FLOAT)
9019
2
                    ? obj.as.array.elems[i].as.float_val
9020
2
                    : (double)obj.as.array.elems[i].as.int_val;
9021
2
                if (v < fmin) fmin = v;
9022
2
            }
9023
1
            return eval_ok(value_float(fmin));
9024
1
        }
9025
1
        int64_t imin = obj.as.array.elems[0].as.int_val;
9026
5
        for (size_t i = 1; i < obj.as.array.len; i++) {
9027
4
            if (obj.as.array.elems[i].as.int_val < imin)
9028
1
                imin = obj.as.array.elems[i].as.int_val;
9029
4
        }
9030
1
        return eval_ok(value_int(imin));
9031
2
    }
9032
    /// @method Array.max() -> Int|Float
9033
    /// @category Array Methods
9034
    /// Return the maximum element (all elements must be numeric).
9035
    /// @example [3, 1, 2].max()  // 3
9036
    /* ── Array: max ── */
9037
1.55k
    if (strcmp(method, "max") == 0 && obj.type == VAL_ARRAY) {
9038
3
        if (arg_count != 0) return eval_err(strdup(".max() takes no arguments"));
9039
3
        if (obj.as.array.len == 0) return eval_err(strdup(".max() on empty array"));
9040
3
        bool max_has_float = false;
9041
10
        for (size_t i = 0; i < obj.as.array.len; i++) {
9042
8
            if (obj.as.array.elems[i].type == VAL_FLOAT) max_has_float = true;
9043
5
            else if (obj.as.array.elems[i].type != VAL_INT)
9044
0
                return eval_err(strdup(".max() requires all elements to be numeric"));
9045
8
        }
9046
2
        if (max_has_float) {
9047
1
            double fmax = (obj.as.array.elems[0].type == VAL_FLOAT)
9048
1
                ? obj.as.array.elems[0].as.float_val
9049
1
                : (double)obj.as.array.elems[0].as.int_val;
9050
3
            for (size_t i = 1; i < obj.as.array.len; i++) {
9051
2
                double v = (obj.as.array.elems[i].type == VAL_FLOAT)
9052
2
                    ? obj.as.array.elems[i].as.float_val
9053
2
                    : (double)obj.as.array.elems[i].as.int_val;
9054
2
                if (v > fmax) fmax = v;
9055
2
            }
9056
1
            return eval_ok(value_float(fmax));
9057
1
        }
9058
1
        int64_t imax = obj.as.array.elems[0].as.int_val;
9059
5
        for (size_t i = 1; i < obj.as.array.len; i++) {
9060
4
            if (obj.as.array.elems[i].as.int_val > imax)
9061
2
                imax = obj.as.array.elems[i].as.int_val;
9062
4
        }
9063
1
        return eval_ok(value_int(imax));
9064
2
    }
9065
    /// @method Array.first() -> Any|Unit
9066
    /// @category Array Methods
9067
    /// Return the first element, or unit if the array is empty.
9068
    /// @example [1, 2, 3].first()  // 1
9069
    /* ── Array: first ── */
9070
1.54k
    if (strcmp(method, "first") == 0 && obj.type == VAL_ARRAY) {
9071
9
        if (arg_count != 0) return eval_err(strdup(".first() takes no arguments"));
9072
9
        if (obj.as.array.len == 0) return eval_ok(value_unit());
9073
8
        return eval_ok(value_deep_clone(&obj.as.array.elems[0]));
9074
9
    }
9075
    /// @method Array.last() -> Any|Unit
9076
    /// @category Array Methods
9077
    /// Return the last element, or unit if the array is empty.
9078
    /// @example [1, 2, 3].last()  // 3
9079
    /* ── Array: last ── */
9080
1.53k
    if (strcmp(method, "last") == 0 && obj.type == VAL_ARRAY) {
9081
3
        if (arg_count != 0) return eval_err(strdup(".last() takes no arguments"));
9082
3
        if (obj.as.array.len == 0) return eval_ok(value_unit());
9083
2
        return eval_ok(value_deep_clone(&obj.as.array.elems[obj.as.array.len - 1]));
9084
3
    }
9085
    /* ── Map methods ── */
9086
1.53k
    if (obj.type == VAL_MAP) {
9087
        /// @method Map.get(key: String) -> Any|Unit
9088
        /// @category Map Methods
9089
        /// Get the value for a key, or unit if not found.
9090
        /// @example m.get("name")  // "Alice"
9091
1.30k
        if (strcmp(method, "get") == 0) {
9092
1.00k
            if (arg_count != 1 || args[0].type != VAL_STR)
9093
0
                return eval_err(strdup(".get() expects 1 string argument"));
9094
1.00k
            LatValue *found = (LatValue *)lat_map_get(obj.as.map.map, args[0].as.str_val);
9095
1.00k
            return eval_ok(found ? value_deep_clone(found) : value_nil());
9096
1.00k
        }
9097
        /// @method Map.has(key: String) -> Bool
9098
        /// @category Map Methods
9099
        /// Check if the map contains the given key.
9100
        /// @example m.has("name")  // true
9101
302
        if (strcmp(method, "has") == 0) {
9102
112
            if (arg_count != 1 || args[0].type != VAL_STR)
9103
0
                return eval_err(strdup(".has() expects 1 string argument"));
9104
112
            return eval_ok(value_bool(lat_map_contains(obj.as.map.map, args[0].as.str_val)));
9105
112
        }
9106
        /// @method Map.keys() -> Array
9107
        /// @category Map Methods
9108
        /// Return an array of all keys in the map.
9109
        /// @example m.keys()  // ["name", "age"]
9110
190
        if (strcmp(method, "keys") == 0) {
9111
15
            size_t n = lat_map_len(obj.as.map.map);
9112
15
            LatValue *keys = malloc((n > 0 ? n : 1) * sizeof(LatValue));
9113
15
            size_t ki = 0;
9114
255
            for (size_t i = 0; i < obj.as.map.map->cap; i++) {
9115
240
                if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) {
9116
25
                    keys[ki++] = value_string(obj.as.map.map->entries[i].key);
9117
25
                }
9118
240
            }
9119
15
            LatValue arr = value_array(keys, ki);
9120
15
            free(keys);
9121
15
            return eval_ok(arr);
9122
15
        }
9123
        /// @method Map.values() -> Array
9124
        /// @category Map Methods
9125
        /// Return an array of all values in the map.
9126
        /// @example m.values()  // ["Alice", 30]
9127
175
        if (strcmp(method, "values") == 0) {
9128
1
            size_t n = lat_map_len(obj.as.map.map);
9129
1
            LatValue *vals = malloc((n > 0 ? n : 1) * sizeof(LatValue));
9130
1
            size_t vi = 0;
9131
17
            for (size_t i = 0; i < obj.as.map.map->cap; i++) {
9132
16
                if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) {
9133
1
                    vals[vi++] = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value);
9134
1
                }
9135
16
            }
9136
1
            LatValue arr = value_array(vals, vi);
9137
1
            free(vals);
9138
1
            return eval_ok(arr);
9139
1
        }
9140
        /// @method Map.len() -> Int
9141
        /// @category Map Methods
9142
        /// Return the number of key-value pairs in the map.
9143
        /// @example m.len()  // 2
9144
174
        if (strcmp(method, "len") == 0) {
9145
0
            return eval_ok(value_int((int64_t)lat_map_len(obj.as.map.map)));
9146
0
        }
9147
        /// @method Map.entries() -> Array
9148
        /// @category Map Methods
9149
        /// Return an array of [key, value] pairs.
9150
        /// @example m.entries()  // [["name", "Alice"], ["age", 30]]
9151
174
        if (strcmp(method, "entries") == 0) {
9152
1
            if (arg_count != 0) return eval_err(strdup(".entries() takes no arguments"));
9153
1
            size_t n = lat_map_len(obj.as.map.map);
9154
1
            LatValue *entries = malloc((n > 0 ? n : 1) * sizeof(LatValue));
9155
1
            size_t ei = 0;
9156
17
            for (size_t i = 0; i < obj.as.map.map->cap; i++) {
9157
16
                if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) {
9158
1
                    LatValue pair_elems[2];
9159
1
                    pair_elems[0] = value_string(obj.as.map.map->entries[i].key);
9160
1
                    pair_elems[1] = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value);
9161
1
                    entries[ei++] = value_array(pair_elems, 2);
9162
1
                }
9163
16
            }
9164
1
            LatValue arr = value_array(entries, ei);
9165
1
            free(entries);
9166
1
            return eval_ok(arr);
9167
1
        }
9168
        /// @method Map.merge(other: Map) -> Unit
9169
        /// @category Map Methods
9170
        /// Merge another map into this one (mutates in place).
9171
        /// @example m.merge(other_map)
9172
173
        if (strcmp(method, "merge") == 0) {
9173
0
            if (arg_count != 1) return eval_err(strdup(".merge() expects exactly 1 argument"));
9174
0
            if (args[0].type != VAL_MAP) return eval_err(strdup(".merge() argument must be a Map"));
9175
0
            LatMap *other = args[0].as.map.map;
9176
0
            for (size_t i = 0; i < other->cap; i++) {
9177
0
                if (other->entries[i].state == MAP_OCCUPIED) {
9178
0
                    LatValue cloned = value_deep_clone((LatValue *)other->entries[i].value);
9179
0
                    lat_map_set(obj.as.map.map, other->entries[i].key, &cloned);
9180
0
                }
9181
0
            }
9182
0
            return eval_ok(value_unit());
9183
0
        }
9184
        /// @method Map.for_each(fn: Closure) -> Unit
9185
        /// @category Map Methods
9186
        /// Call fn(key, value) for each entry in the map.
9187
        /// @example m.for_each(|k, v| { print(k, v) })
9188
173
        if (strcmp(method, "for_each") == 0) {
9189
1
            if (arg_count != 1 || args[0].type != VAL_CLOSURE)
9190
0
                return eval_err(strdup(".for_each() expects 1 closure argument"));
9191
17
            for (size_t i = 0; i < obj.as.map.map->cap; i++) {
9192
16
                if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) {
9193
1
                    LatValue call_args[2];
9194
1
                    call_args[0] = value_string(obj.as.map.map->entries[i].key);
9195
1
                    call_args[1] = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value);
9196
1
                    EvalResult r = call_closure(ev,
9197
1
                        args[0].as.closure.param_names,
9198
1
                        args[0].as.closure.param_count,
9199
1
                        args[0].as.closure.body,
9200
1
                        args[0].as.closure.captured_env,
9201
1
                        call_args, 2,
9202
1
                        args[0].as.closure.default_values, args[0].as.closure.has_variadic);
9203
1
                    if (!IS_OK(r)) return r;
9204
1
                    value_free(&r.value);
9205
1
                }
9206
16
            }
9207
1
            return eval_ok(value_unit());
9208
1
        }
9209
        /// @method Map.filter(fn: Closure) -> Map
9210
        /// @category Map Methods
9211
        /// Return a new map with only entries where fn(key, value) returns true.
9212
        /// @example m.filter(|k, v| { v > 0 })
9213
172
        if (strcmp(method, "filter") == 0) {
9214
2
            if (arg_count != 1 || args[0].type != VAL_CLOSURE)
9215
0
                return eval_err(strdup(".filter() expects 1 closure argument"));
9216
2
            LatValue result = value_map_new();
9217
34
            for (size_t i = 0; i < obj.as.map.map->cap; i++) {
9218
32
                if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) {
9219
4
                    LatValue call_args[2];
9220
4
                    call_args[0] = value_string(obj.as.map.map->entries[i].key);
9221
4
                    call_args[1] = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value);
9222
4
                    EvalResult r = call_closure(ev,
9223
4
                        args[0].as.closure.param_names,
9224
4
                        args[0].as.closure.param_count,
9225
4
                        args[0].as.closure.body,
9226
4
                        args[0].as.closure.captured_env,
9227
4
                        call_args, 2,
9228
4
                        args[0].as.closure.default_values, args[0].as.closure.has_variadic);
9229
4
                    if (!IS_OK(r)) { value_free(&result); return r; }
9230
4
                    if (value_is_truthy(&r.value)) {
9231
2
                        LatValue cloned = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value);
9232
2
                        lat_map_set(result.as.map.map, obj.as.map.map->entries[i].key, &cloned);
9233
2
                    }
9234
4
                    value_free(&r.value);
9235
4
                }
9236
32
            }
9237
2
            return eval_ok(result);
9238
2
        }
9239
        /// @method Map.map(fn: Closure) -> Map
9240
        /// @category Map Methods
9241
        /// Return a new map with values transformed by fn(key, value).
9242
        /// @example m.map(|k, v| { v * 2 })
9243
170
        if (strcmp(method, "map") == 0) {
9244
2
            if (arg_count != 1 || args[0].type != VAL_CLOSURE)
9245
0
                return eval_err(strdup(".map() expects 1 closure argument"));
9246
2
            LatValue result = value_map_new();
9247
34
            for (size_t i = 0; i < obj.as.map.map->cap; i++) {
9248
32
                if (obj.as.map.map->entries[i].state == MAP_OCCUPIED) {
9249
4
                    LatValue call_args[2];
9250
4
                    call_args[0] = value_string(obj.as.map.map->entries[i].key);
9251
4
                    call_args[1] = value_deep_clone((LatValue *)obj.as.map.map->entries[i].value);
9252
4
                    EvalResult r = call_closure(ev,
9253
4
                        args[0].as.closure.param_names,
9254
4
                        args[0].as.closure.param_count,
9255
4
                        args[0].as.closure.body,
9256
4
                        args[0].as.closure.captured_env,
9257
4
                        call_args, 2,
9258
4
                        args[0].as.closure.default_values, args[0].as.closure.has_variadic);
9259
4
                    if (!IS_OK(r)) { value_free(&result); return r; }
9260
4
                    lat_map_set(result.as.map.map, obj.as.map.map->entries[i].key, &r.value);
9261
4
                }
9262
32
            }
9263
2
            return eval_ok(result);
9264
2
        }
9265
170
    }
9266
    /* ── String methods ── */
9267
401
    if (obj.type == VAL_STR) {
9268
        /// @method String.contains(substr: String) -> Bool
9269
        /// @category String Methods
9270
        /// Check if the string contains a substring.
9271
        /// @example "hello world".contains("world")  // true
9272
192
        if (strcmp(method, "contains") == 0) {
9273
5
            if (arg_count != 1 || args[0].type != VAL_STR)
9274
0
                return eval_err(strdup(".contains() expects 1 string argument"));
9275
5
            return eval_ok(value_bool(lat_str_contains(obj.as.str_val, args[0].as.str_val)));
9276
5
        }
9277
        /// @method String.starts_with(prefix: String) -> Bool
9278
        /// @category String Methods
9279
        /// Check if the string starts with the given prefix.
9280
        /// @example "hello".starts_with("he")  // true
9281
187
        if (strcmp(method, "starts_with") == 0) {
9282
45
            if (arg_count != 1 || args[0].type != VAL_STR)
9283
0
                return eval_err(strdup(".starts_with() expects 1 string argument"));
9284
45
            return eval_ok(value_bool(lat_str_starts_with(obj.as.str_val, args[0].as.str_val)));
9285
45
        }
9286
        /// @method String.ends_with(suffix: String) -> Bool
9287
        /// @category String Methods
9288
        /// Check if the string ends with the given suffix.
9289
        /// @example "hello".ends_with("lo")  // true
9290
142
        if (strcmp(method, "ends_with") == 0) {
9291
2
            if (arg_count != 1 || args[0].type != VAL_STR)
9292
0
                return eval_err(strdup(".ends_with() expects 1 string argument"));
9293
2
            return eval_ok(value_bool(lat_str_ends_with(obj.as.str_val, args[0].as.str_val)));
9294
2
        }
9295
        /// @method String.trim() -> String
9296
        /// @category String Methods
9297
        /// Remove leading and trailing whitespace.
9298
        /// @example "  hello  ".trim()  // "hello"
9299
140
        if (strcmp(method, "trim") == 0) {
9300
43
            return eval_ok(value_string_owned(lat_str_trim(obj.as.str_val)));
9301
43
        }
9302
        /// @method String.to_upper() -> String
9303
        /// @category String Methods
9304
        /// Convert the string to uppercase.
9305
        /// @example "hello".to_upper()  // "HELLO"
9306
97
        if (strcmp(method, "to_upper") == 0) {
9307
3
            return eval_ok(value_string_owned(lat_str_to_upper(obj.as.str_val)));
9308
3
        }
9309
        /// @method String.to_lower() -> String
9310
        /// @category String Methods
9311
        /// Convert the string to lowercase.
9312
        /// @example "HELLO".to_lower()  // "hello"
9313
94
        if (strcmp(method, "to_lower") == 0) {
9314
2
            return eval_ok(value_string_owned(lat_str_to_lower(obj.as.str_val)));
9315
2
        }
9316
        /// @method String.replace(old: String, new: String) -> String
9317
        /// @category String Methods
9318
        /// Replace all occurrences of a substring.
9319
        /// @example "hello world".replace("world", "there")  // "hello there"
9320
92
        if (strcmp(method, "replace") == 0) {
9321
2
            if (arg_count != 2 || args[0].type != VAL_STR || args[1].type != VAL_STR)
9322
0
                return eval_err(strdup(".replace() expects 2 string arguments"));
9323
2
            return eval_ok(value_string_owned(lat_str_replace(obj.as.str_val, args[0].as.str_val, args[1].as.str_val)));
9324
2
        }
9325
        /// @method String.split(sep: String) -> Array
9326
        /// @category String Methods
9327
        /// Split the string by a separator, returning an array of parts.
9328
        /// @example "a,b,c".split(",")  // ["a", "b", "c"]
9329
90
        if (strcmp(method, "split") == 0) {
9330
11
            if (arg_count != 1 || args[0].type != VAL_STR)
9331
0
                return eval_err(strdup(".split() expects 1 string argument"));
9332
11
            size_t count;
9333
11
            char **parts = lat_str_split(obj.as.str_val, args[0].as.str_val, &count);
9334
11
            LatValue *elems = malloc(count * sizeof(LatValue));
9335
27
            for (size_t i = 0; i < count; i++) {
9336
16
                elems[i] = value_string_owned(parts[i]);
9337
16
            }
9338
11
            free(parts);
9339
11
            LatValue arr = value_array(elems, count);
9340
11
            free(elems);
9341
11
            return eval_ok(arr);
9342
11
        }
9343
        /// @method String.index_of(substr: String) -> Int
9344
        /// @category String Methods
9345
        /// Return the index of the first occurrence of substr, or -1 if not found.
9346
        /// @example "hello".index_of("ll")  // 2
9347
79
        if (strcmp(method, "index_of") == 0) {
9348
27
            if (arg_count != 1 || args[0].type != VAL_STR)
9349
0
                return eval_err(strdup(".index_of() expects 1 string argument"));
9350
27
            return eval_ok(value_int(lat_str_index_of(obj.as.str_val, args[0].as.str_val)));
9351
27
        }
9352
        /// @method String.substring(start: Int, end: Int) -> String
9353
        /// @category String Methods
9354
        /// Extract a substring from start (inclusive) to end (exclusive).
9355
        /// @example "hello".substring(1, 4)  // "ell"
9356
52
        if (strcmp(method, "substring") == 0) {
9357
31
            if (arg_count != 2 || args[0].type != VAL_INT || args[1].type != VAL_INT)
9358
0
                return eval_err(strdup(".substring() expects 2 integer arguments"));
9359
31
            return eval_ok(value_string_owned(lat_str_substring(obj.as.str_val, args[0].as.int_val, args[1].as.int_val)));
9360
31
        }
9361
        /// @method String.chars() -> Array
9362
        /// @category String Methods
9363
        /// Split the string into an array of single-character strings.
9364
        /// @example "abc".chars()  // ["a", "b", "c"]
9365
21
        if (strcmp(method, "chars") == 0) {
9366
7
            size_t slen = strlen(obj.as.str_val);
9367
7
            LatValue *elems = malloc((slen > 0 ? slen : 1) * sizeof(LatValue));
9368
94
            for (size_t i = 0; i < slen; i++) {
9369
87
                char buf[2] = { obj.as.str_val[i], '\0' };
9370
87
                elems[i] = value_string(buf);
9371
87
            }
9372
7
            LatValue arr = value_array(elems, slen);
9373
7
            free(elems);
9374
7
            return eval_ok(arr);
9375
7
        }
9376
        /// @method String.bytes() -> Array
9377
        /// @category String Methods
9378
        /// Return an array of byte values (integers) for the string.
9379
        /// @example "AB".bytes()  // [65, 66]
9380
14
        if (strcmp(method, "bytes") == 0) {
9381
1
            size_t slen = strlen(obj.as.str_val);
9382
1
            LatValue *elems = malloc((slen > 0 ? slen : 1) * sizeof(LatValue));
9383
4
            for (size_t i = 0; i < slen; i++) {
9384
3
                elems[i] = value_int((int64_t)(unsigned char)obj.as.str_val[i]);
9385
3
            }
9386
1
            LatValue arr = value_array(elems, slen);
9387
1
            free(elems);
9388
1
            return eval_ok(arr);
9389
1
        }
9390
        /// @method String.reverse() -> String
9391
        /// @category String Methods
9392
        /// Return the string with characters in reverse order.
9393
        /// @example "hello".reverse()  // "olleh"
9394
13
        if (strcmp(method, "reverse") == 0) {
9395
2
            return eval_ok(value_string_owned(lat_str_reverse(obj.as.str_val)));
9396
2
        }
9397
        /// @method String.repeat(n: Int) -> String
9398
        /// @category String Methods
9399
        /// Repeat the string n times.
9400
        /// @example "ab".repeat(3)  // "ababab"
9401
11
        if (strcmp(method, "repeat") == 0) {
9402
2
            if (arg_count != 1 || args[0].type != VAL_INT)
9403
0
                return eval_err(strdup(".repeat() expects 1 integer argument"));
9404
2
            return eval_ok(value_string_owned(lat_str_repeat(obj.as.str_val, (size_t)args[0].as.int_val)));
9405
2
        }
9406
        /// @method String.trim_start() -> String
9407
        /// @category String Methods
9408
        /// Remove leading whitespace.
9409
        /// @example "  hello".trim_start()  // "hello"
9410
9
        if (strcmp(method, "trim_start") == 0) {
9411
1
            if (arg_count != 0) return eval_err(strdup(".trim_start() takes no arguments"));
9412
1
            const char *s = obj.as.str_val;
9413
1
            size_t len = strlen(s);
9414
1
            size_t start = 0;
9415
3
            while (start < len && isspace((unsigned char)s[start])) start++;
9416
1
            char *result = malloc(len - start + 1);
9417
1
            memcpy(result, s + start, len - start);
9418
1
            result[len - start] = '\0';
9419
1
            return eval_ok(value_string_owned(result));
9420
1
        }
9421
        /// @method String.trim_end() -> String
9422
        /// @category String Methods
9423
        /// Remove trailing whitespace.
9424
        /// @example "hello  ".trim_end()  // "hello"
9425
8
        if (strcmp(method, "trim_end") == 0) {
9426
1
            if (arg_count != 0) return eval_err(strdup(".trim_end() takes no arguments"));
9427
1
            const char *s = obj.as.str_val;
9428
1
            size_t len = strlen(s);
9429
1
            size_t end = len;
9430
3
            while (end > 0 && isspace((unsigned char)s[end - 1])) end--;
9431
1
            char *result = malloc(end + 1);
9432
1
            memcpy(result, s, end);
9433
1
            result[end] = '\0';
9434
1
            return eval_ok(value_string_owned(result));
9435
1
        }
9436
        /// @method String.pad_left(n: Int, ch: String) -> String
9437
        /// @category String Methods
9438
        /// Pad the string on the left to reach length n using character ch.
9439
        /// @example "42".pad_left(5, "0")  // "00042"
9440
7
        if (strcmp(method, "pad_left") == 0) {
9441
1
            if (arg_count != 2) return eval_err(strdup(".pad_left() expects 2 arguments (n, ch)"));
9442
1
            if (args[0].type != VAL_INT) return eval_err(strdup(".pad_left() first argument must be an integer"));
9443
1
            if (args[1].type != VAL_STR) return eval_err(strdup(".pad_left() second argument must be a string"));
9444
1
            if (strlen(args[1].as.str_val) != 1) return eval_err(strdup(".pad_left() padding must be a single character"));
9445
1
            const char *s = obj.as.str_val;
9446
1
            size_t slen = strlen(s);
9447
1
            size_t target = (size_t)args[0].as.int_val;
9448
1
            if (slen >= target) return eval_ok(value_string(s));
9449
1
            size_t pad_count = target - slen;
9450
1
            char *result = malloc(target + 1);
9451
1
            char ch = args[1].as.str_val[0];
9452
1
            memset(result, ch, pad_count);
9453
1
            memcpy(result + pad_count, s, slen);
9454
1
            result[target] = '\0';
9455
1
            return eval_ok(value_string_owned(result));
9456
1
        }
9457
        /// @method String.pad_right(n: Int, ch: String) -> String
9458
        /// @category String Methods
9459
        /// Pad the string on the right to reach length n using character ch.
9460
        /// @example "42".pad_right(5, "0")  // "42000"
9461
6
        if (strcmp(method, "pad_right") == 0) {
9462
1
            if (arg_count != 2) return eval_err(strdup(".pad_right() expects 2 arguments (n, ch)"));
9463
1
            if (args[0].type != VAL_INT) return eval_err(strdup(".pad_right() first argument must be an integer"));
9464
1
            if (args[1].type != VAL_STR) return eval_err(strdup(".pad_right() second argument must be a string"));
9465
1
            if (strlen(args[1].as.str_val) != 1) return eval_err(strdup(".pad_right() padding must be a single character"));
9466
1
            const char *s = obj.as.str_val;
9467
1
            size_t slen = strlen(s);
9468
1
            size_t target = (size_t)args[0].as.int_val;
9469
1
            if (slen >= target) return eval_ok(value_string(s));
9470
1
            size_t pad_count = target - slen;
9471
1
            char *result = malloc(target + 1);
9472
1
            memcpy(result, s, slen);
9473
1
            char ch = args[1].as.str_val[0];
9474
1
            memset(result + slen, ch, pad_count);
9475
1
            result[target] = '\0';
9476
1
            return eval_ok(value_string_owned(result));
9477
1
        }
9478
        /// @method String.count(substr: String) -> Int
9479
        /// @category String Methods
9480
        /// Count non-overlapping occurrences of a substring.
9481
        /// @example "ababa".count("ab")  // 2
9482
5
        if (strcmp(method, "count") == 0) {
9483
3
            if (arg_count != 1 || args[0].type != VAL_STR)
9484
0
                return eval_err(strdup(".count() expects 1 string argument"));
9485
3
            const char *haystack = obj.as.str_val;
9486
3
            const char *needle = args[0].as.str_val;
9487
3
            size_t needle_len = strlen(needle);
9488
3
            int64_t count = 0;
9489
3
            if (needle_len == 0) {
9490
0
                return eval_err(strdup(".count() substring must not be empty"));
9491
0
            }
9492
3
            const char *p = haystack;
9493
6
            while ((p = strstr(p, needle)) != NULL) {
9494
3
                count++;
9495
3
                p += needle_len;
9496
3
            }
9497
3
            return eval_ok(value_int(count));
9498
3
        }
9499
        /// @method String.is_empty() -> Bool
9500
        /// @category String Methods
9501
        /// Check if the string is empty.
9502
        /// @example "".is_empty()  // true
9503
2
        if (strcmp(method, "is_empty") == 0) {
9504
2
            if (arg_count != 0)
9505
0
                return eval_err(strdup(".is_empty() takes no arguments"));
9506
2
            return eval_ok(value_bool(obj.as.str_val[0] == '\0'));
9507
2
        }
9508
2
    }
9509
9510
209
    if (strcmp(method, "get") == 0 && obj.type != VAL_REF) {
9511
0
        if (obj.type != VAL_STRUCT) return eval_err(strdup(".get() is not defined on non-struct"));
9512
0
        if (arg_count != 1) return eval_err(strdup(".get() expects exactly 1 argument"));
9513
0
        if (args[0].type != VAL_STR) return eval_err(strdup(".get() key must be a string"));
9514
0
        for (size_t i = 0; i < obj.as.strct.field_count; i++) {
9515
0
            if (strcmp(obj.as.strct.field_names[i], args[0].as.str_val) == 0) {
9516
0
                return eval_ok(value_deep_clone(&obj.as.strct.field_values[i]));
9517
0
            }
9518
0
        }
9519
0
        char *err = NULL;
9520
0
        (void)asprintf(&err, "struct has no field '%s'", args[0].as.str_val);
9521
0
        return eval_err(err);
9522
0
    }
9523
9524
    /* ── Callable struct fields: obj.method(args) where field is a closure ── */
9525
209
    if (obj.type == VAL_STRUCT) {
9526
20
        for (size_t i = 0; i < obj.as.strct.field_count; i++) {
9527
14
            if (strcmp(obj.as.strct.field_names[i], method) == 0 &&
9528
14
                obj.as.strct.field_values[i].type == VAL_CLOSURE) {
9529
4
                LatValue *cl = &obj.as.strct.field_values[i];
9530
                /* Prepend self (the struct) as the first argument */
9531
4
                size_t total = 1 + arg_count;
9532
4
                LatValue *full_args = malloc(total * sizeof(LatValue));
9533
4
                full_args[0] = value_deep_clone(&obj);
9534
6
                for (size_t j = 0; j < arg_count; j++) {
9535
2
                    full_args[j + 1] = value_deep_clone(&args[j]);
9536
2
                }
9537
4
                EvalResult r = call_closure(ev,
9538
4
                    cl->as.closure.param_names, cl->as.closure.param_count,
9539
4
                    cl->as.closure.body, cl->as.closure.captured_env,
9540
4
                    full_args, total,
9541
4
                    cl->as.closure.default_values, cl->as.closure.has_variadic);
9542
                /* call_closure takes ownership of args via env_define;
9543
                   env_pop_scope frees them — do NOT value_free here */
9544
4
                free(full_args);
9545
4
                return r;
9546
4
            }
9547
14
        }
9548
10
    }
9549
9550
    /* ── Channel methods ── */
9551
205
    if (obj.type == VAL_CHANNEL) {
9552
21
        LatChannel *ch = obj.as.channel.ch;
9553
        /// @method Channel.send(val: Any) -> Unit
9554
        /// @category Channel Methods
9555
        /// Send a crystal (frozen) value on the channel.
9556
        /// @example ch.send(freeze(42))
9557
21
        if (strcmp(method, "send") == 0) {
9558
10
            if (arg_count != 1)
9559
0
                return eval_err(strdup(".send() expects exactly 1 argument"));
9560
10
            if (!value_is_crystal(&args[0]) && args[0].type != VAL_INT &&
9561
10
                args[0].type != VAL_FLOAT && args[0].type != VAL_BOOL &&
9562
10
                args[0].type != VAL_UNIT) {
9563
1
                return eval_err(strdup("can only send crystal (frozen) values on a channel"));
9564
1
            }
9565
            /* Deep-clone into malloc-backed memory (no heap/arena pointers) */
9566
9
            DualHeap *saved_heap = ev->heap;
9567
9
            value_set_heap(NULL);
9568
9
            value_set_arena(NULL);
9569
9
            LatValue detached = value_deep_clone(&args[0]);
9570
9
            value_set_heap(saved_heap);
9571
9
            bool ok = channel_send(ch, detached);
9572
9
            if (!ok) return eval_err(strdup("cannot send on a closed channel"));
9573
9
            return eval_ok(value_unit());
9574
9
        }
9575
        /// @method Channel.recv() -> Any|Unit
9576
        /// @category Channel Methods
9577
        /// Receive a value from the channel, blocking until available. Returns unit if closed.
9578
        /// @example ch.recv()  // 42
9579
11
        if (strcmp(method, "recv") == 0) {
9580
8
            if (arg_count != 0)
9581
0
                return eval_err(strdup(".recv() takes no arguments"));
9582
8
            bool ok;
9583
8
            LatValue val = channel_recv(ch, &ok);
9584
8
            if (!ok) return eval_ok(value_unit());
9585
7
            return eval_ok(val);
9586
8
        }
9587
        /// @method Channel.close() -> Unit
9588
        /// @category Channel Methods
9589
        /// Close the channel, preventing further sends.
9590
        /// @example ch.close()
9591
3
        if (strcmp(method, "close") == 0) {
9592
3
            if (arg_count != 0)
9593
0
                return eval_err(strdup(".close() takes no arguments"));
9594
3
            channel_close(ch);
9595
3
            return eval_ok(value_unit());
9596
3
        }
9597
3
    }
9598
9599
    /* Fallback: check for callable field (e.g. module map with closure values).
9600
     * call_closure consumes args, but the caller also frees them, so we
9601
     * deep-clone args before passing. */
9602
184
    if (obj.type == VAL_MAP) {
9603
168
        LatValue *field = (LatValue *)lat_map_get(obj.as.map.map, method);
9604
168
        if (field && field->type == VAL_CLOSURE) {
9605
168
            if (field->as.closure.native_fn && !field->as.closure.body
9606
168
                && field->as.closure.default_values == (struct Expr **)(uintptr_t)0x1) {
9607
                /* VM-style native from builtin module */
9608
2
                typedef LatValue (*VMNativeFn)(LatValue *, int);
9609
2
                VMNativeFn fn = (VMNativeFn)field->as.closure.native_fn;
9610
2
                LatRuntime *prev_rt = lat_runtime_current();
9611
2
                LatRuntime tmp_rt;
9612
2
                if (!prev_rt) {
9613
2
                    memset(&tmp_rt, 0, sizeof(tmp_rt));
9614
2
                    lat_runtime_set_current(&tmp_rt);
9615
2
                }
9616
2
                LatRuntime *rt = lat_runtime_current();
9617
2
                LatValue result = fn(args, (int)arg_count);
9618
2
                EvalResult res;
9619
2
                if (rt->error) {
9620
0
                    char *msg = rt->error;
9621
0
                    rt->error = NULL;
9622
0
                    value_free(&result);
9623
0
                    res = eval_err(msg);
9624
2
                } else {
9625
2
                    res = eval_ok(result);
9626
2
                }
9627
2
                if (!prev_rt) lat_runtime_set_current(NULL);
9628
2
                return res;
9629
2
            }
9630
166
            LatValue *cloned_args = malloc(arg_count * sizeof(LatValue));
9631
410
            for (size_t i = 0; i < arg_count; i++)
9632
244
                cloned_args[i] = value_deep_clone(&args[i]);
9633
166
            EvalResult res = call_closure(ev,
9634
166
                field->as.closure.param_names,
9635
166
                field->as.closure.param_count,
9636
166
                field->as.closure.body,
9637
166
                field->as.closure.captured_env,
9638
166
                cloned_args, arg_count,
9639
166
                field->as.closure.default_values, field->as.closure.has_variadic);
9640
166
            free(cloned_args);
9641
166
            return res;
9642
168
        }
9643
168
    } else if (obj.type == VAL_STRUCT) {
9644
12
        for (size_t i = 0; i < obj.as.strct.field_count; i++) {
9645
6
            if (strcmp(obj.as.strct.field_names[i], method) == 0 &&
9646
6
                obj.as.strct.field_values[i].type == VAL_CLOSURE) {
9647
0
                LatValue *field = &obj.as.strct.field_values[i];
9648
0
                LatValue *cloned_args = malloc(arg_count * sizeof(LatValue));
9649
0
                for (size_t j = 0; j < arg_count; j++)
9650
0
                    cloned_args[j] = value_deep_clone(&args[j]);
9651
0
                EvalResult res = call_closure(ev,
9652
0
                    field->as.closure.param_names,
9653
0
                    field->as.closure.param_count,
9654
0
                    field->as.closure.body,
9655
0
                    field->as.closure.captured_env,
9656
0
                    cloned_args, arg_count,
9657
0
                    field->as.closure.default_values, field->as.closure.has_variadic);
9658
0
                free(cloned_args);
9659
0
                return res;
9660
0
            }
9661
6
        }
9662
6
    }
9663
9664
    /* ── Trait impl method dispatch ── */
9665
16
    {
9666
16
        const char *type_name = NULL;
9667
16
        if (obj.type == VAL_STRUCT) type_name = obj.as.strct.name;
9668
10
        else if (obj.type == VAL_INT) type_name = "Int";
9669
10
        else if (obj.type == VAL_FLOAT) type_name = "Float";
9670
10
        else if (obj.type == VAL_STR) type_name = "String";
9671
10
        else if (obj.type == VAL_BOOL) type_name = "Bool";
9672
10
        else if (obj.type == VAL_ARRAY) type_name = "Array";
9673
10
        else if (obj.type == VAL_MAP) type_name = "Map";
9674
9675
16
        if (type_name) {
9676
            /* Search all impl blocks for this type */
9677
51
            for (size_t i = 0; i < ev->impl_registry.cap; i++) {
9678
51
                if (ev->impl_registry.entries[i].state != MAP_OCCUPIED) continue;
9679
7
                ImplBlock **ib_ptr = (ImplBlock **)ev->impl_registry.entries[i].value;
9680
7
                ImplBlock *ib = *ib_ptr;
9681
7
                if (strcmp(ib->type_name, type_name) != 0) continue;
9682
7
                for (size_t m = 0; m < ib->method_count; m++) {
9683
7
                    if (strcmp(ib->methods[m].name, method) != 0) continue;
9684
                    /* Found a matching impl method — call it with self as first arg.
9685
                     * call_fn takes ownership of args via env_define, so do NOT free them. */
9686
6
                    FnDecl *fn = &ib->methods[m];
9687
6
                    size_t total = 1 + arg_count;
9688
6
                    LatValue *full_args = malloc(total * sizeof(LatValue));
9689
6
                    full_args[0] = value_deep_clone(&obj);
9690
7
                    for (size_t j = 0; j < arg_count; j++)
9691
1
                        full_args[j + 1] = value_deep_clone(&args[j]);
9692
6
                    EvalResult r = call_fn(ev, fn, full_args, total, NULL);
9693
6
                    free(full_args);
9694
6
                    return r;
9695
7
                }
9696
6
            }
9697
6
        }
9698
16
    }
9699
9700
    /* ── Ref methods ── */
9701
10
    if (obj.type == VAL_REF) {
9702
10
        LatRef *ref = obj.as.ref.ref;
9703
10
        LatValue *inner = &ref->value;
9704
9705
        /* Ref-specific methods */
9706
        /// @method Ref.get() -> Any
9707
        /// @category Ref Methods
9708
        /// Return a deep clone of the wrapped value.
9709
        /// @example r.get()
9710
10
        if (strcmp(method, "get") == 0 && arg_count == 0) {
9711
3
            return eval_ok(value_deep_clone(inner));
9712
3
        }
9713
        /// @method Ref.deref() -> Any
9714
        /// @category Ref Methods
9715
        /// Alias for get(). Return a deep clone of the wrapped value.
9716
        /// @example r.deref()
9717
7
        if (strcmp(method, "deref") == 0 && arg_count == 0) {
9718
1
            return eval_ok(value_deep_clone(inner));
9719
1
        }
9720
        /// @method Ref.set(value: Any) -> Unit
9721
        /// @category Ref Methods
9722
        /// Replace the inner value (all holders see the change).
9723
        /// @example r.set(42)
9724
6
        if (strcmp(method, "set") == 0 && arg_count == 1) {
9725
2
            if (obj.phase == VTAG_CRYSTAL)
9726
0
                return eval_err(strdup("cannot set on a frozen Ref"));
9727
2
            value_free(inner);
9728
2
            *inner = value_deep_clone(&args[0]);
9729
2
            return eval_ok(value_unit());
9730
2
        }
9731
        /// @method Ref.inner_type() -> String
9732
        /// @category Ref Methods
9733
        /// Return the type name of the wrapped value.
9734
        /// @example r.inner_type()
9735
4
        if (strcmp(method, "inner_type") == 0 && arg_count == 0) {
9736
2
            return eval_ok(value_string(value_type_name(inner)));
9737
2
        }
9738
9739
        /* Map proxy (when inner is VAL_MAP) */
9740
2
        if (inner->type == VAL_MAP) {
9741
2
            if (strcmp(method, "get") == 0 && arg_count == 1) {
9742
0
                if (args[0].type != VAL_STR) return eval_ok(value_nil());
9743
0
                LatValue *found = lat_map_get(inner->as.map.map, args[0].as.str_val);
9744
0
                return eval_ok(found ? value_deep_clone(found) : value_nil());
9745
0
            }
9746
2
            if (strcmp(method, "set") == 0 && arg_count == 2) {
9747
0
                if (obj.phase == VTAG_CRYSTAL)
9748
0
                    return eval_err(strdup("cannot set on a frozen Ref"));
9749
0
                if (args[0].type != VAL_STR)
9750
0
                    return eval_err(strdup(".set() key must be a string"));
9751
0
                LatValue *old = (LatValue *)lat_map_get(inner->as.map.map, args[0].as.str_val);
9752
0
                if (old) value_free(old);
9753
0
                LatValue cloned = value_deep_clone(&args[1]);
9754
0
                lat_map_set(inner->as.map.map, args[0].as.str_val, &cloned);
9755
0
                return eval_ok(value_unit());
9756
0
            }
9757
2
            if (strcmp(method, "has") == 0 && arg_count == 1) {
9758
2
                bool found = args[0].type == VAL_STR && lat_map_contains(inner->as.map.map, args[0].as.str_val);
9759
2
                return eval_ok(value_bool(found));
9760
2
            }
9761
0
            if (strcmp(method, "contains") == 0 && arg_count == 1) {
9762
0
                bool found = false;
9763
0
                for (size_t i = 0; i < inner->as.map.map->cap; i++) {
9764
0
                    if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
9765
0
                    LatValue *mv = (LatValue *)inner->as.map.map->entries[i].value;
9766
0
                    if (value_eq(mv, &args[0])) { found = true; break; }
9767
0
                }
9768
0
                return eval_ok(value_bool(found));
9769
0
            }
9770
0
            if (strcmp(method, "keys") == 0 && arg_count == 0) {
9771
0
                size_t n = lat_map_len(inner->as.map.map);
9772
0
                LatValue *elems = malloc((n > 0 ? n : 1) * sizeof(LatValue));
9773
0
                size_t ei = 0;
9774
0
                for (size_t i = 0; i < inner->as.map.map->cap; i++) {
9775
0
                    if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
9776
0
                    elems[ei++] = value_string(inner->as.map.map->entries[i].key);
9777
0
                }
9778
0
                LatValue arr = value_array(elems, ei); free(elems);
9779
0
                return eval_ok(arr);
9780
0
            }
9781
0
            if (strcmp(method, "values") == 0 && arg_count == 0) {
9782
0
                size_t n = lat_map_len(inner->as.map.map);
9783
0
                LatValue *elems = malloc((n > 0 ? n : 1) * sizeof(LatValue));
9784
0
                size_t ei = 0;
9785
0
                for (size_t i = 0; i < inner->as.map.map->cap; i++) {
9786
0
                    if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
9787
0
                    LatValue *mv = (LatValue *)inner->as.map.map->entries[i].value;
9788
0
                    elems[ei++] = value_deep_clone(mv);
9789
0
                }
9790
0
                LatValue arr = value_array(elems, ei); free(elems);
9791
0
                return eval_ok(arr);
9792
0
            }
9793
0
            if (strcmp(method, "entries") == 0 && arg_count == 0) {
9794
0
                size_t n = lat_map_len(inner->as.map.map);
9795
0
                LatValue *elems = malloc((n > 0 ? n : 1) * sizeof(LatValue));
9796
0
                size_t ei = 0;
9797
0
                for (size_t i = 0; i < inner->as.map.map->cap; i++) {
9798
0
                    if (inner->as.map.map->entries[i].state != MAP_OCCUPIED) continue;
9799
0
                    LatValue pair[2];
9800
0
                    pair[0] = value_string(inner->as.map.map->entries[i].key);
9801
0
                    pair[1] = value_deep_clone((LatValue *)inner->as.map.map->entries[i].value);
9802
0
                    elems[ei++] = value_array(pair, 2);
9803
0
                }
9804
0
                LatValue arr = value_array(elems, ei); free(elems);
9805
0
                return eval_ok(arr);
9806
0
            }
9807
0
            if (strcmp(method, "len") == 0 && arg_count == 0) {
9808
0
                return eval_ok(value_int((int64_t)lat_map_len(inner->as.map.map)));
9809
0
            }
9810
0
        }
9811
9812
        /* Array proxy */
9813
0
        if (inner->type == VAL_ARRAY) {
9814
0
            if (strcmp(method, "len") == 0 && arg_count == 0)
9815
0
                return eval_ok(value_int((int64_t)inner->as.array.len));
9816
0
            if (strcmp(method, "contains") == 0 && arg_count == 1) {
9817
0
                for (size_t i = 0; i < inner->as.array.len; i++) {
9818
0
                    if (value_eq(&inner->as.array.elems[i], &args[0]))
9819
0
                        return eval_ok(value_bool(true));
9820
0
                }
9821
0
                return eval_ok(value_bool(false));
9822
0
            }
9823
0
        }
9824
9825
0
        char *rerr = NULL;
9826
0
        (void)asprintf(&rerr, "Ref has no method '%s'", method);
9827
0
        return eval_err(rerr);
9828
0
    }
9829
9830
0
    char *err = NULL;
9831
0
    (void)asprintf(&err, "unknown method '.%s()' on %s", method, value_type_name(&obj));
9832
0
    return eval_err(err);
9833
10
}
9834
9835
/* ── Evaluator lifecycle ── */
9836
9837
865
Evaluator *evaluator_new(void) {
9838
865
    Evaluator *ev = calloc(1, sizeof(Evaluator));
9839
865
    ev->env = env_new();
9840
865
    ev->mode = MODE_CASUAL;
9841
865
    ev->struct_defs = lat_map_new(sizeof(StructDecl *));
9842
865
    ev->enum_defs = lat_map_new(sizeof(EnumDecl *));
9843
865
    ev->fn_defs = lat_map_new(sizeof(FnDecl *));
9844
865
    ev->trait_defs = lat_map_new(sizeof(TraitDecl *));
9845
865
    ev->impl_registry = lat_map_new(sizeof(ImplBlock *));
9846
865
    stats_init(&ev->stats);
9847
865
    ev->heap = dual_heap_new();
9848
865
    ev->gc_roots = lat_vec_new(sizeof(LatValue *));
9849
865
    ev->saved_envs = lat_vec_new(sizeof(Env *));
9850
865
    ev->gc_stress = false;
9851
865
    ev->no_regions = false;
9852
865
    ev->required_files = lat_map_new(sizeof(bool));
9853
865
    ev->module_cache = lat_map_new(sizeof(LatValue));
9854
865
    ev->loaded_extensions = lat_map_new(sizeof(LatValue));
9855
865
    ev->module_exprs = lat_vec_new(sizeof(Expr *));
9856
865
    ev->bonds = NULL;
9857
865
    ev->bond_count = 0;
9858
865
    ev->bond_cap = 0;
9859
865
    ev->tracked_vars = NULL;
9860
865
    ev->tracked_count = 0;
9861
865
    ev->tracked_cap = 0;
9862
865
    ev->reactions = NULL;
9863
865
    ev->reaction_count = 0;
9864
865
    ev->reaction_cap = 0;
9865
865
    ev->defer_stack = NULL;
9866
865
    ev->defer_count = 0;
9867
865
    ev->defer_cap = 0;
9868
865
    ev->assertions_enabled = true;
9869
865
    value_set_heap(ev->heap);
9870
865
    return ev;
9871
865
}
9872
9873
865
void evaluator_free(Evaluator *ev) {
9874
865
    if (!ev) return;
9875
865
    net_tls_cleanup();
9876
865
    env_free(ev->env);            /* lat_free → fluid_dealloc removes from heap */
9877
865
    lat_map_free(&ev->struct_defs);
9878
865
    lat_map_free(&ev->enum_defs);
9879
865
    lat_map_free(&ev->fn_defs);
9880
865
    lat_map_free(&ev->trait_defs);
9881
865
    lat_map_free(&ev->impl_registry);
9882
865
    lat_map_free(&ev->required_files);
9883
    /* Free cached module maps */
9884
14.7k
    for (size_t i = 0; i < ev->module_cache.cap; i++) {
9885
13.8k
        if (ev->module_cache.entries[i].state == MAP_OCCUPIED) {
9886
81
            LatValue *mv = (LatValue *)ev->module_cache.entries[i].value;
9887
81
            value_free(mv);
9888
81
        }
9889
13.8k
    }
9890
865
    lat_map_free(&ev->module_cache);
9891
    /* Free cached extension maps */
9892
14.7k
    for (size_t i = 0; i < ev->loaded_extensions.cap; i++) {
9893
13.8k
        if (ev->loaded_extensions.entries[i].state == MAP_OCCUPIED) {
9894
15
            LatValue *mv = (LatValue *)ev->loaded_extensions.entries[i].value;
9895
15
            value_free(mv);
9896
15
        }
9897
13.8k
    }
9898
865
    lat_map_free(&ev->loaded_extensions);
9899
    /* Free body Expr wrappers kept alive for module closures */
9900
4.26k
    for (size_t i = 0; i < ev->module_exprs.len; i++) {
9901
3.39k
        Expr **ep = lat_vec_get(&ev->module_exprs, i);
9902
        /* Only free the wrapper node, not the stmts it borrows */
9903
3.39k
        free(*ep);
9904
3.39k
    }
9905
865
    lat_vec_free(&ev->module_exprs);
9906
865
    free(ev->script_dir);
9907
    /* Free bonds */
9908
866
    for (size_t i = 0; i < ev->bond_count; i++) {
9909
1
        free(ev->bonds[i].target);
9910
2
        for (size_t j = 0; j < ev->bonds[i].dep_count; j++) {
9911
1
            free(ev->bonds[i].deps[j]);
9912
1
            if (ev->bonds[i].dep_strategies) free(ev->bonds[i].dep_strategies[j]);
9913
1
        }
9914
1
        free(ev->bonds[i].deps);
9915
1
        free(ev->bonds[i].dep_strategies);
9916
1
    }
9917
865
    free(ev->bonds);
9918
    /* Free tracked variable histories */
9919
878
    for (size_t i = 0; i < ev->tracked_count; i++) {
9920
13
        free(ev->tracked_vars[i].name);
9921
43
        for (size_t j = 0; j < ev->tracked_vars[i].history.count; j++) {
9922
30
            free(ev->tracked_vars[i].history.snapshots[j].phase_name);
9923
30
            free(ev->tracked_vars[i].history.snapshots[j].fn_name);
9924
30
            value_free(&ev->tracked_vars[i].history.snapshots[j].value);
9925
30
        }
9926
13
        free(ev->tracked_vars[i].history.snapshots);
9927
13
    }
9928
865
    free(ev->tracked_vars);
9929
    /* Free phase reactions */
9930
872
    for (size_t i = 0; i < ev->reaction_count; i++) {
9931
7
        free(ev->reactions[i].var_name);
9932
15
        for (size_t j = 0; j < ev->reactions[i].cb_count; j++)
9933
8
            value_free(&ev->reactions[i].callbacks[j]);
9934
7
        free(ev->reactions[i].callbacks);
9935
7
    }
9936
865
    free(ev->reactions);
9937
    /* Free seed crystals */
9938
867
    for (size_t i = 0; i < ev->seed_count; i++) {
9939
2
        free(ev->seeds[i].var_name);
9940
2
        value_free(&ev->seeds[i].contract);
9941
2
    }
9942
865
    free(ev->seeds);
9943
    /* Free pressure entries */
9944
870
    for (size_t i = 0; i < ev->pressure_count; i++) {
9945
5
        free(ev->pressures[i].var_name);
9946
5
        free(ev->pressures[i].mode);
9947
5
    }
9948
865
    free(ev->pressures);
9949
865
    free(ev->defer_stack);
9950
865
    free(ev->call_stack);
9951
865
    value_set_heap(NULL);         /* disconnect before freeing the heap itself */
9952
865
    dual_heap_free(ev->heap);     /* frees any remaining tracked allocs */
9953
865
    lat_vec_free(&ev->gc_roots);
9954
865
    lat_vec_free(&ev->saved_envs);
9955
865
    free(ev);
9956
865
}
9957
9958
26
void evaluator_set_gc_stress(Evaluator *ev, bool enabled) {
9959
26
    ev->gc_stress = enabled;
9960
26
}
9961
9962
0
void evaluator_set_no_regions(Evaluator *ev, bool enabled) {
9963
0
    ev->no_regions = enabled;
9964
0
}
9965
9966
0
void evaluator_set_script_dir(Evaluator *ev, const char *dir) {
9967
0
    free(ev->script_dir);
9968
0
    ev->script_dir = dir ? strdup(dir) : NULL;
9969
0
}
9970
9971
0
void evaluator_set_argv(Evaluator *ev, int argc, char **argv) {
9972
0
    ev->prog_argc = argc;
9973
0
    ev->prog_argv = argv;
9974
0
}
9975
9976
0
void evaluator_set_assertions(Evaluator *ev, bool enabled) {
9977
0
    ev->assertions_enabled = enabled;
9978
0
}
9979
9980
865
char *evaluator_run(Evaluator *ev, const Program *prog) {
9981
865
    ev->mode = prog->mode;
9982
9983
    /* First pass: register structs, enums, functions, traits, and impls */
9984
2.07k
    for (size_t i = 0; i < prog->item_count; i++) {
9985
1.20k
        if (prog->items[i].tag == ITEM_STRUCT) {
9986
44
            StructDecl *ptr = &prog->items[i].as.struct_decl;
9987
44
            lat_map_set(&ev->struct_defs, ptr->name, &ptr);
9988
1.16k
        } else if (prog->items[i].tag == ITEM_ENUM) {
9989
9
            EnumDecl *ptr = &prog->items[i].as.enum_decl;
9990
9
            lat_map_set(&ev->enum_defs, ptr->name, &ptr);
9991
1.15k
        } else if (prog->items[i].tag == ITEM_FUNCTION) {
9992
905
            FnDecl *ptr = &prog->items[i].as.fn_decl;
9993
905
            register_fn_overload(&ev->fn_defs, ptr);
9994
905
        } else if (prog->items[i].tag == ITEM_TRAIT) {
9995
4
            TraitDecl *ptr = &prog->items[i].as.trait_decl;
9996
4
            lat_map_set(&ev->trait_defs, ptr->name, &ptr);
9997
245
        } else if (prog->items[i].tag == ITEM_IMPL) {
9998
5
            ImplBlock *ptr = &prog->items[i].as.impl_block;
9999
5
            char key[512];
10000
5
            snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name);
10001
5
            lat_map_set(&ev->impl_registry, key, &ptr);
10002
5
        }
10003
1.20k
    }
10004
10005
    /* Second pass: execute top-level statements */
10006
2.06k
    for (size_t i = 0; i < prog->item_count; i++) {
10007
1.20k
        if (prog->items[i].tag == ITEM_STMT) {
10008
238
            EvalResult r = eval_stmt(ev, prog->items[i].as.stmt);
10009
238
            if (IS_ERR(r)) {
10010
4
                r.error = ev_attach_trace(ev, r.error);
10011
4
                ev->call_depth = 0;
10012
4
                return r.error;
10013
4
            }
10014
234
            if (IS_SIGNAL(r)) return strdup("unexpected control flow at top level");
10015
234
            value_free(&r.value);
10016
234
        }
10017
1.20k
    }
10018
10019
    /* If there is a main() function, call it */
10020
861
    FnDecl *main_fn = find_fn(ev, "main");
10021
861
    if (main_fn) {
10022
834
        EvalResult r = call_fn(ev, main_fn, NULL, 0, NULL);
10023
834
        if (IS_ERR(r)) {
10024
92
            r.error = ev_attach_trace(ev, r.error);
10025
92
            ev->call_depth = 0;
10026
92
            return r.error;
10027
92
        }
10028
742
        value_free(&r.value);
10029
742
    }
10030
10031
769
    return NULL; /* success */
10032
861
}
10033
10034
0
int evaluator_run_tests(Evaluator *ev, const Program *prog) {
10035
0
    ev->mode = prog->mode;
10036
10037
    /* First pass: register structs, enums, functions, traits, and impls */
10038
0
    for (size_t i = 0; i < prog->item_count; i++) {
10039
0
        if (prog->items[i].tag == ITEM_STRUCT) {
10040
0
            StructDecl *ptr = &prog->items[i].as.struct_decl;
10041
0
            lat_map_set(&ev->struct_defs, ptr->name, &ptr);
10042
0
        } else if (prog->items[i].tag == ITEM_ENUM) {
10043
0
            EnumDecl *ptr = &prog->items[i].as.enum_decl;
10044
0
            lat_map_set(&ev->enum_defs, ptr->name, &ptr);
10045
0
        } else if (prog->items[i].tag == ITEM_FUNCTION) {
10046
0
            FnDecl *ptr = &prog->items[i].as.fn_decl;
10047
0
            register_fn_overload(&ev->fn_defs, ptr);
10048
0
        } else if (prog->items[i].tag == ITEM_TRAIT) {
10049
0
            TraitDecl *ptr = &prog->items[i].as.trait_decl;
10050
0
            lat_map_set(&ev->trait_defs, ptr->name, &ptr);
10051
0
        } else if (prog->items[i].tag == ITEM_IMPL) {
10052
0
            ImplBlock *ptr = &prog->items[i].as.impl_block;
10053
0
            char key[512];
10054
0
            snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name);
10055
0
            lat_map_set(&ev->impl_registry, key, &ptr);
10056
0
        }
10057
0
    }
10058
10059
    /* Second pass: execute top-level statements (setup code) */
10060
0
    for (size_t i = 0; i < prog->item_count; i++) {
10061
0
        if (prog->items[i].tag == ITEM_STMT) {
10062
0
            EvalResult r = eval_stmt(ev, prog->items[i].as.stmt);
10063
0
            if (IS_ERR(r)) {
10064
0
                fprintf(stderr, "setup error: %s\n", r.error);
10065
0
                free(r.error);
10066
0
                return 1;
10067
0
            }
10068
0
            if (IS_SIGNAL(r)) {
10069
0
                fprintf(stderr, "setup error: unexpected control flow at top level\n");
10070
0
                return 1;
10071
0
            }
10072
0
            value_free(&r.value);
10073
0
        }
10074
0
    }
10075
10076
    /* Count tests */
10077
0
    size_t test_count = 0;
10078
0
    for (size_t i = 0; i < prog->item_count; i++) {
10079
0
        if (prog->items[i].tag == ITEM_TEST)
10080
0
            test_count++;
10081
0
    }
10082
10083
0
    if (test_count == 0) {
10084
0
        printf("No tests found.\n");
10085
0
        return 0;
10086
0
    }
10087
10088
0
    printf("Running %zu test%s...\n\n", test_count, test_count == 1 ? "" : "s");
10089
10090
    /* Third pass: run tests */
10091
0
    size_t passed = 0, failed = 0;
10092
0
    for (size_t i = 0; i < prog->item_count; i++) {
10093
0
        if (prog->items[i].tag != ITEM_TEST) continue;
10094
0
        TestDecl *td = &prog->items[i].as.test_decl;
10095
10096
0
        stats_scope_push(&ev->stats);
10097
0
        env_push_scope(ev->env);
10098
10099
0
        bool ok = true;
10100
0
        char *errmsg = NULL;
10101
0
        for (size_t j = 0; j < td->body_count; j++) {
10102
0
            EvalResult r = eval_stmt(ev, td->body[j]);
10103
0
            if (IS_ERR(r)) {
10104
0
                ok = false;
10105
0
                errmsg = ev_attach_trace(ev, r.error);
10106
0
                ev->call_depth = 0;
10107
0
                break;
10108
0
            }
10109
0
            if (IS_SIGNAL(r)) {
10110
0
                ok = false;
10111
0
                errmsg = strdup("unexpected control flow in test");
10112
0
                break;
10113
0
            }
10114
0
            value_free(&r.value);
10115
0
        }
10116
10117
0
        env_pop_scope(ev->env);
10118
0
        stats_scope_pop(&ev->stats);
10119
10120
0
        if (ok) {
10121
0
            passed++;
10122
0
            printf("  ok: %s\n", td->name);
10123
0
        } else {
10124
0
            failed++;
10125
0
            printf("  FAIL: %s\n", td->name);
10126
0
            if (errmsg) {
10127
0
                printf("        %s\n", errmsg);
10128
0
                free(errmsg);
10129
0
            }
10130
0
        }
10131
0
    }
10132
10133
0
    printf("\nResults: %zu passed, %zu failed, %zu total\n", passed, failed, test_count);
10134
0
    return failed > 0 ? 1 : 0;
10135
0
}
10136
10137
0
char *evaluator_run_repl(Evaluator *ev, const Program *prog) {
10138
0
    ev->mode = prog->mode;
10139
10140
    /* First pass: register structs, enums, functions, traits, and impls */
10141
0
    for (size_t i = 0; i < prog->item_count; i++) {
10142
0
        if (prog->items[i].tag == ITEM_STRUCT) {
10143
0
            StructDecl *ptr = &prog->items[i].as.struct_decl;
10144
0
            lat_map_set(&ev->struct_defs, ptr->name, &ptr);
10145
0
        } else if (prog->items[i].tag == ITEM_ENUM) {
10146
0
            EnumDecl *ptr = &prog->items[i].as.enum_decl;
10147
0
            lat_map_set(&ev->enum_defs, ptr->name, &ptr);
10148
0
        } else if (prog->items[i].tag == ITEM_FUNCTION) {
10149
0
            FnDecl *ptr = &prog->items[i].as.fn_decl;
10150
0
            register_fn_overload(&ev->fn_defs, ptr);
10151
0
        } else if (prog->items[i].tag == ITEM_TRAIT) {
10152
0
            TraitDecl *ptr = &prog->items[i].as.trait_decl;
10153
0
            lat_map_set(&ev->trait_defs, ptr->name, &ptr);
10154
0
        } else if (prog->items[i].tag == ITEM_IMPL) {
10155
0
            ImplBlock *ptr = &prog->items[i].as.impl_block;
10156
0
            char key[512];
10157
0
            snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name);
10158
0
            lat_map_set(&ev->impl_registry, key, &ptr);
10159
0
        }
10160
0
    }
10161
10162
    /* Second pass: execute top-level statements (no auto-main) */
10163
0
    for (size_t i = 0; i < prog->item_count; i++) {
10164
0
        if (prog->items[i].tag == ITEM_STMT) {
10165
0
            EvalResult r = eval_stmt(ev, prog->items[i].as.stmt);
10166
0
            if (IS_ERR(r)) {
10167
0
                r.error = ev_attach_trace(ev, r.error);
10168
0
                ev->call_depth = 0;
10169
0
                return r.error;
10170
0
            }
10171
0
            if (IS_SIGNAL(r)) return strdup("unexpected control flow at top level");
10172
0
            value_free(&r.value);
10173
0
        }
10174
0
    }
10175
10176
0
    return NULL; /* success */
10177
0
}
10178
10179
0
EvalResult evaluator_run_repl_result(Evaluator *ev, const Program *prog) {
10180
0
    ev->mode = prog->mode;
10181
10182
    /* First pass: register structs, enums, functions, traits, and impls */
10183
0
    for (size_t i = 0; i < prog->item_count; i++) {
10184
0
        if (prog->items[i].tag == ITEM_STRUCT) {
10185
0
            StructDecl *ptr = &prog->items[i].as.struct_decl;
10186
0
            lat_map_set(&ev->struct_defs, ptr->name, &ptr);
10187
0
        } else if (prog->items[i].tag == ITEM_ENUM) {
10188
0
            EnumDecl *ptr = &prog->items[i].as.enum_decl;
10189
0
            lat_map_set(&ev->enum_defs, ptr->name, &ptr);
10190
0
        } else if (prog->items[i].tag == ITEM_FUNCTION) {
10191
0
            FnDecl *ptr = &prog->items[i].as.fn_decl;
10192
0
            register_fn_overload(&ev->fn_defs, ptr);
10193
0
        } else if (prog->items[i].tag == ITEM_TRAIT) {
10194
0
            TraitDecl *ptr = &prog->items[i].as.trait_decl;
10195
0
            lat_map_set(&ev->trait_defs, ptr->name, &ptr);
10196
0
        } else if (prog->items[i].tag == ITEM_IMPL) {
10197
0
            ImplBlock *ptr = &prog->items[i].as.impl_block;
10198
0
            char key[512];
10199
0
            snprintf(key, sizeof(key), "%s::%s", ptr->type_name, ptr->trait_name);
10200
0
            lat_map_set(&ev->impl_registry, key, &ptr);
10201
0
        }
10202
0
    }
10203
10204
    /* Second pass: execute top-level statements, keep last result */
10205
0
    LatValue last = value_unit();
10206
0
    for (size_t i = 0; i < prog->item_count; i++) {
10207
0
        if (prog->items[i].tag == ITEM_STMT) {
10208
0
            EvalResult r = eval_stmt(ev, prog->items[i].as.stmt);
10209
0
            if (IS_ERR(r)) {
10210
0
                r.error = ev_attach_trace(ev, r.error);
10211
0
                ev->call_depth = 0;
10212
0
                return r;
10213
0
            }
10214
0
            if (IS_SIGNAL(r)) {
10215
0
                value_free(&last);
10216
0
                return eval_err(strdup("unexpected control flow at top level"));
10217
0
            }
10218
0
            value_free(&last);
10219
0
            last = r.value;
10220
0
        }
10221
0
    }
10222
10223
0
    return eval_ok(last);
10224
0
}
10225
10226
12
char *eval_repr(Evaluator *ev, const LatValue *v) {
10227
12
    if (v->type == VAL_STRUCT) {
10228
        /* Look for a "repr" closure field */
10229
8
        for (size_t i = 0; i < v->as.strct.field_count; i++) {
10230
7
            if (strcmp(v->as.strct.field_names[i], "repr") == 0 &&
10231
7
                v->as.strct.field_values[i].type == VAL_CLOSURE) {
10232
2
                const LatValue *cl = &v->as.strct.field_values[i];
10233
2
                LatValue self_arg = value_deep_clone(v);
10234
2
                EvalResult r = call_closure(ev,
10235
2
                    cl->as.closure.param_names,
10236
2
                    cl->as.closure.param_count,
10237
2
                    cl->as.closure.body,
10238
2
                    cl->as.closure.captured_env,
10239
2
                    &self_arg, 1,
10240
2
                    cl->as.closure.default_values,
10241
2
                    cl->as.closure.has_variadic);
10242
2
                if (IS_OK(r) && r.value.type == VAL_STR) {
10243
1
                    char *result = strdup(r.value.as.str_val);
10244
1
                    value_free(&r.value);
10245
1
                    return result;
10246
1
                }
10247
1
                if (IS_OK(r)) value_free(&r.value);
10248
                /* Fall through to default */
10249
1
                break;
10250
2
            }
10251
7
        }
10252
3
    }
10253
11
    return value_repr(v);
10254
12
}
10255
10256
21
const MemoryStats *evaluator_stats(const Evaluator *ev) {
10257
    /* Finalize snapshot metrics from heap state */
10258
21
    MemoryStats *s = (MemoryStats *)&ev->stats;
10259
21
    s->fluid_peak_bytes = ev->heap->fluid->peak_bytes;
10260
21
    s->fluid_live_bytes = ev->heap->fluid->total_bytes;
10261
21
    s->fluid_cumulative_bytes = ev->heap->fluid->cumulative_bytes;
10262
21
    s->region_peak_count = ev->heap->regions->peak_count;
10263
21
    s->region_live_count = ev->heap->regions->count;
10264
21
    s->region_live_data_bytes = region_live_data_bytes(ev->heap->regions);
10265
21
    s->region_cumulative_data_bytes = ev->heap->regions->cumulative_data_bytes;
10266
21
    struct rusage ru;
10267
21
    if (getrusage(RUSAGE_SELF, &ru) == 0) {
10268
#ifdef __linux__
10269
        s->rss_peak_kb = (size_t)ru.ru_maxrss;
10270
#else
10271
21
        s->rss_peak_kb = (size_t)ru.ru_maxrss / 1024;
10272
21
#endif
10273
21
    }
10274
21
    return &ev->stats;
10275
21
}
10276
10277
0
void memory_stats_print(const MemoryStats *s, FILE *out) {
10278
0
    fprintf(out, "=== Memory Statistics ===\n\n");
10279
0
    fprintf(out, "Phase transitions:\n");
10280
0
    fprintf(out, "  freezes:      %zu\n", s->freezes);
10281
0
    fprintf(out, "  thaws:        %zu\n", s->thaws);
10282
0
    fprintf(out, "  deep clones:  %zu\n", s->deep_clones);
10283
0
    fprintf(out, "\nAllocations:\n");
10284
0
    fprintf(out, "  arrays:       %zu\n", s->array_allocs);
10285
0
    fprintf(out, "  structs:      %zu\n", s->struct_allocs);
10286
0
    fprintf(out, "  closures:     %zu\n", s->closure_allocs);
10287
0
    fprintf(out, "  total:        %zu\n", s->array_allocs + s->struct_allocs + s->closure_allocs);
10288
0
    fprintf(out, "\nMemory footprint:\n");
10289
0
    fprintf(out, "  fluid peak:   %zu bytes (%.2f KB)\n",
10290
0
        s->fluid_peak_bytes, (double)s->fluid_peak_bytes / 1024.0);
10291
0
    fprintf(out, "  fluid live:   %zu bytes\n", s->fluid_live_bytes);
10292
0
    fprintf(out, "  fluid total:  %zu bytes (%.2f KB)\n",
10293
0
        s->fluid_cumulative_bytes, (double)s->fluid_cumulative_bytes / 1024.0);
10294
0
    if (s->fluid_peak_bytes > 0)
10295
0
        fprintf(out, "  churn ratio:  %.1fx\n",
10296
0
            (double)s->fluid_cumulative_bytes / (double)s->fluid_peak_bytes);
10297
0
    fprintf(out, "  region peak:  %zu\n", s->region_peak_count);
10298
0
    fprintf(out, "  region live:  %zu (%zu bytes data)\n",
10299
0
        s->region_live_count, s->region_live_data_bytes);
10300
0
    fprintf(out, "  region total: %zu bytes data\n", s->region_cumulative_data_bytes);
10301
0
    if (s->rss_peak_kb > 0)
10302
0
        fprintf(out, "  RSS peak:     %zu KB\n", s->rss_peak_kb);
10303
0
    fprintf(out, "\nScope lifecycle:\n");
10304
0
    fprintf(out, "  pushes:       %zu\n", s->scope_pushes);
10305
0
    fprintf(out, "  pops:         %zu\n", s->scope_pops);
10306
0
    fprintf(out, "  peak depth:   %zu\n", s->peak_scope_depth);
10307
0
    fprintf(out, "\nCalls & bindings:\n");
10308
0
    fprintf(out, "  bindings:     %zu\n", s->bindings_created);
10309
0
    fprintf(out, "  fn calls:     %zu\n", s->fn_calls);
10310
0
    fprintf(out, "  closure calls:%zu\n", s->closure_calls);
10311
0
    fprintf(out, "\nForge blocks:   %zu\n", s->forge_blocks);
10312
0
    fprintf(out, "\nGarbage collection:\n");
10313
0
    fprintf(out, "  gc cycles:    %zu\n", s->gc_cycles);
10314
0
    fprintf(out, "  swept fluid:  %zu (%zu bytes)\n", s->gc_swept_fluid, s->gc_bytes_swept);
10315
0
    fprintf(out, "  swept regions:%zu\n", s->gc_swept_regions);
10316
0
    if (s->gc_cycles > 0)
10317
0
        fprintf(out, "  avg/cycle:    %.2f KB swept\n",
10318
0
            (double)s->gc_bytes_swept / 1024.0 / (double)s->gc_cycles);
10319
0
    fprintf(out, "\nTiming:\n");
10320
0
    fprintf(out, "  gc total:     %.3f ms\n", (double)s->gc_total_ns / 1e6);
10321
0
    fprintf(out, "  freeze total: %.3f ms\n", (double)s->freeze_total_ns / 1e6);
10322
0
    fprintf(out, "  thaw total:   %.3f ms\n", (double)s->thaw_total_ns / 1e6);
10323
0
    if (s->gc_cycles > 0)
10324
0
        fprintf(out, "  avg gc cycle: %.3f ms\n",
10325
0
            (double)s->gc_total_ns / 1e6 / (double)s->gc_cycles);
10326
0
}