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/stackcompiler.c
Line
Count
Source
1
#include "stackcompiler.h"
2
#include "stackopcode.h"
3
#include <stdlib.h>
4
#include <string.h>
5
#include <stdio.h>
6
7
/* ── Compiler state ── */
8
9
static Compiler *current = NULL;
10
static char *compile_error = NULL;
11
12
/* Track declared enums so EXPR_ENUM_VARIANT can fall back to function call */
13
static char **known_enums = NULL;
14
static size_t known_enum_count = 0;
15
static size_t known_enum_cap = 0;
16
17
9
static void register_enum(const char *name) {
18
9
    if (known_enum_count >= known_enum_cap) {
19
9
        known_enum_cap = known_enum_cap ? known_enum_cap * 2 : 8;
20
9
        known_enums = realloc(known_enums, known_enum_cap * sizeof(char *));
21
9
    }
22
9
    known_enums[known_enum_count++] = strdup(name);
23
9
}
24
25
548
static bool is_known_enum(const char *name) {
26
548
    for (size_t i = 0; i < known_enum_count; i++)
27
14
        if (strcmp(known_enums[i], name) == 0) return true;
28
534
    return false;
29
548
}
30
31
949
static void free_known_enums(void) {
32
958
    for (size_t i = 0; i < known_enum_count; i++)
33
9
        free(known_enums[i]);
34
949
    free(known_enums);
35
949
    known_enums = NULL;
36
949
    known_enum_count = 0;
37
949
    known_enum_cap = 0;
38
949
}
39
40
static LatValue const_eval_expr(Expr *e);
41
42
4.29k
static void compiler_init(Compiler *comp, Compiler *enclosing, FunctionType type) {
43
4.29k
    comp->enclosing = enclosing;
44
4.29k
    comp->chunk = chunk_new();
45
4.29k
    comp->type = type;
46
4.29k
    comp->func_name = NULL;
47
4.29k
    comp->arity = 0;
48
4.29k
    comp->local_count = 0;
49
4.29k
    comp->local_cap = 256;
50
4.29k
    comp->locals = malloc(comp->local_cap * sizeof(Local));
51
4.29k
    comp->upvalues = NULL;
52
4.29k
    comp->upvalue_count = 0;
53
4.29k
    comp->scope_depth = (type == FUNC_SCRIPT) ? 0 : 1;
54
4.29k
    comp->break_jumps = NULL;
55
4.29k
    comp->break_count = 0;
56
4.29k
    comp->break_cap = 0;
57
4.29k
    comp->loop_start = 0;
58
4.29k
    comp->loop_depth = 0;
59
4.29k
    comp->loop_break_local_count = 0;
60
4.29k
    comp->loop_continue_local_count = 0;
61
4.29k
    comp->contracts = NULL;
62
4.29k
    comp->contract_count = 0;
63
4.29k
    comp->return_type_name = NULL;
64
65
    /* Reserve slot 0 for the function itself (or empty for script) */
66
4.29k
    if (type != FUNC_SCRIPT) {
67
3.31k
        Local *local = &comp->locals[comp->local_count++];
68
3.31k
        local->name = strdup("");
69
3.31k
        local->depth = 0;
70
3.31k
        local->is_captured = false;
71
3.31k
    }
72
73
4.29k
    current = comp;
74
4.29k
}
75
76
4.29k
static void compiler_cleanup(Compiler *comp) {
77
14.2k
    for (size_t i = 0; i < comp->local_count; i++)
78
9.96k
        free(comp->locals[i].name);
79
4.29k
    free(comp->locals);
80
4.29k
    free(comp->upvalues);
81
4.29k
    free(comp->break_jumps);
82
4.29k
    free(comp->func_name);
83
4.29k
}
84
85
289k
static Chunk *current_chunk(void) { return current->chunk; }
86
87
221k
static void emit_byte(uint8_t byte, int line) {
88
221k
    chunk_write(current_chunk(), byte, line);
89
221k
}
90
91
39.2k
static void emit_bytes(uint8_t b1, uint8_t b2, int line) {
92
39.2k
    emit_byte(b1, line);
93
39.2k
    emit_byte(b2, line);
94
39.2k
}
95
96
14.7k
static void emit_constant_idx(uint8_t op, uint8_t op16, size_t idx, int line) {
97
14.7k
    if (idx <= 255) {
98
14.7k
        emit_bytes(op, (uint8_t)idx, line);
99
14.7k
    } else if (idx <= 65535) {
100
0
        emit_byte(op16, line);
101
0
        emit_byte((uint8_t)((idx >> 8) & 0xff), line);
102
0
        emit_byte((uint8_t)(idx & 0xff), line);
103
0
    } else {
104
0
        compile_error = strdup("too many constants in one chunk (>65535)");
105
0
    }
106
14.7k
}
107
108
6.17k
static size_t emit_constant(LatValue val, int line) {
109
6.17k
    size_t idx = chunk_add_constant(current_chunk(), val);
110
6.17k
    emit_constant_idx(OP_CONSTANT, OP_CONSTANT_16, idx, line);
111
6.17k
    return idx;
112
6.17k
}
113
114
5.77k
static size_t emit_jump(uint8_t op, int line) {
115
5.77k
    emit_byte(op, line);
116
5.77k
    emit_byte(0xff, line);
117
5.77k
    emit_byte(0xff, line);
118
5.77k
    return current_chunk()->code_len - 2;
119
5.77k
}
120
121
5.77k
static void patch_jump(size_t offset) {
122
5.77k
    size_t jump = current_chunk()->code_len - offset - 2;
123
5.77k
    if (jump > 65535) {
124
0
        compile_error = strdup("jump offset too large");
125
0
        return;
126
0
    }
127
5.77k
    current_chunk()->code[offset] = (uint8_t)((jump >> 8) & 0xff);
128
5.77k
    current_chunk()->code[offset + 1] = (uint8_t)(jump & 0xff);
129
5.77k
}
130
131
559
static void emit_loop(size_t loop_start, int line) {
132
559
    emit_byte(OP_LOOP, line);
133
559
    size_t offset = current_chunk()->code_len - loop_start + 2;
134
559
    if (offset > 65535) {
135
0
        compile_error = strdup("loop body too large");
136
0
        return;
137
0
    }
138
559
    emit_byte((uint8_t)((offset >> 8) & 0xff), line);
139
559
    emit_byte((uint8_t)(offset & 0xff), line);
140
559
}
141
142
/* ── Scope management ── */
143
144
3.67k
static void begin_scope(void) { current->scope_depth++; }
145
146
3.66k
static void end_scope(int line) {
147
    /* Run any defers registered at the current scope depth before popping locals.
148
     * Push a dummy value because OP_DEFER_RUN saves/restores TOS for each defer. */
149
3.66k
    emit_byte(OP_UNIT, line);
150
3.66k
    emit_byte(OP_DEFER_RUN, line);
151
3.66k
    emit_byte((uint8_t)current->scope_depth, line);
152
3.66k
    emit_byte(OP_POP, line);  /* pop the dummy unit */
153
3.66k
    current->scope_depth--;
154
4.95k
    while (current->local_count > 0 &&
155
4.95k
           current->locals[current->local_count - 1].depth > current->scope_depth) {
156
1.28k
        if (current->locals[current->local_count - 1].is_captured) {
157
174
            emit_byte(OP_CLOSE_UPVALUE, line);
158
1.11k
        } else {
159
1.11k
            emit_byte(OP_POP, line);
160
1.11k
        }
161
1.28k
        free(current->locals[current->local_count - 1].name);
162
1.28k
        current->local_count--;
163
1.28k
    }
164
3.66k
}
165
166
/* Like end_scope but preserves TOS (the expression result) across local pops.
167
 * Emits OP_SWAP before each OP_POP/OP_CLOSE_UPVALUE to sink the result
168
 * value past each local being removed. */
169
6
static void end_scope_preserve_tos(int line) {
170
    /* Run any defers registered at the current scope depth.
171
     * TOS is already the expression result; OP_DEFER_RUN saves/restores it. */
172
6
    emit_byte(OP_DEFER_RUN, line);
173
6
    emit_byte((uint8_t)current->scope_depth, line);
174
6
    current->scope_depth--;
175
8
    while (current->local_count > 0 &&
176
8
           current->locals[current->local_count - 1].depth > current->scope_depth) {
177
2
        emit_byte(OP_SWAP, line);
178
2
        if (current->locals[current->local_count - 1].is_captured) {
179
0
            emit_byte(OP_CLOSE_UPVALUE, line);
180
2
        } else {
181
2
            emit_byte(OP_POP, line);
182
2
        }
183
2
        free(current->locals[current->local_count - 1].name);
184
2
        current->local_count--;
185
2
    }
186
6
}
187
188
8.91k
static void add_local(const char *name) {
189
8.91k
    if (current->local_count >= current->local_cap) {
190
0
        current->local_cap *= 2;
191
0
        current->locals = realloc(current->locals, current->local_cap * sizeof(Local));
192
0
    }
193
8.91k
    size_t slot = current->local_count;
194
8.91k
    Local *local = &current->locals[current->local_count++];
195
8.91k
    local->name = strdup(name);
196
8.91k
    local->depth = current->scope_depth;
197
8.91k
    local->is_captured = false;
198
    /* Record name in chunk's debug table for runtime tracking support */
199
8.91k
    chunk_set_local_name(current_chunk(), slot, name);
200
8.91k
}
201
202
30.7k
static int resolve_local(Compiler *comp, const char *name) {
203
80.0k
    for (int i = (int)comp->local_count - 1; i >= 0; i--) {
204
68.7k
        if (strcmp(comp->locals[i].name, name) == 0)
205
19.5k
            return i;
206
68.7k
    }
207
11.2k
    return -1;
208
30.7k
}
209
210
/* ── Upvalue resolution ── */
211
212
1.59k
static int add_upvalue(Compiler *comp, uint8_t index, bool is_local) {
213
    /* Check if we already have this upvalue */
214
2.64k
    for (size_t i = 0; i < comp->upvalue_count; i++) {
215
1.71k
        if (comp->upvalues[i].index == index && comp->upvalues[i].is_local == is_local)
216
667
            return (int)i;
217
1.71k
    }
218
930
    if (comp->upvalue_count >= 256) {
219
0
        compile_error = strdup("too many upvalues in one function");
220
0
        return -1;
221
0
    }
222
930
    comp->upvalues = realloc(comp->upvalues, (comp->upvalue_count + 1) * sizeof(CompilerUpvalue));
223
930
    comp->upvalues[comp->upvalue_count].index = index;
224
930
    comp->upvalues[comp->upvalue_count].is_local = is_local;
225
930
    return (int)comp->upvalue_count++;
226
930
}
227
228
11.2k
static int resolve_upvalue(Compiler *comp, const char *name) {
229
11.2k
    if (!comp->enclosing) return -1;
230
231
6.79k
    int local = resolve_local(comp->enclosing, name);
232
6.79k
    if (local != -1) {
233
1.48k
        comp->enclosing->locals[local].is_captured = true;
234
1.48k
        return add_upvalue(comp, (uint8_t)local, true);
235
1.48k
    }
236
237
5.31k
    int upvalue = resolve_upvalue(comp->enclosing, name);
238
5.31k
    if (upvalue != -1)
239
116
        return add_upvalue(comp, (uint8_t)upvalue, false);
240
241
5.19k
    return -1;
242
5.31k
}
243
244
/* ── Break/continue helpers ── */
245
246
98
static void push_break_jump(size_t offset) {
247
98
    if (current->break_count >= current->break_cap) {
248
98
        current->break_cap = current->break_cap ? current->break_cap * 2 : 8;
249
98
        current->break_jumps = realloc(current->break_jumps, current->break_cap * sizeof(size_t));
250
98
    }
251
98
    current->break_jumps[current->break_count++] = offset;
252
98
}
253
254
/* ── Compile expressions ── */
255
256
static void compile_expr(const Expr *e, int line);
257
static void compile_stmt(const Stmt *s);
258
259
/* Compile a statement and emit OP_RESET_EPHEMERAL to reclaim temporaries. */
260
12.1k
static void compile_stmt_reset(const Stmt *s) {
261
12.1k
    compile_stmt(s);
262
12.1k
    if (!compile_error)
263
12.1k
        emit_byte(OP_RESET_EPHEMERAL, s->line);
264
12.1k
}
265
266
static void compile_function_body(FunctionType type, const char *name,
267
                                  Param *params, size_t param_count,
268
                                  Stmt **body, size_t body_count,
269
                                  ContractClause *contracts, size_t contract_count,
270
                                  TypeExpr *return_type, int line);
271
272
/* Compile a list of statements into a standalone sub-chunk.
273
 * Saves/restores the current compiler so this can be called mid-compilation. */
274
14
static Chunk *compile_sub_body(Stmt **stmts, size_t count, int line) {
275
14
    Compiler *saved = current;
276
14
    Compiler sub;
277
14
    compiler_init(&sub, NULL, FUNC_SCRIPT);
278
279
    /* If last statement is an expression, use its value as the sub-body result */
280
14
    if (count > 0 && stmts[count - 1]->tag == STMT_EXPR) {
281
15
        for (size_t i = 0; i + 1 < count; i++)
282
2
            compile_stmt(stmts[i]);
283
13
        compile_expr(stmts[count - 1]->as.expr, line);
284
13
    } else {
285
4
        for (size_t i = 0; i < count; i++)
286
3
            compile_stmt(stmts[i]);
287
1
        emit_byte(OP_UNIT, line);
288
1
    }
289
14
    emit_byte(OP_RETURN, line);
290
291
14
    Chunk *ch = sub.chunk;
292
14
    compiler_cleanup(&sub);
293
14
    current = saved;
294
14
    return ch;
295
14
}
296
297
/* Compile a single expression into a standalone sub-chunk (evaluates and returns value). */
298
6
static Chunk *compile_sub_expr(const Expr *expr, int line) {
299
6
    Compiler *saved = current;
300
6
    Compiler sub;
301
6
    compiler_init(&sub, NULL, FUNC_SCRIPT);
302
303
6
    compile_expr(expr, line);
304
6
    emit_byte(OP_RETURN, line);
305
306
6
    Chunk *ch = sub.chunk;
307
6
    compiler_cleanup(&sub);
308
6
    current = saved;
309
6
    return ch;
310
6
}
311
312
/* Store a pre-compiled Chunk* as a VAL_CLOSURE constant in the current chunk. */
313
20
static size_t add_chunk_constant(Chunk *ch) {
314
20
    LatValue fn_val;
315
20
    memset(&fn_val, 0, sizeof(fn_val));
316
20
    fn_val.type = VAL_CLOSURE;
317
20
    fn_val.phase = VTAG_UNPHASED;
318
20
    fn_val.region_id = (size_t)-1;
319
20
    fn_val.as.closure.body = NULL;
320
20
    fn_val.as.closure.native_fn = ch;
321
20
    return chunk_add_constant(current_chunk(), fn_val);
322
20
}
323
324
46.6k
static void compile_expr(const Expr *e, int line) {
325
46.6k
    if (compile_error) return;
326
46.6k
    if (e->line > 0) line = e->line;
327
328
46.6k
    switch (e->tag) {
329
3.06k
        case EXPR_INT_LIT:
330
3.06k
            if (e->as.int_val >= -128 && e->as.int_val <= 127) {
331
3.01k
                emit_bytes(OP_LOAD_INT8, (uint8_t)(int8_t)e->as.int_val, line);
332
3.01k
            } else {
333
49
                emit_constant(value_int(e->as.int_val), line);
334
49
            }
335
3.06k
            break;
336
337
107
        case EXPR_FLOAT_LIT:
338
107
            emit_constant(value_float(e->as.float_val), line);
339
107
            break;
340
341
787
        case EXPR_BOOL_LIT:
342
787
            emit_byte(e->as.bool_val ? OP_TRUE : OP_FALSE, line);
343
787
            break;
344
345
339
        case EXPR_NIL_LIT:
346
339
            emit_byte(OP_NIL, line);
347
339
            break;
348
349
5.94k
        case EXPR_STRING_LIT:
350
5.94k
            emit_constant(value_string(e->as.str_val), line);
351
5.94k
            break;
352
353
15.8k
        case EXPR_IDENT: {
354
15.8k
            int slot = resolve_local(current, e->as.str_val);
355
15.8k
            if (slot >= 0) {
356
10.1k
                emit_bytes(OP_GET_LOCAL, (uint8_t)slot, line);
357
10.1k
            } else {
358
5.73k
                int upvalue = resolve_upvalue(current, e->as.str_val);
359
5.73k
                if (upvalue >= 0) {
360
1.48k
                    emit_bytes(OP_GET_UPVALUE, (uint8_t)upvalue, line);
361
4.24k
                } else {
362
4.24k
                    size_t idx = chunk_add_constant(current_chunk(), value_string(e->as.str_val));
363
4.24k
                    emit_constant_idx(OP_GET_GLOBAL, OP_GET_GLOBAL_16, idx, line);
364
4.24k
                }
365
5.73k
            }
366
15.8k
            break;
367
0
        }
368
369
2.97k
        case EXPR_BINOP: {
370
            /* Constant folding: evaluate at compile time if both operands are literals */
371
2.97k
            if ((e->as.binop.left->tag == EXPR_INT_LIT || e->as.binop.left->tag == EXPR_FLOAT_LIT) &&
372
2.97k
                (e->as.binop.right->tag == EXPR_INT_LIT || e->as.binop.right->tag == EXPR_FLOAT_LIT)) {
373
36
                bool both_int = (e->as.binop.left->tag == EXPR_INT_LIT &&
374
36
                                 e->as.binop.right->tag == EXPR_INT_LIT);
375
36
                int64_t li = e->as.binop.left->tag == EXPR_INT_LIT ? e->as.binop.left->as.int_val : 0;
376
36
                int64_t ri = e->as.binop.right->tag == EXPR_INT_LIT ? e->as.binop.right->as.int_val : 0;
377
36
                double lf = e->as.binop.left->tag == EXPR_FLOAT_LIT ? e->as.binop.left->as.float_val : (double)li;
378
36
                double rf = e->as.binop.right->tag == EXPR_FLOAT_LIT ? e->as.binop.right->as.float_val : (double)ri;
379
36
                bool folded = true;
380
36
                switch (e->as.binop.op) {
381
7
                    case BINOP_ADD:
382
7
                        if (both_int) { int64_t v = li + ri; if (v >= -128 && v <= 127) emit_bytes(OP_LOAD_INT8, (uint8_t)(int8_t)v, line); else emit_constant(value_int(v), line); }
383
0
                        else emit_constant(value_float(lf + rf), line);
384
7
                        break;
385
0
                    case BINOP_SUB:
386
0
                        if (both_int) { int64_t v = li - ri; if (v >= -128 && v <= 127) emit_bytes(OP_LOAD_INT8, (uint8_t)(int8_t)v, line); else emit_constant(value_int(v), line); }
387
0
                        else emit_constant(value_float(lf - rf), line);
388
0
                        break;
389
0
                    case BINOP_MUL:
390
0
                        if (both_int) { int64_t v = li * ri; if (v >= -128 && v <= 127) emit_bytes(OP_LOAD_INT8, (uint8_t)(int8_t)v, line); else emit_constant(value_int(v), line); }
391
0
                        else emit_constant(value_float(lf * rf), line);
392
0
                        break;
393
11
                    case BINOP_DIV:
394
11
                        if (both_int) { if (ri == 0) { folded = false; break; } int64_t v = li / ri; if (v >= -128 && v <= 127) emit_bytes(OP_LOAD_INT8, (uint8_t)(int8_t)v, line); else emit_constant(value_int(v), line); }
395
2
                        else { if (rf == 0.0) { folded = false; break; } emit_constant(value_float(lf / rf), line); }
396
1
                        break;
397
1
                    case BINOP_MOD:
398
1
                        if (both_int) { if (ri == 0) { folded = false; break; } int64_t v = li % ri; if (v >= -128 && v <= 127) emit_bytes(OP_LOAD_INT8, (uint8_t)(int8_t)v, line); else emit_constant(value_int(v), line); }
399
0
                        else folded = false;
400
1
                        break;
401
1
                    case BINOP_LT:    emit_constant(value_bool(both_int ? li < ri : lf < rf), line); break;
402
1
                    case BINOP_GT:    emit_constant(value_bool(both_int ? li > ri : lf > rf), line); break;
403
1
                    case BINOP_LTEQ:  emit_constant(value_bool(both_int ? li <= ri : lf <= rf), line); break;
404
1
                    case BINOP_GTEQ:  emit_constant(value_bool(both_int ? li >= ri : lf >= rf), line); break;
405
2
                    case BINOP_EQ:    emit_constant(value_bool(both_int ? li == ri : lf == rf), line); break;
406
2
                    case BINOP_NEQ:   emit_constant(value_bool(both_int ? li != ri : lf != rf), line); break;
407
9
                    default: folded = false; break;
408
36
                }
409
36
                if (folded) break;
410
36
            }
411
            /* Unary negation of int literal (constant fold -N) handled by EXPR_UNARYOP below */
412
413
            /* Short-circuit AND/OR */
414
2.96k
            if (e->as.binop.op == BINOP_AND) {
415
31
                compile_expr(e->as.binop.left, line);
416
31
                size_t end_jump = emit_jump(OP_JUMP_IF_FALSE, line);
417
31
                emit_byte(OP_POP, line);
418
31
                compile_expr(e->as.binop.right, line);
419
31
                patch_jump(end_jump);
420
31
                break;
421
31
            }
422
2.93k
            if (e->as.binop.op == BINOP_OR) {
423
2
                compile_expr(e->as.binop.left, line);
424
2
                size_t end_jump = emit_jump(OP_JUMP_IF_TRUE, line);
425
2
                emit_byte(OP_POP, line);
426
2
                compile_expr(e->as.binop.right, line);
427
2
                patch_jump(end_jump);
428
2
                break;
429
2
            }
430
            /* Nil coalesce */
431
2.92k
            if (e->as.binop.op == BINOP_NIL_COALESCE) {
432
6
                compile_expr(e->as.binop.left, line);
433
6
                size_t end_jump = emit_jump(OP_JUMP_IF_NOT_NIL, line);
434
6
                emit_byte(OP_POP, line);
435
6
                compile_expr(e->as.binop.right, line);
436
6
                patch_jump(end_jump);
437
6
                break;
438
6
            }
439
            /* Normal binary ops */
440
2.92k
            compile_expr(e->as.binop.left, line);
441
2.92k
            compile_expr(e->as.binop.right, line);
442
2.92k
            switch (e->as.binop.op) {
443
960
                case BINOP_ADD:     emit_byte(OP_ADD, line); break;
444
89
                case BINOP_SUB:     emit_byte(OP_SUB, line); break;
445
36
                case BINOP_MUL:     emit_byte(OP_MUL, line); break;
446
12
                case BINOP_DIV:     emit_byte(OP_DIV, line); break;
447
9
                case BINOP_MOD:     emit_byte(OP_MOD, line); break;
448
923
                case BINOP_EQ:      emit_byte(OP_EQ, line); break;
449
267
                case BINOP_NEQ:     emit_byte(OP_NEQ, line); break;
450
191
                case BINOP_LT:      emit_byte(OP_LT, line); break;
451
213
                case BINOP_GT:      emit_byte(OP_GT, line); break;
452
73
                case BINOP_LTEQ:    emit_byte(OP_LTEQ, line); break;
453
131
                case BINOP_GTEQ:    emit_byte(OP_GTEQ, line); break;
454
5
                case BINOP_BIT_AND: emit_byte(OP_BIT_AND, line); break;
455
4
                case BINOP_BIT_OR:  emit_byte(OP_BIT_OR, line); break;
456
4
                case BINOP_BIT_XOR: emit_byte(OP_BIT_XOR, line); break;
457
3
                case BINOP_LSHIFT:  emit_byte(OP_LSHIFT, line); break;
458
2
                case BINOP_RSHIFT:  emit_byte(OP_RSHIFT, line); break;
459
0
                default: break; /* AND/OR/NIL_COALESCE handled above */
460
2.92k
            }
461
2.92k
            break;
462
2.92k
        }
463
464
2.92k
        case EXPR_UNARYOP:
465
            /* Constant fold unary negation of literals */
466
205
            if (e->as.unaryop.op == UNOP_NEG && e->as.unaryop.operand->tag == EXPR_INT_LIT) {
467
14
                int64_t v = -e->as.unaryop.operand->as.int_val;
468
14
                if (v >= -128 && v <= 127)
469
14
                    emit_bytes(OP_LOAD_INT8, (uint8_t)(int8_t)v, line);
470
0
                else
471
0
                    emit_constant(value_int(v), line);
472
14
                break;
473
14
            }
474
191
            if (e->as.unaryop.op == UNOP_NEG && e->as.unaryop.operand->tag == EXPR_FLOAT_LIT) {
475
1
                emit_constant(value_float(-e->as.unaryop.operand->as.float_val), line);
476
1
                break;
477
1
            }
478
190
            if (e->as.unaryop.op == UNOP_NOT && e->as.unaryop.operand->tag == EXPR_BOOL_LIT) {
479
0
                emit_constant(value_bool(!e->as.unaryop.operand->as.bool_val), line);
480
0
                break;
481
0
            }
482
190
            compile_expr(e->as.unaryop.operand, line);
483
190
            switch (e->as.unaryop.op) {
484
1
                case UNOP_NEG:     emit_byte(OP_NEG, line); break;
485
186
                case UNOP_NOT:     emit_byte(OP_NOT, line); break;
486
3
                case UNOP_BIT_NOT: emit_byte(OP_BIT_NOT, line); break;
487
190
            }
488
190
            break;
489
490
1.09k
        case EXPR_PRINT: {
491
2.19k
            for (size_t i = 0; i < e->as.print.arg_count; i++)
492
1.09k
                compile_expr(e->as.print.args[i], line);
493
1.09k
            emit_bytes(OP_PRINT, (uint8_t)e->as.print.arg_count, line);
494
1.09k
            break;
495
190
        }
496
497
2.42k
        case EXPR_IF: {
498
2.42k
            compile_expr(e->as.if_expr.cond, line);
499
2.42k
            size_t then_jump = emit_jump(OP_JUMP_IF_FALSE, line);
500
2.42k
            emit_byte(OP_POP, line); /* pop condition (then path) */
501
502
            /* Then branch (scoped so locals like 'flux j' are cleaned up) */
503
2.42k
            begin_scope();
504
            /* If last stmt is STMT_EXPR, use its value as the branch result */
505
2.42k
            if (e->as.if_expr.then_count > 0 &&
506
2.42k
                e->as.if_expr.then_stmts[e->as.if_expr.then_count - 1]->tag == STMT_EXPR) {
507
1.53k
                for (size_t i = 0; i + 1 < e->as.if_expr.then_count; i++)
508
561
                    compile_stmt_reset(e->as.if_expr.then_stmts[i]);
509
                /* Compile last expression WITHOUT pop — value stays on stack */
510
974
                compile_expr(e->as.if_expr.then_stmts[e->as.if_expr.then_count - 1]->as.expr, line);
511
1.45k
            } else {
512
3.49k
                for (size_t i = 0; i < e->as.if_expr.then_count; i++)
513
2.04k
                    compile_stmt_reset(e->as.if_expr.then_stmts[i]);
514
1.45k
                emit_byte(OP_UNIT, line);
515
1.45k
            }
516
2.42k
            end_scope(line);
517
518
2.42k
            size_t else_jump = emit_jump(OP_JUMP, line);
519
2.42k
            patch_jump(then_jump);
520
2.42k
            emit_byte(OP_POP, line); /* pop condition (else path) */
521
522
            /* Else branch */
523
2.42k
            if (e->as.if_expr.else_stmts) {
524
432
                begin_scope();
525
432
                if (e->as.if_expr.else_count > 0 &&
526
432
                    e->as.if_expr.else_stmts[e->as.if_expr.else_count - 1]->tag == STMT_EXPR) {
527
424
                    for (size_t i = 0; i + 1 < e->as.if_expr.else_count; i++)
528
83
                        compile_stmt_reset(e->as.if_expr.else_stmts[i]);
529
341
                    compile_expr(e->as.if_expr.else_stmts[e->as.if_expr.else_count - 1]->as.expr, line);
530
341
                } else {
531
222
                    for (size_t i = 0; i < e->as.if_expr.else_count; i++)
532
131
                        compile_stmt_reset(e->as.if_expr.else_stmts[i]);
533
91
                    emit_byte(OP_UNIT, line);
534
91
                }
535
432
                end_scope(line);
536
1.99k
            } else {
537
1.99k
                emit_byte(OP_NIL, line);
538
1.99k
            }
539
2.42k
            patch_jump(else_jump);
540
2.42k
            break;
541
190
        }
542
543
3
        case EXPR_BLOCK: {
544
3
            begin_scope();
545
            /* If last stmt is an expression, use its value as the block result */
546
3
            if (e->as.block.count > 0 &&
547
3
                e->as.block.stmts[e->as.block.count - 1]->tag == STMT_EXPR) {
548
2
                for (size_t i = 0; i + 1 < e->as.block.count; i++)
549
1
                    compile_stmt_reset(e->as.block.stmts[i]);
550
1
                compile_expr(e->as.block.stmts[e->as.block.count - 1]->as.expr, line);
551
1
                end_scope_preserve_tos(line);
552
2
            } else {
553
7
                for (size_t i = 0; i < e->as.block.count; i++)
554
5
                    compile_stmt_reset(e->as.block.stmts[i]);
555
2
                end_scope(line);
556
2
                emit_byte(OP_UNIT, line);
557
2
            }
558
3
            break;
559
190
        }
560
561
5.36k
        case EXPR_CALL: {
562
            /* Intercept phase system special forms */
563
5.36k
            if (e->as.call.func->tag == EXPR_IDENT) {
564
5.36k
                const char *fn = e->as.call.func->as.str_val;
565
566
5.36k
                if (strcmp(fn, "react") == 0 && e->as.call.arg_count == 2 &&
567
5.36k
                    e->as.call.args[0]->tag == EXPR_IDENT) {
568
9
                    const char *var_name = e->as.call.args[0]->as.str_val;
569
9
                    compile_expr(e->as.call.args[1], line);
570
9
                    size_t idx = chunk_add_constant(current_chunk(), value_string(var_name));
571
9
                    emit_bytes(OP_REACT, (uint8_t)idx, line);
572
9
                    break;
573
9
                }
574
5.35k
                if (strcmp(fn, "unreact") == 0 && e->as.call.arg_count == 1 &&
575
5.35k
                    e->as.call.args[0]->tag == EXPR_IDENT) {
576
1
                    const char *var_name = e->as.call.args[0]->as.str_val;
577
1
                    size_t idx = chunk_add_constant(current_chunk(), value_string(var_name));
578
1
                    emit_bytes(OP_UNREACT, (uint8_t)idx, line);
579
1
                    break;
580
1
                }
581
5.35k
                if (strcmp(fn, "bond") == 0 && e->as.call.arg_count >= 2 &&
582
5.35k
                    e->as.call.args[0]->tag == EXPR_IDENT) {
583
15
                    const char *target = e->as.call.args[0]->as.str_val;
584
15
                    size_t target_idx = chunk_add_constant(current_chunk(), value_string(target));
585
                    /* Check if last arg is a string literal (strategy) */
586
15
                    size_t dep_end = e->as.call.arg_count;
587
15
                    const char *strategy = "mirror";
588
15
                    Expr *last_arg = e->as.call.args[e->as.call.arg_count - 1];
589
15
                    if (last_arg->tag == EXPR_STRING_LIT) {
590
3
                        strategy = last_arg->as.str_val;
591
3
                        dep_end--;
592
3
                    }
593
32
                    for (size_t i = 1; i < dep_end; i++) {
594
17
                        const char *dep_name = (e->as.call.args[i]->tag == EXPR_IDENT)
595
17
                            ? e->as.call.args[i]->as.str_val : "";
596
17
                        size_t dep_idx = chunk_add_constant(current_chunk(), value_string(dep_name));
597
17
                        emit_bytes(OP_CONSTANT, (uint8_t)dep_idx, line);
598
17
                        size_t strat_idx = chunk_add_constant(current_chunk(), value_string(strategy));
599
17
                        emit_bytes(OP_CONSTANT, (uint8_t)strat_idx, line);
600
17
                        emit_bytes(OP_BOND, (uint8_t)target_idx, line);
601
17
                        if (i + 1 < dep_end) emit_byte(OP_POP, line);
602
17
                    }
603
15
                    break;
604
15
                }
605
5.34k
                if (strcmp(fn, "unbond") == 0 && e->as.call.arg_count >= 2 &&
606
5.34k
                    e->as.call.args[0]->tag == EXPR_IDENT) {
607
1
                    const char *target = e->as.call.args[0]->as.str_val;
608
1
                    size_t target_idx = chunk_add_constant(current_chunk(), value_string(target));
609
2
                    for (size_t i = 1; i < e->as.call.arg_count; i++) {
610
1
                        const char *dep_name = (e->as.call.args[i]->tag == EXPR_IDENT)
611
1
                            ? e->as.call.args[i]->as.str_val : "";
612
1
                        size_t dep_idx = chunk_add_constant(current_chunk(), value_string(dep_name));
613
1
                        emit_bytes(OP_CONSTANT, (uint8_t)dep_idx, line);
614
1
                        emit_bytes(OP_UNBOND, (uint8_t)target_idx, line);
615
1
                        if (i + 1 < e->as.call.arg_count) emit_byte(OP_POP, line);
616
1
                    }
617
1
                    break;
618
1
                }
619
5.34k
                if (strcmp(fn, "seed") == 0 && e->as.call.arg_count == 2 &&
620
5.34k
                    e->as.call.args[0]->tag == EXPR_IDENT) {
621
4
                    const char *var_name = e->as.call.args[0]->as.str_val;
622
4
                    compile_expr(e->as.call.args[1], line);
623
4
                    size_t idx = chunk_add_constant(current_chunk(), value_string(var_name));
624
4
                    emit_bytes(OP_SEED, (uint8_t)idx, line);
625
4
                    break;
626
4
                }
627
5.33k
                if (strcmp(fn, "unseed") == 0 && e->as.call.arg_count == 1 &&
628
5.33k
                    e->as.call.args[0]->tag == EXPR_IDENT) {
629
1
                    const char *var_name = e->as.call.args[0]->as.str_val;
630
1
                    size_t idx = chunk_add_constant(current_chunk(), value_string(var_name));
631
1
                    emit_bytes(OP_UNSEED, (uint8_t)idx, line);
632
1
                    break;
633
1
                }
634
                /* track(var) / history(var) / phases(var): pass var name as string */
635
5.33k
                if ((strcmp(fn, "track") == 0 || strcmp(fn, "history") == 0 ||
636
5.33k
                     strcmp(fn, "phases") == 0) &&
637
5.33k
                    e->as.call.arg_count == 1 &&
638
5.33k
                    e->as.call.args[0]->tag == EXPR_IDENT) {
639
7
                    compile_expr(e->as.call.func, line);
640
7
                    emit_constant(value_string(e->as.call.args[0]->as.str_val), line);
641
7
                    emit_bytes(OP_CALL, 1, line);
642
7
                    break;
643
7
                }
644
                /* rewind(var, n): pass var name as string, compile second arg normally */
645
5.32k
                if (strcmp(fn, "rewind") == 0 && e->as.call.arg_count == 2 &&
646
5.32k
                    e->as.call.args[0]->tag == EXPR_IDENT) {
647
3
                    compile_expr(e->as.call.func, line);
648
3
                    emit_constant(value_string(e->as.call.args[0]->as.str_val), line);
649
3
                    compile_expr(e->as.call.args[1], line);
650
3
                    emit_bytes(OP_CALL, 2, line);
651
3
                    break;
652
3
                }
653
                /* pressurize(var, mode) / depressurize(var): pass var name as string */
654
5.32k
                if (strcmp(fn, "pressurize") == 0 && e->as.call.arg_count == 2 &&
655
5.32k
                    e->as.call.args[0]->tag == EXPR_IDENT) {
656
6
                    compile_expr(e->as.call.func, line);
657
6
                    emit_constant(value_string(e->as.call.args[0]->as.str_val), line);
658
6
                    compile_expr(e->as.call.args[1], line);
659
6
                    emit_bytes(OP_CALL, 2, line);
660
6
                    break;
661
6
                }
662
5.32k
                if (strcmp(fn, "depressurize") == 0 && e->as.call.arg_count == 1 &&
663
5.32k
                    e->as.call.args[0]->tag == EXPR_IDENT) {
664
1
                    compile_expr(e->as.call.func, line);
665
1
                    emit_constant(value_string(e->as.call.args[0]->as.str_val), line);
666
1
                    emit_bytes(OP_CALL, 1, line);
667
1
                    break;
668
1
                }
669
5.32k
            }
670
5.31k
            compile_expr(e->as.call.func, line);
671
12.4k
            for (size_t i = 0; i < e->as.call.arg_count; i++)
672
7.15k
                compile_expr(e->as.call.args[i], line);
673
5.31k
            emit_bytes(OP_CALL, (uint8_t)e->as.call.arg_count, line);
674
5.31k
            break;
675
5.36k
        }
676
677
623
        case EXPR_ARRAY: {
678
623
            bool has_spread = false;
679
1.69k
            for (size_t i = 0; i < e->as.array.count; i++) {
680
1.07k
                if (e->as.array.elems[i]->tag == EXPR_SPREAD) {
681
6
                    has_spread = true;
682
6
                    break;
683
6
                }
684
1.07k
            }
685
1.70k
            for (size_t i = 0; i < e->as.array.count; i++)
686
1.08k
                compile_expr(e->as.array.elems[i], line);
687
623
            emit_bytes(OP_BUILD_ARRAY, (uint8_t)e->as.array.count, line);
688
623
            if (has_spread)
689
6
                emit_byte(OP_ARRAY_FLATTEN, line);
690
623
            break;
691
5.36k
        }
692
693
10
        case EXPR_RANGE: {
694
10
            compile_expr(e->as.range.start, line);
695
10
            compile_expr(e->as.range.end, line);
696
10
            emit_byte(OP_BUILD_RANGE, line);
697
10
            break;
698
5.36k
        }
699
700
11
        case EXPR_TUPLE: {
701
42
            for (size_t i = 0; i < e->as.tuple.count; i++)
702
31
                compile_expr(e->as.tuple.elems[i], line);
703
11
            emit_bytes(OP_BUILD_TUPLE, (uint8_t)e->as.tuple.count, line);
704
11
            break;
705
5.36k
        }
706
707
978
        case EXPR_INDEX: {
708
978
            compile_expr(e->as.index.object, line);
709
978
            size_t end_jump = 0;
710
978
            if (e->as.index.optional) {
711
1
                size_t skip = emit_jump(OP_JUMP_IF_NOT_NIL, line);
712
1
                end_jump = emit_jump(OP_JUMP, line);
713
1
                patch_jump(skip);
714
1
            }
715
978
            compile_expr(e->as.index.index, line);
716
978
            emit_byte(OP_INDEX, line);
717
978
            if (e->as.index.optional)
718
1
                patch_jump(end_jump);
719
978
            break;
720
5.36k
        }
721
722
73
        case EXPR_FIELD_ACCESS: {
723
73
            compile_expr(e->as.field_access.object, line);
724
73
            size_t end_jump = 0;
725
73
            if (e->as.field_access.optional) {
726
8
                size_t skip = emit_jump(OP_JUMP_IF_NOT_NIL, line);
727
8
                end_jump = emit_jump(OP_JUMP, line);
728
8
                patch_jump(skip);
729
8
            }
730
73
            size_t idx = chunk_add_constant(current_chunk(), value_string(e->as.field_access.field));
731
73
            emit_bytes(OP_GET_FIELD, (uint8_t)idx, line);
732
73
            if (e->as.field_access.optional)
733
8
                patch_jump(end_jump);
734
73
            break;
735
5.36k
        }
736
737
5.22k
        case EXPR_METHOD_CALL: {
738
5.22k
            bool opt = e->as.method_call.optional;
739
            /* If object is a local variable, use OP_INVOKE_LOCAL to mutate in-place */
740
5.22k
            if (e->as.method_call.object->tag == EXPR_IDENT) {
741
5.09k
                int slot = resolve_local(current, e->as.method_call.object->as.str_val);
742
5.09k
                if (slot >= 0) {
743
4.88k
                    size_t end_jump = 0;
744
4.88k
                    if (opt) {
745
                        /* Check the local for nil before invoking */
746
1
                        emit_bytes(OP_GET_LOCAL, (uint8_t)slot, line);
747
1
                        size_t skip = emit_jump(OP_JUMP_IF_NOT_NIL, line);
748
                        /* Is nil — pop the peeked value, push nil as result */
749
1
                        emit_byte(OP_POP, line);
750
1
                        emit_byte(OP_NIL, line);
751
1
                        end_jump = emit_jump(OP_JUMP, line);
752
1
                        patch_jump(skip);
753
1
                        emit_byte(OP_POP, line); /* pop the peeked non-nil value */
754
1
                    }
755
10.6k
                    for (size_t i = 0; i < e->as.method_call.arg_count; i++)
756
5.72k
                        compile_expr(e->as.method_call.args[i], line);
757
4.88k
                    size_t idx = chunk_add_constant(current_chunk(), value_string(e->as.method_call.method));
758
4.88k
                    emit_byte(OP_INVOKE_LOCAL, line);
759
4.88k
                    emit_byte((uint8_t)slot, line);
760
4.88k
                    emit_byte((uint8_t)idx, line);
761
4.88k
                    emit_byte((uint8_t)e->as.method_call.arg_count, line);
762
4.88k
                    if (opt) patch_jump(end_jump);
763
4.88k
                    break;
764
4.88k
                }
765
                /* If not a local and not an upvalue, it's a global —
766
                 * use OP_INVOKE_GLOBAL for write-back of mutating builtins */
767
210
                int upvalue = resolve_upvalue(current, e->as.method_call.object->as.str_val);
768
210
                if (upvalue < 0) {
769
210
                    size_t end_jump = 0;
770
210
                    if (opt) {
771
0
                        size_t tmp_idx = chunk_add_constant(current_chunk(),
772
0
                            value_string(e->as.method_call.object->as.str_val));
773
0
                        emit_constant_idx(OP_GET_GLOBAL, OP_GET_GLOBAL_16, tmp_idx, line);
774
0
                        size_t skip = emit_jump(OP_JUMP_IF_NOT_NIL, line);
775
0
                        emit_byte(OP_POP, line);
776
0
                        emit_byte(OP_NIL, line);
777
0
                        end_jump = emit_jump(OP_JUMP, line);
778
0
                        patch_jump(skip);
779
0
                        emit_byte(OP_POP, line);
780
0
                    }
781
478
                    for (size_t i = 0; i < e->as.method_call.arg_count; i++)
782
268
                        compile_expr(e->as.method_call.args[i], line);
783
210
                    size_t name_idx = chunk_add_constant(current_chunk(),
784
210
                        value_string(e->as.method_call.object->as.str_val));
785
210
                    size_t method_idx = chunk_add_constant(current_chunk(),
786
210
                        value_string(e->as.method_call.method));
787
210
                    emit_byte(OP_INVOKE_GLOBAL, line);
788
210
                    emit_byte((uint8_t)name_idx, line);
789
210
                    emit_byte((uint8_t)method_idx, line);
790
210
                    emit_byte((uint8_t)e->as.method_call.arg_count, line);
791
210
                    if (opt) patch_jump(end_jump);
792
210
                    break;
793
210
                }
794
210
            }
795
133
            compile_expr(e->as.method_call.object, line);
796
133
            size_t end_jump = 0;
797
133
            if (opt) {
798
0
                size_t skip = emit_jump(OP_JUMP_IF_NOT_NIL, line);
799
0
                end_jump = emit_jump(OP_JUMP, line);
800
0
                patch_jump(skip);
801
0
            }
802
196
            for (size_t i = 0; i < e->as.method_call.arg_count; i++)
803
63
                compile_expr(e->as.method_call.args[i], line);
804
133
            size_t idx = chunk_add_constant(current_chunk(), value_string(e->as.method_call.method));
805
133
            emit_byte(OP_INVOKE, line);
806
133
            emit_byte((uint8_t)idx, line);
807
133
            emit_byte((uint8_t)e->as.method_call.arg_count, line);
808
133
            if (opt) patch_jump(end_jump);
809
133
            break;
810
5.22k
        }
811
812
46
        case EXPR_STRUCT_LIT: {
813
            /* Push field values in order */
814
134
            for (size_t i = 0; i < e->as.struct_lit.field_count; i++)
815
88
                compile_expr(e->as.struct_lit.fields[i].value, line);
816
46
            size_t name_idx = chunk_add_constant(current_chunk(), value_string(e->as.struct_lit.name));
817
46
            emit_byte(OP_BUILD_STRUCT, line);
818
46
            emit_byte((uint8_t)name_idx, line);
819
46
            emit_byte((uint8_t)e->as.struct_lit.field_count, line);
820
            /* Also store field names in constants for the VM to use */
821
134
            for (size_t i = 0; i < e->as.struct_lit.field_count; i++)
822
88
                chunk_add_constant(current_chunk(), value_string(e->as.struct_lit.fields[i].name));
823
46
            break;
824
5.22k
        }
825
826
646
        case EXPR_CLOSURE: {
827
            /* Compile as anonymous function */
828
646
            Compiler func_comp;
829
646
            compiler_init(&func_comp, current, FUNC_CLOSURE);
830
646
            func_comp.arity = (int)e->as.closure.param_count;
831
832
            /* Add params as locals */
833
1.35k
            for (size_t i = 0; i < e->as.closure.param_count; i++)
834
712
                add_local(e->as.closure.params[i]);
835
836
            /* Compile body - if the body is a block, compile statements directly
837
             * at the closure's scope level (no extra begin_scope/end_scope) so
838
             * that OP_RETURN handles cleanup and the result value isn't lost. */
839
646
            if (e->as.closure.body->tag == EXPR_BLOCK) {
840
623
                Expr *block = e->as.closure.body;
841
623
                if (block->as.block.count > 0 &&
842
623
                    block->as.block.stmts[block->as.block.count - 1]->tag == STMT_EXPR) {
843
136
                    for (size_t i = 0; i + 1 < block->as.block.count; i++)
844
10
                        compile_stmt(block->as.block.stmts[i]);
845
126
                    compile_expr(block->as.block.stmts[block->as.block.count - 1]->as.expr, line);
846
497
                } else {
847
1.86k
                    for (size_t i = 0; i < block->as.block.count; i++)
848
1.36k
                        compile_stmt(block->as.block.stmts[i]);
849
497
                    emit_byte(OP_UNIT, line);
850
497
                }
851
623
            } else {
852
23
                compile_expr(e->as.closure.body, line);
853
23
            }
854
646
            emit_byte(OP_RETURN, line);
855
856
646
            Chunk *fn_chunk = func_comp.chunk;
857
646
            size_t upvalue_count = func_comp.upvalue_count;
858
646
            CompilerUpvalue *upvalues = NULL;
859
646
            if (upvalue_count > 0) {
860
524
                upvalues = malloc(upvalue_count * sizeof(CompilerUpvalue));
861
524
                memcpy(upvalues, func_comp.upvalues, upvalue_count * sizeof(CompilerUpvalue));
862
524
            }
863
864
            /* Store default parameter values and variadic flag on the chunk */
865
646
            {
866
646
                int dc = 0;
867
646
                bool vd = e->as.closure.has_variadic;
868
646
                if (e->as.closure.default_values) {
869
199
                    for (size_t i = 0; i < e->as.closure.param_count; i++) {
870
103
                        if (e->as.closure.default_values[i]) dc++;
871
103
                    }
872
96
                }
873
646
                fn_chunk->default_count = dc;
874
646
                fn_chunk->fn_has_variadic = vd;
875
646
                if (dc > 0) {
876
5
                    fn_chunk->default_values = malloc(dc * sizeof(LatValue));
877
5
                    int di = 0;
878
16
                    for (size_t i = 0; i < e->as.closure.param_count; i++) {
879
11
                        if (e->as.closure.default_values && e->as.closure.default_values[i]) {
880
7
                            fn_chunk->default_values[di++] = const_eval_expr(e->as.closure.default_values[i]);
881
7
                        }
882
11
                    }
883
5
                }
884
646
            }
885
886
646
            compiler_cleanup(&func_comp);
887
646
            current = func_comp.enclosing;
888
889
            /* Store the function's chunk as a constant in the enclosing chunk.
890
             * We use a closure value with body=NULL to indicate a compiled function. */
891
646
            LatValue fn_val;
892
646
            memset(&fn_val, 0, sizeof(fn_val));
893
646
            fn_val.type = VAL_CLOSURE;
894
646
            fn_val.phase = VTAG_UNPHASED;
895
646
            fn_val.region_id = (size_t)-1;
896
646
            if (e->as.closure.param_count > 0) {
897
646
                fn_val.as.closure.param_names = malloc(e->as.closure.param_count * sizeof(char *));
898
1.35k
                for (size_t i = 0; i < e->as.closure.param_count; i++)
899
712
                    fn_val.as.closure.param_names[i] = strdup(e->as.closure.params[i]);
900
646
            } else {
901
0
                fn_val.as.closure.param_names = NULL;
902
0
            }
903
646
            fn_val.as.closure.param_count = (size_t)func_comp.arity;
904
646
            fn_val.as.closure.body = NULL;
905
646
            fn_val.as.closure.captured_env = NULL;
906
646
            fn_val.as.closure.default_values = NULL;
907
646
            fn_val.as.closure.has_variadic = false;
908
646
            fn_val.as.closure.native_fn = fn_chunk;
909
646
            size_t fn_idx = chunk_add_constant(current_chunk(), fn_val);
910
911
646
            if (fn_idx <= 255) {
912
646
                emit_byte(OP_CLOSURE, line);
913
646
                emit_byte((uint8_t)fn_idx, line);
914
646
            } else {
915
0
                emit_byte(OP_CLOSURE_16, line);
916
0
                emit_byte((uint8_t)((fn_idx >> 8) & 0xff), line);
917
0
                emit_byte((uint8_t)(fn_idx & 0xff), line);
918
0
            }
919
646
            emit_byte((uint8_t)upvalue_count, line);
920
1.57k
            for (size_t i = 0; i < upvalue_count; i++) {
921
930
                emit_byte(upvalues[i].is_local ? 1 : 0, line);
922
930
                emit_byte(upvalues[i].index, line);
923
930
            }
924
646
            free(upvalues);
925
646
            break;
926
5.22k
        }
927
928
15
        case EXPR_MATCH: {
929
15
            compile_expr(e->as.match_expr.scrutinee, line);
930
15
            size_t *end_jumps = malloc(e->as.match_expr.arm_count * sizeof(size_t));
931
15
            size_t end_jump_count = 0;
932
933
            /* Stack invariant: scrutinee S stays on stack across all arms.
934
             * Non-binding: DUP -> [S, S']. Check -> [S, S', bool].
935
             *   Match: pop bool+S'+S, body -> [result].
936
             *   No match: pop bool+S' -> [S], next arm.
937
             * Binding: DUP -> [S, S']. S' becomes local, guard references it.
938
             *   Match: body -> [S, n, result], swap+end_scope+swap+pop -> [result].
939
             *   No match: pop guard_bool, pop n -> [S], next arm. */
940
941
49
            for (size_t i = 0; i < e->as.match_expr.arm_count; i++) {
942
34
                MatchArm *arm = &e->as.match_expr.arms[i];
943
944
34
                if (arm->pattern->tag == PAT_BINDING) {
945
                    /* Binding: Track scrutinee with a dummy local so that the
946
                     * binding variable and body-locals get correct slot indices.
947
                     * Stack: [..., S]. After DUP: [..., S, S']. */
948
5
                    begin_scope();
949
5
                    add_local("");  /* dummy slot tracks scrutinee S */
950
5
                    emit_byte(OP_DUP, line); /* [S, S'] */
951
5
                    add_local(arm->pattern->as.binding_name);
952
953
                    /* Compile guard (or TRUE if none) */
954
5
                    if (arm->guard)
955
2
                        compile_expr(arm->guard, line); /* [S, n, guard_bool] */
956
3
                    else
957
3
                        emit_byte(OP_TRUE, line);       /* [S, n, true] */
958
959
5
                    size_t next_arm = emit_jump(OP_JUMP_IF_FALSE, line);
960
5
                    emit_byte(OP_POP, line); /* pop bool: [S, n] */
961
962
                    /* Compile arm body in a nested scope so body-locals
963
                     * are cleaned up before the binding variable. */
964
5
                    begin_scope();
965
5
                    if (arm->body_count > 0 &&
966
5
                        arm->body[arm->body_count - 1]->tag == STMT_EXPR) {
967
6
                        for (size_t j = 0; j + 1 < arm->body_count; j++)
968
1
                            compile_stmt_reset(arm->body[j]);
969
5
                        compile_expr(arm->body[arm->body_count - 1]->as.expr, line);
970
5
                        end_scope_preserve_tos(line);
971
5
                    } else {
972
0
                        for (size_t j = 0; j < arm->body_count; j++)
973
0
                            compile_stmt_reset(arm->body[j]);
974
0
                        end_scope(line);
975
0
                        emit_byte(OP_UNIT, line);
976
0
                    }
977
978
                    /* Stack: [S(dummy), n(binding), result].
979
                     * Swap result past n, then pop n and S via end_scope. */
980
5
                    emit_byte(OP_SWAP, line);  /* [S, result, n] */
981
5
                    emit_byte(OP_POP, line);   /* [S, result] — pop n manually */
982
5
                    emit_byte(OP_SWAP, line);  /* [result, S] */
983
5
                    emit_byte(OP_POP, line);   /* [result] — pop S manually */
984
                    /* Remove locals from compiler without emitting more pops */
985
5
                    current->scope_depth--;
986
15
                    while (current->local_count > 0 &&
987
15
                           current->locals[current->local_count - 1].depth > current->scope_depth) {
988
10
                        free(current->locals[current->local_count - 1].name);
989
10
                        current->local_count--;
990
10
                    }
991
992
5
                    end_jumps[end_jump_count++] = emit_jump(OP_JUMP, line);
993
994
5
                    patch_jump(next_arm);
995
                    /* JUMP_IF_FALSE doesn't pop: [S, n, false] */
996
5
                    emit_byte(OP_POP, line); /* pop false: [S, n] */
997
5
                    emit_byte(OP_POP, line); /* pop n: [S] */
998
29
                } else {
999
                    /* Non-binding: LITERAL, WILDCARD, RANGE */
1000
29
                    emit_byte(OP_DUP, line); /* [S, S'] */
1001
1002
                    /* Pattern check — all leave [S, S', bool] */
1003
29
                    switch (arm->pattern->tag) {
1004
9
                        case PAT_LITERAL:
1005
9
                            emit_byte(OP_DUP, line); /* [S, S', S''] */
1006
9
                            compile_expr(arm->pattern->as.literal, line);
1007
9
                            emit_byte(OP_EQ, line);  /* [S, S', bool] */
1008
9
                            break;
1009
18
                        case PAT_WILDCARD:
1010
18
                            if (arm->pattern->phase_qualifier == PHASE_FLUID) {
1011
                                /* fluid _ => check phase is NOT crystal */
1012
3
                                emit_byte(OP_DUP, line);       /* [S, S', S''] */
1013
3
                                emit_byte(OP_IS_CRYSTAL, line); /* [S, S', is_crystal] */
1014
3
                                emit_byte(OP_NOT, line);       /* [S, S', !is_crystal] */
1015
15
                            } else if (arm->pattern->phase_qualifier == PHASE_CRYSTAL) {
1016
                                /* crystal _ => check phase IS crystal */
1017
2
                                emit_byte(OP_DUP, line);       /* [S, S', S''] */
1018
2
                                emit_byte(OP_IS_CRYSTAL, line); /* [S, S', is_crystal] */
1019
13
                            } else {
1020
13
                                emit_byte(OP_TRUE, line); /* [S, S', true] */
1021
13
                            }
1022
18
                            break;
1023
2
                        case PAT_RANGE:
1024
2
                            emit_byte(OP_DUP, line);
1025
2
                            compile_expr(arm->pattern->as.range.start, line);
1026
2
                            emit_byte(OP_GTEQ, line);
1027
2
                            {
1028
2
                                size_t range_fail = emit_jump(OP_JUMP_IF_FALSE, line);
1029
2
                                emit_byte(OP_POP, line);
1030
2
                                emit_byte(OP_DUP, line);
1031
2
                                compile_expr(arm->pattern->as.range.end, line);
1032
2
                                emit_byte(OP_LTEQ, line);
1033
2
                                size_t range_done = emit_jump(OP_JUMP, line);
1034
2
                                patch_jump(range_fail);
1035
2
                                patch_jump(range_done);
1036
2
                            }
1037
2
                            break;
1038
0
                        default:
1039
0
                            break;
1040
29
                    }
1041
1042
                    /* Guard */
1043
29
                    if (arm->guard) {
1044
0
                        size_t guard_skip = emit_jump(OP_JUMP_IF_FALSE, line);
1045
0
                        emit_byte(OP_POP, line);
1046
0
                        compile_expr(arm->guard, line);
1047
0
                        size_t guard_done = emit_jump(OP_JUMP, line);
1048
0
                        patch_jump(guard_skip);
1049
0
                        patch_jump(guard_done);
1050
0
                    }
1051
1052
29
                    size_t next_arm = emit_jump(OP_JUMP_IF_FALSE, line);
1053
29
                    emit_byte(OP_POP, line); /* pop bool: [S, S'] */
1054
29
                    emit_byte(OP_POP, line); /* pop S': [S] */
1055
29
                    emit_byte(OP_POP, line); /* pop S: [] */
1056
1057
                    /* Compile arm body */
1058
29
                    if (arm->body_count > 0 &&
1059
29
                        arm->body[arm->body_count - 1]->tag == STMT_EXPR) {
1060
29
                        for (size_t j = 0; j + 1 < arm->body_count; j++)
1061
0
                            compile_stmt_reset(arm->body[j]);
1062
29
                        compile_expr(arm->body[arm->body_count - 1]->as.expr, line);
1063
29
                    } else {
1064
0
                        for (size_t j = 0; j < arm->body_count; j++)
1065
0
                            compile_stmt_reset(arm->body[j]);
1066
0
                        emit_byte(OP_UNIT, line);
1067
0
                    }
1068
1069
29
                    end_jumps[end_jump_count++] = emit_jump(OP_JUMP, line);
1070
1071
29
                    patch_jump(next_arm);
1072
                    /* JUMP_IF_FALSE doesn't pop: [S, S', false] */
1073
29
                    emit_byte(OP_POP, line); /* pop false: [S, S'] */
1074
29
                    emit_byte(OP_POP, line); /* pop S': [S] */
1075
29
                }
1076
34
            }
1077
1078
            /* No arm matched - pop scrutinee, push nil */
1079
15
            emit_byte(OP_POP, line);
1080
15
            emit_byte(OP_NIL, line);
1081
1082
49
            for (size_t i = 0; i < end_jump_count; i++)
1083
34
                patch_jump(end_jumps[i]);
1084
15
            free(end_jumps);
1085
15
            break;
1086
15
        }
1087
1088
105
        case EXPR_TRY_CATCH: {
1089
105
            size_t handler_jump = emit_jump(OP_PUSH_EXCEPTION_HANDLER, line);
1090
1091
            /* Save compiler state before try body so we can restore it
1092
             * for the catch path (the error handler resets the stack). */
1093
105
            size_t saved_local_count = current->local_count;
1094
105
            int saved_scope_depth = current->scope_depth;
1095
1096
            /* Try body — if last stmt is an expression, use its value */
1097
105
            if (e->as.try_catch.try_count > 0 &&
1098
105
                e->as.try_catch.try_stmts[e->as.try_catch.try_count - 1]->tag == STMT_EXPR) {
1099
136
                for (size_t i = 0; i + 1 < e->as.try_catch.try_count; i++)
1100
31
                    compile_stmt(e->as.try_catch.try_stmts[i]);
1101
105
                compile_expr(e->as.try_catch.try_stmts[e->as.try_catch.try_count - 1]->as.expr, line);
1102
105
            } else {
1103
0
                for (size_t i = 0; i < e->as.try_catch.try_count; i++)
1104
0
                    compile_stmt(e->as.try_catch.try_stmts[i]);
1105
0
                emit_byte(OP_UNIT, line);
1106
0
            }
1107
1108
105
            emit_byte(OP_POP_EXCEPTION_HANDLER, line);
1109
105
            size_t end_jump = emit_jump(OP_JUMP, line);
1110
1111
            /* Catch block — restore compiler state to match the stack
1112
             * state after the error handler fires (stack reset + error pushed). */
1113
105
            patch_jump(handler_jump);
1114
            /* Free any locals created in the try body */
1115
109
            while (current->local_count > saved_local_count) {
1116
4
                free(current->locals[--current->local_count].name);
1117
4
            }
1118
105
            current->scope_depth = saved_scope_depth;
1119
1120
            /* Error value is on stack */
1121
105
            if (e->as.try_catch.catch_var) {
1122
105
                add_local(e->as.try_catch.catch_var);
1123
105
                size_t catch_slot = current->local_count - 1;
1124
1125
105
                if (e->as.try_catch.catch_count > 0 &&
1126
105
                    e->as.try_catch.catch_stmts[e->as.try_catch.catch_count - 1]->tag == STMT_EXPR) {
1127
76
                    for (size_t i = 0; i + 1 < e->as.try_catch.catch_count; i++)
1128
0
                        compile_stmt(e->as.try_catch.catch_stmts[i]);
1129
76
                    compile_expr(e->as.try_catch.catch_stmts[e->as.try_catch.catch_count - 1]->as.expr, line);
1130
76
                } else {
1131
58
                    for (size_t i = 0; i < e->as.try_catch.catch_count; i++)
1132
29
                        compile_stmt(e->as.try_catch.catch_stmts[i]);
1133
29
                    emit_byte(OP_UNIT, line);
1134
29
                }
1135
1136
                /* Result is on TOS, catch var is below. Overwrite catch var
1137
                 * with the result, pop the extra copy, then remove the local. */
1138
105
                emit_bytes(OP_SET_LOCAL, (uint8_t)catch_slot, line);
1139
105
                emit_byte(OP_POP, line);
1140
105
                free(current->locals[--current->local_count].name);
1141
105
            } else {
1142
0
                emit_byte(OP_POP, line);
1143
1144
0
                if (e->as.try_catch.catch_count > 0 &&
1145
0
                    e->as.try_catch.catch_stmts[e->as.try_catch.catch_count - 1]->tag == STMT_EXPR) {
1146
0
                    for (size_t i = 0; i + 1 < e->as.try_catch.catch_count; i++)
1147
0
                        compile_stmt(e->as.try_catch.catch_stmts[i]);
1148
0
                    compile_expr(e->as.try_catch.catch_stmts[e->as.try_catch.catch_count - 1]->as.expr, line);
1149
0
                } else {
1150
0
                    for (size_t i = 0; i < e->as.try_catch.catch_count; i++)
1151
0
                        compile_stmt(e->as.try_catch.catch_stmts[i]);
1152
0
                    emit_byte(OP_UNIT, line);
1153
0
                }
1154
0
            }
1155
1156
105
            patch_jump(end_jump);
1157
105
            break;
1158
15
        }
1159
1160
6
        case EXPR_TRY_PROPAGATE: {
1161
6
            compile_expr(e->as.try_propagate_expr, line);
1162
6
            emit_byte(OP_TRY_UNWRAP, line);
1163
6
            break;
1164
15
        }
1165
1166
16
        case EXPR_INTERP_STRING: {
1167
            /* Build interpolated string: part0 + expr0 + part1 + expr1 + ... + partN */
1168
16
            bool first = true;
1169
42
            for (size_t i = 0; i < e->as.interp.count; i++) {
1170
                /* String part before expression */
1171
26
                if (e->as.interp.parts[i] && e->as.interp.parts[i][0] != '\0') {
1172
14
                    emit_constant(value_string(e->as.interp.parts[i]), line);
1173
14
                    if (!first) emit_byte(OP_ADD, line);
1174
14
                    first = false;
1175
14
                }
1176
                /* Expression */
1177
26
                compile_expr(e->as.interp.exprs[i], line);
1178
26
                if (!first) emit_byte(OP_ADD, line);
1179
26
                first = false;
1180
26
            }
1181
            /* Final string part */
1182
16
            if (e->as.interp.parts[e->as.interp.count] &&
1183
16
                e->as.interp.parts[e->as.interp.count][0] != '\0') {
1184
2
                emit_constant(value_string(e->as.interp.parts[e->as.interp.count]), line);
1185
2
                if (!first) emit_byte(OP_ADD, line);
1186
2
                first = false;
1187
2
            }
1188
16
            if (first) {
1189
                /* Empty interpolated string */
1190
0
                emit_constant(value_string(""), line);
1191
0
            }
1192
16
            break;
1193
15
        }
1194
1195
548
        case EXPR_ENUM_VARIANT: {
1196
548
            if (!is_known_enum(e->as.enum_variant.enum_name)) {
1197
                /* Not a declared enum — fall back to global function call
1198
                 * e.g. Map::new() calls the "Map::new" builtin */
1199
534
                char key[256];
1200
534
                snprintf(key, sizeof(key), "%s::%s",
1201
534
                         e->as.enum_variant.enum_name, e->as.enum_variant.variant_name);
1202
534
                size_t fn_idx = chunk_add_constant(current_chunk(), value_string(key));
1203
534
                emit_constant_idx(OP_GET_GLOBAL, OP_GET_GLOBAL_16, fn_idx, line);
1204
579
                for (size_t i = 0; i < e->as.enum_variant.arg_count; i++)
1205
45
                    compile_expr(e->as.enum_variant.args[i], line);
1206
534
                emit_bytes(OP_CALL, (uint8_t)e->as.enum_variant.arg_count, line);
1207
534
                break;
1208
534
            }
1209
20
            for (size_t i = 0; i < e->as.enum_variant.arg_count; i++)
1210
6
                compile_expr(e->as.enum_variant.args[i], line);
1211
14
            size_t enum_idx = chunk_add_constant(current_chunk(), value_string(e->as.enum_variant.enum_name));
1212
14
            size_t var_idx = chunk_add_constant(current_chunk(), value_string(e->as.enum_variant.variant_name));
1213
14
            emit_byte(OP_BUILD_ENUM, line);
1214
14
            emit_byte((uint8_t)enum_idx, line);
1215
14
            emit_byte((uint8_t)var_idx, line);
1216
14
            emit_byte((uint8_t)e->as.enum_variant.arg_count, line);
1217
14
            break;
1218
548
        }
1219
1220
83
        case EXPR_FREEZE: {
1221
            /* ── Partial freeze: freeze(s.field) for struct fields ── */
1222
83
            if (e->as.freeze.expr->tag == EXPR_FIELD_ACCESS) {
1223
3
                Expr *parent = e->as.freeze.expr->as.field_access.object;
1224
3
                const char *field = e->as.freeze.expr->as.field_access.field;
1225
3
                if (parent->tag == EXPR_IDENT) {
1226
3
                    const char *pname = parent->as.str_val;
1227
3
                    emit_constant(value_string(field), line);
1228
3
                    size_t pname_idx = chunk_add_constant(current_chunk(), value_string(pname));
1229
3
                    int slot = resolve_local(current, pname);
1230
3
                    emit_byte(OP_FREEZE_FIELD, line);
1231
3
                    emit_byte((uint8_t)pname_idx, line);
1232
3
                    if (slot >= 0) {
1233
3
                        emit_byte(0, line);
1234
3
                        emit_byte((uint8_t)slot, line);
1235
3
                    } else {
1236
0
                        int upvalue = resolve_upvalue(current, pname);
1237
0
                        if (upvalue >= 0) {
1238
0
                            emit_byte(1, line);
1239
0
                            emit_byte((uint8_t)upvalue, line);
1240
0
                        } else {
1241
0
                            emit_byte(2, line);
1242
0
                            emit_byte(0, line);
1243
0
                        }
1244
0
                    }
1245
3
                } else {
1246
                    /* Non-ident parent: fall back to OP_FREEZE on field value */
1247
0
                    compile_expr(e->as.freeze.expr, line);
1248
0
                    emit_byte(OP_FREEZE, line);
1249
0
                }
1250
3
                break;
1251
3
            }
1252
1253
            /* ── Partial freeze: freeze(m["key"]) for map keys ── */
1254
80
            if (e->as.freeze.expr->tag == EXPR_INDEX) {
1255
2
                Expr *parent = e->as.freeze.expr->as.index.object;
1256
2
                if (parent->tag == EXPR_IDENT) {
1257
2
                    const char *pname = parent->as.str_val;
1258
2
                    compile_expr(e->as.freeze.expr->as.index.index, line);
1259
2
                    size_t pname_idx = chunk_add_constant(current_chunk(), value_string(pname));
1260
2
                    int slot = resolve_local(current, pname);
1261
2
                    emit_byte(OP_FREEZE_FIELD, line);
1262
2
                    emit_byte((uint8_t)pname_idx, line);
1263
2
                    if (slot >= 0) {
1264
2
                        emit_byte(0, line);
1265
2
                        emit_byte((uint8_t)slot, line);
1266
2
                    } else {
1267
0
                        int upvalue = resolve_upvalue(current, pname);
1268
0
                        if (upvalue >= 0) {
1269
0
                            emit_byte(1, line);
1270
0
                            emit_byte((uint8_t)upvalue, line);
1271
0
                        } else {
1272
0
                            emit_byte(2, line);
1273
0
                            emit_byte(0, line);
1274
0
                        }
1275
0
                    }
1276
2
                } else {
1277
0
                    compile_expr(e->as.freeze.expr, line);
1278
0
                    emit_byte(OP_FREEZE, line);
1279
0
                }
1280
2
                break;
1281
2
            }
1282
1283
            /* ── Freeze-except: freeze(x) except ["field1", ...] ── */
1284
78
            if (e->as.freeze.except_count > 0 && e->as.freeze.expr->tag == EXPR_IDENT) {
1285
3
                const char *name = e->as.freeze.expr->as.str_val;
1286
3
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(name));
1287
6
                for (size_t i = 0; i < e->as.freeze.except_count; i++)
1288
3
                    compile_expr(e->as.freeze.except_fields[i], line);
1289
3
                emit_byte(OP_FREEZE_EXCEPT, line);
1290
3
                emit_byte((uint8_t)name_idx, line);
1291
3
                int slot = resolve_local(current, name);
1292
3
                if (slot >= 0) {
1293
3
                    emit_byte(0, line);
1294
3
                    emit_byte((uint8_t)slot, line);
1295
3
                } else {
1296
0
                    int upvalue = resolve_upvalue(current, name);
1297
0
                    if (upvalue >= 0) {
1298
0
                        emit_byte(1, line);
1299
0
                        emit_byte((uint8_t)upvalue, line);
1300
0
                    } else {
1301
0
                        emit_byte(2, line);
1302
0
                        emit_byte(0, line);
1303
0
                    }
1304
0
                }
1305
3
                emit_byte((uint8_t)e->as.freeze.except_count, line);
1306
3
                break;
1307
3
            }
1308
1309
            /* ── Normal freeze (with optional contract) ── */
1310
75
            compile_expr(e->as.freeze.expr, line);
1311
1312
            /* Freeze contract: freeze(x) where |v| { ... } */
1313
75
            if (e->as.freeze.contract && e->as.freeze.expr->tag == EXPR_IDENT) {
1314
                /* Stack: [value]. Handler saves this state. */
1315
6
                size_t handler_jump = emit_jump(OP_PUSH_EXCEPTION_HANDLER, line);
1316
6
                emit_byte(OP_DUP, line);
1317
6
                compile_expr(e->as.freeze.contract, line);
1318
6
                emit_byte(OP_SWAP, line);
1319
6
                emit_byte(OP_CALL, line);
1320
6
                emit_byte(1, line);
1321
6
                emit_byte(OP_POP, line);
1322
6
                emit_byte(OP_POP_EXCEPTION_HANDLER, line);
1323
6
                size_t past_catch = emit_jump(OP_JUMP, line);
1324
                /* Catch: stack restored to [value], then error pushed → [value, error] */
1325
6
                patch_jump(handler_jump);
1326
6
                {
1327
6
                    size_t prefix_idx = chunk_add_constant(current_chunk(),
1328
6
                        value_string("freeze contract failed: "));
1329
6
                    emit_byte(OP_CONSTANT, line);
1330
6
                    emit_byte((uint8_t)prefix_idx, line);
1331
6
                }
1332
6
                emit_byte(OP_SWAP, line);
1333
6
                emit_byte(OP_CONCAT, line);
1334
                /* [value, "freeze contract failed: <msg>"] → throw */
1335
6
                emit_byte(OP_SWAP, line);
1336
6
                emit_byte(OP_POP, line); /* pop value */
1337
6
                emit_byte(OP_THROW, line);
1338
6
                patch_jump(past_catch);
1339
                /* Stack: [value] — proceed with freeze */
1340
6
            }
1341
1342
75
            if (e->as.freeze.expr->tag == EXPR_IDENT) {
1343
48
                const char *name = e->as.freeze.expr->as.str_val;
1344
48
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(name));
1345
48
                int slot = resolve_local(current, name);
1346
48
                if (slot >= 0) {
1347
47
                    emit_byte(OP_FREEZE_VAR, line);
1348
47
                    emit_byte((uint8_t)name_idx, line);
1349
47
                    emit_byte(0, line);
1350
47
                    emit_byte((uint8_t)slot, line);
1351
47
                } else {
1352
1
                    int upvalue = resolve_upvalue(current, name);
1353
1
                    if (upvalue >= 0) {
1354
0
                        emit_byte(OP_FREEZE_VAR, line);
1355
0
                        emit_byte((uint8_t)name_idx, line);
1356
0
                        emit_byte(1, line);
1357
0
                        emit_byte((uint8_t)upvalue, line);
1358
1
                    } else {
1359
1
                        emit_byte(OP_FREEZE_VAR, line);
1360
1
                        emit_byte((uint8_t)name_idx, line);
1361
1
                        emit_byte(2, line);
1362
1
                        emit_byte(0, line);
1363
1
                    }
1364
1
                }
1365
48
            } else {
1366
27
                emit_byte(OP_FREEZE, line);
1367
27
            }
1368
75
            break;
1369
78
        }
1370
1371
14
        case EXPR_THAW: {
1372
14
            compile_expr(e->as.freeze_expr, line);
1373
14
            if (e->as.freeze_expr->tag == EXPR_IDENT) {
1374
14
                const char *name = e->as.freeze_expr->as.str_val;
1375
14
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(name));
1376
14
                int slot = resolve_local(current, name);
1377
14
                if (slot >= 0) {
1378
13
                    emit_byte(OP_THAW_VAR, line);
1379
13
                    emit_byte((uint8_t)name_idx, line);
1380
13
                    emit_byte(0, line); /* loc_type = local */
1381
13
                    emit_byte((uint8_t)slot, line);
1382
13
                } else {
1383
1
                    int upvalue = resolve_upvalue(current, name);
1384
1
                    if (upvalue >= 0) {
1385
0
                        emit_byte(OP_THAW_VAR, line);
1386
0
                        emit_byte((uint8_t)name_idx, line);
1387
0
                        emit_byte(1, line); /* loc_type = upvalue */
1388
0
                        emit_byte((uint8_t)upvalue, line);
1389
1
                    } else {
1390
1
                        emit_byte(OP_THAW_VAR, line);
1391
1
                        emit_byte((uint8_t)name_idx, line);
1392
1
                        emit_byte(2, line); /* loc_type = global */
1393
1
                        emit_byte(0, line);
1394
1
                    }
1395
1
                }
1396
14
            } else {
1397
0
                emit_byte(OP_THAW, line);
1398
0
            }
1399
14
            break;
1400
78
        }
1401
1402
3
        case EXPR_CLONE: {
1403
3
            compile_expr(e->as.freeze_expr, line);
1404
3
            emit_byte(OP_CLONE, line);
1405
3
            break;
1406
78
        }
1407
1408
1
        case EXPR_FORGE: {
1409
1
            begin_scope();
1410
            /* If last stmt is an expression, use its value as the forge result */
1411
1
            if (e->as.block.count > 0 &&
1412
1
                e->as.block.stmts[e->as.block.count - 1]->tag == STMT_EXPR) {
1413
3
                for (size_t i = 0; i + 1 < e->as.block.count; i++)
1414
2
                    compile_stmt(e->as.block.stmts[i]);
1415
1
                compile_expr(e->as.block.stmts[e->as.block.count - 1]->as.expr, line);
1416
1
            } else {
1417
0
                for (size_t i = 0; i < e->as.block.count; i++)
1418
0
                    compile_stmt(e->as.block.stmts[i]);
1419
0
                emit_byte(OP_UNIT, line);
1420
0
            }
1421
1
            end_scope(line);
1422
1
            emit_byte(OP_FREEZE, line); /* auto-freeze the result */
1423
1
            break;
1424
78
        }
1425
1426
7
        case EXPR_ANNEAL: {
1427
            /* anneal(target, closure) - thaw, call closure, refreeze
1428
             * Must check: target is crystal; wrap closure errors with "anneal failed:" */
1429
1430
            /* Phase check: target must be crystal */
1431
7
            compile_expr(e->as.anneal.expr, line);   /* push target value */
1432
7
            emit_byte(OP_IS_CRYSTAL, line);           /* replace with bool */
1433
7
            size_t anneal_ok = emit_jump(OP_JUMP_IF_FALSE, line);
1434
            /* is_crystal=true → continue */
1435
7
            emit_byte(OP_POP, line);  /* pop true */
1436
7
            size_t anneal_past_check = emit_jump(OP_JUMP, line);
1437
            /* is_crystal=false → error */
1438
7
            patch_jump(anneal_ok);
1439
7
            emit_byte(OP_POP, line);  /* pop false */
1440
7
            {
1441
7
                size_t err_idx = chunk_add_constant(current_chunk(),
1442
7
                    value_string("anneal requires a crystal value"));
1443
7
                emit_bytes(OP_CONSTANT, (uint8_t)err_idx, line);
1444
7
                emit_byte(OP_THROW, line);
1445
7
            }
1446
7
            patch_jump(anneal_past_check);
1447
1448
            /* Wrap in try/catch for error prefix */
1449
7
            size_t anneal_handler = emit_jump(OP_PUSH_EXCEPTION_HANDLER, line);
1450
7
            size_t saved_lc = current->local_count;
1451
7
            int saved_sd = current->scope_depth;
1452
1453
7
            compile_expr(e->as.anneal.closure, line);
1454
7
            compile_expr(e->as.anneal.expr, line);
1455
7
            emit_byte(OP_THAW, line);
1456
7
            emit_bytes(OP_CALL, 1, line);
1457
7
            if (e->as.anneal.expr->tag == EXPR_IDENT) {
1458
6
                const char *name = e->as.anneal.expr->as.str_val;
1459
6
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(name));
1460
6
                int slot = resolve_local(current, name);
1461
6
                if (slot >= 0) {
1462
6
                    emit_byte(OP_FREEZE_VAR, line);
1463
6
                    emit_byte((uint8_t)name_idx, line);
1464
6
                    emit_byte(0, line);
1465
6
                    emit_byte((uint8_t)slot, line);
1466
6
                } else {
1467
0
                    int upvalue = resolve_upvalue(current, name);
1468
0
                    if (upvalue >= 0) {
1469
0
                        emit_byte(OP_FREEZE_VAR, line);
1470
0
                        emit_byte((uint8_t)name_idx, line);
1471
0
                        emit_byte(1, line);
1472
0
                        emit_byte((uint8_t)upvalue, line);
1473
0
                    } else {
1474
0
                        emit_byte(OP_FREEZE_VAR, line);
1475
0
                        emit_byte((uint8_t)name_idx, line);
1476
0
                        emit_byte(2, line);
1477
0
                        emit_byte(0, line);
1478
0
                    }
1479
0
                }
1480
6
            } else {
1481
1
                emit_byte(OP_FREEZE, line);
1482
1
            }
1483
1484
            /* End of try block — pop handler, jump past catch */
1485
7
            emit_byte(OP_POP_EXCEPTION_HANDLER, line);
1486
7
            size_t anneal_end = emit_jump(OP_JUMP, line);
1487
1488
            /* Catch block — wrap error with "anneal failed: " prefix */
1489
7
            patch_jump(anneal_handler);
1490
7
            while (current->local_count > saved_lc) {
1491
0
                free(current->locals[--current->local_count].name);
1492
0
            }
1493
7
            current->scope_depth = saved_sd;
1494
            /* Error string is on TOS. Concatenate with prefix. */
1495
7
            {
1496
7
                size_t prefix_idx = chunk_add_constant(current_chunk(),
1497
7
                    value_string("anneal failed: "));
1498
7
                emit_bytes(OP_CONSTANT, (uint8_t)prefix_idx, line);
1499
7
                emit_byte(OP_SWAP, line);
1500
7
                emit_byte(OP_CONCAT, line);
1501
7
                emit_byte(OP_THROW, line);
1502
7
            }
1503
7
            patch_jump(anneal_end);
1504
7
            break;
1505
78
        }
1506
1507
4
        case EXPR_CRYSTALLIZE: {
1508
            /* crystallize(target) { body } — freeze target, run body, thaw target
1509
             * If already crystal, just run body without thaw afterward. */
1510
4
            if (e->as.crystallize.expr->tag == EXPR_IDENT) {
1511
4
                const char *name = e->as.crystallize.expr->as.str_val;
1512
4
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(name));
1513
4
                int loc_type = 2; uint8_t slot = 0;
1514
4
                int local_slot = resolve_local(current, name);
1515
4
                if (local_slot != -1) { loc_type = 0; slot = (uint8_t)local_slot; }
1516
0
                else {
1517
0
                    int up_idx = resolve_upvalue(current, name);
1518
0
                    if (up_idx != -1) { loc_type = 1; slot = (uint8_t)up_idx; }
1519
0
                }
1520
                /* Check if already crystal — save result as a local */
1521
4
                begin_scope();
1522
4
                compile_expr(e->as.crystallize.expr, line); /* push current value */
1523
4
                emit_byte(OP_IS_CRYSTAL, line);              /* replace with bool */
1524
4
                add_local(""); /* track the was_crystal flag */
1525
4
                int flag_slot = current->local_count - 1;
1526
                /* Freeze the variable */
1527
4
                compile_expr(e->as.crystallize.expr, line);
1528
4
                emit_byte(OP_FREEZE_VAR, line);
1529
4
                emit_byte((uint8_t)name_idx, line);
1530
4
                emit_byte((uint8_t)loc_type, line);
1531
4
                emit_byte(slot, line);
1532
4
                emit_byte(OP_POP, line); /* discard freeze result */
1533
                /* Execute body */
1534
10
                for (size_t i = 0; i < e->as.crystallize.body_count; i++)
1535
6
                    compile_stmt(e->as.crystallize.body[i]);
1536
                /* Conditional thaw: only if was NOT already crystal */
1537
4
                emit_bytes(OP_GET_LOCAL, (uint8_t)flag_slot, line); /* push was_crystal */
1538
4
                size_t skip_thaw = emit_jump(OP_JUMP_IF_FALSE, line);
1539
                /* was_crystal=true → skip thaw */
1540
4
                emit_byte(OP_POP, line);  /* pop true */
1541
4
                size_t past_thaw = emit_jump(OP_JUMP, line);
1542
                /* was_crystal=false → do thaw */
1543
4
                patch_jump(skip_thaw);
1544
4
                emit_byte(OP_POP, line);  /* pop false */
1545
4
                compile_expr(e->as.crystallize.expr, line);
1546
4
                emit_byte(OP_THAW_VAR, line);
1547
4
                emit_byte((uint8_t)name_idx, line);
1548
4
                emit_byte((uint8_t)loc_type, line);
1549
4
                emit_byte(slot, line);
1550
4
                emit_byte(OP_POP, line); /* discard thaw result */
1551
4
                patch_jump(past_thaw);
1552
4
                end_scope(line); /* clean up was_crystal flag */
1553
4
                emit_byte(OP_UNIT, line);
1554
4
            } else {
1555
                /* Fallback: just freeze the expression */
1556
0
                compile_expr(e->as.crystallize.expr, line);
1557
0
                emit_byte(OP_FREEZE, line);
1558
0
            }
1559
4
            break;
1560
78
        }
1561
1562
5
        case EXPR_SUBLIMATE: {
1563
5
            compile_expr(e->as.freeze_expr, line);
1564
5
            if (e->as.freeze_expr->tag == EXPR_IDENT) {
1565
5
                const char *name = e->as.freeze_expr->as.str_val;
1566
5
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(name));
1567
5
                int slot = resolve_local(current, name);
1568
5
                if (slot >= 0) {
1569
5
                    emit_byte(OP_SUBLIMATE_VAR, line);
1570
5
                    emit_byte((uint8_t)name_idx, line);
1571
5
                    emit_byte(0, line); /* loc_type = local */
1572
5
                    emit_byte((uint8_t)slot, line);
1573
5
                } else {
1574
0
                    int upvalue = resolve_upvalue(current, name);
1575
0
                    if (upvalue >= 0) {
1576
0
                        emit_byte(OP_SUBLIMATE_VAR, line);
1577
0
                        emit_byte((uint8_t)name_idx, line);
1578
0
                        emit_byte(1, line); /* loc_type = upvalue */
1579
0
                        emit_byte((uint8_t)upvalue, line);
1580
0
                    } else {
1581
0
                        emit_byte(OP_SUBLIMATE_VAR, line);
1582
0
                        emit_byte((uint8_t)name_idx, line);
1583
0
                        emit_byte(2, line); /* loc_type = global */
1584
0
                        emit_byte(0, line);
1585
0
                    }
1586
0
                }
1587
5
            } else {
1588
0
                emit_byte(OP_SUBLIMATE, line);
1589
0
            }
1590
5
            break;
1591
78
        }
1592
1593
9
        case EXPR_SPREAD: {
1594
9
            compile_expr(e->as.spread_expr, line);
1595
9
            break;
1596
78
        }
1597
1598
1
        case EXPR_SPAWN: {
1599
            /* Compile as OP_SCOPE with 0 spawns (sub-chunk so return works) */
1600
1
            Chunk *spawn_ch = compile_sub_body(
1601
1
                e->as.block.stmts, e->as.block.count, line);
1602
1
            uint8_t body_idx = (uint8_t)add_chunk_constant(spawn_ch);
1603
1
            emit_byte(OP_SCOPE, line);
1604
1
            emit_byte(0, line);        /* spawn_count = 0 */
1605
1
            emit_byte(body_idx, line); /* sync_idx */
1606
1
            break;
1607
78
        }
1608
1609
3
        case EXPR_SCOPE: {
1610
#ifdef __EMSCRIPTEN__
1611
            /* WASM fallback: run as synchronous block */
1612
            for (size_t i = 0; i < e->as.block.count; i++)
1613
                compile_stmt(e->as.block.stmts[i]);
1614
            emit_byte(OP_UNIT, line);
1615
#else
1616
3
            size_t total = e->as.block.count;
1617
1618
            /* Count spawns and collect non-spawn stmts */
1619
3
            size_t spawn_count = 0;
1620
9
            for (size_t i = 0; i < total; i++) {
1621
6
                Stmt *s = e->as.block.stmts[i];
1622
6
                if (s->tag == STMT_EXPR && s->as.expr->tag == EXPR_SPAWN)
1623
3
                    spawn_count++;
1624
6
            }
1625
3
            size_t sync_count = total - spawn_count;
1626
1627
            /* Compile sync body (all non-spawn stmts together) */
1628
3
            uint8_t sync_idx = 0xFF;
1629
3
            if (sync_count > 0) {
1630
1
                Stmt **sync_stmts = malloc(sync_count * sizeof(Stmt *));
1631
1
                size_t si = 0;
1632
4
                for (size_t i = 0; i < total; i++) {
1633
3
                    Stmt *s = e->as.block.stmts[i];
1634
3
                    if (!(s->tag == STMT_EXPR && s->as.expr->tag == EXPR_SPAWN))
1635
3
                        sync_stmts[si++] = s;
1636
3
                }
1637
1
                Chunk *sync_chunk = compile_sub_body(sync_stmts, sync_count, line);
1638
1
                sync_idx = (uint8_t)add_chunk_constant(sync_chunk);
1639
1
                free(sync_stmts);
1640
1
            }
1641
1642
            /* Compile each spawn body */
1643
3
            uint8_t *spawn_indices = NULL;
1644
3
            if (spawn_count > 0)
1645
2
                spawn_indices = malloc(spawn_count * sizeof(uint8_t));
1646
3
            size_t spi = 0;
1647
9
            for (size_t i = 0; i < total; i++) {
1648
6
                Stmt *s = e->as.block.stmts[i];
1649
6
                if (s->tag == STMT_EXPR && s->as.expr->tag == EXPR_SPAWN) {
1650
3
                    Expr *spawn_expr = s->as.expr;
1651
3
                    Chunk *spawn_ch = compile_sub_body(
1652
3
                        spawn_expr->as.block.stmts,
1653
3
                        spawn_expr->as.block.count, line);
1654
3
                    spawn_indices[spi++] = (uint8_t)add_chunk_constant(spawn_ch);
1655
3
                }
1656
6
            }
1657
1658
            /* Emit: OP_SCOPE spawn_count sync_idx [spawn_idx...] */
1659
3
            emit_byte(OP_SCOPE, line);
1660
3
            emit_byte((uint8_t)spawn_count, line);
1661
3
            emit_byte(sync_idx, line);
1662
6
            for (size_t i = 0; i < spawn_count; i++)
1663
3
                emit_byte(spawn_indices[i], line);
1664
3
            free(spawn_indices);
1665
3
#endif
1666
3
            break;
1667
78
        }
1668
1669
5
        case EXPR_SELECT: {
1670
#ifdef __EMSCRIPTEN__
1671
            emit_byte(OP_NIL, line);
1672
#else
1673
5
            size_t arm_count = e->as.select_expr.arm_count;
1674
5
            SelectArm *arms = e->as.select_expr.arms;
1675
1676
5
            emit_byte(OP_SELECT, line);
1677
5
            emit_byte((uint8_t)arm_count, line);
1678
1679
14
            for (size_t i = 0; i < arm_count; i++) {
1680
9
                uint8_t flags = 0;
1681
9
                if (arms[i].is_default) flags |= 0x01;
1682
9
                if (arms[i].is_timeout) flags |= 0x02;
1683
9
                if (arms[i].binding_name) flags |= 0x04;
1684
9
                emit_byte(flags, line);
1685
1686
                /* Channel or timeout expression chunk */
1687
9
                if (arms[i].is_default) {
1688
3
                    emit_byte(0xFF, line);
1689
6
                } else if (arms[i].is_timeout) {
1690
0
                    Chunk *to_ch = compile_sub_expr(arms[i].timeout_expr, line);
1691
0
                    emit_byte((uint8_t)add_chunk_constant(to_ch), line);
1692
6
                } else {
1693
6
                    Chunk *ch_ch = compile_sub_expr(arms[i].channel_expr, line);
1694
6
                    emit_byte((uint8_t)add_chunk_constant(ch_ch), line);
1695
6
                }
1696
1697
                /* Body chunk */
1698
9
                Chunk *body_ch = compile_sub_body(arms[i].body, arms[i].body_count, line);
1699
9
                emit_byte((uint8_t)add_chunk_constant(body_ch), line);
1700
1701
                /* Binding name (string constant or 0xFF) */
1702
9
                if (arms[i].binding_name) {
1703
6
                    emit_byte((uint8_t)chunk_add_constant(current_chunk(),
1704
6
                                   value_string(arms[i].binding_name)), line);
1705
6
                } else {
1706
3
                    emit_byte(0xFF, line);
1707
3
                }
1708
9
            }
1709
5
#endif
1710
5
            break;
1711
78
        }
1712
1713
0
        default:
1714
0
            emit_byte(OP_NIL, line);
1715
0
            break;
1716
46.6k
    }
1717
46.6k
}
1718
1719
/* ── Emit ensure contract checks (postconditions) ── */
1720
/* Expects the return value on TOS. Leaves it on TOS unchanged. */
1721
5.69k
static void emit_ensure_checks(int line) {
1722
5.69k
    if (!current->contracts || current->contract_count == 0) return;
1723
30
    for (size_t i = 0; i < current->contract_count; i++) {
1724
17
        if (!current->contracts[i].is_ensure) continue;
1725
        /* Stack: [..., return_val]
1726
         * Need to call closure(return_val) and check result.
1727
         * OP_CALL 1 expects: [..., callee, arg0] */
1728
5
        emit_byte(OP_DUP, line);            /* [..., ret, ret_copy] */
1729
5
        compile_expr(current->contracts[i].condition, line); /* [..., ret, ret_copy, closure] */
1730
5
        emit_byte(OP_SWAP, line);           /* [..., ret, closure, ret_copy] */
1731
5
        emit_bytes(OP_CALL, 1, line);       /* [..., ret, result] */
1732
5
        size_t ok_jump = emit_jump(OP_JUMP_IF_TRUE, line);
1733
5
        emit_byte(OP_POP, line);            /* pop false */
1734
5
        emit_byte(OP_POP, line);            /* pop return value */
1735
5
        const char *user_msg = current->contracts[i].message ?
1736
5
            current->contracts[i].message : "condition not met";
1737
5
        char full_msg[512];
1738
5
        snprintf(full_msg, sizeof(full_msg), "ensure failed in '%s': %s",
1739
5
                 current->func_name ? current->func_name : "<anonymous>", user_msg);
1740
5
        emit_constant(value_string(full_msg), line);
1741
5
        emit_byte(OP_THROW, line);
1742
5
        patch_jump(ok_jump);
1743
5
        emit_byte(OP_POP, line);            /* pop true */
1744
5
    }
1745
13
}
1746
1747
/* ── Emit return type check (if return_type_name is set) ── */
1748
/* Expects the return value on TOS. Leaves it on TOS unchanged. */
1749
5.69k
static void emit_return_type_check(int line) {
1750
5.69k
    if (!current->return_type_name) return;
1751
3.43k
    if (strcmp(current->return_type_name, "Any") == 0 || strcmp(current->return_type_name, "any") == 0) return;
1752
3.11k
    char err_msg[512];
1753
3.11k
    snprintf(err_msg, sizeof(err_msg), "function '%s' return type expects %s, got %%s",
1754
3.11k
             current->func_name ? current->func_name : "<anonymous>",
1755
3.11k
             current->return_type_name);
1756
3.11k
    size_t type_idx = chunk_add_constant(current_chunk(), value_string(current->return_type_name));
1757
3.11k
    size_t err_idx = chunk_add_constant(current_chunk(), value_string(err_msg));
1758
3.11k
    emit_byte(OP_CHECK_RETURN_TYPE, line);
1759
3.11k
    emit_byte((uint8_t)type_idx, line);
1760
3.11k
    emit_byte((uint8_t)err_idx, line);
1761
3.11k
}
1762
1763
/* Emit write-back chain for nested index assignment.
1764
 * After OP_SET_INDEX leaves a modified intermediate on the stack,
1765
 * walk up through parent EXPR_INDEX nodes to the root variable. */
1766
0
static void emit_index_write_back(Expr *node, int line) {
1767
0
    if (node->as.index.object->tag == EXPR_IDENT) {
1768
0
        const char *name = node->as.index.object->as.str_val;
1769
0
        int slot = resolve_local(current, name);
1770
0
        if (slot >= 0) {
1771
0
            compile_expr(node->as.index.index, line);
1772
0
            emit_bytes(OP_SET_INDEX_LOCAL, (uint8_t)slot, line);
1773
0
        } else {
1774
            /* Global/upvalue: get parent, set index, write back */
1775
0
            compile_expr(node->as.index.object, line);
1776
0
            compile_expr(node->as.index.index, line);
1777
0
            emit_byte(OP_SET_INDEX, line);
1778
0
            int upvalue = resolve_upvalue(current, name);
1779
0
            if (upvalue >= 0) {
1780
0
                emit_bytes(OP_SET_UPVALUE, (uint8_t)upvalue, line);
1781
0
            } else {
1782
0
                size_t gidx = chunk_add_constant(current_chunk(), value_string(name));
1783
0
                emit_constant_idx(OP_SET_GLOBAL, OP_SET_GLOBAL_16, gidx, line);
1784
0
            }
1785
0
        }
1786
0
    } else if (node->as.index.object->tag == EXPR_INDEX) {
1787
        /* Intermediate level: compile parent, set index, recurse up */
1788
0
        compile_expr(node->as.index.object, line);
1789
0
        compile_expr(node->as.index.index, line);
1790
0
        emit_byte(OP_SET_INDEX, line);
1791
0
        emit_index_write_back(node->as.index.object, line);
1792
0
    }
1793
0
}
1794
1795
/* ── Compile statements ── */
1796
1797
13.6k
static void compile_stmt(const Stmt *s) {
1798
13.6k
    if (compile_error) return;
1799
1800
13.6k
    int line = s->line;
1801
13.6k
    switch (s->tag) {
1802
4.45k
        case STMT_EXPR:
1803
4.45k
            compile_expr(s->as.expr, line);
1804
4.45k
            emit_byte(OP_POP, line);
1805
4.45k
            break;
1806
1807
4.44k
        case STMT_BINDING: {
1808
4.44k
            if (s->as.binding.value)
1809
4.44k
                compile_expr(s->as.binding.value, line);
1810
0
            else
1811
0
                emit_byte(OP_NIL, line);
1812
1813
            /* Apply phase tag for flux/fix declarations */
1814
4.44k
            if (s->as.binding.phase == PHASE_FLUID)
1815
1.10k
                emit_byte(OP_MARK_FLUID, line);
1816
3.34k
            else if (s->as.binding.phase == PHASE_CRYSTAL)
1817
15
                emit_byte(OP_FREEZE, line);
1818
1819
4.44k
            if (current->scope_depth > 0) {
1820
4.36k
                add_local(s->as.binding.name);
1821
4.36k
            } else {
1822
81
                size_t idx = chunk_add_constant(current_chunk(),
1823
81
                    value_string(s->as.binding.name));
1824
81
                emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, idx, line);
1825
81
            }
1826
4.44k
            break;
1827
0
        }
1828
1829
917
        case STMT_ASSIGN: {
1830
            /* Detect i += 1 / i -= 1 → OP_INC_LOCAL / OP_DEC_LOCAL
1831
             * The parser desugars i += 1 into STMT_ASSIGN(IDENT("i"), BINOP(ADD, IDENT("i"), INT(1)))
1832
             */
1833
917
            if (s->as.assign.target->tag == EXPR_IDENT &&
1834
917
                s->as.assign.value->tag == EXPR_BINOP) {
1835
457
                const char *name = s->as.assign.target->as.str_val;
1836
457
                Expr *val = s->as.assign.value;
1837
457
                if (val->as.binop.left->tag == EXPR_IDENT &&
1838
457
                    strcmp(val->as.binop.left->as.str_val, name) == 0 &&
1839
457
                    val->as.binop.right->tag == EXPR_INT_LIT &&
1840
457
                    val->as.binop.right->as.int_val == 1) {
1841
248
                    int slot = resolve_local(current, name);
1842
248
                    if (slot >= 0) {
1843
246
                        if (val->as.binop.op == BINOP_ADD) {
1844
217
                            emit_bytes(OP_INC_LOCAL, (uint8_t)slot, line);
1845
217
                            break; /* INC_LOCAL doesn't push; skip OP_POP at end */
1846
217
                        }
1847
29
                        if (val->as.binop.op == BINOP_SUB) {
1848
29
                            emit_bytes(OP_DEC_LOCAL, (uint8_t)slot, line);
1849
29
                            break;
1850
29
                        }
1851
29
                    }
1852
248
                }
1853
457
            }
1854
1855
917
            bool skip_pop = false;
1856
671
            compile_expr(s->as.assign.value, line);
1857
671
            if (s->as.assign.target->tag == EXPR_IDENT) {
1858
622
                const char *name = s->as.assign.target->as.str_val;
1859
622
                int slot = resolve_local(current, name);
1860
622
                if (slot >= 0) {
1861
617
                    emit_bytes(OP_SET_LOCAL_POP, (uint8_t)slot, line);
1862
617
                    skip_pop = true;
1863
617
                } else {
1864
5
                    int upvalue = resolve_upvalue(current, name);
1865
5
                    if (upvalue >= 0) {
1866
0
                        emit_bytes(OP_SET_UPVALUE, (uint8_t)upvalue, line);
1867
5
                    } else {
1868
5
                        size_t idx = chunk_add_constant(current_chunk(), value_string(name));
1869
5
                        emit_constant_idx(OP_SET_GLOBAL, OP_SET_GLOBAL_16, idx, line);
1870
5
                    }
1871
5
                }
1872
622
            } else if (s->as.assign.target->tag == EXPR_FIELD_ACCESS) {
1873
14
                compile_expr(s->as.assign.target->as.field_access.object, line);
1874
14
                size_t idx = chunk_add_constant(current_chunk(),
1875
14
                    value_string(s->as.assign.target->as.field_access.field));
1876
14
                emit_bytes(OP_SET_FIELD, (uint8_t)idx, line);
1877
                /* Write back the modified object to the variable */
1878
14
                Expr *obj_expr = s->as.assign.target->as.field_access.object;
1879
14
                if (obj_expr->tag == EXPR_IDENT) {
1880
12
                    const char *name = obj_expr->as.str_val;
1881
12
                    int slot = resolve_local(current, name);
1882
12
                    if (slot >= 0) {
1883
12
                        emit_bytes(OP_SET_LOCAL, (uint8_t)slot, line);
1884
12
                    } else {
1885
0
                        int upvalue = resolve_upvalue(current, name);
1886
0
                        if (upvalue >= 0) {
1887
0
                            emit_bytes(OP_SET_UPVALUE, (uint8_t)upvalue, line);
1888
0
                        } else {
1889
0
                            size_t gidx = chunk_add_constant(current_chunk(), value_string(name));
1890
0
                            emit_constant_idx(OP_SET_GLOBAL, OP_SET_GLOBAL_16, gidx, line);
1891
0
                        }
1892
0
                    }
1893
12
                }
1894
35
            } else if (s->as.assign.target->tag == EXPR_INDEX) {
1895
35
                Expr *target = s->as.assign.target;
1896
                /* If object is a local, use OP_SET_INDEX_LOCAL to mutate in-place */
1897
35
                if (target->as.index.object->tag == EXPR_IDENT) {
1898
35
                    int slot = resolve_local(current,
1899
35
                        target->as.index.object->as.str_val);
1900
35
                    if (slot >= 0) {
1901
27
                        compile_expr(target->as.index.index, line);
1902
27
                        emit_bytes(OP_SET_INDEX_LOCAL, (uint8_t)slot, line);
1903
27
                        break; /* OP_SET_INDEX_LOCAL pushes nothing, skip OP_POP */
1904
27
                    }
1905
35
                }
1906
                /* Nested index (e.g. m[i][j] = val): compile intermediate,
1907
                 * SET_INDEX, then write-back chain to root variable */
1908
8
                if (target->as.index.object->tag == EXPR_INDEX) {
1909
0
                    compile_expr(target->as.index.object, line);
1910
0
                    compile_expr(target->as.index.index, line);
1911
0
                    emit_byte(OP_SET_INDEX, line);
1912
0
                    emit_index_write_back(target->as.index.object, line);
1913
0
                    break; /* write-back handles everything, skip OP_POP */
1914
0
                }
1915
                /* Fallback: non-local single-level index (global/upvalue) */
1916
8
                compile_expr(target->as.index.object, line);
1917
8
                compile_expr(target->as.index.index, line);
1918
8
                emit_byte(OP_SET_INDEX, line);
1919
                /* Write back the modified object to the variable */
1920
8
                if (target->as.index.object->tag == EXPR_IDENT) {
1921
8
                    const char *name = target->as.index.object->as.str_val;
1922
8
                    int upvalue = resolve_upvalue(current, name);
1923
8
                    if (upvalue >= 0) {
1924
0
                        emit_bytes(OP_SET_UPVALUE, (uint8_t)upvalue, line);
1925
8
                    } else {
1926
8
                        size_t gidx = chunk_add_constant(current_chunk(), value_string(name));
1927
8
                        emit_constant_idx(OP_SET_GLOBAL, OP_SET_GLOBAL_16, gidx, line);
1928
8
                    }
1929
8
                }
1930
8
            }
1931
644
            if (!skip_pop) emit_byte(OP_POP, line);
1932
644
            break;
1933
671
        }
1934
1935
3.02k
        case STMT_RETURN:
1936
3.02k
            if (s->as.return_expr)
1937
3.02k
                compile_expr(s->as.return_expr, line);
1938
1
            else
1939
1
                emit_byte(OP_UNIT, line);
1940
3.02k
            emit_return_type_check(line);
1941
3.02k
            emit_ensure_checks(0);
1942
3.02k
            emit_byte(OP_DEFER_RUN, line);
1943
3.02k
            emit_byte(0, line);  /* scope_depth 0 = run all defers */
1944
3.02k
            emit_byte(OP_RETURN, line);
1945
3.02k
            break;
1946
1947
120
        case STMT_WHILE: {
1948
120
            size_t saved_break_count = current->break_count;
1949
120
            size_t saved_loop_start = current->loop_start;
1950
120
            int saved_loop_depth = current->loop_depth;
1951
120
            size_t saved_break_lc = current->loop_break_local_count;
1952
120
            size_t saved_continue_lc = current->loop_continue_local_count;
1953
1954
120
            current->loop_break_local_count = current->local_count;
1955
120
            current->loop_continue_local_count = current->local_count;
1956
120
            current->loop_start = current_chunk()->code_len;
1957
120
            current->loop_depth++;
1958
1959
120
            compile_expr(s->as.while_loop.cond, line);
1960
120
            size_t exit_jump = emit_jump(OP_JUMP_IF_FALSE, line);
1961
120
            emit_byte(OP_POP, line);
1962
1963
120
            begin_scope();
1964
517
            for (size_t i = 0; i < s->as.while_loop.body_count; i++)
1965
397
                compile_stmt_reset(s->as.while_loop.body[i]);
1966
120
            end_scope(0);
1967
1968
120
            emit_loop(current->loop_start, line);
1969
120
            patch_jump(exit_jump);
1970
120
            emit_byte(OP_POP, line);
1971
1972
            /* Patch break jumps */
1973
131
            for (size_t i = saved_break_count; i < current->break_count; i++)
1974
11
                patch_jump(current->break_jumps[i]);
1975
120
            current->break_count = saved_break_count;
1976
120
            current->loop_start = saved_loop_start;
1977
120
            current->loop_depth = saved_loop_depth;
1978
120
            current->loop_break_local_count = saved_break_lc;
1979
120
            current->loop_continue_local_count = saved_continue_lc;
1980
120
            break;
1981
671
        }
1982
1983
116
        case STMT_LOOP: {
1984
116
            size_t saved_break_count = current->break_count;
1985
116
            size_t saved_loop_start = current->loop_start;
1986
116
            int saved_loop_depth = current->loop_depth;
1987
116
            size_t saved_break_lc = current->loop_break_local_count;
1988
116
            size_t saved_continue_lc = current->loop_continue_local_count;
1989
1990
116
            current->loop_break_local_count = current->local_count;
1991
116
            current->loop_continue_local_count = current->local_count;
1992
116
            current->loop_start = current_chunk()->code_len;
1993
116
            current->loop_depth++;
1994
1995
116
            begin_scope();
1996
580
            for (size_t i = 0; i < s->as.loop.body_count; i++)
1997
464
                compile_stmt_reset(s->as.loop.body[i]);
1998
116
            end_scope(0);
1999
2000
116
            emit_loop(current->loop_start, line);
2001
2002
            /* Patch break jumps */
2003
203
            for (size_t i = saved_break_count; i < current->break_count; i++)
2004
87
                patch_jump(current->break_jumps[i]);
2005
116
            current->break_count = saved_break_count;
2006
116
            current->loop_start = saved_loop_start;
2007
116
            current->loop_depth = saved_loop_depth;
2008
116
            current->loop_break_local_count = saved_break_lc;
2009
116
            current->loop_continue_local_count = saved_continue_lc;
2010
116
            break;
2011
671
        }
2012
2013
282
        case STMT_FOR: {
2014
282
            size_t saved_break_count = current->break_count;
2015
282
            size_t saved_loop_start = current->loop_start;
2016
282
            int saved_loop_depth = current->loop_depth;
2017
282
            size_t saved_break_lc = current->loop_break_local_count;
2018
282
            size_t saved_continue_lc = current->loop_continue_local_count;
2019
2020
282
            current->loop_break_local_count = current->local_count;
2021
2022
282
            begin_scope();
2023
            /* Compile iterator expression and init */
2024
282
            compile_expr(s->as.for_loop.iter, line);
2025
282
            emit_byte(OP_ITER_INIT, line);
2026
2027
            /* Track the iterator state (collection + index) as anonymous locals
2028
             * so subsequent local slot numbers are correct. */
2029
282
            add_local("");   /* collection */
2030
282
            add_local("");   /* index */
2031
2032
            /* continue should preserve iterator state but pop loop var + body locals */
2033
282
            current->loop_continue_local_count = current->local_count;
2034
2035
282
            current->loop_start = current_chunk()->code_len;
2036
282
            current->loop_depth++;
2037
2038
            /* OP_ITER_NEXT pushes next value or jumps to end */
2039
282
            size_t exit_jump = emit_jump(OP_ITER_NEXT, line);
2040
2041
            /* Bind loop variable */
2042
282
            add_local(s->as.for_loop.var);
2043
2044
            /* Compile body in inner scope */
2045
282
            begin_scope();
2046
924
            for (size_t i = 0; i < s->as.for_loop.body_count; i++)
2047
642
                compile_stmt_reset(s->as.for_loop.body[i]);
2048
282
            end_scope(0);
2049
2050
            /* Pop loop variable */
2051
282
            emit_byte(OP_POP, line);
2052
282
            free(current->locals[current->local_count - 1].name);
2053
282
            current->local_count--;
2054
2055
282
            emit_loop(current->loop_start, line);
2056
2057
282
            patch_jump(exit_jump);
2058
            /* Pop iterator state (two values: index + collection) */
2059
282
            emit_byte(OP_POP, line);
2060
282
            emit_byte(OP_POP, line);
2061
            /* Remove iterator placeholder locals */
2062
282
            free(current->locals[current->local_count - 1].name);
2063
282
            current->local_count--;
2064
282
            free(current->locals[current->local_count - 1].name);
2065
282
            current->local_count--;
2066
2067
282
            end_scope(0);
2068
2069
            /* Patch break jumps */
2070
282
            for (size_t i = saved_break_count; i < current->break_count; i++)
2071
0
                patch_jump(current->break_jumps[i]);
2072
282
            current->break_count = saved_break_count;
2073
282
            current->loop_start = saved_loop_start;
2074
282
            current->loop_depth = saved_loop_depth;
2075
282
            current->loop_break_local_count = saved_break_lc;
2076
282
            current->loop_continue_local_count = saved_continue_lc;
2077
282
            break;
2078
671
        }
2079
2080
98
        case STMT_BREAK: {
2081
98
            if (current->loop_depth == 0) {
2082
0
                compile_error = strdup("break outside of loop");
2083
0
                return;
2084
0
            }
2085
            /* Pop locals declared inside the loop before jumping out */
2086
195
            for (size_t i = current->local_count; i > current->loop_break_local_count; i--) {
2087
97
                if (current->locals[i - 1].is_captured) {
2088
0
                    emit_byte(OP_CLOSE_UPVALUE, line);
2089
97
                } else {
2090
97
                    emit_byte(OP_POP, line);
2091
97
                }
2092
97
            }
2093
98
            size_t jump = emit_jump(OP_JUMP, line);
2094
98
            push_break_jump(jump);
2095
98
            break;
2096
98
        }
2097
2098
41
        case STMT_CONTINUE: {
2099
41
            if (current->loop_depth == 0) {
2100
0
                compile_error = strdup("continue outside of loop");
2101
0
                return;
2102
0
            }
2103
            /* Pop locals declared inside the loop before jumping back */
2104
162
            for (size_t i = current->local_count; i > current->loop_continue_local_count; i--) {
2105
121
                if (current->locals[i - 1].is_captured) {
2106
0
                    emit_byte(OP_CLOSE_UPVALUE, line);
2107
121
                } else {
2108
121
                    emit_byte(OP_POP, line);
2109
121
                }
2110
121
            }
2111
41
            emit_loop(current->loop_start, line);
2112
41
            break;
2113
41
        }
2114
2115
8
        case STMT_DESTRUCTURE: {
2116
8
            compile_expr(s->as.destructure.value, line);
2117
8
            if (current->scope_depth > 0) {
2118
                /* Store source as hidden local so each extraction can reference it */
2119
8
                size_t src_slot = current->local_count;
2120
8
                add_local("");  /* hidden local */
2121
8
                if (s->as.destructure.kind == DESTRUCT_ARRAY) {
2122
18
                    for (size_t i = 0; i < s->as.destructure.name_count; i++) {
2123
12
                        emit_bytes(OP_GET_LOCAL, (uint8_t)src_slot, line);
2124
12
                        emit_constant(value_int((int64_t)i), line);
2125
12
                        emit_byte(OP_INDEX, line);
2126
12
                        add_local(s->as.destructure.names[i]);
2127
12
                    }
2128
6
                    if (s->as.destructure.rest_name) {
2129
                        /* ...rest: slice array from name_count to end */
2130
3
                        emit_bytes(OP_GET_LOCAL, (uint8_t)src_slot, line);
2131
3
                        int64_t start = (int64_t)s->as.destructure.name_count;
2132
3
                        emit_constant(value_int(start), line);
2133
                        /* Use native "len" to get end index */
2134
3
                        emit_bytes(OP_GET_LOCAL, (uint8_t)src_slot, line);
2135
3
                        size_t len_idx = chunk_add_constant(current_chunk(), value_string("len"));
2136
3
                        emit_bytes(OP_INVOKE, (uint8_t)len_idx, line);
2137
3
                        emit_byte(0, line); /* 0 args to .len() */
2138
3
                        emit_byte(OP_BUILD_RANGE, line);
2139
3
                        emit_byte(OP_INDEX, line);
2140
3
                        add_local(s->as.destructure.rest_name);
2141
3
                    }
2142
6
                } else {
2143
6
                    for (size_t i = 0; i < s->as.destructure.name_count; i++) {
2144
4
                        emit_bytes(OP_GET_LOCAL, (uint8_t)src_slot, line);
2145
4
                        size_t fidx = chunk_add_constant(current_chunk(),
2146
4
                            value_string(s->as.destructure.names[i]));
2147
4
                        emit_bytes(OP_GET_FIELD, (uint8_t)fidx, line);
2148
4
                        add_local(s->as.destructure.names[i]);
2149
4
                    }
2150
2
                }
2151
                /* Hidden local cleaned up when enclosing scope ends */
2152
8
            } else {
2153
                /* Global path: OP_DUP works because DEFINE_GLOBAL pops each value */
2154
0
                if (s->as.destructure.kind == DESTRUCT_ARRAY) {
2155
0
                    for (size_t i = 0; i < s->as.destructure.name_count; i++) {
2156
0
                        emit_byte(OP_DUP, line);
2157
0
                        emit_constant(value_int((int64_t)i), line);
2158
0
                        emit_byte(OP_INDEX, line);
2159
0
                        size_t idx = chunk_add_constant(current_chunk(),
2160
0
                            value_string(s->as.destructure.names[i]));
2161
0
                        emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, idx, line);
2162
0
                    }
2163
0
                    if (s->as.destructure.rest_name) {
2164
                        /* Stack: [source]
2165
                         * Need: source[name_count..source.len()] as rest */
2166
0
                        int64_t start = (int64_t)s->as.destructure.name_count;
2167
0
                        emit_byte(OP_DUP, line);        /* [source, source] */
2168
0
                        emit_byte(OP_DUP, line);        /* [source, source, source] */
2169
0
                        size_t len_idx2 = chunk_add_constant(current_chunk(), value_string("len"));
2170
0
                        emit_bytes(OP_INVOKE, (uint8_t)len_idx2, line);
2171
0
                        emit_byte(0, line);             /* [source, source, len] — invoke consumes TOS source, pushes len */
2172
0
                        emit_constant(value_int(start), line); /* [source, source, len, start] */
2173
0
                        emit_byte(OP_SWAP, line);       /* [source, source, start, len] */
2174
0
                        emit_byte(OP_BUILD_RANGE, line);/* [source, source, Range(start..len)] */
2175
0
                        emit_byte(OP_INDEX, line);      /* [source, rest_slice] */
2176
0
                        size_t rest_idx = chunk_add_constant(current_chunk(),
2177
0
                            value_string(s->as.destructure.rest_name));
2178
0
                        emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, rest_idx, line);
2179
0
                    }
2180
0
                } else {
2181
0
                    for (size_t i = 0; i < s->as.destructure.name_count; i++) {
2182
0
                        emit_byte(OP_DUP, line);
2183
0
                        size_t fidx = chunk_add_constant(current_chunk(),
2184
0
                            value_string(s->as.destructure.names[i]));
2185
0
                        emit_bytes(OP_GET_FIELD, (uint8_t)fidx, line);
2186
0
                        size_t nidx = chunk_add_constant(current_chunk(),
2187
0
                            value_string(s->as.destructure.names[i]));
2188
0
                        emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, nidx, line);
2189
0
                    }
2190
0
                }
2191
0
                emit_byte(OP_POP, line); /* pop the original value */
2192
0
            }
2193
8
            break;
2194
41
        }
2195
2196
6
        case STMT_DEFER: {
2197
            /* Emit OP_DEFER_PUSH with scope_depth + offset past the defer body.
2198
             * Format: OP_DEFER_PUSH, scope_depth(1), offset(2).
2199
             * The scope_depth byte lets OP_DEFER_RUN run only defers
2200
             * registered at a given scope level. */
2201
6
            emit_byte(OP_DEFER_PUSH, line);
2202
6
            emit_byte((uint8_t)current->scope_depth, line);
2203
            /* Emit 2-byte jump offset placeholder */
2204
6
            emit_byte(0xff, line);
2205
6
            emit_byte(0xff, line);
2206
6
            size_t defer_jump = current_chunk()->code_len - 2;
2207
12
            for (size_t i = 0; i < s->as.defer.body_count; i++)
2208
6
                compile_stmt(s->as.defer.body[i]);
2209
6
            emit_byte(OP_UNIT, line);   /* implicit return value for defer block */
2210
6
            emit_byte(OP_RETURN, line); /* return from defer block */
2211
6
            patch_jump(defer_jump);
2212
6
            break;
2213
41
        }
2214
2215
94
        case STMT_IMPORT: {
2216
94
            size_t path_idx = chunk_add_constant(current_chunk(),
2217
94
                value_string(s->as.import.module_path));
2218
94
            emit_bytes(OP_IMPORT, (uint8_t)path_idx, line);
2219
94
            if (s->as.import.alias) {
2220
77
                if (current->scope_depth > 0) {
2221
0
                    add_local(s->as.import.alias);
2222
77
                } else {
2223
77
                    size_t name_idx = chunk_add_constant(current_chunk(),
2224
77
                        value_string(s->as.import.alias));
2225
77
                    emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, name_idx, line);
2226
77
                }
2227
77
            } else if (s->as.import.selective_names && s->as.import.selective_count > 0) {
2228
                /* Selective import: import { x, y } from "path" */
2229
37
                for (size_t i = 0; i < s->as.import.selective_count; i++) {
2230
20
                    emit_byte(OP_DUP, line);  /* duplicate module map */
2231
20
                    size_t field_idx = chunk_add_constant(current_chunk(),
2232
20
                        value_string(s->as.import.selective_names[i]));
2233
20
                    emit_bytes(OP_GET_FIELD, (uint8_t)field_idx, line);
2234
                    /* Check that the export exists (not nil) */
2235
20
                    emit_byte(OP_DUP, line);
2236
20
                    size_t import_ok = emit_jump(OP_JUMP_IF_NOT_NIL, line);
2237
                    /* nil → error: module does not export this name */
2238
20
                    emit_byte(OP_POP, line); /* pop nil dup */
2239
20
                    emit_byte(OP_POP, line); /* pop nil val */
2240
20
                    emit_byte(OP_POP, line); /* pop module map */
2241
20
                    {
2242
20
                        char err_msg[512];
2243
20
                        snprintf(err_msg, sizeof(err_msg),
2244
20
                            "module '%s' does not export '%s'",
2245
20
                            s->as.import.module_path, s->as.import.selective_names[i]);
2246
20
                        size_t err_idx = chunk_add_constant(current_chunk(), value_string(err_msg));
2247
20
                        emit_bytes(OP_CONSTANT, (uint8_t)err_idx, line);
2248
20
                        emit_byte(OP_THROW, line);
2249
20
                    }
2250
20
                    patch_jump(import_ok);
2251
20
                    emit_byte(OP_POP, line); /* pop non-nil dup */
2252
20
                    if (current->scope_depth > 0) {
2253
0
                        add_local(s->as.import.selective_names[i]);
2254
20
                    } else {
2255
20
                        size_t name_idx = chunk_add_constant(current_chunk(),
2256
20
                            value_string(s->as.import.selective_names[i]));
2257
20
                        emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, name_idx, line);
2258
20
                    }
2259
20
                }
2260
17
                emit_byte(OP_POP, line);  /* pop original module map */
2261
17
            } else {
2262
0
                emit_byte(OP_POP, line);
2263
0
            }
2264
94
            break;
2265
41
        }
2266
2267
0
        default:
2268
0
            break;
2269
13.6k
    }
2270
13.6k
}
2271
2272
/* ── Constant-evaluate a default parameter expression ── */
2273
2274
39
static LatValue const_eval_expr(Expr *e) {
2275
39
    if (!e) return value_nil();
2276
39
    switch (e->tag) {
2277
33
        case EXPR_INT_LIT:    return value_int(e->as.int_val);
2278
0
        case EXPR_FLOAT_LIT:  return value_float(e->as.float_val);
2279
6
        case EXPR_STRING_LIT: return value_string(e->as.str_val);
2280
0
        case EXPR_BOOL_LIT:   return value_bool(e->as.bool_val);
2281
0
        case EXPR_NIL_LIT:    return value_nil();
2282
0
        default:              return value_nil();
2283
39
    }
2284
39
}
2285
2286
/* ── Compile function body (for ITEM_FUNCTION and closures) ── */
2287
2288
static void compile_function_body(FunctionType type, const char *name,
2289
                                  Param *params, size_t param_count,
2290
                                  Stmt **body, size_t body_count,
2291
                                  ContractClause *contracts, size_t contract_count,
2292
2.66k
                                  TypeExpr *return_type, int line) {
2293
2.66k
    Compiler func_comp;
2294
2.66k
    compiler_init(&func_comp, current, type);
2295
2.66k
    func_comp.func_name = name ? strdup(name) : NULL;
2296
2.66k
    func_comp.chunk->name = name ? strdup(name) : NULL;
2297
2298
    /* For impl methods, self occupies slot 0 (the reserved slot).
2299
     * Rename it from "" to "self" and skip it in the param loop. */
2300
2.66k
    size_t first_param = 0;
2301
2.66k
    if (param_count > 0 && strcmp(params[0].name, "self") == 0) {
2302
6
        free(func_comp.locals[0].name);
2303
6
        func_comp.locals[0].name = strdup("self");
2304
6
        first_param = 1;
2305
6
    }
2306
2.66k
    func_comp.arity = (int)(param_count - first_param);
2307
2.66k
    func_comp.contracts = contracts;
2308
2.66k
    func_comp.contract_count = contract_count;
2309
2.66k
    func_comp.return_type_name = (return_type && return_type->name) ? return_type->name : NULL;
2310
2311
    /* Add remaining params as locals */
2312
5.50k
    for (size_t i = first_param; i < param_count; i++)
2313
2.84k
        add_local(params[i].name);
2314
2315
    /* Emit runtime parameter type checks */
2316
5.47k
    for (size_t i = first_param; i < param_count; i++) {
2317
2.84k
        if (params[i].is_variadic) break;
2318
2.81k
        if (!params[i].ty.name || strcmp(params[i].ty.name, "Any") == 0 || strcmp(params[i].ty.name, "any") == 0) continue;
2319
        /* Resolve slot: slot 0 is reserved, params start at slot 1 (or first_param-adjusted) */
2320
2.02k
        int slot = resolve_local(current, params[i].name);
2321
2.02k
        if (slot < 0) continue;
2322
2.02k
        char err_msg[512];
2323
2.02k
        snprintf(err_msg, sizeof(err_msg), "function '%s' parameter '%s' expects type %s, got %%s",
2324
2.02k
                 name ? name : "<anonymous>", params[i].name, params[i].ty.name);
2325
2.02k
        size_t type_idx = chunk_add_constant(current_chunk(), value_string(params[i].ty.name));
2326
2.02k
        size_t err_idx = chunk_add_constant(current_chunk(), value_string(err_msg));
2327
2.02k
        emit_byte(OP_CHECK_TYPE, line);
2328
2.02k
        emit_byte((uint8_t)slot, line);
2329
2.02k
        emit_byte((uint8_t)type_idx, line);
2330
2.02k
        emit_byte((uint8_t)err_idx, line);
2331
2.02k
    }
2332
2333
    /* Compile require contracts (preconditions) */
2334
2.67k
    for (size_t i = 0; i < contract_count; i++) {
2335
8
        if (contracts[i].is_ensure) continue;
2336
6
        compile_expr(contracts[i].condition, line);
2337
6
        size_t ok_jump = emit_jump(OP_JUMP_IF_TRUE, line);
2338
6
        emit_byte(OP_POP, line);
2339
        /* Format message to match tree-walker: "require failed in '<name>': <msg>" */
2340
6
        const char *user_msg = contracts[i].message ? contracts[i].message : "condition not met";
2341
6
        char full_msg[512];
2342
6
        snprintf(full_msg, sizeof(full_msg), "require failed in '%s': %s",
2343
6
                 name ? name : "<anonymous>", user_msg);
2344
6
        emit_constant(value_string(full_msg), line);
2345
6
        emit_byte(OP_THROW, line);
2346
6
        patch_jump(ok_jump);
2347
6
        emit_byte(OP_POP, line); /* pop the true */
2348
6
    }
2349
2350
    /* Compile body statements */
2351
10.2k
    for (size_t i = 0; i < body_count; i++)
2352
7.56k
        compile_stmt_reset(body[i]);
2353
2354
    /* Implicit unit return */
2355
2.66k
    emit_byte(OP_UNIT, line);
2356
2.66k
    emit_return_type_check(line);
2357
2.66k
    emit_ensure_checks(line);
2358
2.66k
    emit_byte(OP_DEFER_RUN, line);
2359
2.66k
    emit_byte(0, line);  /* scope_depth 0 = run all defers */
2360
2.66k
    emit_byte(OP_RETURN, line);
2361
2362
2.66k
    Chunk *fn_chunk = func_comp.chunk;
2363
2.66k
    size_t upvalue_count = func_comp.upvalue_count;
2364
2.66k
    CompilerUpvalue *upvalues = NULL;
2365
2.66k
    if (upvalue_count > 0) {
2366
0
        upvalues = malloc(upvalue_count * sizeof(CompilerUpvalue));
2367
0
        memcpy(upvalues, func_comp.upvalues, upvalue_count * sizeof(CompilerUpvalue));
2368
0
    }
2369
2370
    /* Store default parameter values and variadic flag on the chunk */
2371
2.66k
    int default_count = 0;
2372
2.66k
    bool has_variadic = false;
2373
5.50k
    for (size_t i = first_param; i < param_count; i++) {
2374
2.84k
        if (params[i].default_value) default_count++;
2375
2.84k
        if (params[i].is_variadic) has_variadic = true;
2376
2.84k
    }
2377
2.66k
    fn_chunk->default_count = default_count;
2378
2.66k
    fn_chunk->fn_has_variadic = has_variadic;
2379
2.66k
    if (default_count > 0) {
2380
32
        fn_chunk->default_values = malloc(default_count * sizeof(LatValue));
2381
32
        int di = 0;
2382
125
        for (size_t i = first_param; i < param_count; i++) {
2383
93
            if (params[i].default_value) {
2384
32
                fn_chunk->default_values[di++] = const_eval_expr(params[i].default_value);
2385
32
            }
2386
93
        }
2387
32
    }
2388
2389
    /* Store per-parameter phase constraints (for phase dispatch/checking) */
2390
2.66k
    {
2391
2.66k
        bool has_phase_constraints = false;
2392
5.46k
        for (size_t i = first_param; i < param_count; i++) {
2393
2.84k
            if (params[i].is_variadic) break;
2394
2.81k
            if (params[i].ty.phase != PHASE_UNSPECIFIED) {
2395
13
                has_phase_constraints = true;
2396
13
                break;
2397
13
            }
2398
2.81k
        }
2399
2.66k
        if (has_phase_constraints) {
2400
13
            int pc = (int)(param_count - first_param);
2401
13
            fn_chunk->param_phases = calloc(pc, sizeof(uint8_t));
2402
13
            fn_chunk->param_phase_count = pc;
2403
26
            for (size_t i = first_param; i < param_count; i++) {
2404
13
                if (params[i].is_variadic) break;
2405
13
                fn_chunk->param_phases[i - first_param] = (uint8_t)params[i].ty.phase;
2406
13
            }
2407
13
        }
2408
2.66k
    }
2409
2410
2.66k
    compiler_cleanup(&func_comp);
2411
2.66k
    current = func_comp.enclosing;
2412
2413
    /* Store the function's chunk as a constant. */
2414
2.66k
    LatValue fn_val;
2415
2.66k
    memset(&fn_val, 0, sizeof(fn_val));
2416
2.66k
    fn_val.type = VAL_CLOSURE;
2417
2.66k
    fn_val.phase = VTAG_UNPHASED;
2418
2.66k
    fn_val.region_id = (size_t)-1;
2419
    /* Set param_names for display (e.g. value_repr shows <closure|name|>) */
2420
2.66k
    if (param_count > 0) {
2421
1.69k
        fn_val.as.closure.param_names = malloc(param_count * sizeof(char *));
2422
4.54k
        for (size_t i = 0; i < param_count; i++)
2423
2.85k
            fn_val.as.closure.param_names[i] = strdup(params[i].name);
2424
1.69k
    } else {
2425
966
        fn_val.as.closure.param_names = NULL;
2426
966
    }
2427
2.66k
    fn_val.as.closure.param_count = param_count;
2428
2.66k
    fn_val.as.closure.body = NULL;
2429
2.66k
    fn_val.as.closure.captured_env = NULL;
2430
2.66k
    fn_val.as.closure.default_values = NULL;
2431
2.66k
    fn_val.as.closure.has_variadic = false;
2432
2.66k
    fn_val.as.closure.native_fn = fn_chunk;
2433
2.66k
    size_t fn_idx = chunk_add_constant(current_chunk(), fn_val);
2434
2435
2.66k
    if (fn_idx <= 255) {
2436
2.66k
        emit_byte(OP_CLOSURE, line);
2437
2.66k
        emit_byte((uint8_t)fn_idx, line);
2438
2.66k
    } else {
2439
0
        emit_byte(OP_CLOSURE_16, line);
2440
0
        emit_byte((uint8_t)((fn_idx >> 8) & 0xff), line);
2441
0
        emit_byte((uint8_t)(fn_idx & 0xff), line);
2442
0
    }
2443
2.66k
    emit_byte((uint8_t)upvalue_count, line);
2444
2.66k
    for (size_t i = 0; i < upvalue_count; i++) {
2445
0
        emit_byte(upvalues[i].is_local ? 1 : 0, line);
2446
0
        emit_byte(upvalues[i].index, line);
2447
0
    }
2448
2.66k
    free(upvalues);
2449
2.66k
}
2450
2451
/* ── Public API ── */
2452
2453
862
Chunk *stack_compile(const Program *prog, char **error) {
2454
862
    Compiler top;
2455
862
    compiler_init(&top, NULL, FUNC_SCRIPT);
2456
862
    *error = NULL;
2457
2458
    /* Compile top-level items */
2459
2.06k
    for (size_t i = 0; i < prog->item_count; i++) {
2460
1.20k
        switch (prog->items[i].tag) {
2461
244
            case ITEM_STMT:
2462
244
                compile_stmt_reset(prog->items[i].as.stmt);
2463
244
                break;
2464
2465
899
            case ITEM_FUNCTION: {
2466
899
                FnDecl *fn = &prog->items[i].as.fn_decl;
2467
899
                compile_function_body(FUNC_FUNCTION, fn->name,
2468
899
                                      fn->params, fn->param_count,
2469
899
                                      fn->body, fn->body_count,
2470
899
                                      fn->contracts, fn->contract_count,
2471
899
                                      fn->return_type, 0);
2472
                /* Define the function as a global */
2473
899
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(fn->name));
2474
899
                emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, name_idx, 0);
2475
899
                break;
2476
0
            }
2477
2478
44
            case ITEM_STRUCT: {
2479
                /* Store struct metadata as a constant for OP_BUILD_STRUCT to use.
2480
                 * We emit field names as an array constant. */
2481
44
                StructDecl *sd = &prog->items[i].as.struct_decl;
2482
44
                LatValue *field_names = malloc(sd->field_count * sizeof(LatValue));
2483
128
                for (size_t j = 0; j < sd->field_count; j++)
2484
84
                    field_names[j] = value_string(sd->fields[j].name);
2485
44
                LatValue arr = value_array(field_names, sd->field_count);
2486
44
                free(field_names);
2487
                /* Store as global "__struct_<name>" */
2488
44
                char meta_name[256];
2489
44
                snprintf(meta_name, sizeof(meta_name), "__struct_%s", sd->name);
2490
44
                size_t arr_idx = chunk_add_constant(current_chunk(), arr);
2491
44
                emit_bytes(OP_CONSTANT, (uint8_t)arr_idx, 0);
2492
44
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(meta_name));
2493
44
                emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, name_idx, 0);
2494
                /* Alloy: emit per-field phase metadata if any field has a phase annotation */
2495
44
                {
2496
44
                    bool has_phase = false;
2497
122
                    for (size_t j = 0; j < sd->field_count; j++) {
2498
81
                        if (sd->fields[j].ty.phase != PHASE_UNSPECIFIED) { has_phase = true; break; }
2499
81
                    }
2500
44
                    if (has_phase) {
2501
3
                        LatValue *phases = malloc(sd->field_count * sizeof(LatValue));
2502
9
                        for (size_t j = 0; j < sd->field_count; j++)
2503
6
                            phases[j] = value_int((int64_t)sd->fields[j].ty.phase);
2504
3
                        LatValue phase_arr = value_array(phases, sd->field_count);
2505
3
                        free(phases);
2506
3
                        char phase_meta[256];
2507
3
                        snprintf(phase_meta, sizeof(phase_meta), "__struct_phases_%s", sd->name);
2508
3
                        size_t pi = chunk_add_constant(current_chunk(), phase_arr);
2509
3
                        emit_bytes(OP_CONSTANT, (uint8_t)pi, 0);
2510
3
                        size_t pn = chunk_add_constant(current_chunk(), value_string(phase_meta));
2511
3
                        emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, pn, 0);
2512
3
                    }
2513
44
                }
2514
44
                break;
2515
0
            }
2516
2517
9
            case ITEM_ENUM: {
2518
                /* Store enum metadata */
2519
9
                EnumDecl *ed = &prog->items[i].as.enum_decl;
2520
9
                register_enum(ed->name);
2521
9
                char meta_name[256];
2522
9
                snprintf(meta_name, sizeof(meta_name), "__enum_%s", ed->name);
2523
9
                emit_byte(OP_TRUE, 0);
2524
9
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(meta_name));
2525
9
                emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, name_idx, 0);
2526
9
                break;
2527
0
            }
2528
2529
5
            case ITEM_IMPL: {
2530
                /* Compile impl methods and register them */
2531
5
                ImplBlock *ib = &prog->items[i].as.impl_block;
2532
11
                for (size_t j = 0; j < ib->method_count; j++) {
2533
6
                    FnDecl *method = &ib->methods[j];
2534
6
                    compile_function_body(FUNC_FUNCTION, method->name,
2535
6
                                          method->params, method->param_count,
2536
6
                                          method->body, method->body_count,
2537
6
                                          method->contracts, method->contract_count,
2538
6
                                          method->return_type, 0);
2539
                    /* Register as "TypeName::method" global */
2540
6
                    char key[256];
2541
6
                    snprintf(key, sizeof(key), "%s::%s", ib->type_name, method->name);
2542
6
                    size_t key_idx = chunk_add_constant(current_chunk(), value_string(key));
2543
6
                    emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, key_idx, 0);
2544
6
                }
2545
5
                break;
2546
0
            }
2547
2548
4
            case ITEM_TRAIT:
2549
6
            case ITEM_TEST:
2550
                /* Traits are metadata only, tests are skipped in normal compilation */
2551
6
                break;
2552
1.20k
        }
2553
2554
1.20k
        if (compile_error) {
2555
0
            *error = compile_error;
2556
0
            compile_error = NULL;
2557
0
            chunk_free(top.chunk);
2558
0
            compiler_cleanup(&top);
2559
0
            current = NULL;
2560
0
            free_known_enums();
2561
0
            return NULL;
2562
0
        }
2563
1.20k
    }
2564
2565
    /* If a main() function was defined, auto-call it */
2566
862
    bool has_main = false;
2567
1.23k
    for (size_t i = 0; i < prog->item_count; i++) {
2568
1.20k
        if (prog->items[i].tag == ITEM_FUNCTION &&
2569
1.20k
            strcmp(prog->items[i].as.fn_decl.name, "main") == 0) {
2570
834
            has_main = true;
2571
834
            break;
2572
834
        }
2573
1.20k
    }
2574
862
    if (has_main) {
2575
834
        size_t main_idx = chunk_add_constant(current_chunk(), value_string("main"));
2576
834
        emit_constant_idx(OP_GET_GLOBAL, OP_GET_GLOBAL_16, main_idx, 0);
2577
834
        emit_bytes(OP_CALL, 0, 0);
2578
834
        emit_byte(OP_POP, 0);
2579
834
    }
2580
2581
862
    emit_byte(OP_UNIT, 0);
2582
862
    emit_byte(OP_RETURN, 0);
2583
2584
862
    Chunk *result = top.chunk;
2585
862
    compiler_cleanup(&top);
2586
862
    current = NULL;
2587
862
    free_known_enums();
2588
862
    return result;
2589
862
}
2590
2591
87
Chunk *stack_compile_module(const Program *prog, char **error) {
2592
87
    Compiler top;
2593
87
    compiler_init(&top, NULL, FUNC_SCRIPT);
2594
87
    *error = NULL;
2595
2596
1.86k
    for (size_t i = 0; i < prog->item_count; i++) {
2597
1.78k
        switch (prog->items[i].tag) {
2598
19
            case ITEM_STMT:
2599
19
                compile_stmt_reset(prog->items[i].as.stmt);
2600
19
                break;
2601
1.75k
            case ITEM_FUNCTION: {
2602
1.75k
                FnDecl *fn = &prog->items[i].as.fn_decl;
2603
1.75k
                compile_function_body(FUNC_FUNCTION, fn->name,
2604
1.75k
                                      fn->params, fn->param_count,
2605
1.75k
                                      fn->body, fn->body_count,
2606
1.75k
                                      fn->contracts, fn->contract_count,
2607
1.75k
                                      fn->return_type, 0);
2608
1.75k
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(fn->name));
2609
1.75k
                emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, name_idx, 0);
2610
1.75k
                break;
2611
0
            }
2612
3
            case ITEM_STRUCT: {
2613
3
                StructDecl *sd = &prog->items[i].as.struct_decl;
2614
3
                LatValue *field_names = malloc(sd->field_count * sizeof(LatValue));
2615
8
                for (size_t j = 0; j < sd->field_count; j++)
2616
5
                    field_names[j] = value_string(sd->fields[j].name);
2617
3
                LatValue arr = value_array(field_names, sd->field_count);
2618
3
                free(field_names);
2619
3
                char meta_name[256];
2620
3
                snprintf(meta_name, sizeof(meta_name), "__struct_%s", sd->name);
2621
3
                size_t arr_idx = chunk_add_constant(current_chunk(), arr);
2622
3
                emit_bytes(OP_CONSTANT, (uint8_t)arr_idx, 0);
2623
3
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(meta_name));
2624
3
                emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, name_idx, 0);
2625
                /* Alloy: emit per-field phase metadata if any field has a phase annotation */
2626
3
                {
2627
3
                    bool has_phase = false;
2628
8
                    for (size_t j = 0; j < sd->field_count; j++) {
2629
5
                        if (sd->fields[j].ty.phase != PHASE_UNSPECIFIED) { has_phase = true; break; }
2630
5
                    }
2631
3
                    if (has_phase) {
2632
0
                        LatValue *phases = malloc(sd->field_count * sizeof(LatValue));
2633
0
                        for (size_t j = 0; j < sd->field_count; j++)
2634
0
                            phases[j] = value_int((int64_t)sd->fields[j].ty.phase);
2635
0
                        LatValue phase_arr = value_array(phases, sd->field_count);
2636
0
                        free(phases);
2637
0
                        char phase_meta[256];
2638
0
                        snprintf(phase_meta, sizeof(phase_meta), "__struct_phases_%s", sd->name);
2639
0
                        size_t pi = chunk_add_constant(current_chunk(), phase_arr);
2640
0
                        emit_bytes(OP_CONSTANT, (uint8_t)pi, 0);
2641
0
                        size_t pn = chunk_add_constant(current_chunk(), value_string(phase_meta));
2642
0
                        emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, pn, 0);
2643
0
                    }
2644
3
                }
2645
3
                break;
2646
0
            }
2647
0
            case ITEM_ENUM: {
2648
0
                EnumDecl *ed = &prog->items[i].as.enum_decl;
2649
0
                register_enum(ed->name);
2650
0
                char meta_name[256];
2651
0
                snprintf(meta_name, sizeof(meta_name), "__enum_%s", ed->name);
2652
0
                emit_byte(OP_TRUE, 0);
2653
0
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(meta_name));
2654
0
                emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, name_idx, 0);
2655
0
                break;
2656
0
            }
2657
0
            case ITEM_IMPL: {
2658
0
                ImplBlock *ib = &prog->items[i].as.impl_block;
2659
0
                for (size_t j = 0; j < ib->method_count; j++) {
2660
0
                    FnDecl *method = &ib->methods[j];
2661
0
                    compile_function_body(FUNC_FUNCTION, method->name,
2662
0
                                          method->params, method->param_count,
2663
0
                                          method->body, method->body_count,
2664
0
                                          method->contracts, method->contract_count,
2665
0
                                          method->return_type, 0);
2666
0
                    char key[256];
2667
0
                    snprintf(key, sizeof(key), "%s::%s", ib->type_name, method->name);
2668
0
                    size_t key_idx = chunk_add_constant(current_chunk(), value_string(key));
2669
0
                    emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, key_idx, 0);
2670
0
                }
2671
0
                break;
2672
0
            }
2673
0
            case ITEM_TRAIT:
2674
0
            case ITEM_TEST:
2675
0
                break;
2676
1.78k
        }
2677
2678
1.78k
        if (compile_error) {
2679
0
            *error = compile_error;
2680
0
            compile_error = NULL;
2681
0
            chunk_free(top.chunk);
2682
0
            compiler_cleanup(&top);
2683
0
            current = NULL;
2684
0
            free_known_enums();
2685
0
            return NULL;
2686
0
        }
2687
1.78k
    }
2688
2689
    /* No auto-call of main() for modules */
2690
87
    emit_byte(OP_UNIT, 0);
2691
87
    emit_byte(OP_RETURN, 0);
2692
2693
87
    Chunk *result = top.chunk;
2694
2695
    /* Copy export metadata from Program to Chunk */
2696
87
    if (prog->has_exports) {
2697
6
        result->has_exports = true;
2698
6
        result->export_count = prog->export_count;
2699
6
        result->export_names = malloc(prog->export_count * sizeof(char *));
2700
18
        for (size_t i = 0; i < prog->export_count; i++)
2701
12
            result->export_names[i] = strdup(prog->export_names[i]);
2702
6
    }
2703
2704
87
    compiler_cleanup(&top);
2705
87
    current = NULL;
2706
87
    free_known_enums();
2707
87
    return result;
2708
87
}
2709
2710
13
Chunk *stack_compile_repl(const Program *prog, char **error) {
2711
13
    Compiler top;
2712
13
    compiler_init(&top, NULL, FUNC_SCRIPT);
2713
13
    *error = NULL;
2714
2715
26
    for (size_t i = 0; i < prog->item_count; i++) {
2716
13
        switch (prog->items[i].tag) {
2717
11
            case ITEM_STMT: {
2718
11
                bool is_last = (i == prog->item_count - 1);
2719
11
                Stmt *s = prog->items[i].as.stmt;
2720
                /* Last item is a bare expression: keep value on stack */
2721
11
                if (is_last && s->tag == STMT_EXPR) {
2722
6
                    compile_expr(s->as.expr, 0);
2723
                    /* Skip OP_POP — value stays as return */
2724
6
                } else {
2725
5
                    compile_stmt_reset(s);
2726
5
                }
2727
11
                break;
2728
0
            }
2729
1
            case ITEM_FUNCTION: {
2730
1
                FnDecl *fn = &prog->items[i].as.fn_decl;
2731
1
                compile_function_body(FUNC_FUNCTION, fn->name,
2732
1
                                      fn->params, fn->param_count,
2733
1
                                      fn->body, fn->body_count,
2734
1
                                      fn->contracts, fn->contract_count,
2735
1
                                      fn->return_type, 0);
2736
1
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(fn->name));
2737
1
                emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, name_idx, 0);
2738
1
                break;
2739
0
            }
2740
1
            case ITEM_STRUCT: {
2741
1
                StructDecl *sd = &prog->items[i].as.struct_decl;
2742
1
                LatValue *field_names = malloc(sd->field_count * sizeof(LatValue));
2743
3
                for (size_t j = 0; j < sd->field_count; j++)
2744
2
                    field_names[j] = value_string(sd->fields[j].name);
2745
1
                LatValue arr = value_array(field_names, sd->field_count);
2746
1
                free(field_names);
2747
1
                char meta_name[256];
2748
1
                snprintf(meta_name, sizeof(meta_name), "__struct_%s", sd->name);
2749
1
                size_t arr_idx = chunk_add_constant(current_chunk(), arr);
2750
1
                emit_bytes(OP_CONSTANT, (uint8_t)arr_idx, 0);
2751
1
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(meta_name));
2752
1
                emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, name_idx, 0);
2753
1
                {
2754
1
                    bool has_phase = false;
2755
3
                    for (size_t j = 0; j < sd->field_count; j++) {
2756
2
                        if (sd->fields[j].ty.phase != PHASE_UNSPECIFIED) { has_phase = true; break; }
2757
2
                    }
2758
1
                    if (has_phase) {
2759
0
                        LatValue *phases = malloc(sd->field_count * sizeof(LatValue));
2760
0
                        for (size_t j = 0; j < sd->field_count; j++)
2761
0
                            phases[j] = value_int((int64_t)sd->fields[j].ty.phase);
2762
0
                        LatValue phase_arr = value_array(phases, sd->field_count);
2763
0
                        free(phases);
2764
0
                        char phase_meta[256];
2765
0
                        snprintf(phase_meta, sizeof(phase_meta), "__struct_phases_%s", sd->name);
2766
0
                        size_t pi = chunk_add_constant(current_chunk(), phase_arr);
2767
0
                        emit_bytes(OP_CONSTANT, (uint8_t)pi, 0);
2768
0
                        size_t pn = chunk_add_constant(current_chunk(), value_string(phase_meta));
2769
0
                        emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, pn, 0);
2770
0
                    }
2771
1
                }
2772
1
                break;
2773
0
            }
2774
0
            case ITEM_ENUM: {
2775
0
                EnumDecl *ed = &prog->items[i].as.enum_decl;
2776
0
                register_enum(ed->name);
2777
0
                char meta_name[256];
2778
0
                snprintf(meta_name, sizeof(meta_name), "__enum_%s", ed->name);
2779
0
                emit_byte(OP_TRUE, 0);
2780
0
                size_t name_idx = chunk_add_constant(current_chunk(), value_string(meta_name));
2781
0
                emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, name_idx, 0);
2782
0
                break;
2783
0
            }
2784
0
            case ITEM_IMPL: {
2785
0
                ImplBlock *ib = &prog->items[i].as.impl_block;
2786
0
                for (size_t j = 0; j < ib->method_count; j++) {
2787
0
                    FnDecl *method = &ib->methods[j];
2788
0
                    compile_function_body(FUNC_FUNCTION, method->name,
2789
0
                                          method->params, method->param_count,
2790
0
                                          method->body, method->body_count,
2791
0
                                          method->contracts, method->contract_count,
2792
0
                                          method->return_type, 0);
2793
0
                    char key[256];
2794
0
                    snprintf(key, sizeof(key), "%s::%s", ib->type_name, method->name);
2795
0
                    size_t key_idx = chunk_add_constant(current_chunk(), value_string(key));
2796
0
                    emit_constant_idx(OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL_16, key_idx, 0);
2797
0
                }
2798
0
                break;
2799
0
            }
2800
0
            case ITEM_TRAIT:
2801
0
            case ITEM_TEST:
2802
0
                break;
2803
13
        }
2804
2805
13
        if (compile_error) {
2806
0
            *error = compile_error;
2807
0
            compile_error = NULL;
2808
0
            chunk_free(top.chunk);
2809
0
            compiler_cleanup(&top);
2810
0
            current = NULL;
2811
            /* Don't free known enums — they persist across REPL iterations */
2812
0
            return NULL;
2813
0
        }
2814
13
    }
2815
2816
    /* Check if last item was a bare expression (value already on stack) */
2817
13
    bool last_is_expr = (prog->item_count > 0 &&
2818
13
                         prog->items[prog->item_count - 1].tag == ITEM_STMT &&
2819
13
                         prog->items[prog->item_count - 1].as.stmt->tag == STMT_EXPR);
2820
2821
13
    if (last_is_expr) {
2822
6
        emit_byte(OP_RETURN, 0);
2823
7
    } else {
2824
7
        emit_byte(OP_UNIT, 0);
2825
7
        emit_byte(OP_RETURN, 0);
2826
7
    }
2827
2828
13
    Chunk *result = top.chunk;
2829
13
    compiler_cleanup(&top);
2830
13
    current = NULL;
2831
    /* Don't free known enums — they persist across REPL iterations */
2832
13
    return result;
2833
13
}
2834
2835
0
void stack_compiler_free_known_enums(void) {
2836
0
    free_known_enums();
2837
0
}
2838